Home Reference Source

scripts/floorplanner/floorplanner_view.js

import $ from 'jquery';
import {Vector2} from 'three';
import {Utils} from '../core/utils.js';
import {EVENT_UPDATED} from '../core/events.js';

import {Dimensioning} from '../core/dimensioning.js';
import {CarbonSheet} from './carbonsheet.js';

/** */
export const floorplannerModes = {MOVE: 0,DRAW: 1,DELETE: 2};

// grid parameters
export const gridSpacing = 20; // pixels
export const gridWidth = 1;
export const gridColor = '#f1f1f1';

// room config
// export const roomColor = '#f9f9f9';
export const roomColor = '#fedaff66';

// wall config
export const wallWidth = 5;
export const wallWidthHover = 7;
export const wallColor = '#dddddd';
export const wallColorHover = '#008cba';
export const edgeColor = '#888888';
export const edgeColorHover = '#008cba';
export const edgeWidth = 1;

export const deleteColor = '#ff0000';

// corner config
export const cornerRadius = 0;
export const cornerRadiusHover = 7;
export const cornerColor = '#cccccc';
export const cornerColorHover = '#008cba';
/**
 * The View to be used by a Floorplanner to render in/interact with.
 */
export class FloorplannerView2D
{
	constructor(floorplan, viewmodel, canvas)
	{
		this.canvasElement = document.getElementById(canvas);
		this.canvas = canvas;
		this.context = this.canvasElement.getContext('2d');
		this.floorplan = floorplan;
		this.viewmodel = viewmodel;

		var scope = this;
		this._carbonsheet = new CarbonSheet(floorplan, viewmodel, canvas);
		this._carbonsheet.addEventListener(EVENT_UPDATED, function()
				{
					scope.draw();
				});

		this.floorplan.carbonSheet = this._carbonsheet;

		$(window).resize(() => {scope.handleWindowResize();});
		this.handleWindowResize();
	}

	get carbonSheet()
	{
		return this._carbonsheet;
	}

	/** */
	handleWindowResize()
	{
		var canvasSel = $('#' + this.canvas);
		var parent = canvasSel.parent();
		canvasSel.height(parent.innerHeight());
		canvasSel.width(parent.innerWidth());
		this.canvasElement.height = parent.innerHeight();
		this.canvasElement.width = parent.innerWidth();
		this.draw();
	}

	/** */
	draw()
	{
		this.context.clearRect(0, 0, this.canvasElement.width, this.canvasElement.height);

		this._carbonsheet.draw();
		this.drawGrid();
		this.drawOriginCrossHair();

		// this.context.globalAlpha = 0.3;
		this.floorplan.getRooms().forEach((room) => {this.drawRoom(room);});
		// this.context.globalAlpha = 1.0;

		this.floorplan.getWalls().forEach((wall) => {this.drawWall(wall);});
		this.floorplan.getCorners().forEach((corner) => {this.drawCorner(corner);});
		if (this.viewmodel.mode == floorplannerModes.DRAW)
		{
			this.drawTarget(this.viewmodel.targetX, this.viewmodel.targetY, this.viewmodel.lastNode);
			//Enable the below lines for measurement while drawing, still needs work as it is crashing the whole thing
			if(this.viewmodel.lastNode != null)
			{
				var a = new Vector2(this.viewmodel.lastNode.x,this.viewmodel.lastNode.y);
				var b = new Vector2(this.viewmodel.targetX, this.viewmodel.targetY);
				var abvector = b.clone().sub(a);
				var midPoint = abvector.multiplyScalar(0.5).add(a);
				this.drawTextLabel(Dimensioning.cmToMeasure(a.distanceTo(b)), this.viewmodel.convertX(midPoint.x), this.viewmodel.convertY(midPoint.y));
			}
		}
		this.floorplan.getWalls().forEach((wall) => {this.drawWallLabels(wall);});
	}

	drawOriginCrossHair()
	{
		var ox = this.viewmodel.convertX(0);
		var oy = this.viewmodel.convertY(0);
		//draw origin crosshair
		this.context.fillStyle = '#0000FF';
		this.context.fillRect(ox-2, oy-7.5, 4, 15);
		this.context.fillRect(ox-7.5, oy-2, 15, 4);
		this.context.strokeStyle = '#FF0000';
		this.context.fillRect(ox-1.25, oy-5, 2.5, 10);
		this.context.fillRect(ox-5, oy-1.25, 10, 2.5);
	}


