refactor: tile rendering
This commit is contained in:
parent
869829fffa
commit
69ec257012
|
@ -1,17 +1,5 @@
|
|||
export default (Layers) => class PhysicsLayers extends Layers {
|
||||
|
||||
addLayer(layer) {
|
||||
super.addLayer(layer);
|
||||
layer.on('tile-data-changed', layer.onTileDataChanged, layer);
|
||||
}
|
||||
|
||||
removeLayer(layer) {
|
||||
if (-1 !== this.layers.indexOf(layer)) {
|
||||
layer.off('tile-data-changed', layer.onTileDataChanged);
|
||||
}
|
||||
super.removeLayer(layer);
|
||||
}
|
||||
|
||||
set world(world) {
|
||||
for (let index = 0; index < this.layers.length; index++) {
|
||||
this.layers[index].world = world;
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import {EntityListView} from '@avocado/entity';
|
||||
import {Container, Sprite} from '@avocado/graphics';
|
||||
import {Vector} from '@avocado/math';
|
||||
import {Container} from '@avocado/graphics';
|
||||
|
||||
import TilesRenderer from './tiles-renderer';
|
||||
import TilesView from './tiles-view';
|
||||
|
||||
const views = new Set();
|
||||
|
||||
|
@ -11,15 +10,9 @@ export default class LayerView extends Container {
|
|||
constructor(layer, renderer) {
|
||||
super();
|
||||
this.entityListView = new EntityListView(layer.entityList);
|
||||
this.tilesView = new TilesView(layer.tiles, layer.tileset, renderer);
|
||||
this.layer = layer;
|
||||
this.layerContainer = new Container();
|
||||
this.renderer = renderer;
|
||||
// Handle entity add/remove.
|
||||
layer.on('tile-data-changed', this.onLayerTileDataChanged, this);
|
||||
this.onLayerTileDataChanged();
|
||||
layer.on('tilesetChanged', this.onLayerTilesetChanged, this);
|
||||
this.onLayerTilesetChanged(layer.tileset, undefined);
|
||||
this.addChild(this.layerContainer);
|
||||
this.addChild(this.tilesView);
|
||||
this.addChild(this.entityListView);
|
||||
if (module.hot) {
|
||||
views.add(this);
|
||||
|
@ -31,54 +24,20 @@ export default class LayerView extends Container {
|
|||
if (module.hot) {
|
||||
views.delete(this);
|
||||
}
|
||||
this.layer.off('tile-data-changed', this.onLayerTileDataChanged);
|
||||
this.layer.off('tilesetChanged', this.onLayerTilesetChanged);
|
||||
}
|
||||
|
||||
onLayerTileDataChanged() {
|
||||
this.render();
|
||||
}
|
||||
|
||||
onLayerTilesetChanged(oldTileset, newTileset) {
|
||||
if (oldTileset) {
|
||||
oldTileset.off('imageChanged', this.onLayerTilesetImageChanged, this);
|
||||
}
|
||||
if (newTileset) {
|
||||
newTileset.on('imageChanged', this.onLayerTilesetImageChanged, this);
|
||||
}
|
||||
this.onLayerTilesetImageChanged();
|
||||
}
|
||||
|
||||
onLayerTilesetImageChanged() {
|
||||
this.render();
|
||||
}
|
||||
|
||||
render() {
|
||||
this.layerContainer.removeAllChildren();
|
||||
if (!this.renderer) {
|
||||
return;
|
||||
}
|
||||
if (!this.layer.tileset) {
|
||||
return;
|
||||
}
|
||||
if (Vector.isZero(this.layer.tiles.size)) {
|
||||
return;
|
||||
}
|
||||
const tilesRenderer = new TilesRenderer(this.layer.tiles, this.layer.tileset);
|
||||
const chunk = tilesRenderer.renderChunk(this.renderer);
|
||||
const tilesSprite = new Sprite(chunk);
|
||||
tilesSprite.anchor = [0, 0];
|
||||
this.layerContainer.addChild(tilesSprite);
|
||||
renderChunksForExtent(extent) {
|
||||
this.tilesView.renderChunksForExtent(extent);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (module.hot) {
|
||||
module.hot.accept('./tiles-renderer', () => {
|
||||
const it = views.values();
|
||||
for (let value = it.next(); value.done !== true; value = it.next()) {
|
||||
const {value: view} = value;
|
||||
view.render();
|
||||
}
|
||||
});
|
||||
}
|
||||
// if (module.hot) {
|
||||
// module.hot.accept('./tiles-renderer', () => {
|
||||
// const it = views.values();
|
||||
// for (let value = it.next(); value.done !== true; value = it.next()) {
|
||||
// const {value: view} = value;
|
||||
// view.render();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
|
|
@ -7,7 +7,7 @@ export default class LayersView extends Container {
|
|||
constructor(layers, renderer) {
|
||||
super();
|
||||
this.layers = layers;
|
||||
this.layerViews = {};
|
||||
this.layerViews = [];
|
||||
this.renderer = renderer;
|
||||
this.layers.on('layerAdded', this.onLayerAdded, this);
|
||||
for (let i = 0; i < this.layers.layers.length; i++) {
|
||||
|
@ -30,4 +30,10 @@ export default class LayersView extends Container {
|
|||
this.removeChild(layerView);
|
||||
}
|
||||
|
||||
renderChunksForExtent(extent) {
|
||||
for (let i = 0; i < this.layerViews.length; i++) {
|
||||
this.layerViews[i].renderChunksForExtent(extent);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -30,7 +30,6 @@ export default (latus) => {
|
|||
this.entityList.destroy();
|
||||
this.entityList.off('entityAdded', this.onEntityAddedToLayer);
|
||||
this.entityList.off('entityRemoved', this.onEntityRemovedFromLayer);
|
||||
this.tiles.off('tile-data-changed', this.onTileDataChanged);
|
||||
if (this.tileset) {
|
||||
this.tileset.destroy();
|
||||
}
|
||||
|
@ -89,10 +88,6 @@ export default (latus) => {
|
|||
this.emit('entityRemoved', entity);
|
||||
}
|
||||
|
||||
onTileDataChanged() {
|
||||
this.emit('tile-data-changed');
|
||||
}
|
||||
|
||||
removeEntity(entity) {
|
||||
this.entityList.removeEntity(entity);
|
||||
}
|
||||
|
@ -126,10 +121,8 @@ export default (latus) => {
|
|||
setTiles(tiles) {
|
||||
if (this.tiles) {
|
||||
this.stopSynchronizing(this.tiles);
|
||||
this.tiles.off('tile-data-changed', this.onTileDataChanged);
|
||||
}
|
||||
this.tiles = tiles;
|
||||
this.tiles.on('tile-data-changed', this.onTileDataChanged, this);
|
||||
this.startSynchronizing(tiles);
|
||||
}
|
||||
|
||||
|
@ -139,7 +132,6 @@ export default (latus) => {
|
|||
|
||||
tick(elapsed) {
|
||||
this.entityList.tick(elapsed);
|
||||
this.tiles.tick();
|
||||
}
|
||||
|
||||
tileAt(position) {
|
||||
|
|
|
@ -19,8 +19,6 @@ export default (latus) => {
|
|||
);
|
||||
return class Tiles extends decorate(Class) {
|
||||
|
||||
#tileDataChanged = false;
|
||||
|
||||
#packets = [];
|
||||
|
||||
#updates = [];
|
||||
|
@ -37,6 +35,7 @@ export default (latus) => {
|
|||
if ('TilesUpdate' === packet.constructor.type) {
|
||||
const {position: [x, y], size: [w, h], tiles} = packet.data;
|
||||
this.stampAt([x, y, w, h], tiles);
|
||||
this.emit('update', [x, y, w, h]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -140,7 +139,6 @@ export default (latus) => {
|
|||
if ('client' !== process.env.SIDE) {
|
||||
this.#updates.push([[x, y], [1, 1], [tile]]);
|
||||
}
|
||||
this.#tileDataChanged = true;
|
||||
}
|
||||
|
||||
slice(rectangle) {
|
||||
|
@ -198,18 +196,8 @@ export default (latus) => {
|
|||
sx -= w;
|
||||
sy += 1;
|
||||
}
|
||||
if (isDirty) {
|
||||
this.#tileDataChanged = true;
|
||||
if ('client' !== process.env.SIDE) {
|
||||
this.#updates.push([[x, y], [w, h], tiles]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tick() {
|
||||
if (this.#tileDataChanged) {
|
||||
this.emit('tile-data-changed');
|
||||
this.#tileDataChanged = false;
|
||||
if (isDirty && 'client' !== process.env.SIDE) {
|
||||
this.#updates.push([[x, y], [w, h], tiles]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,4 +12,8 @@ export default class RoomView extends Container {
|
|||
this.sort();
|
||||
}
|
||||
|
||||
renderChunksForExtent(extent) {
|
||||
this.layersView.renderChunksForExtent(extent);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,147 +0,0 @@
|
|||
import {
|
||||
Canvas,
|
||||
Container,
|
||||
Image,
|
||||
Sprite,
|
||||
} from '@avocado/graphics';
|
||||
import {
|
||||
noise,
|
||||
Rectangle,
|
||||
Vector,
|
||||
Vertice,
|
||||
} from '@avocado/math';
|
||||
|
||||
export default class TilesRenderer {
|
||||
|
||||
constructor(tiles, tileset) {
|
||||
this.tiles = tiles;
|
||||
this.tileset = tileset;
|
||||
}
|
||||
|
||||
renderChunk(renderer, rectangle) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
rectangle ||= Rectangle.compose([0, 0], this.tiles.size);
|
||||
const slice = this.tiles.slice(rectangle);
|
||||
if (!slice) {
|
||||
return undefined;
|
||||
}
|
||||
const mask = this.renderMask(rectangle);
|
||||
const size = Rectangle.size(rectangle);
|
||||
const {tileSize} = this.tileset;
|
||||
if (mask) {
|
||||
mask.anchor = [0, 0];
|
||||
}
|
||||
const rowWidth = size[0] * tileSize[0];
|
||||
// Render all tiles.
|
||||
const container = new Container();
|
||||
if (mask) {
|
||||
container.mask = mask;
|
||||
}
|
||||
const position = [0, 0];
|
||||
for (let i = 0; i < slice.length; ++i) {
|
||||
const index = slice[i];
|
||||
if (index > 0) {
|
||||
// Lookup subimage.
|
||||
const subimage = this.tileset.subimage(index);
|
||||
if (subimage) {
|
||||
const sprite = new Sprite(subimage);
|
||||
sprite.anchor = [0, 0];
|
||||
sprite.position = position;
|
||||
container.addChild(sprite);
|
||||
}
|
||||
}
|
||||
// Only adds, please.
|
||||
position[0] += tileSize[0];
|
||||
if (rowWidth === position[0]) {
|
||||
position[0] = 0;
|
||||
position[1] += tileSize[1];
|
||||
}
|
||||
}
|
||||
const renderable = new Container();
|
||||
renderable.addChild(container);
|
||||
if (mask) {
|
||||
renderable.addChild(mask);
|
||||
}
|
||||
const canvasSize = Vector.mul(size, tileSize);
|
||||
const canvas = new Canvas(canvasSize);
|
||||
canvas.render(renderable, renderer);
|
||||
container.destroy();
|
||||
if (mask) {
|
||||
mask.destroy();
|
||||
}
|
||||
// Convert to image and return.
|
||||
const image = canvas.toImage();
|
||||
canvas.destroy();
|
||||
return image;
|
||||
}
|
||||
|
||||
renderMask(rectangle) {
|
||||
const hulls = this.tiles.indexHulls(0);
|
||||
if (!hulls.length > 0) {
|
||||
return undefined;
|
||||
}
|
||||
const size = Rectangle.size(rectangle);
|
||||
const {tileSize} = this.tileset;
|
||||
const canvasSize = Vector.mul(size, tileSize);
|
||||
const canvas = window.document.createElement('canvas');
|
||||
[canvas.width, canvas.height] = canvasSize;
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.fillStyle = 'rgba(0, 0, 0, 1)';
|
||||
for (let i = 0; i < hulls.length; ++i) {
|
||||
const scaled = [];
|
||||
for (let j = 0; j < hulls[i].length; j++) {
|
||||
scaled.push(Vector.scale(hulls[i][j], 16));
|
||||
}
|
||||
scaled.push(Vector.scale(hulls[i][0], 16));
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(scaled[0][0], scaled[0][1]);
|
||||
for (let j = 0; j < scaled.length - 1; j++) {
|
||||
const p0 = scaled[j + 0];
|
||||
const p1 = scaled[j + 1];
|
||||
const points = Vertice.bresenham(p0, p1);
|
||||
const isReversed = (
|
||||
(p0[0] > p1[0] && points[0][0] < points[points.length - 1][0])
|
||||
|| (p0[1] > p1[1] && points[0][1] < points[points.length - 1][1])
|
||||
);
|
||||
const u = Vector.scale(
|
||||
Vector.fromRadians((Math.PI * 0.5) + Vector.toRadians(Vector.sub(p1, p0))),
|
||||
2,
|
||||
);
|
||||
for (
|
||||
let k = (isReversed ? points.length - 1 : 0);
|
||||
(isReversed ? k >= 0 : k < points.length);
|
||||
k += (isReversed ? -1 : 1)
|
||||
) {
|
||||
const [x, y] = points[k];
|
||||
const shift = Vector.add(
|
||||
u,
|
||||
Vector.scale(
|
||||
Vector.mul(
|
||||
[
|
||||
noise(Vector.scale([x, y], 10)),
|
||||
noise(Vector.scale([y, x], 10)),
|
||||
],
|
||||
[
|
||||
noise(Vector.scale([x, y], 20)),
|
||||
noise(Vector.scale([y, x], 20)),
|
||||
],
|
||||
),
|
||||
5,
|
||||
),
|
||||
);
|
||||
const [vx, vy] = Vector.add(
|
||||
points[k],
|
||||
shift,
|
||||
);
|
||||
ctx.lineTo(vx, vy);
|
||||
}
|
||||
}
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
}
|
||||
return new Sprite(Image.from(canvas));
|
||||
}
|
||||
|
||||
}
|
252
packages/topdown/src/tiles-view.js
Normal file
252
packages/topdown/src/tiles-view.js
Normal file
|
@ -0,0 +1,252 @@
|
|||
import {
|
||||
Canvas,
|
||||
Container,
|
||||
// Image,
|
||||
Sprite,
|
||||
} from '@avocado/graphics';
|
||||
import {Vector} from '@avocado/math';
|
||||
|
||||
export default class TilesView extends Container {
|
||||
|
||||
static CHUNK_SIZE = [16, 16];
|
||||
|
||||
constructor(tiles, tileset, renderer) {
|
||||
super();
|
||||
this.tiles = tiles;
|
||||
this.tiles.on('update', ([x, y, w, h]) => {
|
||||
const chunks = this.chunksForUnitExtent([x, y, w, h]);
|
||||
for (let i = 0; i < chunks.length; ++i) {
|
||||
const chunk = chunks[i];
|
||||
const rendered = this.rendered[this.constructor.hashChunk(chunk)];
|
||||
if (rendered) {
|
||||
rendered.destroy(true);
|
||||
}
|
||||
this.renderChunk(chunk);
|
||||
}
|
||||
});
|
||||
this.tileset = tileset;
|
||||
this.renderer = renderer;
|
||||
this.rendered = [];
|
||||
}
|
||||
|
||||
chunksForExtent([x, y, w, h]) {
|
||||
const [tw, th] = this.tileset.tileSize;
|
||||
/* eslint-disable no-param-reassign */
|
||||
x /= tw;
|
||||
w /= tw;
|
||||
y /= th;
|
||||
h /= th;
|
||||
/* eslint-enable no-param-reassign */
|
||||
return this.chunksForUnitExtent([x, y, w, h]);
|
||||
}
|
||||
|
||||
chunksForUnitExtent([x, y, w, h]) {
|
||||
const [fw, fh] = this.tiles.size;
|
||||
/* eslint-disable no-param-reassign */
|
||||
if (
|
||||
x >= fw
|
||||
|| y >= fh
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
if (x < 0) {
|
||||
w += x;
|
||||
x = 0;
|
||||
}
|
||||
if (y < 0) {
|
||||
h += y;
|
||||
y = 0;
|
||||
}
|
||||
if (
|
||||
w <= 0
|
||||
|| h <= 0
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
if (w > fw - x) {
|
||||
w = fw - x;
|
||||
}
|
||||
if (h > fh - y) {
|
||||
h = fh - y;
|
||||
}
|
||||
/* eslint-enable no-param-reassign */
|
||||
const [cw, ch] = this.constructor.CHUNK_SIZE;
|
||||
let [cx, cy] = Vector.floor(Vector.div([x, y], [cw, ch]));
|
||||
let [chunksX, chunksY] = Vector.floor(
|
||||
Vector.div([w - 1, h - 1], [cw, ch]),
|
||||
);
|
||||
const [cmx, cmy] = Vector.floor(Vector.mod([x, y], [cw, ch]));
|
||||
const [cmw, cmh] = Vector.mod([w, h], [cw, ch]);
|
||||
if (cmx + cmw >= cw) {
|
||||
chunksX += 1;
|
||||
}
|
||||
if (cmy + cmh >= ch) {
|
||||
chunksY += 1;
|
||||
}
|
||||
const chunks = [];
|
||||
for (let iy = 0; iy <= chunksY; ++iy) {
|
||||
for (let ix = 0; ix <= chunksX; ++ix) {
|
||||
chunks.push([cx, cy]);
|
||||
cx += 1;
|
||||
}
|
||||
cx -= chunksX + 1;
|
||||
cy += 1;
|
||||
}
|
||||
return chunks;
|
||||
}
|
||||
|
||||
static hashChunk(chunk) {
|
||||
// eslint-disable-next-line no-bitwise
|
||||
return (chunk[1] << 16) + chunk[0];
|
||||
}
|
||||
|
||||
renderChunk(chunk) {
|
||||
if (
|
||||
chunk[0] < 0
|
||||
|| chunk[1] < 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const [fw, fh] = this.tiles.size;
|
||||
const [cw, ch] = this.constructor.CHUNK_SIZE;
|
||||
const [cx, cy] = Vector.mul(chunk, [cw, ch]);
|
||||
if (
|
||||
cx >= fw
|
||||
|| cy >= fh
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const sw = cx + cw > fw ? (cx + cw) - fw : cw;
|
||||
const sh = cy + ch > fh ? (cy + ch) - fh : ch;
|
||||
const slice = this.tiles.slice([cx, cy, sw, sh]);
|
||||
if (!slice) {
|
||||
return;
|
||||
}
|
||||
// const mask = this.renderMask(rectangle);
|
||||
const [tw, th] = this.tileset.tileSize;
|
||||
// if (mask) {
|
||||
// mask.anchor = [0, 0];
|
||||
// }
|
||||
const rw = cw * tw;
|
||||
const container = new Container();
|
||||
// if (mask) {
|
||||
// container.mask = mask;
|
||||
// }
|
||||
let [x, y] = [0, 0];
|
||||
for (let i = 0; i < slice.length; ++i) {
|
||||
const index = slice[i];
|
||||
if (index > 0) {
|
||||
const subimage = this.tileset.subimage(index);
|
||||
if (subimage) {
|
||||
const sprite = new Sprite(subimage);
|
||||
sprite.anchor = [0, 0];
|
||||
sprite.position = [x, y];
|
||||
container.addChild(sprite);
|
||||
}
|
||||
}
|
||||
x += tw;
|
||||
if (rw === x) {
|
||||
x = 0;
|
||||
y += th;
|
||||
}
|
||||
}
|
||||
const renderable = new Container();
|
||||
renderable.addChild(container);
|
||||
// if (mask) {
|
||||
// renderable.addChild(mask);
|
||||
// }
|
||||
const canvas = new Canvas([rw, ch * th]);
|
||||
canvas.render(renderable, this.renderer);
|
||||
container.destroy();
|
||||
// if (mask) {
|
||||
// mask.destroy();
|
||||
// }
|
||||
const sprite = new Sprite(canvas.toImage());
|
||||
sprite.anchor = [0, 0];
|
||||
sprite.position = [chunk[0] * cw * tw, chunk[1] * ch * th];
|
||||
canvas.destroy();
|
||||
this.rendered[this.constructor.hashChunk(chunk)] = sprite;
|
||||
this.addChild(sprite);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
renderChunksForExtent(extent) {
|
||||
const chunks = this.chunksForExtent(extent);
|
||||
for (let i = 0; i < chunks.length; ++i) {
|
||||
const chunk = chunks[i];
|
||||
if (!this.rendered[this.constructor.hashChunk(chunk)]) {
|
||||
this.renderChunk(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// renderMask(rectangle) {
|
||||
// const hulls = this.tiles.indexHulls(0);
|
||||
// if (!hulls.length > 0) {
|
||||
// return undefined;
|
||||
// }
|
||||
// const size = Rectangle.size(rectangle);
|
||||
// const {tileSize} = this.tileset;
|
||||
// const canvasSize = Vector.mul(size, tileSize);
|
||||
// const canvas = window.document.createElement('canvas');
|
||||
// [canvas.width, canvas.height] = canvasSize;
|
||||
// const ctx = canvas.getContext('2d');
|
||||
// ctx.fillStyle = 'rgba(255, 255, 255, 1)';
|
||||
// ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
// ctx.fillStyle = 'rgba(0, 0, 0, 1)';
|
||||
// for (let i = 0; i < hulls.length; ++i) {
|
||||
// const scaled = [];
|
||||
// for (let j = 0; j < hulls[i].length; j++) {
|
||||
// scaled.push(Vector.scale(hulls[i][j], 16));
|
||||
// }
|
||||
// scaled.push(Vector.scale(hulls[i][0], 16));
|
||||
// ctx.beginPath();
|
||||
// ctx.moveTo(scaled[0][0], scaled[0][1]);
|
||||
// for (let j = 0; j < scaled.length - 1; j++) {
|
||||
// const p0 = scaled[j + 0];
|
||||
// const p1 = scaled[j + 1];
|
||||
// const points = Vertice.bresenham(p0, p1);
|
||||
// const isReversed = (
|
||||
// (p0[0] > p1[0] && points[0][0] < points[points.length - 1][0])
|
||||
// || (p0[1] > p1[1] && points[0][1] < points[points.length - 1][1])
|
||||
// );
|
||||
// const u = Vector.scale(
|
||||
// Vector.fromRadians((Math.PI * 0.5) + Vector.toRadians(Vector.sub(p1, p0))),
|
||||
// 2,
|
||||
// );
|
||||
// for (
|
||||
// let k = (isReversed ? points.length - 1 : 0);
|
||||
// (isReversed ? k >= 0 : k < points.length);
|
||||
// k += (isReversed ? -1 : 1)
|
||||
// ) {
|
||||
// const [x, y] = points[k];
|
||||
// const shift = Vector.add(
|
||||
// u,
|
||||
// Vector.scale(
|
||||
// Vector.mul(
|
||||
// [
|
||||
// noise(Vector.scale([x, y], 10)),
|
||||
// noise(Vector.scale([y, x], 10)),
|
||||
// ],
|
||||
// [
|
||||
// noise(Vector.scale([x, y], 20)),
|
||||
// noise(Vector.scale([y, x], 20)),
|
||||
// ],
|
||||
// ),
|
||||
// 5,
|
||||
// ),
|
||||
// );
|
||||
// const [vx, vy] = Vector.add(
|
||||
// points[k],
|
||||
// shift,
|
||||
// );
|
||||
// ctx.lineTo(vx, vy);
|
||||
// }
|
||||
// }
|
||||
// ctx.closePath();
|
||||
// ctx.fill();
|
||||
// }
|
||||
// return new Sprite(Image.from(canvas));
|
||||
// }
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user