onsight
Version:
Interactive, easy to use JavaScript game framework.
169 lines (139 loc) • 5.9 kB
JavaScript
import { Box } from './Box.js';
import { Box2 } from '../../math/Box2.js';
import { ColorStyle } from './style/ColorStyle.js';
import { PolyUtils } from '../../utils/PolyUtils.js';
import { Thing } from '../Thing.js';
const PATTERN_SPACING = 5; // space between diamonds
const SIMPLIFY_AMOUNT = 0.2; // 0.1 to 10-ish
let _pattern;
class Sprite extends Box {
#box = new Box2();
constructor(src) {
super();
this.type = 'Sprite';
this.image = document.createElement('img');
this.patternStyle = new ColorStyle('--icon');
this.contours = [];
this.path = new Path2D();
if (src) this.setImage(src);
}
setImage(src) {
const self = this;
this.image.onload = function() {
const width = self.image.naturalWidth;
const height = self.image.naturalWidth;
const halfWidth = width / 2;
const halfHeight = height / 2;
self.box.min.set(-halfWidth, -halfHeight);
self.box.max.set(+halfWidth, +halfHeight);
self.computeBoundingBox();
// Alpha Mask
const maskCanvas = document.createElement('canvas');
maskCanvas.width = width;
maskCanvas.height = height;
const maskContext = maskCanvas.getContext('2d');
maskContext.drawImage(self.image, 0, 0, width, height, 0, 0, width, height);
// Get the alpha channel data from the mask canvas
const imageData = maskContext.getImageData(0, 0, width, height);
const alphaData = imageData.data;
const maskData = maskContext.createImageData(width, height);
const maskPixels = maskData.data;
// Set the mask pixels based on the alpha channel
for (let i = 3; i < alphaData.length; i += 4) {
const alpha = alphaData[i];
maskPixels[i - 3] = alpha;
maskPixels[i - 2] = alpha;
maskPixels[i - 1] = alpha;
maskPixels[i] = alpha;
}
maskContext.putImageData(maskData, 0, 0);
// Find Objects in Pixel Data
self.contours = PolyUtils.findObjects(maskData, SIMPLIFY_AMOUNT);
// Contours to Path2D
const path = new Path2D();
for (const contour of self.contours) {
const outerContour = contour.outerContour;
path.moveTo(outerContour[0][0] - halfWidth, outerContour[0][1] - halfHeight);
for (let i = 1; i < outerContour.length; i++) {
path.lineTo(outerContour[i][0] - halfWidth, outerContour[i][1] - halfHeight);
}
path.closePath();
for (const hole of contour.holes) {
const hl = hole.length - 1;
path.moveTo(hole[hl][0] - halfWidth, hole[hl][1] - halfHeight);
for (let i = hl - 1; i >= 0; i--) {
path.lineTo(hole[i][0] - halfWidth, hole[i][1] - halfHeight);
}
path.closePath();
}
}
self.path = path;
};
this.image.src = src;
}
draw(renderer) {
const context = renderer.context;
const camera = renderer.camera;
// Pattern?
if (!_pattern) {
const color = this.patternStyle.get(context);
const lineWidth = 0.5;
const spacing = PATTERN_SPACING;
_pattern = context.createPattern(createCrossHatchPattern(color, lineWidth, spacing), 'repeat');
}
// Check Bounds
if (this.box.equals(this.#box) === false) this.computeBoundingBox();
// Image Complete?
if (this.image.src.length === 0 || !this.image.complete) return;
// Image Data
const width = this.image.naturalWidth;
const height = this.image.naturalHeight;
const sx = 0;
const sy = 0;
const sw = width;
const sh = height;
const dx = width / -2;
const dy = height / -2;
const dw = width;
const dh = height;
// Transparent Draw
if (context.globalAlpha < 0.05) {
context.save();
context.globalAlpha = 1;
// Create a new path that is scaled based on the camera scale
const scaledPath = new Path2D();
scaledPath.addPath(this.path, new DOMMatrix([ camera.scale, 0, 0, camera.scale, 0, 0 ]));
// Clip using the scaled path
context.scale(1 / camera.scale, 1 / camera.scale);
context.clip(scaledPath);
// Fill the pattern
context.fillStyle = _pattern;
context.fillRect(dx * camera.scale, dy * camera.scale, dw * camera.scale, dh * camera.scale);
// Stroke the scaled path
context.strokeStyle = this.patternStyle.get(context);
context.lineWidth = 1.5;
context.stroke(scaledPath);
context.restore();
// Normal Draw
} else {
context.drawImage(this.image, sx, sy, sw, sh, dx, dy, dw, dh);
}
}
}
Thing.register('Sprite', Sprite);
export { Sprite };
/******************** INTERNAL */
function createCrossHatchPattern(color, lineWidth, spacing) {
const patternCanvas = document.createElement('canvas');
const patternContext = patternCanvas.getContext('2d');
const size = spacing * 2;
patternCanvas.width = size;
patternCanvas.height = size;
patternContext.strokeStyle = color;
patternContext.lineWidth = lineWidth;
patternContext.beginPath();
patternContext.moveTo(0, 0); patternContext.lineTo(size, size);
patternContext.moveTo(size, 0); patternContext.lineTo(0, size);
patternContext.stroke();
return patternCanvas;
}