Looping an Image - javascript

i am having trouble on how i should loop an image of mine like 50 times, but appear on different times.
This is my Code:
//Uploading car
var car1 = new Image();
car1.src = "http://images.clipartpanda.com/car-top-view-clipart-red-racing-car-top-view-fe3a.png";
//Setting properties of car
var x1 = 450;
var y1 = 50;
var speed1 = 10;
var angle1 = -990;
var mod1 = 0.2;
//Interval for animation
var moveInterval = setInterval(function () {
drawCar();
}, 30);
//Drawing the car turning and changing speed
function drawCar() {
x1 += (speed1 * mod1) * Math.cos(Math.PI / 180 * angle1);
y1 += (speed1 * mod1) * Math.sin(Math.PI / 180 * angle1);
context.save();
context.translate(x1, y1);
context.rotate(Math.PI / 180 * angle1);
context.drawImage(car1, -(car1.width / 2), -(car1.height / 2));
context.restore();
draw_multiple();
}
function draw_multiple() {
var i;
var obstacles = []; // How to put array so this code will work with array?
for(i = 0; i < 10 ; i++) {
drawCar ((i*70)+100,100, 30);
}
}
In this code is where i have drawn my image but the last function is the for loop function. Thanks in advance

Something like this
function draw_multiple()
{
var obstacles = {
"item1": (5x3) + 2, // obstacles[0] returns 17
"item2": (2/2) * 5, // obstacles[1] returns 5
"anotherItem": "This is a string" // obstacles[2] returns "This is a string"
};
for(var i=0; i<10 ; i++) {
drawCar ((i*70)+100,100, 30);
}
for(var i=0; i<obstacles.length ; i++) {
drawObstacles( obstacles[i] );
}
}
You need to create the function drawObstacles().

Related

How to plot a imperfect circle in JS with p5

