Compare commits
10 Commits
6b8b20d286
...
54e75eed31
Author | SHA1 | Date | |
---|---|---|---|
54e75eed31 | |||
aa2885c930 | |||
1011baf587 | |||
90256f617e | |||
c44252a14c | |||
d42774ad1f | |||
cf054b01d9 | |||
2356cf43d7 | |||
![]() |
ff593dcd92 | ||
![]() |
627ce43e4f |
10
TODO.md
Normal file
10
TODO.md
Normal file
@ -0,0 +1,10 @@
|
||||
# TODO
|
||||
|
||||
- ellipse
|
||||
- smooth/nosmooth
|
||||
- curves
|
||||
|
||||
- lerp
|
||||
|
||||
- clipping
|
||||
- text
|
@ -1,27 +1,25 @@
|
||||
import { artworld } from "./world.js";
|
||||
import { Vector2 } from "./math.js";
|
||||
import { Color } from "./color.js";
|
||||
import { Random } from "./random.js";
|
||||
|
||||
function cloneObject(obj) {
|
||||
for (let [k, v] of Object.entries(obj)) {
|
||||
console.log(k, v);
|
||||
}
|
||||
}
|
||||
import { World } from "./world.js";
|
||||
import { getVal } from "./variables.js";
|
||||
|
||||
export class Drawable {
|
||||
constructor(parent) {
|
||||
this._parent = parent;
|
||||
this._updates = [];
|
||||
this._parent = null;
|
||||
this._world = null;
|
||||
// if parent is provided, then delegate to parent class
|
||||
if (parent instanceof World) {
|
||||
parent.registerDrawable(this);
|
||||
} else if (parent) {
|
||||
parent.addChild(this);
|
||||
}
|
||||
this._fill = parent ? parent._fill : null;
|
||||
this._stroke = parent ? parent._stroke : null;
|
||||
this._strokeWeight = parent ? parent._strokeWeight : null;
|
||||
this._z_index = parent ? parent._z_index : null;
|
||||
this._zIndex = parent ? parent._zIndex : null;
|
||||
this._posVec = new Vector2(0, 0);
|
||||
if (this._parent) {
|
||||
this._parent.addChild(this);
|
||||
}
|
||||
artworld.register(this);
|
||||
this._animations = [];
|
||||
}
|
||||
|
||||
copy() {
|
||||
@ -32,7 +30,22 @@ export class Drawable {
|
||||
throw new Error("draw() must be implemented on child class");
|
||||
}
|
||||
|
||||
animate(setter, func, args) {
|
||||
this._animations.push({ setter, func, args });
|
||||
return this;
|
||||
}
|
||||
|
||||
update(t) {
|
||||
for (let anim of this._animations) {
|
||||
this[anim.setter](anim.func(t), anim.args);
|
||||
}
|
||||
}
|
||||
|
||||
// setters
|
||||
world(world) {
|
||||
this._world = world;
|
||||
return this;
|
||||
}
|
||||
|
||||
fill(color) {
|
||||
this._fill = color;
|
||||
@ -65,7 +78,7 @@ export class Drawable {
|
||||
}
|
||||
|
||||
z(scalar) {
|
||||
this._z_index = scalar;
|
||||
this._zIndex = scalar;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -77,10 +90,7 @@ export class Drawable {
|
||||
}
|
||||
|
||||
random() {
|
||||
this._posVec = new Vector2(
|
||||
Random.under(artworld.width),
|
||||
Random.under(artworld.height),
|
||||
);
|
||||
this._posVec = new Vector2(Random.under(100), Random.under(100));
|
||||
this._stroke = new Color(
|
||||
Random.under(360),
|
||||
Random.between(30, 60),
|
||||
@ -99,14 +109,14 @@ export class Drawable {
|
||||
get worldPos() {
|
||||
// offset from parent if needed
|
||||
return this._parent
|
||||
? this._parent.worldPos.add(this._posVec)
|
||||
: this._posVec;
|
||||
? this._parent.worldPos.add(getVal(this._posVec))
|
||||
: getVal(this._posVec);
|
||||
}
|
||||
}
|
||||
|
||||
export class Group extends Drawable {
|
||||
constructor() {
|
||||
super();
|
||||
constructor(parent) {
|
||||
super(parent);
|
||||
this._children = [];
|
||||
}
|
||||
|
||||
@ -127,6 +137,9 @@ export class Group extends Drawable {
|
||||
newGroup._parent = this._parent;
|
||||
newGroup._parent.addChild(newGroup);
|
||||
}
|
||||
if (this._world) {
|
||||
this._world.registerDrawable(newGroup);
|
||||
}
|
||||
|
||||
return newGroup;
|
||||
}
|
||||
@ -135,7 +148,11 @@ export class Group extends Drawable {
|
||||
|
||||
addChild(drawable) {
|
||||
this._children.push(drawable);
|
||||
// probably alraedy true, but make sure
|
||||
drawable._parent = this;
|
||||
if (this._world) {
|
||||
this._world.registerDrawable(drawable);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
export { artworld } from "./world.js";
|
||||
export { Color, Pico8 } from "./color.js";
|
||||
export { Vector2, degToRad, radToDeg } from "./math.js";
|
||||
export { Line, Rect, Circle, Arc } from "./shapes.js";
|
||||
export { Line, Rect, Circle, Arc, Polygon } from "./shapes.js";
|
||||
export { Group } from "./drawable.js";
|
||||
export { Random } from "./random.js";
|
||||
export { Var } from "./variables.js";
|
||||
export { World } from "./world.js";
|
||||
|
@ -27,6 +27,16 @@ export class Vector2 {
|
||||
return new Vector2(s * this.x, s * this.y);
|
||||
}
|
||||
|
||||
distance(other) {
|
||||
return Math.sqrt(
|
||||
Math.pow(this.x - other.x, 2) + Math.pow(this.y - other.y, 2),
|
||||
);
|
||||
}
|
||||
|
||||
get magnitude() {
|
||||
return Math.sqrt(this.x * this.x + this.y * this.y);
|
||||
}
|
||||
|
||||
static random(x, y) {
|
||||
let theta = Random.radians();
|
||||
// if neither specified, use (1, 1)
|
||||
|
@ -16,4 +16,8 @@ export class Random {
|
||||
static radians() {
|
||||
return Math.random() * Math.PI * 2;
|
||||
}
|
||||
|
||||
static choice(array) {
|
||||
return array[Math.floor(Random.under(array.length))];
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Vector2 } from "./math.js";
|
||||
import { Drawable } from "./drawable.js";
|
||||
import { Random } from "./random.js";
|
||||
import { getVal } from "./variables.js";
|
||||
|
||||
function makeCopy(Cls, obj, override) {
|
||||
// make new object, will be registered with world, but no _parent yet
|
||||
@ -11,6 +12,10 @@ function makeCopy(Cls, obj, override) {
|
||||
if (newObj._parent) {
|
||||
newObj._parent.addChild(newObj);
|
||||
}
|
||||
// attach to world
|
||||
if (newObj._world) {
|
||||
newObj._world.registerDrawable(newObj);
|
||||
}
|
||||
return newObj;
|
||||
}
|
||||
|
||||
@ -24,10 +29,18 @@ export class Line extends Drawable {
|
||||
return makeCopy(Line, this, overrides);
|
||||
}
|
||||
|
||||
random(range = 100) {
|
||||
super.random();
|
||||
this._offsetVec = Vector2.random(range, range);
|
||||
return this;
|
||||
}
|
||||
|
||||
draw() {
|
||||
artworld.setStrokeColor(this._stroke);
|
||||
artworld.setStrokeWeight(this._strokeWeight);
|
||||
artworld.drawLine(this.worldPos, this.worldPos.add(this._offsetVec));
|
||||
this._world.prepareDraw(this);
|
||||
this._world.drawLine(
|
||||
this.worldPos,
|
||||
this.worldPos.add(getVal(this._offsetVec)),
|
||||
);
|
||||
}
|
||||
|
||||
to(vec) {
|
||||
@ -48,13 +61,26 @@ export class Arc extends Drawable {
|
||||
this._startAngle = 0;
|
||||
this._endAngle = 180;
|
||||
}
|
||||
|
||||
copy(overrides) {
|
||||
return makeCopy(Arc, this, overrides);
|
||||
}
|
||||
|
||||
random(range = 100) {
|
||||
super.random();
|
||||
this._r = Random.between(20, range);
|
||||
this._startAngle = Random.radians();
|
||||
this._endAngle = Random.radians();
|
||||
return this;
|
||||
}
|
||||
draw() {
|
||||
artworld.prepareDraw(this);
|
||||
artworld.drawArc(this.worldPos, this._r, this._startAngle, this._endAngle);
|
||||
this._world.prepareDraw(this);
|
||||
this._world.drawArc(
|
||||
this.worldPos,
|
||||
this._r,
|
||||
this._startAngle,
|
||||
this._endAngle,
|
||||
);
|
||||
}
|
||||
|
||||
radius(scalar) {
|
||||
@ -80,9 +106,15 @@ export class Circle extends Drawable {
|
||||
return makeCopy(Circle, this, overrides);
|
||||
}
|
||||
|
||||
random(range = 100) {
|
||||
super.random();
|
||||
this._r = Random.between(20, range);
|
||||
return this;
|
||||
}
|
||||
|
||||
draw() {
|
||||
artworld.prepareDraw(this);
|
||||
artworld.drawArc(this.worldPos, this._r, 0, 2 * Math.PI);
|
||||
this._world.prepareDraw(this);
|
||||
this._world.drawArc(this.worldPos, this._r, 0, 2 * Math.PI);
|
||||
}
|
||||
|
||||
radius(scalar) {
|
||||
@ -98,7 +130,7 @@ export class Rect extends Drawable {
|
||||
this._height = 50;
|
||||
}
|
||||
|
||||
random(range) {
|
||||
random(range = 100) {
|
||||
super.random();
|
||||
this._width = Random.between(20, range);
|
||||
this._height = Random.between(20, range);
|
||||
@ -109,8 +141,8 @@ export class Rect extends Drawable {
|
||||
}
|
||||
|
||||
draw() {
|
||||
artworld.prepareDraw(this);
|
||||
artworld.drawRect(this.worldPos, this._width, this._height);
|
||||
this._world.prepareDraw(this);
|
||||
this._world.drawRect(this.worldPos, this._width, this._height);
|
||||
}
|
||||
|
||||
size(w, h) {
|
||||
@ -139,3 +171,67 @@ export class Rect extends Drawable {
|
||||
// );
|
||||
// }
|
||||
}
|
||||
|
||||
export class Polygon extends Drawable {
|
||||
constructor(parent) {
|
||||
super(parent);
|
||||
this._points = [];
|
||||
}
|
||||
|
||||
random(n = 7) {
|
||||
super.random();
|
||||
let points = [];
|
||||
let i;
|
||||
// generate points
|
||||
for (i = 0; i < n; i++) {
|
||||
points.push(
|
||||
new Vector2(Random.between(50, 200), Random.between(50, 200)),
|
||||
);
|
||||
}
|
||||
// sort points by distance for mostly convex (but not always) polygons
|
||||
this._points.push(points.shift()); // take first element
|
||||
while (points.length) {
|
||||
let closestIdx = 0;
|
||||
let closestDist = 999999999;
|
||||
let dist;
|
||||
for (let j = 0; j < points.length; j++) {
|
||||
dist = this._points[this._points.length - 1].distance(points[j]);
|
||||
if (dist < closestDist) {
|
||||
closestDist = dist;
|
||||
closestIdx = j;
|
||||
}
|
||||
}
|
||||
this._points.push(points[closestIdx]);
|
||||
points.splice(closestIdx, 1);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
copy(overrides) {
|
||||
return makeCopy(Polygon, this, overrides);
|
||||
}
|
||||
|
||||
draw() {
|
||||
this._world.prepareDraw(this);
|
||||
this._world.drawPolygon(this.worldPos, this._points);
|
||||
}
|
||||
|
||||
point(vector, to_modify = null) {
|
||||
if (to_modify !== null) {
|
||||
this._points[to_modify] = vector;
|
||||
} else {
|
||||
this._points.push(point);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
// TODO
|
||||
// contains(x, y) {
|
||||
// return (
|
||||
// x > this.x &&
|
||||
// x < this.x + this._width &&
|
||||
// y > this.y &&
|
||||
// y < this.pos.y + this._height
|
||||
// );
|
||||
// }
|
||||
}
|
||||
|
34
artworld/timer.js
Normal file
34
artworld/timer.js
Normal file
@ -0,0 +1,34 @@
|
||||
export class Timer {
|
||||
constructor() {
|
||||
this._pausedAt = null;
|
||||
this._elapsed = 0;
|
||||
this._lastStarted = performance.now();
|
||||
}
|
||||
|
||||
pause() {
|
||||
if (!this.isPaused) {
|
||||
this._pausedAt = performance.now();
|
||||
// store time since last pause
|
||||
this._elapsed += this._pausedAt - this._lastStarted;
|
||||
}
|
||||
}
|
||||
|
||||
unpause() {
|
||||
if (this.isPaused) {
|
||||
this.lastStarted = performance.now();
|
||||
this._pausedAt = null;
|
||||
}
|
||||
}
|
||||
|
||||
get isPaused() {
|
||||
return this._pausedAt !== null;
|
||||
}
|
||||
|
||||
time() {
|
||||
if (this.isPaused) {
|
||||
return this._elapsed;
|
||||
} else {
|
||||
return this._elapsed + (performance.now() - this._lastStarted);
|
||||
}
|
||||
}
|
||||
}
|
29
artworld/variables.js
Normal file
29
artworld/variables.js
Normal file
@ -0,0 +1,29 @@
|
||||
export function getVal(x) {
|
||||
return x && x.getValue ? x.getValue() : x;
|
||||
}
|
||||
|
||||
export class Var {
|
||||
constructor(value) {
|
||||
if (typeof value === "function") {
|
||||
this._updateFunc = value;
|
||||
this._value = this._updateFunc(0);
|
||||
} else {
|
||||
this._value = value;
|
||||
this._updateFunc = null;
|
||||
}
|
||||
}
|
||||
|
||||
updateFunc(func) {
|
||||
this._updateFunc = func;
|
||||
}
|
||||
|
||||
update(t) {
|
||||
if (this._updateFunc) {
|
||||
this._value = this._updateFunc(t);
|
||||
}
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return this._value;
|
||||
}
|
||||
}
|
@ -1,11 +1,20 @@
|
||||
export class World {
|
||||
constructor() {
|
||||
this.drawables = [];
|
||||
this.ctx = null;
|
||||
}
|
||||
import { Timer } from "./timer.js";
|
||||
import { getVal, Var } from "./variables.js";
|
||||
|
||||
bindCanvas(id) {
|
||||
this.ctx = document.getElementById(id).getContext("2d");
|
||||
export class World {
|
||||
constructor(canvasId) {
|
||||
this.ctx = null;
|
||||
this.drawables = [];
|
||||
this.variables = [];
|
||||
this.backgroundColor = "white";
|
||||
this.timer = new Timer();
|
||||
this.targetFrameRate = 60;
|
||||
this.stepSize = 1000 / this.targetFrameRate;
|
||||
this.lastTick = this.timer.time();
|
||||
this.numTicks = 0;
|
||||
this.ctx = document.getElementById(canvasId).getContext("2d");
|
||||
this.ctx.canvas.width = 1000;
|
||||
this.ctx.canvas.height = 1000;
|
||||
}
|
||||
|
||||
get width() {
|
||||
@ -16,14 +25,55 @@ export class World {
|
||||
return this.ctx.canvas.height;
|
||||
}
|
||||
|
||||
register(drawable) {
|
||||
this.drawables.push(drawable);
|
||||
var(value) {
|
||||
let v = new Var(value);
|
||||
this.variables.push(v);
|
||||
return v;
|
||||
}
|
||||
|
||||
registerDrawable(thing) {
|
||||
this.drawables.push(thing);
|
||||
thing._world = this;
|
||||
return thing;
|
||||
}
|
||||
|
||||
draw() {
|
||||
for (let d of this.drawables.sort((a, b) => a._z_index - b._z_index)) {
|
||||
this.ctx.fillStyle = this.backgroundColor;
|
||||
this.ctx.beginPath();
|
||||
this.ctx.fillRect(0, 0, this.width, this.height);
|
||||
this.ctx.fill();
|
||||
this.ctx.save();
|
||||
this.ctx.translate(500, 500);
|
||||
for (let d of this.drawables.sort((a, b) => a._zIndex - b._zIndex)) {
|
||||
d.draw();
|
||||
}
|
||||
this.ctx.restore();
|
||||
}
|
||||
|
||||
tick() {
|
||||
this.numTicks++;
|
||||
for (let d of this.drawables) {
|
||||
d.update(this.numTicks);
|
||||
}
|
||||
for (let v of this.variables) {
|
||||
v.update(this.numTicks);
|
||||
}
|
||||
}
|
||||
|
||||
get updatesPerSecond() {
|
||||
return (this.numTicks / this.timer.time()) * 1000;
|
||||
}
|
||||
|
||||
loopStep() {
|
||||
window.requestAnimationFrame(() => this.loopStep());
|
||||
|
||||
let curTime = this.timer.time();
|
||||
// tick appropriate number of times
|
||||
while (curTime - this.lastTick > this.stepSize) {
|
||||
this.lastTick += this.stepSize;
|
||||
this.tick();
|
||||
}
|
||||
this.draw();
|
||||
}
|
||||
|
||||
// Canvas 2D /////////////////
|
||||
@ -31,21 +81,21 @@ export class World {
|
||||
prepareDraw(drawable) {
|
||||
this._stroke = false;
|
||||
this._fill = false;
|
||||
if (drawable._stroke) {
|
||||
this.ctx.lineWidth = drawable._strokeWeight;
|
||||
this.ctx.strokeStyle = drawable._stroke.toStr
|
||||
? drawable._stroke.toStr()
|
||||
: drawable._stroke;
|
||||
let stroke = getVal(drawable._stroke);
|
||||
let fill = getVal(drawable._fill);
|
||||
if (stroke) {
|
||||
this.ctx.lineWidth = getVal(drawable._strokeWeight);
|
||||
this.ctx.strokeStyle = stroke.toStr ? stroke.toStr() : stroke;
|
||||
this._stroke = true;
|
||||
} else if (drawable._fill) {
|
||||
this.ctx.fillStyle = drawable._fill.toStr
|
||||
? drawable._fill.toStr()
|
||||
: drawable._fill;
|
||||
} else if (fill) {
|
||||
this.ctx.fillStyle = fill.toStr ? fill.toStr() : fill;
|
||||
this._fill = true;
|
||||
}
|
||||
}
|
||||
|
||||
drawLine(from, to) {
|
||||
from = getVal(from);
|
||||
to = getVal(to);
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(from.x, from.y);
|
||||
this.ctx.lineTo(to.x, to.y);
|
||||
@ -53,19 +103,38 @@ export class World {
|
||||
}
|
||||
|
||||
drawArc(center, radius, fromAngle, toAngle) {
|
||||
center = getVal(center);
|
||||
this.ctx.beginPath();
|
||||
this.ctx.arc(center.x, center.y, radius, fromAngle, toAngle);
|
||||
this.ctx.arc(
|
||||
center.x,
|
||||
center.y,
|
||||
getVal(radius),
|
||||
getVal(fromAngle),
|
||||
getVal(toAngle),
|
||||
);
|
||||
if (this._stroke) this.ctx.stroke();
|
||||
if (this._fill) this.ctx.fill();
|
||||
}
|
||||
|
||||
drawRect(center, width, height) {
|
||||
center = getVal(center);
|
||||
width = getVal(width);
|
||||
height = getVal(height);
|
||||
this.ctx.beginPath();
|
||||
this.ctx.rect(center.x - width / 2, center.y - height / 2, width, height);
|
||||
if (this._stroke) this.ctx.stroke();
|
||||
if (this._fill) this.ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
// singleton (for now)
|
||||
export const artworld = new World();
|
||||
drawPolygon(center, points) {
|
||||
center = getVal(center);
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(center.x + points[0].x, center.y + points[0].y);
|
||||
for (let i = 1; i < points.length; i++) {
|
||||
this.ctx.lineTo(center.x + points[i].x, center.y + points[i].y);
|
||||
}
|
||||
this.ctx.lineTo(center.x + points[0].x, center.y + points[0].y);
|
||||
if (this._stroke) this.ctx.stroke();
|
||||
if (this._fill) this.ctx.fill();
|
||||
}
|
||||
}
|
||||
|
55
examples/balls.js
Normal file
55
examples/balls.js
Normal file
@ -0,0 +1,55 @@
|
||||
import {
|
||||
World,
|
||||
Color,
|
||||
Pico8,
|
||||
Group,
|
||||
Circle,
|
||||
Random,
|
||||
Vector2,
|
||||
degToRad,
|
||||
} from "../artworld/index.js";
|
||||
let world = new World("mainCanvas");
|
||||
window.world = world;
|
||||
|
||||
class Ball extends Circle {
|
||||
constructor() {
|
||||
super(world);
|
||||
this.speed = new Vector2(0, Random.between(9, 15));
|
||||
}
|
||||
|
||||
update() {
|
||||
this.move(this.speed);
|
||||
if (this.worldPos.y > world.height + 20) {
|
||||
this.y(-20);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GravityBall extends Circle {
|
||||
constructor() {
|
||||
super(world);
|
||||
this.accel = new Vector2(0, 0.5);
|
||||
this.speed = Vector2.random(0, 5);
|
||||
}
|
||||
|
||||
update() {
|
||||
this.speed = this.speed.add(this.accel);
|
||||
this.move(this.speed);
|
||||
if (this.worldPos.y > world.height - 10) {
|
||||
this.speed = this.speed.scale(-0.98); // bounce w/ dampening
|
||||
this.y(world.height - 10.01);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < 21; i++) {
|
||||
new Ball()
|
||||
.pos(new Vector2(40 * i, 0))
|
||||
.radius(10)
|
||||
.fill(Pico8.BLUE);
|
||||
new GravityBall()
|
||||
.pos(new Vector2(20 + 40 * i, 0))
|
||||
.radius(10)
|
||||
.fill(Pico8.PURPLE);
|
||||
}
|
||||
world.loopStep();
|
@ -1,38 +0,0 @@
|
||||
<html>
|
||||
<head> </head>
|
||||
<body>
|
||||
<canvas id="mainCanvas" width="500" height="500"></canvas>
|
||||
<script type="module">
|
||||
import {
|
||||
artworld,
|
||||
Color,
|
||||
Pico8,
|
||||
Group,
|
||||
Circle,
|
||||
Random,
|
||||
Vector2,
|
||||
degToRad,
|
||||
} from "../artworld/index.js";
|
||||
artworld.bindCanvas("mainCanvas");
|
||||
window.artworld = artworld;
|
||||
|
||||
function* color_cycle() {
|
||||
while (true) {
|
||||
yield Pico8.RED;
|
||||
yield Pico8.ORANGE;
|
||||
yield Pico8.YELLOW;
|
||||
}
|
||||
}
|
||||
let cycle = color_cycle();
|
||||
|
||||
let g = new Group().pos(new Vector2(250, 250));
|
||||
for (let r = 20; r < 100; r += 12) {
|
||||
new Circle(g).radius(r).fill(cycle.next().value).z(-r).strokeWeight(0);
|
||||
}
|
||||
for (let r = 100; r < 250; r += 12) {
|
||||
new Circle(g).radius(r).fill(cycle.next().value).z(-r).strokeWeight(0);
|
||||
}
|
||||
artworld.draw();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
18
examples/circles.js
Normal file
18
examples/circles.js
Normal file
@ -0,0 +1,18 @@
|
||||
import { World, Pico8, Group, Circle, Vector2 } from "../artworld/index.js";
|
||||
let world = new World("mainCanvas");
|
||||
window.world = world;
|
||||
|
||||
function* color_cycle() {
|
||||
while (true) {
|
||||
yield Pico8.RED;
|
||||
yield Pico8.ORANGE;
|
||||
yield Pico8.YELLOW;
|
||||
}
|
||||
}
|
||||
let cycle = color_cycle();
|
||||
|
||||
let g = new Group(world);
|
||||
for (let r = 20; r < 500; r += 20) {
|
||||
new Circle(g).radius(r).fill(cycle.next().value).z(-r).strokeWeight(0);
|
||||
}
|
||||
world.draw();
|
51
examples/clock.html
Normal file
51
examples/clock.html
Normal file
@ -0,0 +1,51 @@
|
||||
<html>
|
||||
<head> </head>
|
||||
<body>
|
||||
<canvas id="mainCanvas" width="500" height="500"></canvas>
|
||||
<script type="module">
|
||||
import {
|
||||
artworld,
|
||||
Color,
|
||||
Pico8,
|
||||
Group,
|
||||
Rect,
|
||||
Random,
|
||||
Vector2,
|
||||
Line,
|
||||
Circle,
|
||||
degToRad,
|
||||
} from "../artworld/index.js";
|
||||
artworld.bindCanvas("mainCanvas");
|
||||
window.artworld = artworld;
|
||||
|
||||
function colorFunc(t) {
|
||||
return [Pico8.RED, Pico8.ORANGE, Pico8.GREEN, Pico8.BLUE][
|
||||
Math.floor(t / 60) % 4
|
||||
];
|
||||
}
|
||||
|
||||
function sizeFuncFactory(minSize, factor) {
|
||||
return (t) => Math.sin(t / 100) * factor + minSize;
|
||||
}
|
||||
|
||||
function timeAngle(t) {
|
||||
return (t / 6000) * Math.PI * 2;
|
||||
}
|
||||
|
||||
let g = new Group().pos(new Vector2(250, 250));
|
||||
new Circle(g)
|
||||
.fill(Pico8.BLACK)
|
||||
.z(1)
|
||||
.animate("radius", sizeFuncFactory(200, 50));
|
||||
new Circle(g)
|
||||
.z(10)
|
||||
.animate("fill", colorFunc)
|
||||
.animate("radius", sizeFuncFactory(190, 50));
|
||||
new Circle(g).radius(20).fill(Pico8.BLACK).z(50);
|
||||
|
||||
new Line(g).to(Vector2.polar(200, 0)).z(100).animate("angle", timeAngle);
|
||||
|
||||
artworld.loopStep();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,28 +0,0 @@
|
||||
<html>
|
||||
<head> </head>
|
||||
<body>
|
||||
<canvas id="mainCanvas" width="500" height="500"></canvas>
|
||||
<script type="module">
|
||||
import {
|
||||
artworld,
|
||||
Color,
|
||||
Pico8,
|
||||
Group,
|
||||
Circle,
|
||||
Random,
|
||||
Vector2,
|
||||
} from "../artworld/index.js";
|
||||
artworld.bindCanvas("mainCanvas");
|
||||
window.artworld = artworld;
|
||||
|
||||
let g = new Group().pos(new Vector2(0, 0));
|
||||
let c = new Circle(g).radius(80).stroke(Pico8.RED).strokeWeight(5);
|
||||
for (let i = 0; i < 15; i++) {
|
||||
c = c.copy().move(new Vector2(45, 45));
|
||||
}
|
||||
g.copy().move(new Vector2(200, 0)).stroke(Pico8.GREEN);
|
||||
g.copy().move(new Vector2(-200, 0)).stroke(Pico8.BLUE);
|
||||
artworld.draw();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
20
examples/copies.js
Normal file
20
examples/copies.js
Normal file
@ -0,0 +1,20 @@
|
||||
import {
|
||||
World,
|
||||
Color,
|
||||
Pico8,
|
||||
Group,
|
||||
Circle,
|
||||
Random,
|
||||
Vector2,
|
||||
} from "../artworld/index.js";
|
||||
let world = new World("mainCanvas");
|
||||
window.world = world;
|
||||
|
||||
let g = new Group(world).pos(new Vector2(0, 0));
|
||||
let c = new Circle(g).radius(80).stroke(Pico8.RED).strokeWeight(5);
|
||||
for (let i = 0; i < 15; i++) {
|
||||
c = c.copy().move(new Vector2(45, 45));
|
||||
}
|
||||
g.copy().move(new Vector2(200, 0)).stroke(Pico8.GREEN);
|
||||
g.copy().move(new Vector2(-200, 0)).stroke(Pico8.BLUE);
|
||||
world.draw();
|
41
examples/dataclock.js
Normal file
41
examples/dataclock.js
Normal file
@ -0,0 +1,41 @@
|
||||
import {
|
||||
World,
|
||||
Pico8,
|
||||
Group,
|
||||
Vector2,
|
||||
Line,
|
||||
Circle,
|
||||
} from "../artworld/index.js";
|
||||
|
||||
let world = new World("mainCanvas");
|
||||
window.world = world;
|
||||
// This is an experiment to separate the declaration of
|
||||
// "what the data does" from "what it looks like".
|
||||
|
||||
// First we use `Var` to define "what the data does"
|
||||
// variables are expected to regularly *vary*!
|
||||
|
||||
// Variables are defined as functions that update with time.
|
||||
// It is also possible to define static variables.
|
||||
let color = world.var(
|
||||
(t) =>
|
||||
[Pico8.RED, Pico8.ORANGE, Pico8.GREEN, Pico8.BLUE][Math.floor(t / 60) % 4],
|
||||
);
|
||||
// We can create dependencies between them: here the inner & outer
|
||||
// radii should be 10 pixels apart.
|
||||
let outerR = world.var((t) => Math.sin(t / 100) * 50 + 200);
|
||||
let innerR = world.var(() => outerR.getValue() - 10);
|
||||
// We take this composition a bit further here, the radius of the clock
|
||||
// hand grows & shrinks with the edge.
|
||||
let lineTo = world.var((t) => {
|
||||
let angle = (t / 6000) * Math.PI * 2;
|
||||
return Vector2.polar(innerR.getValue(), angle);
|
||||
});
|
||||
|
||||
let g = new Group(world);
|
||||
new Circle(g).fill(Pico8.BLACK).z(1).radius(outerR);
|
||||
new Circle(g).z(10).fill(color).radius(innerR);
|
||||
new Circle(g).radius(20).fill(Pico8.BLACK).z(50);
|
||||
new Line(g).to(lineTo).z(100);
|
||||
|
||||
world.loopStep();
|
113
examples/gallery.html
Normal file
113
examples/gallery.html
Normal file
@ -0,0 +1,113 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>JS Module Loader</title>
|
||||
<style>
|
||||
body {
|
||||
display: flex;
|
||||
font-family: Arial, sans-serif;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
#sidebar {
|
||||
width: 200px;
|
||||
background: #f4f4f4;
|
||||
padding: 10px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
#file-list button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin: 5px 0;
|
||||
padding: 10px;
|
||||
text-align: left;
|
||||
border: none;
|
||||
background: #ddd;
|
||||
cursor: pointer;
|
||||
}
|
||||
#file-list button:hover {
|
||||
background: #bbb;
|
||||
}
|
||||
#main {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px;
|
||||
}
|
||||
#mainCanvas {
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
background: #eee;
|
||||
flex: 1;
|
||||
}
|
||||
#output {
|
||||
white-space: pre-wrap;
|
||||
background: #222;
|
||||
color: #ffb300;
|
||||
padding: 10px;
|
||||
overflow: auto;
|
||||
font-family: monospace;
|
||||
flex: 1;
|
||||
max-height: 50%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="sidebar">
|
||||
<h3>Files</h3>
|
||||
<div id="file-list"></div>
|
||||
</div>
|
||||
|
||||
<div id="main">
|
||||
<canvas id="mainCanvas"></canvas>
|
||||
<div id="output">Script output will appear here...</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const jsFiles = [
|
||||
"balls.js",
|
||||
"circles.js",
|
||||
"copies.js",
|
||||
"dataclock.js",
|
||||
"lines.js",
|
||||
"liskov.js",
|
||||
"polygons.js",
|
||||
"rect.js",
|
||||
"spiral.js",
|
||||
];
|
||||
|
||||
async function loadAndRunModule(fileName) {
|
||||
const canvas = document.getElementById("mainCanvas");
|
||||
const outputDiv = document.getElementById("output");
|
||||
|
||||
// Clear the canvas
|
||||
const ctx = canvas.getContext("2d");
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
outputDiv.textContent = `Loading ${fileName}...\n`;
|
||||
|
||||
try {
|
||||
// import the module dynamically with cache busting
|
||||
const module = await import(`./${fileName}?t=${Date.now()}`);
|
||||
// TODO: get source code
|
||||
} catch (err) {
|
||||
outputDiv.textContent += `Error loading ${fileName}:\n${err}`;
|
||||
}
|
||||
}
|
||||
|
||||
function renderFileList() {
|
||||
const fileListDiv = document.getElementById("file-list");
|
||||
jsFiles.forEach((file) => {
|
||||
const btn = document.createElement("button");
|
||||
btn.textContent = file;
|
||||
btn.onclick = () => loadAndRunModule(file);
|
||||
fileListDiv.appendChild(btn);
|
||||
});
|
||||
}
|
||||
|
||||
renderFileList();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,19 +0,0 @@
|
||||
<html>
|
||||
<head> </head>
|
||||
<body>
|
||||
<canvas id="mainCanvas" width="800" height="800"></canvas>
|
||||
<script type="module">
|
||||
import { artworld, Color, Line, Vector2 } from "../artworld/index.js";
|
||||
artworld.bindCanvas("mainCanvas");
|
||||
window.artworld = artworld;
|
||||
for (let i = 0; i < 10; i++) {
|
||||
new Line()
|
||||
.pos(new Vector2(5 * i * 14, 5))
|
||||
.to(new Vector2(5 * i * 14, 140))
|
||||
.stroke(new Color(20 * i, 50, 50))
|
||||
.strokeWeight(1 + i);
|
||||
}
|
||||
artworld.draw();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
11
examples/lines.js
Normal file
11
examples/lines.js
Normal file
@ -0,0 +1,11 @@
|
||||
import { World, Color, Line, Vector2 } from "../artworld/index.js";
|
||||
let world = new World("mainCanvas");
|
||||
window.world = world;
|
||||
for (let i = 0; i < 10; i++) {
|
||||
new Line(world)
|
||||
.pos(new Vector2(5 * i * 14, 5))
|
||||
.to(new Vector2(5 * i * 14, 140))
|
||||
.stroke(new Color(20 * i, 50, 50))
|
||||
.strokeWeight(1 + i);
|
||||
}
|
||||
world.draw();
|
10
examples/liskov.js
Normal file
10
examples/liskov.js
Normal file
@ -0,0 +1,10 @@
|
||||
import { World, Rect, Arc, Line, Circle, Random } from "../artworld/index.js";
|
||||
let world = new World("mainCanvas");
|
||||
|
||||
let types = [Rect, Line, Circle, Arc];
|
||||
|
||||
for (let i = 0; i < 100; i++) {
|
||||
let Cls = Random.choice(types);
|
||||
new Cls(world).random().strokeWeight(2);
|
||||
}
|
||||
world.draw();
|
9
examples/polygons.js
Normal file
9
examples/polygons.js
Normal file
@ -0,0 +1,9 @@
|
||||
import { World, Polygon } from "../artworld/index.js";
|
||||
let world = new World("mainCanvas");
|
||||
|
||||
for (let i = 1; i < 8; i++) {
|
||||
new Polygon(world).random(3 + i);
|
||||
new Polygon(world).random(i * 10);
|
||||
}
|
||||
|
||||
world.draw();
|
11
examples/rect.js
Normal file
11
examples/rect.js
Normal file
@ -0,0 +1,11 @@
|
||||
import { World, Pico8, Rect } from "../artworld/index.js";
|
||||
let world = new World("mainCanvas");
|
||||
window.world = world;
|
||||
|
||||
for (let i = 0; i < 50; i++) {
|
||||
new Rect(world).random(200).fill(Pico8.BLACK).z(10);
|
||||
new Rect(world).random(150).fill(Pico8.DARK_BLUE).z(15);
|
||||
new Rect(world).random(100).fill(Pico8.DARK_GREY).z(20);
|
||||
new Rect(world).random(50).fill(Pico8.LIGHT_GREY).z(30);
|
||||
}
|
||||
world.draw();
|
@ -1,28 +0,0 @@
|
||||
<html>
|
||||
<head> </head>
|
||||
<body>
|
||||
<canvas id="mainCanvas" width="500" height="500"></canvas>
|
||||
<script type="module">
|
||||
import {
|
||||
artworld,
|
||||
Color,
|
||||
Pico8,
|
||||
Group,
|
||||
Rect,
|
||||
Random,
|
||||
Vector2,
|
||||
degToRad,
|
||||
} from "../artworld/index.js";
|
||||
artworld.bindCanvas("mainCanvas");
|
||||
window.artworld = artworld;
|
||||
|
||||
for (let i = 0; i < 50; i++) {
|
||||
new Rect().random(200).fill(Pico8.BLACK).z(10);
|
||||
new Rect().random(150).fill(Pico8.DARK_BLUE).z(15);
|
||||
new Rect().random(100).fill(Pico8.DARK_GREY).z(20);
|
||||
new Rect().random(50).fill(Pico8.LIGHT_GREY).z(30);
|
||||
}
|
||||
artworld.draw();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,58 +0,0 @@
|
||||
<html>
|
||||
<head> </head>
|
||||
<body>
|
||||
<canvas id="mainCanvas" width="800" height="800"></canvas>
|
||||
<script type="module">
|
||||
import {
|
||||
artworld,
|
||||
Color,
|
||||
Group,
|
||||
Line,
|
||||
Random,
|
||||
Vector2,
|
||||
degToRad,
|
||||
} from "../artworld/index.js";
|
||||
artworld.bindCanvas("mainCanvas");
|
||||
window.artworld = artworld;
|
||||
|
||||
function* spirals() {
|
||||
while (true) {
|
||||
let g = new Group();
|
||||
for (let d = 0; d < 180; d += 10) {
|
||||
if (Random.chance(0.9)) {
|
||||
new Line(g).to(Vector2.polar(190 - d, degToRad(d)));
|
||||
}
|
||||
}
|
||||
yield g;
|
||||
}
|
||||
}
|
||||
|
||||
function makeGrid(iterable, rows, cols, opts) {
|
||||
opts = opts || {};
|
||||
let width = opts.width || 50;
|
||||
let height = opts.height || 50;
|
||||
let xOff = opts.xOff || 0;
|
||||
let yOff = opts.yOff || 0;
|
||||
for (let c = 0; c < cols; c++) {
|
||||
for (let r = 0; r < rows; r++) {
|
||||
let result = iterable.next().value;
|
||||
if (result === undefined) {
|
||||
return;
|
||||
} else {
|
||||
result.pos(new Vector2(width * c + xOff, height * r + yOff));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
makeGrid(spirals(), 4, 3, {
|
||||
width: 220,
|
||||
height: 120,
|
||||
xOff: 80,
|
||||
yOff: 10,
|
||||
});
|
||||
|
||||
artworld.draw();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
51
examples/spiral.js
Normal file
51
examples/spiral.js
Normal file
@ -0,0 +1,51 @@
|
||||
import {
|
||||
World,
|
||||
Pico8,
|
||||
Group,
|
||||
Line,
|
||||
Random,
|
||||
Vector2,
|
||||
degToRad,
|
||||
} from "../artworld/index.js";
|
||||
const world = new World("mainCanvas");
|
||||
window.world = world;
|
||||
|
||||
function* spirals() {
|
||||
while (true) {
|
||||
let g = new Group(world);
|
||||
let rc = Random.choice(Object.values(Pico8));
|
||||
for (let d = 0; d < 180; d += 10) {
|
||||
if (Random.chance(0.9)) {
|
||||
new Line(g).to(Vector2.polar(190 - d, degToRad(d))).stroke(rc);
|
||||
}
|
||||
}
|
||||
yield g;
|
||||
}
|
||||
}
|
||||
|
||||
function makeGrid(iterable, rows, cols, opts) {
|
||||
opts = opts || {};
|
||||
let width = opts.width || 50;
|
||||
let height = opts.height || 50;
|
||||
let xOff = opts.xOff || 0;
|
||||
let yOff = opts.yOff || 0;
|
||||
for (let c = 0; c < cols; c++) {
|
||||
for (let r = 0; r < rows; r++) {
|
||||
let result = iterable.next().value;
|
||||
if (result === undefined) {
|
||||
return;
|
||||
} else {
|
||||
result.pos(new Vector2(width * c + xOff, height * r + yOff));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
makeGrid(spirals(), 4, 3, {
|
||||
width: 220,
|
||||
height: 120,
|
||||
xOff: 80,
|
||||
yOff: 10,
|
||||
});
|
||||
|
||||
world.draw();
|
Loading…
Reference in New Issue
Block a user