Start to add OpenGL support to SolveSpace, for the graphical view.
I've got the user interface to pan and rotate an object, more or less works. [git-p4: depot-paths = "//depot/solvespace/": change = 1654]
This commit is contained in:
parent
fcdf43d487
commit
e426fe8a53
3
Makefile
3
Makefile
|
@ -12,9 +12,10 @@ W32OBJS = $(OBJDIR)\w32main.obj \
|
||||||
SSOBJS = $(OBJDIR)\solvespace.obj \
|
SSOBJS = $(OBJDIR)\solvespace.obj \
|
||||||
$(OBJDIR)\cmdline.obj \
|
$(OBJDIR)\cmdline.obj \
|
||||||
$(OBJDIR)\graphicswin.obj \
|
$(OBJDIR)\graphicswin.obj \
|
||||||
|
$(OBJDIR)\util.obj \
|
||||||
|
|
||||||
|
|
||||||
LIBS = user32.lib gdi32.lib comctl32.lib advapi32.lib
|
LIBS = user32.lib gdi32.lib comctl32.lib advapi32.lib opengl32.lib glu32.lib
|
||||||
|
|
||||||
all: $(OBJDIR)/solvespace.exe
|
all: $(OBJDIR)/solvespace.exe
|
||||||
@cp $(OBJDIR)/solvespace.exe .
|
@cp $(OBJDIR)/solvespace.exe .
|
||||||
|
|
15
dsc.h
15
dsc.h
|
@ -5,10 +5,23 @@
|
||||||
typedef unsigned long DWORD;
|
typedef unsigned long DWORD;
|
||||||
typedef unsigned char BYTE;
|
typedef unsigned char BYTE;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct VectorTag Vector;
|
||||||
|
|
||||||
|
typedef struct VectorTag {
|
||||||
double x, y, z;
|
double x, y, z;
|
||||||
|
|
||||||
|
Vector Cross(Vector b);
|
||||||
|
double Vector::Dot(Vector b);
|
||||||
|
Vector RotatedAbout(Vector axis, double theta);
|
||||||
|
double Magnitude(void);
|
||||||
|
Vector ScaledBy(double v);
|
||||||
|
|
||||||
} Vector;
|
} Vector;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
double x, y;
|
||||||
|
} Point2d;
|
||||||
|
|
||||||
template <class T, class H> struct IdList {
|
template <class T, class H> struct IdList {
|
||||||
typedef struct {
|
typedef struct {
|
||||||
T v;
|
T v;
|
||||||
|
|
102
graphicswin.cpp
102
graphicswin.cpp
|
@ -1,5 +1,9 @@
|
||||||
#include "solvespace.h"
|
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
#include <windows.h>
|
||||||
|
#include <gl/gl.h>
|
||||||
|
#include <gl/glu.h>
|
||||||
|
|
||||||
|
#include "solvespace.h"
|
||||||
|
|
||||||
const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = {
|
const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = {
|
||||||
{ 0, "&File", 0, NULL },
|
{ 0, "&File", 0, NULL },
|
||||||
|
@ -33,3 +37,99 @@ const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = {
|
||||||
{ -1 },
|
{ -1 },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void GraphicsWindow::Init(void) {
|
||||||
|
offset.x = offset.y = offset.z = 0.9;
|
||||||
|
scale = 1;
|
||||||
|
projRight.x = 1; projRight.y = projRight.z = 0;
|
||||||
|
projDown.y = 1; projDown.z = projDown.x = 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphicsWindow::NormalizeProjectionVectors(void) {
|
||||||
|
Vector norm = projRight.Cross(projDown);
|
||||||
|
projDown = norm.Cross(projRight);
|
||||||
|
|
||||||
|
projDown = projDown.ScaledBy(1/projDown.Magnitude());
|
||||||
|
projRight = projRight.ScaledBy(1/projRight.Magnitude());
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphicsWindow::MouseMoved(double x, double y, bool leftDown,
|
||||||
|
bool middleDown, bool rightDown, bool shiftDown, bool ctrlDown)
|
||||||
|
{
|
||||||
|
if(middleDown) {
|
||||||
|
double dx = (x - orig.mouse.x) / scale;
|
||||||
|
double dy = (y - orig.mouse.y) / scale;
|
||||||
|
|
||||||
|
if(shiftDown) {
|
||||||
|
offset.x = orig.offset.x + dx*projRight.x + dy*projDown.x;
|
||||||
|
offset.y = orig.offset.y + dx*projRight.y + dy*projDown.y;
|
||||||
|
offset.z = orig.offset.z + dx*projRight.z + dy*projDown.z;
|
||||||
|
} else if(ctrlDown) {
|
||||||
|
double theta = atan2(orig.mouse.y, orig.mouse.x);
|
||||||
|
theta -= atan2(y, x);
|
||||||
|
|
||||||
|
Vector normal = orig.projRight.Cross(orig.projDown);
|
||||||
|
projRight = orig.projRight.RotatedAbout(normal, theta);
|
||||||
|
projDown = orig.projDown.RotatedAbout(normal, theta);
|
||||||
|
|
||||||
|
NormalizeProjectionVectors();
|
||||||
|
} else {
|
||||||
|
double s = 0.3*(PI/180); // degrees per pixel
|
||||||
|
projRight = orig.projRight.RotatedAbout(orig.projDown, -s*dx);
|
||||||
|
projDown = orig.projDown.RotatedAbout(orig.projRight, s*dy);
|
||||||
|
|
||||||
|
NormalizeProjectionVectors();
|
||||||
|
|
||||||
|
orig.projRight = projRight;
|
||||||
|
orig.projDown = projDown;
|
||||||
|
orig.mouse.x = x;
|
||||||
|
orig.mouse.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
Invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphicsWindow::MouseMiddleDown(double x, double y) {
|
||||||
|
orig.offset = offset;
|
||||||
|
orig.projDown = projDown;
|
||||||
|
orig.projRight = projRight;
|
||||||
|
orig.mouse.x = x;
|
||||||
|
orig.mouse.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphicsWindow::MouseLeftDown(double x, double y) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphicsWindow::Paint(int w, int h) {
|
||||||
|
glViewport(0, 0, w, h);
|
||||||
|
|
||||||
|
glMatrixMode(GL_PROJECTION);
|
||||||
|
glLoadIdentity();
|
||||||
|
|
||||||
|
glMatrixMode(GL_MODELVIEW);
|
||||||
|
glLoadIdentity();
|
||||||
|
|
||||||
|
glScaled(scale*2.0/w, scale*2.0/h, 0);
|
||||||
|
|
||||||
|
double tx = projRight.Dot(offset);
|
||||||
|
double ty = projDown.Dot(offset);
|
||||||
|
double mat[16];
|
||||||
|
MakeMatrix(mat, projRight.x, projRight.y, projRight.z, tx,
|
||||||
|
projDown.x, projDown.y, projDown.z, ty,
|
||||||
|
0, 0, 0, 0,
|
||||||
|
0, 0, 0, 1);
|
||||||
|
glMultMatrixd(mat);
|
||||||
|
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
|
||||||
|
glClearIndex((GLfloat)0);
|
||||||
|
glClearDepth(1.0);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
|
|
||||||
|
GLUquadricObj *quadObj;
|
||||||
|
quadObj = gluNewQuadric();
|
||||||
|
gluQuadricDrawStyle(quadObj, GLU_LINE);
|
||||||
|
gluSphere(quadObj, 300, 4, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ SolveSpace SS;
|
||||||
|
|
||||||
void SolveSpace::Init(void) {
|
void SolveSpace::Init(void) {
|
||||||
TW.Init();
|
TW.Init();
|
||||||
|
GW.Init();
|
||||||
|
|
||||||
int i;
|
int i;
|
||||||
for(i = 0; i < 20; i++) {
|
for(i = 0; i < 20; i++) {
|
||||||
|
|
10
solvespace.h
10
solvespace.h
|
@ -5,6 +5,7 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <math.h>
|
||||||
#include "dsc.h"
|
#include "dsc.h"
|
||||||
#include "ui.h"
|
#include "ui.h"
|
||||||
#include "sketch.h"
|
#include "sketch.h"
|
||||||
|
@ -13,8 +14,17 @@
|
||||||
#define oops() do { dbp("oops at line %d, file %s", __LINE__, __FILE__); \
|
#define oops() do { dbp("oops at line %d, file %s", __LINE__, __FILE__); \
|
||||||
exit(-1); } while(0)
|
exit(-1); } while(0)
|
||||||
void dbp(char *str, ...);
|
void dbp(char *str, ...);
|
||||||
|
void Invalidate(void);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#define arraylen(x) (sizeof((x))/sizeof((x)[0]))
|
#define arraylen(x) (sizeof((x))/sizeof((x)[0]))
|
||||||
|
#define PI (3.1415926535897931)
|
||||||
|
void MakeMatrix(double *mat, double a11, double a12, double a13, double a14,
|
||||||
|
double a21, double a22, double a23, double a24,
|
||||||
|
double a31, double a32, double a33, double a34,
|
||||||
|
double a41, double a42, double a43, double a44);
|
||||||
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
TextWindow TW;
|
TextWindow TW;
|
||||||
|
|
18
ui.h
18
ui.h
|
@ -64,16 +64,26 @@ typedef struct {
|
||||||
// coordinates of the 3d sketch points. We will use an axonometric
|
// coordinates of the 3d sketch points. We will use an axonometric
|
||||||
// projection.
|
// projection.
|
||||||
Vector offset;
|
Vector offset;
|
||||||
double scale;
|
|
||||||
Vector projRight;
|
Vector projRight;
|
||||||
Vector projDown;
|
Vector projDown;
|
||||||
|
double scale;
|
||||||
|
struct {
|
||||||
|
Vector offset;
|
||||||
|
Vector projRight;
|
||||||
|
Vector projDown;
|
||||||
|
Point2d mouse;
|
||||||
|
} orig;
|
||||||
|
|
||||||
|
void Init(void);
|
||||||
|
void NormalizeProjectionVectors(void);
|
||||||
|
|
||||||
// These are called by the platform-specific code.
|
// These are called by the platform-specific code.
|
||||||
void Paint(void);
|
void Paint(int w, int h);
|
||||||
void MouseMoved(double x, double y, bool leftDown, bool middleDown,
|
void MouseMoved(double x, double y, bool leftDown, bool middleDown,
|
||||||
bool rightDown);
|
bool rightDown, bool shiftDown, bool ctrlDown);
|
||||||
void MouseLeftClick(double x, double y);
|
void MouseLeftDown(double x, double y);
|
||||||
void MouseLeftDoubleClick(double x, double y);
|
void MouseLeftDoubleClick(double x, double y);
|
||||||
|
void MouseMiddleDown(double x, double y);
|
||||||
void MouseScroll(int delta);
|
void MouseScroll(int delta);
|
||||||
} GraphicsWindow;
|
} GraphicsWindow;
|
||||||
|
|
||||||
|
|
80
util.cpp
Normal file
80
util.cpp
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
#include "solvespace.h"
|
||||||
|
|
||||||
|
void MakeMatrix(double *mat, double a11, double a12, double a13, double a14,
|
||||||
|
double a21, double a22, double a23, double a24,
|
||||||
|
double a31, double a32, double a33, double a34,
|
||||||
|
double a41, double a42, double a43, double a44)
|
||||||
|
{
|
||||||
|
mat[ 0] = a11;
|
||||||
|
mat[ 1] = a21;
|
||||||
|
mat[ 2] = a31;
|
||||||
|
mat[ 3] = a41;
|
||||||
|
mat[ 4] = a12;
|
||||||
|
mat[ 5] = a22;
|
||||||
|
mat[ 6] = a32;
|
||||||
|
mat[ 7] = a42;
|
||||||
|
mat[ 8] = a13;
|
||||||
|
mat[ 9] = a23;
|
||||||
|
mat[10] = a33;
|
||||||
|
mat[11] = a43;
|
||||||
|
mat[12] = a14;
|
||||||
|
mat[13] = a24;
|
||||||
|
mat[14] = a34;
|
||||||
|
mat[15] = a44;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Vector Vector::Cross(Vector b)
|
||||||
|
{
|
||||||
|
Vector r;
|
||||||
|
|
||||||
|
r.x = -(z*b.y) + (y*b.z);
|
||||||
|
r.y = (z*b.x) - (x*b.z);
|
||||||
|
r.z = -(y*b.x) + (x*b.y);
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
double Vector::Dot(Vector b)
|
||||||
|
{
|
||||||
|
return (x*b.x + y*b.y + z*b.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector Vector::RotatedAbout(Vector axis, double theta)
|
||||||
|
{
|
||||||
|
double c = cos(theta);
|
||||||
|
double s = sin(theta);
|
||||||
|
|
||||||
|
Vector r;
|
||||||
|
|
||||||
|
r.x = (x)*(c + (1 - c)*(axis.x)*(axis.x)) +
|
||||||
|
(y)*((1 - c)*(axis.x)*(axis.y) - s*(axis.z)) +
|
||||||
|
(z)*((1 - c)*(axis.x)*(axis.z) + s*(axis.y));
|
||||||
|
|
||||||
|
r.y = (x)*((1 - c)*(axis.y)*(axis.x) + s*(axis.z)) +
|
||||||
|
(y)*(c + (1 - c)*(axis.y)*(axis.y)) +
|
||||||
|
(z)*((1 - c)*(axis.y)*(axis.z) - s*(axis.x));
|
||||||
|
|
||||||
|
r.z = (x)*((1 - c)*(axis.z)*(axis.x) - s*(axis.y)) +
|
||||||
|
(y)*((1 - c)*(axis.z)*(axis.y) + s*(axis.x)) +
|
||||||
|
(z)*(c + (1 - c)*(axis.z)*(axis.z));
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
double Vector::Magnitude(void)
|
||||||
|
{
|
||||||
|
return sqrt(x*x + y*y + z*z);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector Vector::ScaledBy(double v)
|
||||||
|
{
|
||||||
|
Vector r;
|
||||||
|
|
||||||
|
r.x = x * v;
|
||||||
|
r.y = y * v;
|
||||||
|
r.z = z * v;
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <commctrl.h>
|
#include <commctrl.h>
|
||||||
|
#include <gl/gl.h>
|
||||||
|
#include <gl/glu.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
@ -197,6 +199,40 @@ LRESULT CALLBACK TextWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static HGLRC CreateGlContext(HDC hdc)
|
||||||
|
{
|
||||||
|
PIXELFORMATDESCRIPTOR pfd;
|
||||||
|
int pixelFormat;
|
||||||
|
|
||||||
|
memset(&pfd, 0, sizeof(pfd));
|
||||||
|
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
|
||||||
|
pfd.nVersion = 1;
|
||||||
|
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |
|
||||||
|
PFD_DOUBLEBUFFER;
|
||||||
|
pfd.dwLayerMask = PFD_MAIN_PLANE;
|
||||||
|
pfd.iPixelType = PFD_TYPE_COLORINDEX;
|
||||||
|
pfd.cColorBits = 8;
|
||||||
|
pfd.cDepthBits = 16;
|
||||||
|
pfd.cAccumBits = 0;
|
||||||
|
pfd.cStencilBits = 0;
|
||||||
|
|
||||||
|
pixelFormat = ChoosePixelFormat(hdc, &pfd);
|
||||||
|
if(!pixelFormat) oops();
|
||||||
|
|
||||||
|
if(!SetPixelFormat(hdc, pixelFormat, &pfd)) oops();
|
||||||
|
|
||||||
|
HGLRC hgrc = wglCreateContext(hdc);
|
||||||
|
wglMakeCurrent(hdc, hgrc);
|
||||||
|
|
||||||
|
return hgrc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Invalidate(void)
|
||||||
|
{
|
||||||
|
InvalidateRect(GraphicsWnd, NULL, FALSE);
|
||||||
|
InvalidateRect(TextWnd, NULL, FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
LRESULT CALLBACK GraphicsWndProc(HWND hwnd, UINT msg, WPARAM wParam,
|
LRESULT CALLBACK GraphicsWndProc(HWND hwnd, UINT msg, WPARAM wParam,
|
||||||
LPARAM lParam)
|
LPARAM lParam)
|
||||||
{
|
{
|
||||||
|
@ -208,6 +244,64 @@ LRESULT CALLBACK GraphicsWndProc(HWND hwnd, UINT msg, WPARAM wParam,
|
||||||
InvalidateRect(TextWnd, NULL, FALSE);
|
InvalidateRect(TextWnd, NULL, FALSE);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case WM_ERASEBKGND:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WM_SIZE:
|
||||||
|
InvalidateRect(GraphicsWnd, NULL, FALSE);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WM_PAINT: {
|
||||||
|
InvalidateRect(GraphicsWnd, NULL, FALSE);
|
||||||
|
PAINTSTRUCT ps;
|
||||||
|
HDC hdc = BeginPaint(hwnd, &ps);
|
||||||
|
|
||||||
|
HGLRC hgrc = CreateGlContext(hdc);
|
||||||
|
|
||||||
|
RECT r;
|
||||||
|
GetClientRect(GraphicsWnd, &r);
|
||||||
|
int w = r.right - r.left;
|
||||||
|
int h = r.bottom - r.top;
|
||||||
|
|
||||||
|
SS.GW.Paint(w, h);
|
||||||
|
|
||||||
|
SwapBuffers(hdc);
|
||||||
|
|
||||||
|
wglMakeCurrent(NULL, NULL);
|
||||||
|
wglDeleteContext(hgrc);
|
||||||
|
|
||||||
|
EndPaint(hwnd, &ps);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case WM_MOUSEMOVE:
|
||||||
|
case WM_LBUTTONDOWN:
|
||||||
|
case WM_MBUTTONDOWN: {
|
||||||
|
int x = LOWORD(lParam);
|
||||||
|
int y = HIWORD(lParam);
|
||||||
|
|
||||||
|
RECT r;
|
||||||
|
GetClientRect(GraphicsWnd, &r);
|
||||||
|
x = x - (r.right - r.left)/2;
|
||||||
|
y = (r.bottom - r.top)/2 - y;
|
||||||
|
|
||||||
|
if(msg == WM_LBUTTONDOWN) {
|
||||||
|
SS.GW.MouseLeftDown(x, y);
|
||||||
|
} else if(msg == WM_MBUTTONDOWN) {
|
||||||
|
SS.GW.MouseMiddleDown(x, y);
|
||||||
|
} else if(msg == WM_MOUSEMOVE) {
|
||||||
|
SS.GW.MouseMoved(x, y,
|
||||||
|
!!(wParam & MK_LBUTTON),
|
||||||
|
!!(wParam & MK_MBUTTON),
|
||||||
|
!!(wParam & MK_RBUTTON),
|
||||||
|
!!(wParam & MK_SHIFT),
|
||||||
|
!!(wParam & MK_CONTROL));
|
||||||
|
} else {
|
||||||
|
oops();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case WM_CLOSE:
|
case WM_CLOSE:
|
||||||
case WM_DESTROY:
|
case WM_DESTROY:
|
||||||
PostQuitMessage(0);
|
PostQuitMessage(0);
|
||||||
|
@ -260,7 +354,7 @@ static void CreateMainWindows(void)
|
||||||
wc.style = CS_BYTEALIGNCLIENT | CS_BYTEALIGNWINDOW | CS_OWNDC |
|
wc.style = CS_BYTEALIGNCLIENT | CS_BYTEALIGNWINDOW | CS_OWNDC |
|
||||||
CS_DBLCLKS;
|
CS_DBLCLKS;
|
||||||
wc.lpfnWndProc = (WNDPROC)GraphicsWndProc;
|
wc.lpfnWndProc = (WNDPROC)GraphicsWndProc;
|
||||||
wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
|
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
|
||||||
wc.lpszClassName = "GraphicsWnd";
|
wc.lpszClassName = "GraphicsWnd";
|
||||||
wc.lpszMenuName = NULL;
|
wc.lpszMenuName = NULL;
|
||||||
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
||||||
|
@ -271,8 +365,8 @@ static void CreateMainWindows(void)
|
||||||
HMENU top = CreateGraphicsWindowMenus();
|
HMENU top = CreateGraphicsWindowMenus();
|
||||||
GraphicsWnd = CreateWindowEx(0, "GraphicsWnd", "SolveSpace (View Sketch)",
|
GraphicsWnd = CreateWindowEx(0, "GraphicsWnd", "SolveSpace (View Sketch)",
|
||||||
WS_OVERLAPPED | WS_THICKFRAME | WS_CLIPCHILDREN | WS_MAXIMIZEBOX |
|
WS_OVERLAPPED | WS_THICKFRAME | WS_CLIPCHILDREN | WS_MAXIMIZEBOX |
|
||||||
WS_MINIMIZEBOX | WS_SYSMENU | WS_SIZEBOX,
|
WS_MINIMIZEBOX | WS_SYSMENU | WS_SIZEBOX | WS_CLIPSIBLINGS,
|
||||||
600, 300, 400, 400, NULL, top, Instance, NULL);
|
600, 300, 200, 200, NULL, top, Instance, NULL);
|
||||||
if(!GraphicsWnd) oops();
|
if(!GraphicsWnd) oops();
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user