I've created a script to calculate coordinates of a circle in JS. I'm using p5.js to draw the circle but when I run the script nothing happens. I assume it has to do with the way I'm plotting the vertices?
var xValues = [];
var yValues = [];
function setup() {
createCanvas(400, 400);
background(220);
crookedCircle(10, 10, 10, 10);
}
function draw() {}
function crookedCircle(radius, steps, centerX, centerY) {
for (var i = 0; i < steps; i++) {
xValues[i] = (centerX + radius * Math.cos(2 * Math.PI * i / steps));
yValues[i] = (centerY + radius * Math.sin(2 * Math.PI * i / steps));
for (let x = 0; x < xValues.length; x++) {
for (let y = 0; y < yValues.length; y++) {
//console.log("x: "+xValues[x] + " y: "+yValues[y])
beginShape();
vertex(xValues[x] + random(-10, 10), yValues[y]) + random(-10, 10);
endShape(CLOSE);
}
}
}
}
Here I cleaned what was written, I can annotate it to explain if you wish so. Also instead of random, I recommend you explore the noise() function here, which would make the circle look smoother.
function setup() {
createCanvas(400, 400);
background(220);
crookedCircle(10, 10, width / 2, height / 2);
}
function draw() {}
function crookedCircle(radius, steps, centerX, centerY) {
var xValues = [];
var yValues = [];
for (var i = 0; i < steps; i++) {
let rad = radius + random(-radius / 10,radius / 10) // you can change the 10 here to how intense you want the change to be;
xValues[i] = (centerX + rad * cos(2 * PI * i / steps));
yValues[i] = (centerY + rad * sin(2 * PI * i / steps));
}
beginShape();
for(let i = 0; i < xValues.length; i ++){
curveVertex(xValues[i], yValues[i]);
}
endShape(CLOSE);
}
You draw many many shapes with just 1 point. beginShape and endShape encloses the vertices of a shape. Therefore you have to call beginShape before the loop and endShape after the loop:
function crookedCircle(radius, steps, centerX, centerY) {
beginShape();
for (var i = 0; i < steps; i++) {
// [...]
}
endShape(CLOSE);
}
One loop is enough if you want to draw 1 circle:
var xValues = [];
var yValues = [];
function setup() {
createCanvas(400, 400);
}
function draw() {
background(220);
fill(255)
crookedCircle(100, 90, 120, 120);
}
function crookedCircle(radius, steps, centerX, centerY) {
for (var i = 0; i < steps; i++) {
xValues[i] = centerX + radius * Math.cos(2 * Math.PI * i / steps);
yValues[i] = centerY + radius * Math.sin(2 * Math.PI * i / steps);
}
beginShape();
for(let i = 0; i < steps; i ++) {
vertex(xValues[i] + random(-2, 2), yValues[i] + random(-2, 2));
}
endShape(CLOSE);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>

Force calculation for magnetic pendulum and numerical integration

I've been working on a simulation of a magnetic pendulum (Magnetic Pendulum for reference). Now, I have a question concerning how you calculate the forces/acceleration:
In examples you find online of the magnetic pendulum (such as the one provided above), and other physics force and acceleration calculations, the forces are calculated at a starting position, the position updated according to time-step dt and then new forces at time t+dt calculated to get a new position and so on. Basic numerical integration, makes sense.
However, I noticed, that if I calculate the forces this way, things don't go as one would expect. For example: when the pendulum is attracted by a magnet it just stops at the particluar magnet even tough you would expect it to "overswing" a bit. Therefore, I introduced three variables for each force which is all previous forces added together, and now it somehow seems to work correctly.
I'm wondering if
the way of calculating the forces I used makes sense in a physics simulation and
if not, then what am I doing wrong? Because it seems to work for other people.
Here's my code:
p5.disableFriendlyErrors = true;
let magnete = [];
let particles = [];
var maganzahl = 3; //number of magnets
var magradius = 100; //radius of magnets from mid-point
var G = 0.002; //coefficient of force towards middle (gravity)
var R = 0.2; //friction coefficient
var h = 50; //height of bob over magnets
var M = 150000; //strength of magnets
var m = 1; //mass (might not work)
var dt = 0.25; //timestep
var d = 2; //pixel density
var counter2 = 0;
var Reset;
var end = false;
//--------------------------------------------
function setup() {
createCanvas(600, 600);
pixelDensity(d);
background(60);
Reset = createButton('Reset');
Reset.position(width + 19, 49);
Reset.mousePressed(resetcanvas);
//construction of magnets
for (var i = 0; i < maganzahl; i++) {
magnete.push(new Magnet((width / 2) + magradius * cos(TWO_PI * (i / maganzahl)), (height / 2) + magradius * sin(TWO_PI * (i / maganzahl)), i));
}
}
//construction of a new "starting position" by mouse-click
function mousePressed() {
if (mouseX < width && mouseY < height) {
particles.push(new Particle(mouseX, mouseY));
}
}
function draw() {
for (var i = 0; i < particles.length; i++) {
if (particles[i].counter < 1000) {
//5 updates per frame(to speed it up)
for (var k = 0; k < 5; k++) {
for (var j = 0; j < magnete.length; j++) {
particles[i].attracted(magnete[j]);
}
particles[i].update();
particles[i].show2();
}
} else if (particles[i].counter < 1001) {
particles[i].counter += 1;
var nearest = [];
for (var j = 0; j < magnete.length; j++) {
nearest.push(particles[i].near(magnete[j]));
}
if (nearest.indexOf(min(nearest)) == 0) {
var c = color("green");
}
if (nearest.indexOf(min(nearest)) == 1) {
var c = color("purple");
}
if (nearest.indexOf(min(nearest)) == 2) {
var c = color("orange");
}
if (nearest.indexOf(min(nearest)) == 3) {
var c = color("blue");
}
if (nearest.indexOf(min(nearest)) == 4) {
var c = color("red");
}
if (nearest.indexOf(min(nearest)) == 5) {
var c = color("yellow");
}
//show particle trace according to nearest magnet
particles[i].show(c);
}
}
//displaying magnets
for (var i = 0; i < magnete.length; i++) {
magnete[i].show();
}
//displaying mid-point
stroke(255);
circle(width / 2, height / 2, 3);
}
function resetcanvas() {
background(60);
}
function Particle(x, y) {
this.orgpos = createVector(x, y);
this.pos = createVector(x, y);
this.prev = createVector(x, y);
this.vel = createVector();
this.acc = createVector();
this.accpre = createVector();
this.accprepre = createVector();
this.velprediction = this.vel;
this.magnetf = createVector();
this.gravity = createVector();
this.friction = createVector();
this.shape = new Array();
this.counter = 0;
//calculating new positions
this.update = function() {
//predictor for velocity -> Beeman's algorithm
this.velprediction.add(this.accpre.mult(3 / 2 * dt).add(this.accprepre.mult(-1 / 2 * dt)));
var momgrav = createVector(width / 2 - this.pos.x, height / 2 - this.pos.y);
var momfric = createVector(this.velprediction.x, this.velprediction.y);
momgrav.mult(G * m); //force due to gravity
momfric.mult(-R); //force due to friction
this.gravity.add(momgrav);
this.friction.add(momfric);
//a = F/m
this.acc = createVector((this.magnetf.x + this.gravity.x + this.friction.x) / m, (this.magnetf.y + this.gravity.y + this.friction.y) / m);
//-=Beeman's Algorithm=-
this.vel.add(this.acc.mult(dt * 1 / 3).add(this.accpre.mult(dt * 5 / 6)).add(this.accprepre.mult(-1 / 6 * dt)));
this.pos.add(this.vel.mult(dt).add(this.accpre.mult(dt * dt * 2 / 3)).add(this.accprepre.mult(-1 / 6 * dt * dt)));
this.accprepre = createVector(this.accpre.x, this.accpre.y);
this.accpre = createVector(this.acc.x, this.acc.y);
this.velprediction = createVector(this.vel.x, this.vel.y);
this.counter += 1;
this.shape.push(new p5.Vector(this.pos.x, this.pos.y));
}
//calculating force due to magnets -> attracted called earlier than update in sketch.js
this.attracted = function(target) {
var magn = createVector(target.pos.x - this.pos.x, target.pos.y - this.pos.y);
var dist = sqrt(sq(h) + sq(magn.x) + sq(magn.y)); //distance bob - magnet
strength = M / (Math.pow(dist, 3));
magn.mult(strength);
this.magnetf.add(magn);
}
//calculating distance to target
this.near = function(target) {
var dist = sqrt(sq(h) + sq(this.pos.x - target.pos.x) + sq(this.pos.y - target.pos.y));
return (dist);
}
//trace
this.show = function(col) {
beginShape();
stroke(col);
for (var i = 0; i < this.shape.length - 1; i++) {
line(this.shape[i].x, this.shape[i].y, this.shape[i + 1].x, this.shape[i + 1].y);
strokeWeight(2);
noFill();
}
endShape();
}
//dots
this.show2 = function() {
strokeWeight(1)
point(this.pos.x, this.pos.y);
}
}
function Magnet(x, y, n) {
this.pos = createVector(x, y);
this.n = n;
this.show = function() {
noStroke();
//color for each magnet
if (n == 0) {
fill("green");
}
if (n == 1) {
fill("purple");
}
if (n == 2) {
fill("orange");
}
if (n == 3) {
fill("blue");
}
if (n == 4) {
fill("red");
}
if (n == 5) {
fill("yellow");
}
strokeWeight(4);
circle(this.pos.x, this.pos.y, 10);
}
}
Any help would be greatly appreciated!
I found the issue! So apparently in p5.js you have to be careful with your vector calculations. If you for example do something like:
[some variable] = [Vector].add([anotherVector].mult(2));
both [Vector] and [anotherVector] change their value. Makes sense now that I think about it...
The fact that it still seemed to work somewhat "realistically" and that I managed to generate some beautiful pictures using it (Such as this one 1 and this one 2) is still quite mysterious to me but I guess sometimes numbers just do their magic ;)
Run it:
Code Preview
If you want to change some variables/edit:
p5.js Web Editor