	/** */
	drawWall(wall)
	{
		var hover = (wall === this.viewmodel.activeWall);
		var color = wallColor;
		if (hover && this.viewmodel.mode == floorplannerModes.DELETE)
		{
			color = deleteColor;
		}
		else if (hover)
		{
			color = wallColorHover;
		}
		this.drawLine(this.viewmodel.convertX(wall.getStartX()),this.viewmodel.convertY(wall.getStartY()),this.viewmodel.convertX(wall.getEndX()),this.viewmodel.convertY(wall.getEndY()),hover ? wallWidthHover : wallWidth,color);
		if (!hover && wall.frontEdge)
		{
			this.drawEdge(wall.frontEdge, hover);
		}
		if (!hover && wall.backEdge)
		{
			this.drawEdge(wall.backEdge, hover);
		}
	}

	/** */
	drawWallLabels(wall)
	{
		// we'll just draw the shorter label... idk
		if (wall.backEdge && wall.frontEdge)
		{
			if (wall.backEdge.interiorDistance() < wall.frontEdge.interiorDistance())
			{
				this.drawEdgeLabel(wall.backEdge);
				this.drawEdgeLabelExterior(wall.backEdge);
			}
			else
			{
				this.drawEdgeLabel(wall.frontEdge);
				this.drawEdgeLabelExterior(wall.frontEdge);
			}
		}
		else if (wall.backEdge)
		{
			this.drawEdgeLabel(wall.backEdge);
			this.drawEdgeLabelExterior(wall.backEdge);
		}
		else if (wall.frontEdge)
		{
			this.drawEdgeLabel(wall.frontEdge);
			this.drawEdgeLabelExterior(wall.frontEdge);
		}
		this.drawWallLabelsMiddle(wall);
	}

	drawWallLabelsMiddle(wall)
	{
			var pos = wall.wallCenter();
			var length = wall.wallLength();
			if (length < 60)
			{
				// dont draw labels on walls this short
				return;
			}
			this.drawTextLabel(`m:${Dimensioning.cmToMeasure(length)}` ,this.viewmodel.convertX(pos.x),this.viewmodel.convertY(pos.y));
	}

	/** */
	drawEdgeLabelExterior(edge)
	{
		var pos = edge.exteriorCenter();
		var length = edge.exteriorDistance();
		if (length < 60)
		{
			// dont draw labels on walls this short
			return;
		}
		this.drawTextLabel(`e:${Dimensioning.cmToMeasure(length)}` ,this.viewmodel.convertX(pos.x),this.viewmodel.convertY(pos.y+40));
	}

	/** */
	drawEdgeLabel(edge)
	{
		var pos = edge.interiorCenter();
		var length = edge.interiorDistance();
		if (length < 60)
		{
			// dont draw labels on walls this short
			return;
		}
		this.drawTextLabel(`i:${Dimensioning.cmToMeasure(length)}` ,this.viewmodel.convertX(pos.x),this.viewmodel.convertY(pos.y-40));
	}

	drawTextLabel(label, x, y, textcolor='#000000', strokecolor='#ffffff', style='normal')
	{
		this.context.font = `${style} 12px Arial`;
		this.context.fillStyle = textcolor;
		this.context.textBaseline = 'middle';
		this.context.textAlign = 'center';
		this.context.strokeStyle = strokecolor;
		this.context.lineWidth = 4;
		this.context.strokeText(label,x,y);
		this.context.fillText(label,x,y);
	}

	/** */
	drawEdge(edge, hover)
	{
		var color = edgeColor;
		if (hover && this.viewmodel.mode == floorplannerModes.DELETE)
		{
			color = deleteColor;
		}
		else if (hover)
		{
			color = edgeColorHover;
		}
		var corners = edge.corners();

		var scope = this;
		this.drawPolygon(Utils.map(corners, function (corner) {return scope.viewmodel.convertX(corner.x);}),Utils.map(corners, function (corner) {return scope.viewmodel.convertY(corner.y);}),false,null,true,color,edgeWidth);
	}

