summaryrefslogtreecommitdiff
path: root/src/scripts/views/mapview.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/scripts/views/mapview.ts')
-rw-r--r--src/scripts/views/mapview.ts373
1 files changed, 373 insertions, 0 deletions
diff --git a/src/scripts/views/mapview.ts b/src/scripts/views/mapview.ts
new file mode 100644
index 00000000..ae7fd5cb
--- /dev/null
+++ b/src/scripts/views/mapview.ts
@@ -0,0 +1,373 @@
+///<reference path="../../../typings/globals/createjs-lib/index.d.ts" />
+///<reference path="../../../typings/globals/easeljs/index.d.ts" />
+///<reference path="../../../typings/globals/tweenjs/index.d.ts" />
+///<reference path="../../../typings/globals/preloadjs/index.d.ts" />
+///<reference path="../definitions.ts" />
+///<reference path="../controllers/mapcontroller.ts" />
+import * as $ from "jquery";
+import {Util} from "../util";
+import {MapController, CELL_SIZE} from "../controllers/mapcontroller";
+import {GridLayer} from "./layers/grid";
+import {RoomLayer} from "./layers/room";
+import {HoverLayer} from "./layers/hover";
+import {WallLayer} from "./layers/wall";
+import {DCObjectLayer} from "./layers/dcobject";
+import {GrayLayer} from "./layers/gray";
+import {RoomTextLayer} from "./layers/roomtext";
+
+
+/**
+ * Class responsible for rendering the map, by delegating the rendering tasks to appropriate instances.
+ */
+export class MapView {
+ public static MAP_SIZE = 100;
+ public static CELL_SIZE_METERS = 0.5;
+ public static MIN_ZOOM = 0.5;
+ public static DEFAULT_ZOOM = 2;
+ public static MAX_ZOOM = 6;
+ public static GAP_CORRECTION_DELTA = 0.2;
+ public static ANIMATION_LENGTH = 250;
+
+ // Models
+ public simulation: ISimulation;
+ public currentDatacenter: IDatacenter;
+
+ // Controllers
+ public mapController: MapController;
+
+ // Canvas objects
+ public stage: createjs.Stage;
+ public mapContainer: createjs.Container;
+
+ // Flag indicating whether the scene should be redrawn
+ public updateScene: boolean;
+ public animating: boolean;
+
+ // Subviews
+ public gridLayer: GridLayer;
+ public roomLayer: RoomLayer;
+ public dcObjectLayer: DCObjectLayer;
+ public roomTextLayer: RoomTextLayer;
+ public hoverLayer: HoverLayer;
+ public wallLayer: WallLayer;
+ public grayLayer: GrayLayer;
+
+ // Dynamic canvas attributes
+ public canvasWidth: number;
+ public canvasHeight: number;
+
+
+ /**
+ * Draws a line from (x1, y1) to (x2, y2).
+ *
+ * @param x1 The x coord. of start point
+ * @param y1 The y coord. of start point
+ * @param x2 The x coord. of end point
+ * @param y2 The y coord. of end point
+ * @param lineWidth The width of the line to be drawn
+ * @param color The color to be used
+ * @param container The container to be drawn to
+ */
+ public static drawLine(x1: number, y1: number, x2: number, y2: number,
+ lineWidth: number, color: string, container: createjs.Container): createjs.Shape {
+ let line = new createjs.Shape();
+ line.graphics.setStrokeStyle(lineWidth).beginStroke(color);
+ line.graphics.moveTo(x1, y1);
+ line.graphics.lineTo(x2, y2);
+ container.addChild(line);
+ return line;
+ }
+
+ /**
+ * Draws a tile at the given location with the given color.
+ *
+ * @param position The grid coordinates of the tile
+ * @param color The color with which the rectangle should be drawn
+ * @param container The container to be drawn to
+ * @param sizeX Optional parameter specifying the width of the tile to be drawn (in grid units)
+ * @param sizeY Optional parameter specifying the height of the tile to be drawn (in grid units)
+ */
+ public static drawRectangle(position: IGridPosition, color: string, container: createjs.Container,
+ sizeX?: number, sizeY?: number): createjs.Shape {
+ let tile = new createjs.Shape();
+ tile.graphics.setStrokeStyle(0);
+ tile.graphics.beginFill(color);
+ tile.graphics.drawRect(
+ position.x * CELL_SIZE - MapView.GAP_CORRECTION_DELTA,
+ position.y * CELL_SIZE - MapView.GAP_CORRECTION_DELTA,
+ CELL_SIZE * (sizeX === undefined ? 1 : sizeX) + MapView.GAP_CORRECTION_DELTA * 2,
+ CELL_SIZE * (sizeY === undefined ? 1 : sizeY) + MapView.GAP_CORRECTION_DELTA * 2
+ );
+ container.addChild(tile);
+ return tile;
+ }
+
+ /**
+ * Draws a tile at the given location with the given color, and add it to the given shape object.
+ *
+ * The fill color must be set beforehand, in order to not set it repeatedly and produce unwanted transparent overlap
+ * artifacts.
+ *
+ * @param position The grid coordinates of the tile
+ * @param shape The shape to be drawn to
+ * @param sizeX Optional parameter specifying the width of the tile to be drawn (in grid units)
+ * @param sizeY Optional parameter specifying the height of the tile to be drawn (in grid units)
+ */
+ public static drawRectangleToShape(position: IGridPosition, shape: createjs.Shape,
+ sizeX?: number, sizeY?: number) {
+ shape.graphics.drawRect(
+ position.x * CELL_SIZE - MapView.GAP_CORRECTION_DELTA,
+ position.y * CELL_SIZE - MapView.GAP_CORRECTION_DELTA,
+ CELL_SIZE * (sizeX === undefined ? 1 : sizeX) + MapView.GAP_CORRECTION_DELTA * 2,
+ CELL_SIZE * (sizeY === undefined ? 1 : sizeY) + MapView.GAP_CORRECTION_DELTA * 2
+ );
+ }
+
+ constructor(simulation: ISimulation, stage: createjs.Stage) {
+ this.simulation = simulation;
+ let path = this.simulation.paths[this.simulation.paths.length - 1];
+ this.currentDatacenter = path.sections[path.sections.length - 1].datacenter;
+
+ this.stage = stage;
+
+ console.log("THE DATA", simulation);
+
+ let canvas = $("#main-canvas");
+ this.canvasWidth = canvas.width();
+ this.canvasHeight = canvas.height();
+
+ this.mapContainer = new createjs.Container();
+
+ this.initializeLayers();
+
+ this.drawMap();
+ this.updateScene = true;
+ this.animating = false;
+
+ this.mapController = new MapController(this);
+
+ // Zoom DC to fit, if rooms are present
+ if (this.currentDatacenter.rooms.length > 0) {
+ this.zoomOutOnDC();
+ }
+
+ // Checks at every rendering tick whether the scene has changed, and updates accordingly
+ createjs.Ticker.addEventListener("tick", (event: createjs.TickerEvent) => {
+ if (this.updateScene || this.animating) {
+ if (this.mapController.isInHoverMode()) {
+ this.hoverLayer.draw();
+ }
+
+ this.updateScene = false;
+ this.stage.update(event);
+ }
+ });
+ }
+
+ private initializeLayers(): void {
+ this.gridLayer = new GridLayer(this);
+ this.roomLayer = new RoomLayer(this);
+ this.dcObjectLayer = new DCObjectLayer(this);
+ this.roomTextLayer = new RoomTextLayer(this);
+ this.hoverLayer = new HoverLayer(this);
+ this.wallLayer = new WallLayer(this);
+ this.grayLayer = new GrayLayer(this);
+ }
+
+ /**
+ * Triggers a redraw and re-population action on all layers.
+ */
+ public redrawMap(): void {
+ this.gridLayer.draw();
+ this.roomLayer.draw();
+ this.dcObjectLayer.populateObjectList();
+ this.dcObjectLayer.draw();
+ this.roomTextLayer.draw();
+ this.hoverLayer.initialDraw();
+ this.wallLayer.generateWalls();
+ this.wallLayer.draw();
+ this.grayLayer.draw(true);
+ this.updateScene = true;
+ }
+
+ /**
+ * Zooms in on a given position with a given amount.
+ *
+ * @param position The position that should appear centered after the zoom action
+ * @param amount The amount of zooming that should be performed
+ */
+ public zoom(position: number[], amount: number): void {
+ const newZoom = this.mapContainer.scaleX + 0.01 * amount;
+
+ // Check whether zooming too far in / out
+ if (newZoom > MapView.MAX_ZOOM ||
+ newZoom < MapView.MIN_ZOOM) {
+ return;
+ }
+
+ // Calculate position difference if zoomed, in order to later compensate for this
+ // unwanted movement
+ let oldPosition = [
+ position[0] - this.mapContainer.x, position[1] - this.mapContainer.y
+ ];
+ let newPosition = [
+ (oldPosition[0] / this.mapContainer.scaleX) * newZoom,
+ (oldPosition[1] / this.mapContainer.scaleX) * newZoom
+ ];
+ let positionDelta = [
+ newPosition[0] - oldPosition[0], newPosition[1] - oldPosition[1]
+ ];
+
+ // Apply the transformation operation to keep the selected position static
+ let newX = this.mapContainer.x - positionDelta[0];
+ let newY = this.mapContainer.y - positionDelta[1];
+
+ let finalPos = this.mapController.checkCanvasMovement(newX, newY, newZoom);
+
+ if (!this.animating) {
+ this.animate(this.mapContainer, {
+ scaleX: newZoom, scaleY: newZoom,
+ x: finalPos.x, y: finalPos.y
+ });
+ }
+ }
+
+ /**
+ * Adjusts the viewing scale to fully display a selected room and center it in view.
+ *
+ * @param room The room to be centered
+ * @param redraw Optional argument specifying whether this is a scene redraw
+ */
+ public zoomInOnRoom(room: IRoom, redraw?: boolean): void {
+ this.zoomInOnRooms([room]);
+
+ if (redraw === undefined || redraw === false) {
+ if (!this.grayLayer.isGrayedOut()) {
+ this.grayLayer.currentRoom = room;
+ this.grayLayer.draw();
+ }
+ }
+
+ this.updateScene = true;
+ }
+
+ /**
+ * Zooms out to global building view.
+ */
+ public zoomOutOnDC(): void {
+ this.grayLayer.clear();
+
+ if (this.currentDatacenter.rooms.length > 0) {
+ this.zoomInOnRooms(this.currentDatacenter.rooms);
+ }
+
+ this.updateScene = true;
+ }
+
+ /**
+ * Fits a given list of rooms to view, by scaling the viewport appropriately and moving the mapContainer.
+ *
+ * @param rooms The array of rooms to be viewed
+ */
+ private zoomInOnRooms(rooms: IRoom[]): void {
+ let bounds = Util.calculateRoomListBounds(rooms);
+ let newScale = this.calculateNewScale(bounds);
+
+ // Coordinates of the center of the room, relative to the global origin of the map
+ let roomCenterCoords = [
+ bounds.center[0] * CELL_SIZE * newScale,
+ bounds.center[1] * CELL_SIZE * newScale
+ ];
+ // Coordinates of the center of the stage (the visible part of the canvas), relative to the global map origin
+ let stageCenterCoords = [
+ -this.mapContainer.x + this.canvasWidth / 2,
+ -this.mapContainer.y + this.canvasHeight / 2
+ ];
+
+ let newX = this.mapContainer.x - roomCenterCoords[0] + stageCenterCoords[0];
+ let newY = this.mapContainer.y - roomCenterCoords[1] + stageCenterCoords[1];
+
+ let newPosition = this.mapController.checkCanvasMovement(newX, newY, newScale);
+
+ this.animate(this.mapContainer, {
+ scaleX: newScale, scaleY: newScale,
+ x: newPosition.x, y: newPosition.y
+ });
+ }
+
+ private calculateNewScale(bounds: IBounds): number {
+ const viewPadding = 30;
+ const sideMenuWidth = 350;
+
+ let width = bounds.max[0] - bounds.min[0];
+ let height = bounds.max[1] - bounds.min[1];
+
+ let scaleX = (this.canvasWidth - 2 * sideMenuWidth) / (width * CELL_SIZE + 2 * viewPadding);
+ let scaleY = this.canvasHeight / (height * CELL_SIZE + 2 * viewPadding);
+
+ let newScale = Math.min(scaleX, scaleY);
+
+ if (this.mapContainer.scaleX > MapView.MAX_ZOOM) {
+ newScale = MapView.MAX_ZOOM;
+ } else if (this.mapContainer.scaleX < MapView.MIN_ZOOM) {
+ newScale = MapView.MIN_ZOOM;
+ }
+
+ return newScale;
+ }
+
+ /**
+ * Draws all tiles contained in the MapModel.
+ */
+ private drawMap(): void {
+ // Create and draw the container for the entire map
+ let gridPixelSize = CELL_SIZE * MapView.MAP_SIZE;
+
+ // Add a white background to the entire container
+ let background = new createjs.Shape();
+ background.graphics.beginFill("#fff");
+ background.graphics.drawRect(0, 0,
+ gridPixelSize, gridPixelSize);
+ this.mapContainer.addChild(background);
+
+ this.stage.addChild(this.mapContainer);
+
+ // Set the map container to a default offset and zoom state (overridden if rooms are present)
+ this.mapContainer.x = -50;
+ this.mapContainer.y = -50;
+ this.mapContainer.scaleX = this.mapContainer.scaleY = MapView.DEFAULT_ZOOM;
+
+ this.addLayerContainers();
+ }
+
+ private addLayerContainers(): void {
+ this.mapContainer.addChild(this.gridLayer.container);
+ this.mapContainer.addChild(this.roomLayer.container);
+ this.mapContainer.addChild(this.dcObjectLayer.container);
+ this.mapContainer.addChild(this.roomTextLayer.container);
+ this.mapContainer.addChild(this.hoverLayer.container);
+ this.mapContainer.addChild(this.wallLayer.container);
+ this.mapContainer.addChild(this.grayLayer.container);
+ }
+
+ /**
+ * Wrapper function for TweenJS animate functionality.
+ *
+ * @param target What to animate
+ * @param properties Properties to be passed on to TweenJS
+ * @param callback To be called when animation ready
+ */
+ public animate(target: any, properties: any, callback?: () => any): void {
+ this.animating = true;
+ createjs.Tween.get(target)
+ .to(properties, MapView.ANIMATION_LENGTH, createjs.Ease.getPowInOut(4))
+ .call(() => {
+ this.animating = false;
+ this.updateScene = true;
+
+ if (callback !== undefined) {
+ callback();
+ }
+ });
+ }
+} \ No newline at end of file