How to rotate one circle anti clockwise canvas android - java

I can't solve problem and I need help. I create planets around sun to rotate and I need one planet t rotate anti clockwise and my problem begins. I try everything I'm familiar. I'm beginner and this is simple code but I cant solve it. Please help. Thanks in advance.
I tried with getting anti clockwise just putting minus and this not work. I try with radius and angle and didn't work. I somewhere getting failed in code but I don't know where.
...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
screenDimensions = getDisplayDimensions();
Bitmap bitmap = Bitmap.createBitmap(1200, 2000, Bitmap.Config.ARGB_8888);
bitmap.eraseColor(Color.parseColor("#FFFFFF"));
canvas = new Canvas();
canvas.setBitmap(bitmap);
paint = new Paint();
paint1 = new Paint();
paint2 = new Paint();
paint3 = new Paint();
paint4 = new Paint();
paint.setColor(Color.YELLOW);
paint1.setColor(Color.RED);
paint2.setColor(Color.BLUE);
paint3.setColor(Color.BLACK);
paint4.setColor(Color.MAGENTA);
imageView = findViewById(R.id.imageView3);
imageView.setImageBitmap(bitmap);
timer = new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
runOnUiThread(
new Runnable() {
#Override
public void run() {
animationFrame();
}
}
);
}
}, 2000, 80);
}
private int[] getDisplayDimensions() {
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int width = displayMetrics.widthPixels;
int height = displayMetrics.heightPixels;
return new int[]{width, height};
}
private void animationFrame() {
canvas.drawColor(Color.parseColor("#EBEBEB"));
paint.setColor(Color.YELLOW);
canvas.drawCircle(500f, 750f, 130f, paint);
if (paint1 != null | paint3 != null) {
paint1.setColor(Color.RED);
canvas.drawCircle(400f, 560f, 51f, paint1);
canvas.rotate(10f, 500f, 750f);
paint3.setColor(Color.BLACK);
canvas.rotate(30f, 500f, 750f);
canvas.drawCircle(350f, 1050f, 45, paint3);
}
if (paint2 != null | paint4 != null) {
paint2.setColor(Color.MAGENTA);
canvas.rotate(-10f, 500f, 750f);
canvas.drawCircle(720f, 950f, 48, paint2);
paint4.setColor(Color.GRAY);
canvas.rotate(-25f, 500f, 750f);
canvas.drawCircle(290f, 330f, 42, paint4);
}
imageView.invalidate();
}
}
...
I expected to one of planets get rotation anticlockwise.

EDIT
This code works perfectly for me:
In your case, the rotationPoint is the center point of sun and the bodyCenter is the center point of a planet.
Final points cx and cy are the center point of a planet after rotation around the sun.
class Vector {
float x;
float y;
Vector(float x, float y){
this.x = x;
this.y = y;
}
}
public Vector getRotatedPoint(boolean isClockwise, float rotation, Vector bodyCenter, Vector rotationPoint){
float sin = MathUtils.sin(rotation * MathUtils.degreesToRadians);
float cos = MathUtils.cos(rotation * MathUtils.degreesToRadians);
float xSin = (bodyCenter.x - rotationPoint.x) * sin;
float xCos = (bodyCenter.x - rotationPoint.x) * cos;
float ySin = (bodyCenter.y - rotationPoint.y) * sin;
float yCos = (bodyCenter.y - rotationPoint.y) * cos;
float cx, cy;
if (isClockwise) {
//For clockwise rotation
cx = rotationPoint.x + xCos + ySin;
cy = rotationPoint.y - xSin + yCos;
} else {
//For counter clockwise rotation
cx = rotationPoint.x + xCos - ySin;
cy = rotationPoint.y + xSin + yCos;
}
return new Vector(cx, cy);
}
to use it you have to call first the method and then draw a circle
Vector newCenter = getRotatedPoint(true, 10f, planetCenter, sunCenter)
canvas.drawCircle(newCenter.x, newCenter.y, 48, paint2);

Related

Scaling of a quad is incorrect after resizing the window. How to fix?

