mirror of
https://github.com/Pomax/BezierInfo-2.git
synced 2025-09-25 23:59:02 +02:00
control (#261)
This commit is contained in:
@@ -7,12 +7,30 @@ import { BaseAPI } from "./base-api.js";
|
||||
const MOUSE_PRECISION_ZONE = 5;
|
||||
const TOUCH_PRECISION_ZONE = 30;
|
||||
|
||||
let CURRENT_HUE = 0;
|
||||
|
||||
/**
|
||||
* Our Graphics API, which is the "public" side of the API.
|
||||
*/
|
||||
class GraphicsAPI extends BaseAPI {
|
||||
static get constants() {
|
||||
return [`POINTER`, `HAND`, `PI`, `TAU`, `POLYGON`, `CURVE`, `BEZIER`];
|
||||
return [
|
||||
`POINTER`,
|
||||
`HAND`,
|
||||
`PI`,
|
||||
`TAU`,
|
||||
`POLYGON`,
|
||||
`CURVE`,
|
||||
`BEZIER`,
|
||||
`CENTER`,
|
||||
`LEFT`,
|
||||
`RIGHT`,
|
||||
];
|
||||
}
|
||||
|
||||
draw() {
|
||||
CURRENT_HUE = 0;
|
||||
super.draw();
|
||||
}
|
||||
|
||||
get PI() {
|
||||
@@ -36,11 +54,22 @@ class GraphicsAPI extends BaseAPI {
|
||||
get BEZIER() {
|
||||
return Shape.BEZIER;
|
||||
}
|
||||
get CENTER() {
|
||||
return `center`;
|
||||
}
|
||||
get LEFT() {
|
||||
return `left`;
|
||||
}
|
||||
get RIGHT() {
|
||||
return `right`;
|
||||
}
|
||||
|
||||
onMouseDown(evt) {
|
||||
super.onMouseDown(evt);
|
||||
|
||||
const cdist = evt.targetTouches ? TOUCH_PRECISION_ZONE : MOUSE_PRECISION_ZONE;
|
||||
const cdist = evt.targetTouches
|
||||
? TOUCH_PRECISION_ZONE
|
||||
: MOUSE_PRECISION_ZONE;
|
||||
|
||||
for (let i = 0, e = this.moveable.length, p, d; i < e; i++) {
|
||||
p = this.moveable[i];
|
||||
@@ -92,6 +121,51 @@ class GraphicsAPI extends BaseAPI {
|
||||
this.ctx.rotate(angle);
|
||||
}
|
||||
|
||||
/**
|
||||
* transforms: scale
|
||||
*/
|
||||
scale(x, y) {
|
||||
y = y ?? x;
|
||||
this.ctx.scale(x, y);
|
||||
}
|
||||
/**
|
||||
* transforms: screen to world
|
||||
*/
|
||||
screenToWorld(x, y) {
|
||||
if (y === undefined) {
|
||||
y = x.y;
|
||||
x = x.x;
|
||||
}
|
||||
|
||||
let M = this.ctx.getTransform().invertSelf();
|
||||
|
||||
let ret = {
|
||||
x: x * M.a + y * M.c + M.e,
|
||||
y: x * M.b + y * M.d + M.f,
|
||||
};
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* transforms: world to screen
|
||||
*/
|
||||
worldToScreen(x, y) {
|
||||
if (y === undefined) {
|
||||
y = x.y;
|
||||
x = x.x;
|
||||
}
|
||||
|
||||
let M = this.ctx.getTransform();
|
||||
|
||||
let ret = {
|
||||
x: x * M.a + y * M.c + M.e,
|
||||
y: x * M.b + y * M.d + M.f,
|
||||
};
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* transforms: reset
|
||||
*/
|
||||
@@ -131,6 +205,14 @@ class GraphicsAPI extends BaseAPI {
|
||||
this.canvas.style.cursor = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a random color
|
||||
*/
|
||||
randomColor(a = 1.0) {
|
||||
CURRENT_HUE = (CURRENT_HUE + 73) % 360;
|
||||
return `hsla(${CURRENT_HUE},50%,50%,${a})`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the context fillStyle
|
||||
*/
|
||||
@@ -209,12 +291,18 @@ class GraphicsAPI extends BaseAPI {
|
||||
/**
|
||||
* Draw text on the canvas
|
||||
*/
|
||||
text(str, x, y) {
|
||||
text(str, x, y, alignment) {
|
||||
if (y === undefined) {
|
||||
y = x.y;
|
||||
x = x.x;
|
||||
}
|
||||
const ctx = this.ctx;
|
||||
if (alignment) {
|
||||
ctx.cacheStyle();
|
||||
ctx.textAlign = alignment;
|
||||
}
|
||||
this.ctx.fillText(str, x, y);
|
||||
if (alignment) ctx.restoreStyle();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -225,6 +313,25 @@ class GraphicsAPI extends BaseAPI {
|
||||
this.ctx.strokeRect(x, y, w, h);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a function plot from [start] to [end] in [steps] steps.
|
||||
* Returns the plot shape so that it can be cached for redrawing.
|
||||
*/
|
||||
plot(fn, start = 0, end = 1, steps = 24) {
|
||||
const ctx = this.ctx;
|
||||
ctx.cacheStyle();
|
||||
ctx.fillStyle = `transparent`;
|
||||
const interval = end - start;
|
||||
this.start();
|
||||
for (let i = 0, e = steps - 1, v; i < steps; i++) {
|
||||
v = fn(start + (interval * i) / e);
|
||||
this.vertex(v.x, v.y);
|
||||
}
|
||||
this.end();
|
||||
ctx.restoreStyle();
|
||||
return this.currentShape;
|
||||
}
|
||||
|
||||
/**
|
||||
* A signal for starting a complex shape
|
||||
*/
|
||||
@@ -246,6 +353,14 @@ class GraphicsAPI extends BaseAPI {
|
||||
this.currentShape.vertex({ x, y });
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a previously created shape
|
||||
*/
|
||||
drawShape(shape) {
|
||||
this.currentShape = shape;
|
||||
this.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* A signal to draw the current complex shape
|
||||
*/
|
||||
@@ -254,12 +369,56 @@ class GraphicsAPI extends BaseAPI {
|
||||
let { x, y } = this.currentShape.first;
|
||||
this.ctx.moveTo(x, y);
|
||||
this.currentShape.segments.forEach((s) =>
|
||||
this[`draw${s.type}`](this.ctx, s.points, s.factor)
|
||||
this[`draw${s.type}`](s.points, s.factor)
|
||||
);
|
||||
this.ctx.fill();
|
||||
this.ctx.stroke();
|
||||
}
|
||||
|
||||
/**
|
||||
* Polygon draw function
|
||||
*/
|
||||
drawPolygon(points) {
|
||||
points.forEach((p) => this.ctx.lineTo(p.x, p.y));
|
||||
}
|
||||
|
||||
/**
|
||||
* Curve draw function, which draws a CR curve as a series of Beziers
|
||||
*/
|
||||
drawCatmullRom(points, f) {
|
||||
// invent a virtual first and last point
|
||||
const f0 = points[0],
|
||||
f1 = points[1],
|
||||
fn = f0.reflect(f1),
|
||||
l1 = points[points.length - 2],
|
||||
l0 = points[points.length - 1],
|
||||
ln = l0.reflect(l1),
|
||||
cpoints = [fn, ...points, ln];
|
||||
|
||||
// four point sliding window over the segment
|
||||
for (let i = 0, e = cpoints.length - 3; i < e; i++) {
|
||||
let [c1, c2, c3, c4] = cpoints.slice(i, i + 4);
|
||||
let p2 = {
|
||||
x: c2.x + (c3.x - c1.x) / (6 * f),
|
||||
y: c2.y + (c3.y - c1.y) / (6 * f),
|
||||
};
|
||||
let p3 = {
|
||||
x: c3.x - (c4.x - c2.x) / (6 * f),
|
||||
y: c3.y - (c4.y - c2.y) / (6 * f),
|
||||
};
|
||||
this.ctx.bezierCurveTo(p2.x, p2.y, p3.x, p3.y, c3.x, c3.y);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Curve draw function, which assumes Bezier coordinates
|
||||
*/
|
||||
drawBezier(points) {
|
||||
for (let i = 0, e = points.length; i < e; i += 3) {
|
||||
let [p1, p2, p3] = points.slice(i, i + 3);
|
||||
this.ctx.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Yield a snapshot of the current shape.
|
||||
*/
|
||||
@@ -267,14 +426,6 @@ class GraphicsAPI extends BaseAPI {
|
||||
return this.currentShape;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a previously created shape
|
||||
*/
|
||||
drawShape(shape) {
|
||||
this.currentShape = shape;
|
||||
this.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* convenient grid drawing function
|
||||
*/
|
||||
@@ -287,6 +438,30 @@ class GraphicsAPI extends BaseAPI {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* convenient axis drawing function
|
||||
*
|
||||
* api.drawAxes(pad, "t",0,1, "S","0%","100%");
|
||||
*
|
||||
*/
|
||||
drawAxes(hlabel, hs, he, vlabel, vs, ve) {
|
||||
const h = this.height;
|
||||
const w = this.width;
|
||||
|
||||
this.line(0, 0, w, 0);
|
||||
this.line(0, 0, 0, h);
|
||||
|
||||
const hpos = 0 - 5;
|
||||
this.text(`${hlabel} →`, this.width / 2, hpos, this.CENTER);
|
||||
this.text(hs, 0, hpos, this.CENTER);
|
||||
this.text(he, w, hpos, this.CENTER);
|
||||
|
||||
const vpos = -10;
|
||||
this.text(`${vlabel}\n↓`, vpos, this.height / 2, this.RIGHT);
|
||||
this.text(vs, vpos, 0 + 5, this.RIGHT);
|
||||
this.text(ve, vpos, h, this.RIGHT);
|
||||
}
|
||||
|
||||
/**
|
||||
* math functions
|
||||
*/
|
||||
|
Reference in New Issue
Block a user