How to chage the color of shapes with easeljs

function initCircles() {
circles = [];
for (var i = 0; i < 100; i++) {
var circle = new createjs.Shape();
var r = 7;
var x = window.innerWidth * Math.random();
var y = window.innerHeight * Math.random();
var color = colors[Math.floor(i % colors.length)];
var alpha = 0.2 + Math.random() * 0.5;
circle.alpha = alpha;
circle.radius = r;
circle.graphics.beginFill(color).drawCircle(0, 0, r);
circle.x = x;
circle.y = y;
circles.push(circle);
circle.movement = 'float';
circle.addEventListener("mouseover", function(event) {
circle.graphics.clear().beginFill("red").drawRect(0, 0, 50, 60).endFill();
stage.update(event);
});
stage.addChild(circle);
}
}
I'm trying to add a mouseover listener on the little circles I create on the page, I hope that once I place the cursor on the circle, it becomes a rectangle. However, the rectangle always appear where some other circle exists rather than the circle I point to.
Save your beginFill command, and change it later:
// your other code above
var fillCmd = circle.graphics.beginFill(color).command; // Note the .command at the end
circle.graphics.drawCircle(0, 0, r);
// your other code below
// Later
fillCmd.style = "ff0000";
Here is an article about it, and here are the docs -
Admittedly, this could be documented better. :)
The problem is that you are referencing a mutable variable inside of the closure. There are a couple of ways to solve that.
1) Either somehow reference the circle from the event variable inside of the nested function (if the event has support for that), or
2) Bind the value of the variable inside another function, e.g:
function initCircles() {
circles = [];
for (var i = 0; i < 100; i++) {
var circle = new createjs.Shape();
var r = 7;
var x = window.innerWidth * Math.random();
var y = window.innerHeight * Math.random();
var color = colors[Math.floor(i % colors.length)];
var alpha = 0.2 + Math.random() * 0.5;
circle.alpha = alpha;
circle.radius = r;
circle.graphics.beginFill(color).drawCircle(0, 0, r);
circle.x = x;
circle.y = y;
circles.push(circle);
circle.movement = 'float';
addEventListener(circle);
stage.addChild(circle);
}
function addEventListener(circle) {
circle.addEventListener("mouseover", function(event) {
circle.graphics.clear().beginFill("red").drawRect(0, 0, 50, 60).endFill();
stage.update(event);
});
}
}