If I try to render a square with the same scaling for x and y without resizing the window everything is fine. But after resizing the window there is no longer a square rendered. Instead, you can see a rectangle, even though I am recalculating the projection matrix and passing the matrix to the shader every time the window's width or height changes.
With a different width and height, the scaling of a square is incorrect. Changing the size of the window in my code without resizing the window does not change the scaling.
I have no idea where the error is. Maybe I missed something.
Coordinates:
float[] positions = new float[] {0, 1, 0, 0, 1, 1, 1, 0};
Calculating the projection matrix:
public static void createProjectionMatrix() {
Vector2f windowSize = DisplayManager.getWindowSize();
float aspectRatio = windowSize.x / windowSize.y;
float halfWidth = 1.0f;
float halfHeight = halfWidth / aspectRatio;
float left = -halfWidth;
float right = halfWidth;
float bottom = -halfHeight;
float top = halfHeight;
float far = -1f;
float near = 1f;
Matrix4f matrix = new Matrix4f();
matrix.setIdentity();
matrix.m00 = 2f / (right - left);
matrix.m11 = 2f / (top - bottom);
matrix.m22 = -2f / (far - near);
matrix.m32 = (far + near) / (far - near);
matrix.m30 = (right + left) / (right - left);
matrix.m31 = (top + bottom) / (top - bottom);
projectionMatrix = matrix;
}
Calculation the transformation (Vector2f worldScale is not used):
private Vector2f getDisplayCoords(Vector2f percentage) {
Vector2f v = DisplayManager.getWindowSize();
return new Vector2f(v.x * percentage.x, v.y * percentage.y);
}
public void loadTransformationMatricies() {
Vector2f displaySize = getDisplayCoords(size);
Vector2f displayPos = getDisplayCoords(position);
Vector2f display = DisplayManager.getWindowSize();
Vector2f worldPos = Mouse.getWorldPos(displayPos);
Vector2f worldScale = new Vector2f(displaySize.x / (display.x / 2.0f), displaySize.y / (display.y / 2));
float x, y;
x = worldPos.x;
y = worldPos.y - CORNER_SCALE;
//y is calculated correct. Moving the mouse to the same y value as calculated shows that everything is correctly calculated
System.out.println(y + " | " + Mouse.getWorldPos().y);
transforms[0] = Maths.getTransformationMatrix(new Vector2f(x, y), new Vector2f(CORNER_SCALE, CORNER_SCALE));
}
Check size:
GLFW.glfwSetWindowSizeCallback(WINDOW, new GLFWWindowSizeCallback() {
#Override
public void invoke(long arg0, int arg1, int arg2) {
resized = true;
}
});
Rendering (would normally render more objects):
#Override
protected void render() {
shader.start();
if(DisplayManager.isResized()) {
shader.loadProjectionMatrix(MasterRenderer.getProjectionMatrix());
}
bindModel(Loader.getQuad(), new int[] {0});
for(GUI gui : guis) {
if(DisplayManager.isResized()) {
gui.loadTransformationMatricies();
}
bindTexture(gui.getTexture().getTopLeftCorner(), GL13.GL_TEXTURE0);
Matrix4f[] transforms = gui.getTransformations();
for(int i = 0; i < 1; i++) {
shader.loadTransformationMatrix(transforms[i]);
drawSTRIP(Loader.getQuad());
}
}
unbind(new int[] {0});
shader.stop();
}
Vertexshader:
#version 400 core
in vec2 position;
uniform mat4 transformationMatrix;
uniform mat4 projectionMatrix;
out vec2 textureCoords;
void main (void){
gl_Position = projectionMatrix * transformationMatrix * vec4(position, 0, 1.0);
textureCoords = vec2((position.x+1.0)/2.0, 1 - (position.y+1.0)/2.0);
}
You can find the whole code on Github if you need more information but I only want to know how to fix this specific problem.
https://github.com/StackOverflowEx/GameEngine2D
After the size of the window has changed, the viewport rectangle has to be adjusted to the new size:
Use glViewport to set the viewport rectangle.
Add a method updateViewPort to the class DisplayManager
public static void updateViewPort() {
IntBuffer pWidth = stack.mallocInt(1);
IntBuffer pHeight = stack.mallocInt(1);
GLFW.glfwGetFramebufferSize(WINDOW, pWidth, pHeight);
GL11.glViewport(0, 0, pWidth.get(0), pHeight.get(0));
}
Call the DisplayManager.updateViewPort in the methodprepare in the class MasterRenderer:
private void prepare() {
if(DisplayManager.isResized()) {
DisplayManager.updateViewPort();
createProjectionMatrix();
}
Camera.calcViewMatrix();
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
GL11.glClearColor(1, 0, 0, 1);
}
See also Java Code Examples for org.lwjgl.glfw.GLFW.glfwGetFramebufferSize().