	/** */
	drawRoom(room)
	{
		var scope = this;
		this.drawPolygon(Utils.map(room.corners, (corner) => {return scope.viewmodel.convertX(corner.x);}),Utils.map(room.corners, (corner) =>  {return scope.viewmodel.convertY(corner.y);}), true,roomColor);
		this.drawTextLabel(Dimensioning.cmToMeasure(room.area, 2)+String.fromCharCode(178), this.viewmodel.convertX(room.areaCenter.x), this.viewmodel.convertY(room.areaCenter.y), '#0000FF', '#00FF0000', 'bold');
		this.drawTextLabel(room.name, this.viewmodel.convertX(room.areaCenter.x), this.viewmodel.convertY(room.areaCenter.y+30), '#363636', '#00FF0000', 'bold italic');
	}

	/** */
	drawCorner(corner)
	{
		var cornerX = this.viewmodel.convertX(corner.x);
		var cornerY = this.viewmodel.convertY(corner.y);
		var hover = (corner === this.viewmodel.activeCorner);
		var color = cornerColor;
		if (hover && this.viewmodel.mode == floorplannerModes.DELETE)
		{
			color = deleteColor;
		}
		else if (hover)
		{
			color = cornerColorHover;
		}
		this.drawCircle(cornerX, cornerY, hover ? cornerRadiusHover : cornerRadius,color);
		// let cx = Dimensioning.roundOff(corner.x, 10);
		// let cy = Dimensioning.roundOff(corner.y, 10);
		// var cornerLabel = `(${cx}, ${cy})`;
		// this.drawTextLabel(cornerLabel, cornerX, cornerY);
	}

	/** */
	drawTarget(x, y, lastNode)
	{
		this.drawCircle(this.viewmodel.convertX(x),this.viewmodel.convertY(y),cornerRadiusHover,cornerColorHover);
		if (this.viewmodel.lastNode)
		{
			this.drawLine(this.viewmodel.convertX(lastNode.x),this.viewmodel.convertY(lastNode.y),this.viewmodel.convertX(x),this.viewmodel.convertY(y),wallWidthHover,wallColorHover);
		}
	}

	/** */
	drawLine(startX, startY, endX, endY, width, color)
	{
		// width is an integer
		// color is a hex string, i.e. #ff0000
		this.context.beginPath();
		this.context.moveTo(startX, startY);
		this.context.lineTo(endX, endY);
		this.context.lineWidth = width;
		this.context.strokeStyle = color;
		this.context.stroke();
	}

	/** */
	drawPolygon(xArr, yArr, fill, fillColor, stroke, strokeColor, strokeWidth)
	{
		// fillColor is a hex string, i.e. #ff0000
		fill = fill || false;
		stroke = stroke || false;
		this.context.beginPath();
		this.context.moveTo(xArr[0], yArr[0]);
		for (var i = 1; i < xArr.length; i++)
		{
			this.context.lineTo(xArr[i], yArr[i]);
		}
		this.context.closePath();
		if (fill)
		{
			this.context.fillStyle = fillColor;
			this.context.fill();
		}
		if (stroke)
		{
			this.context.lineWidth = strokeWidth;
			this.context.strokeStyle = strokeColor;
			this.context.stroke();
		}
	}

	/** */
	drawCircle(centerX, centerY, radius, fillColor)
	{
		this.context.beginPath();
		this.context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
		this.context.fillStyle = fillColor;
		this.context.fill();
	}

	/** returns n where -gridSize/2 < n <= gridSize/2  */
	calculateGridOffset(n)
	{
		if (n >= 0)
		{
			return (n + gridSpacing / 2.0) % gridSpacing - gridSpacing / 2.0;
		}
		else
		{
			return (n - gridSpacing / 2.0) % gridSpacing + gridSpacing / 2.0;
		}
	}

	/** */
	drawGrid()
	{
		var offsetX = this.calculateGridOffset(-this.viewmodel.originX);
		var offsetY = this.calculateGridOffset(-this.viewmodel.originY);
		var width = this.canvasElement.width;
		var height = this.canvasElement.height;
		for (var x = 0; x <= (width / gridSpacing); x++)
		{
			this.drawLine(gridSpacing * x + offsetX, 0, gridSpacing * x + offsetX, height, gridWidth, gridColor);
		}
		for (var y = 0; y <= (height / gridSpacing); y++)
		{
			this.drawLine(0, gridSpacing * y + offsetY, width, gridSpacing * y + offsetY, gridWidth, gridColor);
		}
	}
}