This is the package for creating simple p5 canvas in Svelte that has basic camera functionality build-in. With this you can draw different objects inside this environment with automatic screen coordinates to plane coordinates translation. This package also supports grid display as well as some mouse events capturing.
Basic usage:
<script lang="ts">
import Canvas from "camera-canvas-svelte";
const update = (cx) => {
cx.strokeWeight(3);
cx.stroke(0, 132, 255);
cx.rect(0, 0, 1, 1, 0.2);
cx.stroke(255, 0, 0);
cx.rect(1, -1, 1, 1, 0.2);
}
</script>
<Canvas
width={400}
height={400}
minX={-4}
minY={-4}
maxX={4}
maxY={4}
{update}
/>
As result we get following canvas:
Camera can be setup with several parameters passed as props to Canvas component. Following properties are:
width : number // width of the canvas
height : number // height of the canvas
X : number = 0 // sets initial X-position for camera
Y : number = 0 // sets initial Y-position for camera
minX : number = -1000 // sets left boundary for camera
maxX : number = 1000 // sets right boundary for camera
minY : number = -1000 // sets top boundary for camera
maxY : number = 1000 // sets bottom boundary for camera
hideBoundary : boolean = false // whether boundary won't be displayed
update : (cx)=>any // function that will be called each frame
init : (cx)=>any // function that will be called once after it's creation
hideGrid : boolean = false // defines whether gird will be drawn on canvas
gridSize : number = 32 // sets pixel equivalent to 1 unit in plane's space
gridAutoResize : boolean = false // if set to true resizes grid while being zoomed-out to prevent it from filling the screen
gridResizeFactor : number = 4 // number of cells that turns into new one, when resize happens
minGridSize : number = 8 // if gridAutoResize = false, sets smallest unit of length between grid lines
scale : number = 1 // determines grid scale (1 / n - zoom-out by n; n - zoom-in by n)
minScale : number = 1 / 2 ** 4 // minimal scale allowed by zoom
maxScale : number = 2 // maximal scale allowed by zoom
backgroundColor : string | [number|number|number] = "#fff" // sets background color via string or three number array
mouseClicked : (cx, PointerEvent) => any // function that will be called on left mouse click
mousePressed : (cx, PointerEvent) => any // function that will be called on mouse press (and during it each frame)
mouseReleased : (cx, PointerEvent) => any // function that will be called on mouse release
rightMousePressed : (cx, PointerEvent) => any // as mousePressed but only for right button
leftMousePressed : (cx, PointerEvent) => any // as mousePressed but only for right button
Under it's hood this library implements functions of p5.js, which are modified and put into cx
object which is passed into events, update
and init
methods. All the basic drawing functions from p5.js which can be found in p5 documentation were recreated to work with this grid system. The list of supported functions and their references on p5:
2D Primitives:
Curves
Vertex
Colors
Noise
Translate
In order to preserve rest of the functionality other p5 methods can be accessed through variable: cx.p5
.
Some new functions present, that can be used to switch between coordinates:
uvx(x: number): number
uvy(y: number): number
After passing coordinate from the plane it will return coordinate on the screen relative to canvas center. For example, to get coordinate on screen where (0, 0)
is you may use: (cx.uvx(0), cx.uvy(0))
.
Output of this function relies on the position of the coordinate center. In case you need to transform relative sizes, you may consider using function cx.uv(c : number) : number
. For example, you need to set stroke of the line to be 0.1 in plane space. Then you may use function cx.strokeWeight(cx.uv(0.1))
. Otherwise, in case you use just cx.strokeWeight(0.1)
, stroke size will be constant no matter how you zoom in or out.
Opposite transformation from the screen to coordinate space may be implemented with functions:
inx(x: number): number
iny(y: number): number
in(c: number): number
They will return coordinates transformed from coordinate on the screen to the coordinate on the plane.
Some constants from p5 were implemented:
POINTS
POINTS
LINES
TRIANGLES
TRIANGLE_FAN
TRIANGLE_STRIP
QUADS
QUAD_STRIP
CLOSE
PI
TAU
HPI
DEGREES
RADIANS
And some new constants were added:
cx.mx : number
cx.my : number
// x and y coordinates of the mouse cursor on the plane
cx.mouseX : number
cx.mouseY : number
// x and y coordinates of the mouse cursor on the screen relative to canvas center
cx.mouseOutside : boolean
// returns true if the mouse outside of the canvas
Along side p5 functions this library implements new ones.
bound(x: number, min: number, max: number) : number
//returns value of x constrained by min and max values
bounded(x: number, min: number, max: number) : boolean
//checks if value x is between min and max values
region(
x: number,
y: number,
minX: number,
minY: number,
maxX: number,
maxY: number
): boolean
//checks if value x in and y in [minX, maxY]x[minY, maxY] region
rectBounded(
x: number,
y: number,
x0: number,
y0: number,
w: number,
h: number
): boolean
//same as region, but uses top left corner coordinate (x0, y0), w - width and h - height to describe rectangular region
boundedMouseX(min: number, max: number): boolean
boundedMouseY(min: number, max: number): boolean
// check for mouse x and mouse y constrains
regionMouse(
minX: number,
minY: number,
maxX: number,
maxY: number
): boolean
// acts as region() for mouse x and y coordinates
rectMouse(x: number, y: number, w: number, h: number): boolean
//acts as rectBounded() for mouse x and y coordinates
distance(x1: number, y1: number, x2: number, y2: number): number
// return distance between (x1, y1) and (x2, y2)
distanceMouse(x: number, y: number): number
// returns distance between (x,y) and mouse position on plane
update=(cx) => {
cx.strokeWeight(3);
cx.setDash(5, 5);
cx.stroke(128);
cx.bezier(-1, -1.5, 0, -1.5, 0, 1.5, 1, 1.5);
cx.stroke(0, 132, 255);
cx.resetDash();
cx.fill(0, 132, 255, 100);
cx.setShadow(0, 0, 15, "#08f8");
cx.rect(-2, -2, 0.9, 0.9, 0.2);
cx.translate(3, 3);
cx.rect(-2, -2, 0.9, 0.9, 0.2);
}
doubleClicked= (e, cx) => {
if (cx.rectMouse(-2, -2, 1, 1)) {
alert("Rectangle 1 double-clicked");
}
if (cx.rectMouse(1, 1, 1, 1)) {
alert("Rectangle 2 double-clicked");
}
}
Result:
Here alert will be show if double click performed on one of the squares. cx.translate
function shifts coordinates from (0, 0) to (3, 3), thus second rectangle is draw 3 grid units to left and 3 grid units to bottom relative to the first one.
update=(cx) => {
cx.strokeWeight(3);
cx.stroke(128);
cx.line(-0.5, 0.5, 2.5, 0.5);
cx.line(-2.5, -1.5, -2.5, 2.5);
cx.fill(200);
cx.circle(-0.5, 0.5, 0.3);
cx.circle(2.5, 0.5, 0.3);
cx.circle(-2.5, -1.5, 0.3);
cx.circle(-2.5, 2.5, 0.3);
cx.stroke(0, 132, 255);
cx.fill(0, 132, 255, 100);
cx.setShadow(0, 0, 15, "#08f8");
cx.rect(cx.bound(cx.mx - 0.5, -1, 2), 0, 1, 1, 0.2);
cx.rect(-3, cx.bound(cx.my - 0.5, -2, 2), 1, 1, 0.2);
}
Result:
Two rectangles are being bounded to mouseX and mouseY so that they move with mouse. cx.bound
constrains value of mouse coordinate between two positions so that squares don't go out of border.
update=(cx) => {
cx.stroke(0, 132, 255);
cx.strokeWeight(16);
cx.point(-1, 0);
cx.strokeWeight(4);
cx.line(-2, -1, -2, 1);
cx.strokeWeight(cx.uv(0.5));
cx.point(1, 0);
cx.strokeWeight(cx.uv(0.1));
cx.line(2, -1, 2, 1);
}
Result:
Here two coordinates systems are being used to set size for line and point stroke. Regular one sets size unrelated to screen. After applying cx.uv()
to the stroke weight on the right side, point's and line's stroke remains relative to zoom, so it's changes when you zoom in or out.
update=(cx) => {
cx.stroke(0, 132, 255);
cx.fill(0, 132, 255, 100);
cx.setShadow(0, 0, 15, "#08f8");
cx.rect(cx.inx(32), cx.iny(32), cx.in(64), cx.in(64), cx.in(8));
cx.rect(-1, -1, 2, 2);
cx.stroke(128);
cx.setShadow(0, 0, 15, "#0002");
cx.fill(128, 100);
cx.p5.rect(-64, -64, 64, 64, 8);
}
Result:
Here we draw three rectangles using screen space through cx.p5.rect
drawing function or through cx.rect
applying coordinates transform before passing coordinates. As result two rectangles are drawn on screen and do not move or zoom with the plane, while regular rectangle cx.rect(-1, -1, 2, 2)
stays bound to plane.
update=(cx) => {
let t = cx.p5.frameCount / 60;
cx.stroke(0, 132, 255);
cx.fill(0, 132, 255, 100);
cx.setShadow(0, 0, 15, "#08f8");
cx.beginShape();
cx.vertex(0, cx.noise(0, t) * 4);
let n = Math.floor(cx.PI * 50);
let d = 4;
for (let i = 1; i < n - 3; i += 1) {
let r = cx.noise((i / n) * d, t) * 4;
let x = Math.sin((i / n) * cx.TAU) * r;
let y = Math.cos((i / n) * cx.TAU) * r;
cx.vertex(x, y);
}
cx.beginContour();
cx.vertex(0, cx.noise(0, t) * 2);
for (let i = 1; i < n - 3; i += 1) {
let r = cx.noise((i / n) * d, t) * 2;
let x = Math.sin(-(i / n) * cx.TAU) * r;
let y = Math.cos(-(i / n) * cx.TAU) * r;
cx.vertex(x, y);
}
cx.endContour();
cx.endShape(cx.CLOSE);
}
Result:
Here you can see figure that changes over time as frameCount increases each second by 60. We use cx.beginFigure()
to start drawing figure and then in the loop we define it's vertices. Than in that figure we start contour cx.beginContour()
to cut-out inside of that shape. then we close contour and figure via cx.endContour(); cx.endFigure()
. As result we get random figure with random shaped hole in it.