How do I stop the object from going outside the screen?

public class MovingBagView extends View {
private Bitmap bag[] = new Bitmap[2];
private int bagX;
private int bagY = 1000;
private int bagSpeed;
private Boolean touch = false;
private int canvasWidth, canvasHeight;
private Bitmap backgroundImage;
private Paint scorePaint = new Paint();
private Bitmap life[] = new Bitmap[2];
public MovingBagView(Context context) {
super(context);
bag[0] = BitmapFactory.decodeResource(getResources(), R.drawable.bag1);
bag[1] = BitmapFactory.decodeResource(getResources(), R.drawable.bag2);
backgroundImage = BitmapFactory.decodeResource(getResources(), R.drawable.background);
scorePaint.setColor(Color.BLACK);
scorePaint.setTextSize(40);
scorePaint.setTypeface(Typeface.DEFAULT_BOLD);
scorePaint.setAntiAlias(true);
life[0] = BitmapFactory.decodeResource(getResources(), R.drawable.heart);
life[1] = BitmapFactory.decodeResource(getResources(), R.drawable.heart_grey);
bagX = 10;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvasWidth = canvas.getWidth();
canvasHeight = canvas.getHeight();
canvas.drawBitmap(backgroundImage, 0, 0, null);
int minBagX = bag[0].getWidth();
int maxBagX = canvasWidth - bag[0].getWidth() * 3;
bagX = bagX + bagSpeed;
if (bagX < minBagX) {
bagX = minBagX;
}
if (bagX < maxBagX) {
bagX = maxBagX;
}
bagSpeed = bagSpeed + 2;
if (touch) {
canvas.drawBitmap(bag[1], bagX, bagY, null);
}
else {
canvas.drawBitmap(bag[0], bagX, bagY, null);
}
canvas.drawText("Score : ", 20, 60, scorePaint);
canvas.drawBitmap(life[0], 500, 10, null);
canvas.drawBitmap(life[0], 570, 10, null);
canvas.drawBitmap(life[0], 640, 10, null);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
touch = true;
bagSpeed = -12;
}
return true;
}
}
I can tap on the screen to move the object to the left, and the more I tap on it, the further it should move to the left. However, the problem is that the object moves too much to the right, and goes off the screen. I want it to stop at the edge, so its still visible on the screen. Furthermore, the object is positioned in the center bottom of the screen, how do I position it to the bottom left corner? You can see how it works by looking at the GIF below.
This condition is never applied on the code you've provided. It continues to exceed the max value.
if (bagX < maxBagX) {
bagX = maxBagX;
}
It should look like this:
if (bagX >= maxBagX) {
bagX = maxBagX;
}
And the maxBagX value should be canvasWidth - bag[0].getWidth() in order to achieve the right edge of the bag itself. (Unless you're using the multiply 3 times for a different reason, this should be the solution.)
Create object of the layout which ever you are using linear,relative or constraint then fetch width of the screen using that object using
object.getWidth();
I did it like this //the maintainoffscreen function will maintain the path to donot move off screen, and return true ,, else if path does not collide to any side this will return false, and you can move the path by setting offset.
private boolean maintainOffScreen(Path path, float px, float py){
boolean done = true;
RectF rectF = new RectF();
path.computeBounds(rectF,true);
float l = getPathWidth(path) - rectF.right;
float r = getPathWidth(path) + rectF.left;
float t = getPathHeight(path) - rectF.bottom;
float b = getPathHeight(path)+ rectF.top;
if(l < (-1) && r < getWidth() && b < getHeight() && t< (-1)){
//if path does not collide to any side//you can move your path here as well
//by setting Path.offset(px,py)
done = false;
offScreen = OffScreen.NOOFF; //im using these as enum according to my need,
//as this is saving which side of screen collides
}
else if(l >= (-1)){
path.offset(px+(l),py);
offScreen = OffScreen.LEFT;
offscrenn_xl = 1;
done = true;
}
else if(r >= getWidth()){
float s = getWidth() - r;
path.offset(px+(s),py);
offScreen = OffScreen.RIGHT;
offscrenn_xr = s;
done = true;
}
else if(b >= getHeight()){
float s = getHeight() - b;
path.offset(px,py+s);
offScreen = OffScreen.BOTTOM;
offscrenn_yb = s;
done = true;
}
else if(t >= (-1)){
path.offset(px,py+(t));
offScreen = OffScreen.TOP;
offscrenn_yt = t;
done = true;
}
return done;
}
confuse about px,py ? this is the movement of you finger with respect to path
`
final float px = event.getX() - this.startX;
final float py = event.getY() - this.startY;
if(!maintainOffScreen(path,px,py)){path.offset(px,py); }` //you should call
//this function here ,this will move your path to finger if there is no collision.
//and donot forgot to set the startx,and starty yo new positon like this
this.startX = event.getX();
this.startY = event.getY();

OutOfMemory Error need solution

I know it's a common problem for working with bitmaps but I don't know how to go on this with example. Especially because the size of the image view is not big that you would say it should cause a memory error. I think it's because I create the bitmaps to often instead of creating it once ant display it every five seconds but don't know how to do it. First of all the code for creating the bitmaps.
java class trapViews:
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int x = 20;
Random r = new Random();
int i1 = r.nextInt(900 - 200) + 200;
rnd = new Random();
//Linke Seite
System.gc();
Bitmap image = BitmapFactory.decodeResource(getResources(), R.drawable.stachelnstart);
Bitmap resizedBitmap = Bitmap.createScaledBitmap(image, i1, 300, true);
float left = (float) 0;
float top = (float) (getHeight() - resizedBitmap.getHeight());
canvas.drawBitmap(resizedBitmap, left, top, paint);
//rechte Seite
Bitmap images = BitmapFactory.decodeResource(getResources(), R.drawable.stachelnstart1);
Bitmap resizedBitmaps = Bitmap.createScaledBitmap(images, getWidth()-resizedBitmap.getWidth()-OwlHole, 300, true);
float left1 = (float) (getWidth() - resizedBitmaps.getWidth());
float top1 = (float) (getHeight() - resizedBitmaps.getHeight());
canvas.drawBitmap(resizedBitmaps, left1, top1, paint);
}
}
Creates a drawable on the right and left side of the screen with an random length.
Now I call this every 5 sec in the MainActivity with a Handler:
final Handler h = new Handler();
Runnable r = new Runnable() {
public void run() {
System.gc();
traps();
h.postDelayed(this,5000); // Handler neustarten
}
}
private void traps() {
container = (ViewGroup) findViewById(R.id.container);
trapViews tv = new trapViews(this);
container.addView(tv,
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
//tV.setImageCount(8);
h.postDelayed(r, 5000);
}
first of all, it's working like I want it to work. But every time a new drawable appears my game is lagging and after 5-6 times creating one its crashing down
The System.gc() and bitmap.recycle functions aren't working really well. Does anybody have a solution?
You're creating the bitmaps every 5 seconds which is not a good idea as they are always the same. You should create them once instead
trapViews extends View{
Bitmap image;
Bitmap resizedBitmap;
//rechte Seite
Bitmap images ;
Bitmap resizedBitmaps;
trapViews(Context c){
image = BitmapFactory.decodeResource(getResources(), R.drawable.stachelnstart);
images = BitmapFactory.decodeResource(getResources(), R.drawable.stachelnstart1);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int x = 20;
Random r = new Random();
int i1 = r.nextInt(900 - 200) + 200;
rnd = new Random();
//Linke Seite
//I have left this bitmap in the here as it is affected by the random int
resizedBitmap = Bitmap.createScaledBitmap(image, i1, 300, true);
float left = (float) 0;
float top = (float) (getHeight() - resizedBitmap.getHeight());
canvas.drawBitmap(resizedBitmap, left, top, paint);
//create this bitmap here as getWidth() will return 0 if created in the view's constructor
if(resizedBitmaps == null)
resizedBitmaps = Bitmap.createScaledBitmap(images, getWidth()-resizedBitmap.getWidth()-OwlHole, 300, true);
float left1 = (float) (getWidth() - resizedBitmaps.getWidth());
float top1 = (float) (getHeight() - resizedBitmaps.getHeight());
canvas.drawBitmap(resizedBitmaps, left1, top1, paint);
}
}
Remember to call Bitmap.recycle() after you replace a bitmap or when it's not visible anymore.
Creating mutiple bitmaps is not the problem but left unused objects without making use of recycle to free up memory.

Trouble rotating image in Android aplication

I am currently writing an android application that should rotate an image towards a set location based on the users current location. I can tell that the set location is setting correctly, and the current location seems to be updating, but instead of rotating the image, the app just zooms in on the image and then does nothing. Any help would really be appreciated!
public double bearing(double lat1, double lon1, double lat2, double lon2) {
double longitude1 = lon1;
double longitude2 = lon2;
double latitude1 = Math.toRadians(lat1);
double latitude2 = Math.toRadians(lat2);
double longDiff = Math.toRadians(longitude2 - longitude1);
double y = Math.sin(longDiff) * Math.cos(latitude2);
double x = Math.cos(latitude1) * Math.sin(latitude2) - Math.sin(latitude1) * Math.cos(latitude2) * Math.cos(longDiff);
return (Math.toDegrees(Math.atan2(y, x)) + 360) % 360;
}
private void rotateImageView(ImageView imageView, int drawable, float rotate) {
// Decode the drawable into a bitmap
Bitmap bitmapOrg = BitmapFactory.decodeResource(getResources(),
drawable);
// Get the width/height of the drawable
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
int width = bitmapOrg.getWidth(), height = bitmapOrg.getHeight();
// Initialize a new Matrix
Matrix matrix = new Matrix();
// Decide on how much to rotate
rotate = rotate % 360;
// Actually rotate the image
matrix.postRotate(rotate, width, height);
// recreate the new Bitmap via a couple conditions
Bitmap rotatedBitmap = Bitmap.createBitmap(bitmapOrg, 0, 0, width, height, matrix, true);
//BitmapDrawable bmd = new BitmapDrawable( rotatedBitmap );
//imageView.setImageBitmap( rotatedBitmap );
imageView.setImageDrawable(new BitmapDrawable(getResources(), rotatedBitmap));
imageView.setScaleType(ImageView.ScaleType.CENTER);
}
public void onLocationChange() {
// If we don't have a Location, we break out
if (currentLocation == null) return;
double azimuth = bearing(currentLocation.getLatitude(), currentLocation.getLongitude(), baseLocation.getLatitude(), baseLocation.getLongitude());
double baseAzimuth = azimuth;
GeomagneticField geoField = new GeomagneticField(Double
.valueOf(currentLocation.getLatitude()).floatValue(), Double
.valueOf(currentLocation.getLongitude()).floatValue(),
Double.valueOf(currentLocation.getAltitude()).floatValue(),
System.currentTimeMillis()
);
azimuth -= geoField.getDeclination(); // converts magnetic north into true north
// Store the bearingTo in the bearTo variable
float bearTo = currentLocation.bearingTo(baseLocation);
// If the bearTo is smaller than 0, add 360 to get the rotation clockwise.
if (bearTo < 0) {
bearTo = bearTo + 360;
}
//This is where we choose to point it
double direction = bearTo - azimuth;
// If the direction is smaller than 0, add 360 to get the rotation clockwise.
if (direction < 0) {
direction = direction + 360;
}
float fDir = (float) direction;
rotateImageView((ImageView) findViewById(R.id.arrow), R.drawable.ic_launcher, fDir);
}
private Runnable updateTimeChange = new Runnable() {
public void run() {
onLocationChange();
customHandler.postDelayed(this, 500);
}
};
If your target is greater than 11 then you can try view.setRotation() method:
image.setRotation(angle); // this is for your imageview where image is the imageview object
From the developers page. getRotation()
Sets the degrees that the view is rotated around the pivot point. Increasing values result in clockwise rotation.
If you want to setRotation in xml the xml tag is :
android:rotation

Animated zooming out from the right-bottom-corner from JComponent causes stuttering

I'm currently implementing a swing JComponent as an image viewer with the ability to zoom, rotate and display the image centered and all of this animated. I have implemented all those features, but have a problem during zooming out from the right bottom corner of the image.
Everytime the animation starts to stutter and only from the right or bottom edge of the panel.
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
if (this.workingCopy != null) {
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
Point2D center = new Point2D.Double((getPreferredSize().width) / 2, (getPreferredSize().height) / 2);
g2d.scale(getZoom(), getZoom());
g2d.rotate(Math.toRadians(getRotation()), (center.getX() + 0) / getZoom(), (center.getY() + 0) / getZoom());
g2d.drawImage(this.workingCopy,
(int) Math.round(((getPreferredSize().width - (image.getWidth() * getZoom())) / 2) / getZoom()),
(int) Math.round(((getPreferredSize().height - (image.getHeight() * getZoom())) / 2) / getZoom()), null);
}
}
public synchronized void setZoom(final double zoom, boolean animated, final Point point) {
final double oldZoom = getZoom();
final Dimension viewSize = getPreferredSize();
final Rectangle viewRect = getVisibleRect();
// get relative point
double relX = viewRect.getX() / viewSize.getWidth();
double relY = viewRect.getY() / viewSize.getHeight();
// new size
double newViewSizeWidth = (getImageBounds().getWidth() / oldZoom) * zoom;
double newViewSizeHeight = (getImageBounds().getHeight() / oldZoom) * zoom;
double deltaDiffX = (point.getX() - viewRect.getX()) / viewSize.getWidth();
double deltaDiffY = (point.getY() - viewRect.getY()) / viewSize.getHeight();
double newDiffX = newViewSizeWidth * deltaDiffX;
double newDiffY = newViewSizeHeight * deltaDiffY;
double viewPositionX = (newViewSizeWidth * relX) + newDiffX - (point.getX() - viewRect.getX());
double viewPositionY = (newViewSizeHeight * relY) + newDiffY - (point.getY() - viewRect.getY());
final Point newPoint = new Point((int) Math.round(viewPositionX), (int) Math.round(viewPositionY));
if (animated && !zooming) {
Animator animator = new Animator(getAnimationSpeed(), new TimingTargetAdapter() {
#Override
public void begin() {
super.begin();
zooming = true;
}
#Override
public void timingEvent(final float fraction) {
super.timingEvent(fraction);
double zoomDiff = zoom - oldZoom;
setZoom(oldZoom + (fraction * zoomDiff),
new Point(
(int) Math.round(viewRect.getX() - (viewRect.getX() - newPoint.getX()) * fraction),
(int) Math.round(viewRect.getY() - (viewRect.getY() - newPoint.getY()) * fraction)));
}
#Override
public void end() {
super.end();
zooming = false;
}
});
animator.start();
} else {
setZoom(zoom, newPoint);
}
}
Can someone point out what I do I do wrong or forget to concider for the zoom animation ?
Everything works except the the stuttering during the animated zoom out.
Thanks in advance for your help.
Ok, I found the problem by chance. The problem was the JPanel still had a LayoutManager and this caused the problem when zooming out from the right/bottom corner.
After I set the LayoutManager to null, everything worked how it should.

Categories

Resources