Outputting an array of text values in the shape of a circle in p5.js

Ahoy! I am new to programming and working in p5.js. My conundrum is this: I'd like to create a digital clock, and output the numbers on the clock by using some for loops and an array for the clock values (text 1-12). I've figured out how to make a ticking image of a clock...but can't figure the rest out. When I run the below code, it doesn't throw any errors but the text/numbers on the clock aren't executing. I've tried putting the first for loop below within the setup function, and nothing changes. What am I doing wrong? I feel like I'm confused around the second for loop and how to actually print the numbers to the screen, like regarding: (text([i])). Please let me know if I need to clarify more - any help is appreciated! Trying to learn as much as I can.
//Simple second clock.
// An exercise in translating from polar to cartesian coordinates
var radius = 120.0;
var angle = 0.0;
var x=0, y=0;
var digits = [12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
function setup() {
createCanvas(windowWidth,windowHeight);
}
function draw() {
for (var i = 0; i < 12; i++) { //loop for digits. Populate array.
digits[i] = text("[i]", 10, -radius+10);
for (var i = 0; i < digits.length; i++) {
fill(255,0,255)
text([i]);
}
}
background(255);
translate(width/2, height/2);
stroke(205,205,55);
fill(255,0,255);
ellipse(0,0,10,10);
noFill();
ellipse(0,0,250,250);
stroke(25);
fill(205,205,55);
//text("12", 0, -radius+PI+10); //if I were to manually do each number
// text("1", 30, -radius+PI+20);
// text("2", 60, -radius+PI+30);
// text("3", 90, -radius+PI+40);
angle = (second() / 59.0) * TWO_PI;
// memorize this formula, it's helpful
x = cos(angle)* radius;
y = sin(angle)* radius;
stroke(255,0,255);
//draw a line from the center of our screen and as long as our radius
line(0,0,x,y);
ellipse(x,y,10,10);
}
This is really a question about polar coordinates. Your x and y coordinates in the commented section are off. This is the idea:
var angleOffset = -1*PI/2;
for (var i=1; i<=12; i++) {
angle = 2*PI*i/12 + angleOffset;
text(i, radius*cos(angle), radius*sin(angle));
}
Edit: Full working code below
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.4.23/p5.min.js"></script>
<script>
var radius = 120.0;
var angle = 0.0;
var x=0, y=0;
var digits = [12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
function setup() {
createCanvas(windowWidth,windowHeight);
}
function draw() {
for (var i = 0; i < 12; i++) { //loop for digits. Populate array.
digits[i] = text("[i]", 10, -radius+10);
for (var i = 0; i < digits.length; i++) {
fill(255,0,255)
text([i]);
}
}
background(255);
translate(width/2, height/2);
stroke(205,205,55);
fill(255,0,255);
ellipse(0,0,10,10);
noFill();
ellipse(0,0,250,250);
stroke(25);
fill(205,205,55);
var angleOffset = -1*PI/2;
for (var i=1; i<=12; i++) {
angle = 2*PI*i/12 + angleOffset;
text(i, radius*cos(angle), radius*sin(angle));
}
angle = (second() / 59.0) * TWO_PI;
// memorize this formula, it's helpful
x = cos(angle)* radius;
y = sin(angle)* radius;
stroke(255,0,255);
//draw a line from the center of our screen and as long as our radius
line(0,0,x,y);
ellipse(x,y,10,10);
}
setup();
draw();
</script>
// ABC CLOCK
var radius = 120.0;
var angle = 0.0;
var x = 0,
y = 0;
var digits = [];
var se = 0;
var nloops=0
var sentence="12 1 2 3 4 5 6 7 8 9 10 11"
//var sentence="A B C D E F G"
var nl = 0;
var letter1=''
var letter2=''
function setup() {
createCanvas(windowWidth, windowHeight);
digits = sentence.split(" ");
letter1=digits[0]
letter2=digits[0]
nl = digits.length; //numberOfLetters
textAlign(CENTER, CENTER);
frameRate(1);
}
function draw() {
background(9);
textAlign(LEFT, CENTER);
fill(255)
text(letter1+' '+letter2, 40,40)
//text(nloops+' '+se,40,60)
translate(width / 2, height / 2);
textAlign(CENTER, CENTER);
cont = 0;
mod = (2 * PI) / nl;
ia = -HALF_PI; //init angle
pr1= 0.9; //proportion radius letters
pr2= 0.5; //proportion radius letters
rl = radius * pr1; //radius letter
for (var i = 0; i < nl; i++) {
noFill();
stroke(120);
text(digits[i], rl * cos(cont + ia), rl * sin(cont + ia));
cont += mod;
}
angle = (TWO_PI / nl) * se;
angle2 = (TWO_PI / nl) * nloops;
x = rl * cos(angle + ia);
y = rl * sin(angle + ia);
x1 = rl * cos(angle2 + ia);
y1 = rl * sin(angle2 + ia);
stroke(55);
line(0, 0, x * pr1, y * pr1);
stroke(255);
line(0, 0, x1 * pr2, y1 * pr2);
stroke(55);
fill(0);
ellipse(0, 0, 10, 10);
noFill();
ellipse(0, 0, 250, 250);
se += 1;
if(se%nl==0){
nloops+=1
if (nloops%nl==0){
nloops=0
}
se=0
}
letter1=digits[nloops]
letter2=digits[se]
}
A little more developed

Algorithm for drawing a 5 point star

I'm currently working on a solution for drawing a standard 5-point star on the canvas using JavaScript. I'm part way there but can't figure it out entirely. I'd appreciate any tips or pointers anyone might have.
I made some changes to the code that Chris posted so it would work for me:
var alpha = (2 * Math.PI) / 10;
var radius = 12;
var starXY = [100,100]
canvasCtx.beginPath();
for(var i = 11; i != 0; i--)
{
var r = radius*(i % 2 + 1)/2;
var omega = alpha * i;
canvasCtx.lineTo((r * Math.sin(omega)) + starXY[0], (r * Math.cos(omega)) + starXY[1]);
}
canvasCtx.closePath();
canvasCtx.fillStyle = "#000";
canvasCtx.fill();
Hope it helps...
n point star, points are distributed evenly around a circle. Assume the first point is at 0,r (top), with the circle centred on 0,0, and that we can construct it from a series of triangles rotated by 2π/(2n+1):
Define a rotation function:
function rotate2D(vecArr, byRads) {
var mat = [ [Math.cos(byRads), -Math.sin(byRads)],
[Math.sin(byRads), Math.cos(byRads)] ];
var result = [];
for(var i=0; i < vecArr.length; ++i) {
result[i] = [ mat[0][0]*vecArr[i][0] + mat[0][1]*vecArr[i][1],
mat[1][0]*vecArr[i][0] + mat[1][1]*vecArr[i][1] ];
}
return result;
}
Construct a star by rotating n triangles:
function generateStarTriangles(numPoints, r) {
var triangleBase = r * Math.tan(Math.PI/numPoints);
var triangle = [ [0,r], [triangleBase/2,0], [-triangleBase/2,0], [0,r] ];
var result = [];
for(var i = 0; i < numPoints; ++i) {
result[i] = rotate2D(triangle, i*(2*Math.PI/numPoints));
}
return result;
}
Define a function to draw any given array of polygons:
function drawObj(ctx, obj, offset, flipVert) {
var sign=flipVert ? -1 : 1;
for(var objIdx=0; objIdx < obj.length; ++objIdx) {
var elem = obj[objIdx];
ctx.moveTo(elem[0][0] + offset[0], sign*elem[0][1] + offset[1]);
ctx.beginPath();
for(var vert=1; vert < elem.length; ++vert) {
ctx.lineTo(elem[vert][0] + offset[0], sign*elem[vert][1] + offset[1]);
}
ctx.fill();
}
}
Use the above to draw a 5 point star:
var canvas = document.getElementsByTagName('canvas')[0];
var ctx = canvas.getContext('2d');
var offset = [canvas.width/2, canvas.height/2];
ctx.fillStyle="#000000";
var penta = generateStarTriangles(5, 200);
drawObj(ctx, penta, offset, true);
See it here http://jsbin.com/oyonos/2/
This is a problem where Turtle Geometry makes things simple:
5-point star:
repeat 5 times:
fwd 100,
right 144,
fwd 100,
left 72,
You need to draw the inner bits and a complete circle is 2 * PI radians. In the example below r is the radius of the encompassing circle. Code below is from an open source project (http://github.com/CIPowell/PhyloCanvas)
var alpha = (2 * Math.PI) / 10;
// works out the angle between each vertex (5 external + 5 internal = 10)
var r_point = r * 1.75; // r_point is the radius to the external point
for(var i = 11; i != 0; i--) // or i could = 10 and you could use closePath at the end
{
var ra = i % 2 == 1 ? rb: r;
var omega = alpha * i; //omega is the angle of the current point
//cx and cy are the center point of the star.
node.canvas.lineTo(cx + (ra * Math.sin(omega)), cy + (ra * Math.cos(omega)));
}
//Store or fill.
NB: This is one of those many ways to skin a cat things, I'm sure someone else has another way of doing it. Also, the reason for the decremental loop rather than the incremental is preformance. i != 0 is more efficient than i < 10 and i-- is more efficient than i++. But performance matters a lot for my code, it might not be so crucial for yours.
I was looking for such an algorithm myself and wondered if I could invent one myself. Turned out not to be too hard. So here is a small function to create stars and polygons, with options to set the number of point, outer radius, and inner radius (the latter does only apply to stars).
function makeStar(c, s, x, y , p, o, i) {
var ct = c.getContext('2d');
var points = p || 5;
var outer_radius = o || 100;
var inner_radius = i || 40;
var start_x = x || 100;
var start_y = y || 100;
var new_outer_RAD, half_new_outer_RAD;
var RAD_distance = ( 2 * Math.PI / points);
var RAD_half_PI = Math.PI /2;
var i;
ct.moveTo(start_x, start_y);
ct.beginPath();
for (i=0; i <= points; i++) {
new_outer_RAD = (i + 1) * RAD_distance;
half_new_outer_RAD = new_outer_RAD - (RAD_distance / 2);
if (s) {
ct.lineTo(start_x + Math.round(Math.cos(half_new_outer_RAD - RAD_half_PI) * inner_radius), start_y + Math.round(Math.sin(half_new_outer_RAD - RAD_half_PI) * inner_radius));
}
ct.lineTo(start_x + Math.round(Math.cos(new_outer_RAD - RAD_half_PI) * outer_radius), start_y + Math.round(Math.sin(new_outer_RAD - RAD_half_PI) * outer_radius));
}
ct.stroke();
}
var canvas = document.getElementById('canvas');
makeStar(canvas);
makeStar(canvas, true, 120,200, 7, 110, 40);

Categories