diff --git a/src/d2/ImageShape.js b/src/d2/ImageShape.js new file mode 100644 index 0000000..76aaea2 --- /dev/null +++ b/src/d2/ImageShape.js @@ -0,0 +1,43 @@ +import { IMAGE_GUIDE_TRANSPARENCY, DESELECT_TRANSPARENCY } from '../constants/d2Constants.js'; +import { Surface } from 'cal'; + +export default class ImageShape extends Surface { + constructor(shapeData) { + super(); + + this.depth = -1; + this.UID = shapeData.UID; + + this.update(shapeData); + this.setOpaque(true); + } + update(newShapeData) { + let changed; + + if (!this._shapeData || this._shapeData.transform !== newShapeData.transform) { + changed = true; + this.copyMatrix(newShapeData.transform); + } + + if (!this._shapeData || this._shapeData.imageData !== newShapeData.imageData) { + changed = true; + + const newImage = newShapeData.imageData; + const image = this._shapeData ? this._shapeData.imageData : {}; + + if (newImage.width !== image.width || newImage.height !== image.height) { + this.setSize(newImage.width, newImage.height); + this.centerX = newImage.width / 2; + this.centerY = newImage.height / 2; + } + + this.context.drawImage(newImage, 0, 0, newImage.width, newImage.height); + } + + this._shapeData = newShapeData; + return changed; + } + setOpaque(opaque) { + this.alpha = opaque ? IMAGE_GUIDE_TRANSPARENCY : DESELECT_TRANSPARENCY; + } +} diff --git a/src/d2/Shape.js b/src/d2/Shape.js new file mode 100644 index 0000000..868ed4f --- /dev/null +++ b/src/d2/Shape.js @@ -0,0 +1,119 @@ +import { shapeToPoints } from '../shape/shapeToPoints.js'; +import { shapeChanged } from '../shape/shapeDataUtils.js'; +import { Matrix } from 'cal'; +import { LINE_WIDTH } from '../constants/d2Constants.js'; +import { hexToStyle } from '../utils/colorUtils.js'; +import { DESELECT_TRANSPARENCY, FILL_TRANSPARENCY, LINE_TRANSPARENCY } from '../constants/d2Constants.js'; +import { PIXEL_RATIO } from '../constants/general.js'; + +export default class Shape extends Matrix { + constructor(shapeData) { + super(); + + this.visible = true; + this.active = false; + this.depth = shapeData.height + shapeData.z; + + this.color = ''; + + this.UID = shapeData.UID; + + this.update(shapeData); + this.setOpaque(true); + } + update(shapeData) { + let changed = false; + + this.depth = shapeData.height + shapeData.z; + + if (!this._shapeData || shapeChanged(shapeData, this._shapeData)) { + this.shapes = shapeToPoints(shapeData); + + changed = true; + } + + if (!this._shapeData || this._shapeData.transform !== shapeData.transform) { + if (shapeData.transform.matrix.some(value => typeof value !== 'number' || isNaN(value))) { + throw new Error(`Cannot update object ${this.UID}: transform contains invalid values.`); + } + + this.copyMatrix(shapeData.transform); + + changed = true; + } + + if (!this._shapeData || this._shapeData.color !== shapeData.color) { + if (typeof shapeData.color !== 'number' || isNaN(shapeData.color)) { + throw new Error(`Cannot update object ${this.UID}: color is an invalid value.`); + } + this.color = hexToStyle(shapeData.color); + changed = true; + } + + this._shapeData = shapeData; + return changed; + } + setOpaque(opaque) { + const selectTransparency = this._shapeData.fill ? FILL_TRANSPARENCY : LINE_TRANSPARENCY; + this.alpha = opaque ? selectTransparency : DESELECT_TRANSPARENCY; + } + draw(context, matrix) { + context.beginPath(); + + for (let i = 0; i < this.shapes.length; i ++) { + const { points, holes } = this.shapes[i]; + + for (let j = 0; j < points.length; j ++) { + const point = points[j].applyMatrix(matrix); + + if (j === 0) { + context.moveTo(point.x, point.y); + } else { + context.lineTo(point.x, point.y); + } + } + + for (let j = 0; j < holes.length; j ++) { + const hole = holes[j]; + + for (let k = 0; k < hole.length; k ++) { + const point = hole[k].applyMatrix(matrix); + + if (k === 0) { + context.moveTo(point.x, point.y); + } else { + context.lineTo(point.x, point.y); + } + } + } + } + + context.lineCap = 'round'; + context.lineJoin = 'round'; + + const lineWidth = PIXEL_RATIO * LINE_WIDTH; + + context.globalAlpha = this.alpha; + if (this._shapeData.fill) { + context.fillStyle = this.color; + context.fill(); + + context.strokeStyle = 'black'; + context.lineWidth = lineWidth / 2.0; + context.stroke(); + } else { + const outerLineWidth = lineWidth * this.parent.sx; + const innerLineWidth = outerLineWidth - lineWidth; + + context.strokeStyle = 'black'; + context.lineWidth = outerLineWidth; + context.stroke(); + + if (innerLineWidth > 0) { + context.strokeStyle = this.color; + context.lineWidth = innerLineWidth; + context.stroke(); + } + } + } +} diff --git a/src/shape/shapeDataUtils.js b/src/shape/shapeDataUtils.js index 8067816..9ebff38 100644 --- a/src/shape/shapeDataUtils.js +++ b/src/shape/shapeDataUtils.js @@ -1,6 +1,8 @@ import memoize from 'memoizee'; import { Vector } from '@doodle3d/cal'; import { SHAPE_CACHE_LIMIT } from '../constants/general.js'; +import ImageShape from '../d2/ImageShape.js'; +import Shape from '../d2/Shape.js'; export function shapeChanged(oldShapeData, newShapeData) { const pointsChanged = oldShapeData.points !== newShapeData.points;