You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tobspr_shapez.io/src/js/core/draw_utils.js

321 lines
9.4 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/* typehints:start */
import { AtlasSprite } from "./sprites";
import { DrawParameters } from "./draw_parameters";
/* typehints:end */
import { Vector } from "./vector";
import { Rectangle } from "./rectangle";
import { createLogger } from "./logging";
const logger = createLogger("draw_utils");
export function initDrawUtils() {
CanvasRenderingContext2D.prototype.beginRoundedRect = function (x, y, w, h, r) {
if (r < 0.05) {
this.beginPath();
this.rect(x, y, w, h);
return;
}
if (w < 2 * r) {
r = w / 2;
}
if (h < 2 * r) {
r = h / 2;
}
this.beginPath();
this.moveTo(x + r, y);
this.arcTo(x + w, y, x + w, y + h, r);
this.arcTo(x + w, y + h, x, y + h, r);
this.arcTo(x, y + h, x, y, r);
this.arcTo(x, y, x + w, y, r);
// this.closePath();
};
CanvasRenderingContext2D.prototype.beginCircle = function (x, y, r) {
if (r < 0.05) {
this.beginPath();
this.rect(x, y, 1, 1);
return;
}
this.beginPath();
this.arc(x, y, r, 0, 2.0 * Math.PI);
};
}
/**
*
* @param {object} param0
* @param {DrawParameters} param0.parameters
* @param {AtlasSprite} param0.sprite
* @param {number} param0.x
* @param {number} param0.y
* @param {number} param0.angle
* @param {number} param0.size
* @param {number=} param0.offsetX
* @param {number=} param0.offsetY
*/
export function drawRotatedSprite({ parameters, sprite, x, y, angle, size, offsetX = 0, offsetY = 0 }) {
parameters.context.translate(x, y);
parameters.context.rotate(angle);
sprite.drawCachedCentered(parameters, offsetX, offsetY, size, false);
parameters.context.rotate(-angle);
parameters.context.translate(-x, -y);
}
export function drawLineFast(context, { x1, x2, y1, y2, color = null, lineSize = 1 }) {
const dX = x2 - x1;
const dY = y2 - y1;
const angle = Math.atan2(dY, dX) + 0.0 * Math.PI;
const len = Math.hypot(dX, dY);
context.translate(x1, y1);
context.rotate(angle);
if (color) {
context.fillStyle = color;
}
context.fillRect(0, -lineSize / 2, len, lineSize);
context.rotate(-angle);
context.translate(-x1, -y1);
}
const INSIDE = 0;
const LEFT = 1;
const RIGHT = 2;
const BOTTOM = 4;
const TOP = 8;
// https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm
function computeOutCode(x, y, xmin, xmax, ymin, ymax) {
let code = INSIDE;
if (x < xmin)
// to the left of clip window
code |= LEFT;
else if (x > xmax)
// to the right of clip window
code |= RIGHT;
if (y < ymin)
// below the clip window
code |= BOTTOM;
else if (y > ymax)
// above the clip window
code |= TOP;
return code;
}
// CohenSutherland clipping algorithm clips a line from
// P0 = (x0, y0) to P1 = (x1, y1) against a rectangle with
// diagonal from (xmin, ymin) to (xmax, ymax).
/**
*
* @param {CanvasRenderingContext2D} context
*/
export function drawLineFastClipped(context, rect, { x0, y0, x1, y1, color = null, lineSize = 1 }) {
const xmin = rect.x;
const ymin = rect.y;
const xmax = rect.right();
const ymax = rect.bottom();
// compute outcodes for P0, P1, and whatever point lies outside the clip rectangle
let outcode0 = computeOutCode(x0, y0, xmin, xmax, ymin, ymax);
let outcode1 = computeOutCode(x1, y1, xmin, xmax, ymin, ymax);
let accept = false;
// eslint-disable-next-line no-constant-condition
while (true) {
if (!(outcode0 | outcode1)) {
// bitwise OR is 0: both points inside window; trivially accept and exit loop
accept = true;
break;
} else if (outcode0 & outcode1) {
// bitwise AND is not 0: both points share an outside zone (LEFT, RIGHT, TOP,
// or BOTTOM), so both must be outside window; exit loop (accept is false)
break;
} else {
// failed both tests, so calculate the line segment to clip
// from an outside point to an intersection with clip edge
let x, y;
// At least one endpoint is outside the clip rectangle; pick it.
let outcodeOut = outcode0 ? outcode0 : outcode1;
// Now find the intersection point;
// use formulas:
// slope = (y1 - y0) / (x1 - x0)
// x = x0 + (1 / slope) * (ym - y0), where ym is ymin or ymax
// y = y0 + slope * (xm - x0), where xm is xmin or xmax
// No need to worry about divide-by-zero because, in each case, the
// outcode bit being tested guarantees the denominator is non-zero
if (outcodeOut & TOP) {
// point is above the clip window
x = x0 + ((x1 - x0) * (ymax - y0)) / (y1 - y0);
y = ymax;
} else if (outcodeOut & BOTTOM) {
// point is below the clip window
x = x0 + ((x1 - x0) * (ymin - y0)) / (y1 - y0);
y = ymin;
} else if (outcodeOut & RIGHT) {
// point is to the right of clip window
y = y0 + ((y1 - y0) * (xmax - x0)) / (x1 - x0);
x = xmax;
} else if (outcodeOut & LEFT) {
// point is to the left of clip window
y = y0 + ((y1 - y0) * (xmin - x0)) / (x1 - x0);
x = xmin;
}
// Now we move outside point to intersection point to clip
// and get ready for next pass.
if (outcodeOut == outcode0) {
x0 = x;
y0 = y;
outcode0 = computeOutCode(x0, y0, xmin, xmax, ymin, ymax);
} else {
x1 = x;
y1 = y;
outcode1 = computeOutCode(x1, y1, xmin, xmax, ymin, ymax);
}
}
}
if (accept) {
// Following functions are left for implementation by user based on
// their platform (OpenGL/graphics.h etc.)
// DrawRectangle(xmin, ymin, xmax, ymax);
// LineSegment(x0, y0, x1, y1);
drawLineFast(context, {
x1: x0,
y1: y0,
x2: x1,
y2: y1,
color,
lineSize,
});
}
}
/**
* Converts an HSL color value to RGB. Conversion formula
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
* Assumes h, s, and l are contained in the set [0, 1] and
* returns r, g, and b in the set [0, 255].
*
* @param {number} h The hue
* @param {number} s The saturation
* @param {number} l The lightness
* @return {Array} The RGB representation
*/
export function hslToRgb(h, s, l) {
let r;
let g;
let b;
if (s === 0) {
r = g = b = l; // achromatic
} else {
// tslint:disable-next-line:no-shadowed-variable
const hue2rgb = function (p, q, t) {
if (t < 0) {
t += 1;
}
if (t > 1) {
t -= 1;
}
if (t < 1 / 6) {
return p + (q - p) * 6 * t;
}
if (t < 1 / 2) {
return q;
}
if (t < 2 / 3) {
return p + (q - p) * (2 / 3 - t) * 6;
}
return p;
};
let q = l < 0.5 ? l * (1 + s) : l + s - l * s;
let p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
export function wrapText(context, text, x, y, maxWidth, lineHeight, stroke = false) {
var words = text.split(" ");
var line = "";
for (var n = 0; n < words.length; n++) {
var testLine = line + words[n] + " ";
var metrics = context.measureText(testLine);
var testWidth = metrics.width;
if (testWidth > maxWidth && n > 0) {
if (stroke) {
context.strokeText(line, x, y);
} else {
context.fillText(line, x, y);
}
line = words[n] + " ";
y += lineHeight;
} else {
line = testLine;
}
}
if (stroke) {
context.strokeText(line, x, y);
} else {
context.fillText(line, x, y);
}
}
/**
* Returns a rotated trapez, used for spotlight culling
* @param {number} x
* @param {number} y
* @param {number} w
* @param {number} h
* @param {number} leftHeight
* @param {number} angle
*/
export function rotateTrapezRightFaced(x, y, w, h, leftHeight, angle) {
const halfY = y + h / 2;
const points = [
new Vector(x, halfY - leftHeight / 2),
new Vector(x + w, y),
new Vector(x, halfY + leftHeight / 2),
new Vector(x + w, y + h),
];
return Rectangle.getAroundPointsRotated(points, angle);
}
/**
* Converts values from 0 .. 255 to values like 07, 7f, 5d etc
* @param {number} value
* @returns {string}
*/
export function mapClampedColorValueToHex(value) {
const hex = "0123456789abcdef";
return hex[Math.floor(value / 16)] + hex[value % 16];
}
/**
* Converts rgb to a hex string
* @param {number} r
* @param {number} g
* @param {number} b
* @returns {string}
*/
export function rgbToHex(r, g, b) {
return mapClampedColorValueToHex(r) + mapClampedColorValueToHex(g) + mapClampedColorValueToHex(b);
}