Skip to content

Instantly share code, notes, and snippets.

@nedgar
Forked from rveciana/.block
Last active December 26, 2022 12:50
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nedgar/68dc4d4f032fb98e0ee7876780665e33 to your computer and use it in GitHub Desktop.
Save nedgar/68dc4d4f032fb98e0ee7876780665e33 to your computer and use it in GitHub Desktop.
Animated isometric view with D3js
licence: mit
//
IsoBlock.Block = function(pos,size,color) {
// position in 3d space (obj with attrs x,y,z)
this.pos = pos;
// size of each dimension (obj with attrs x,y,z)
this.size = size;
// an array of 3 color shades (light,medium,dark - see colors.js)
// (used for pseudo lighting)
this.color = color || IsoBlock.colors.red;
};
IsoBlock.Block.prototype = {
getBounds: function() {
var p = this.pos;
var s = this.size;
return {
xmin: p.x,
xmax: p.x + s.x,
ymin: p.y,
ymax: p.y + s.y,
zmin: p.z,
zmax: p.z + s.z,
};
},
};
IsoBlock.Camera = function(origin,scale) {
// the pixel location of the isometric origin.
this.origin = origin;
// number of pixels per isometric unit.
this.scale = scale;
};
/*
We have three separate coordinate systems used for different things:
1. Space (3D)
We apply the usual 3D coordinates to define the boxes using x,y,z.
2. Isometric (2D)
When the 3D space is flattened into an isometric view, we use oblique x and y
axes separated by 120 degrees.
All this does is treat all 3d coordinates as if they are at z=0.
For example, if use have a box at (0,0,0) and we raised it to (0,0,1), it would
look to be in the exact same position as a box at (1,1,0), so the 2d isometric
coordinates are (1,1). This is a side effect of the isometric perspective. So
the isometric 2D coordinates gets the "apparent" coordinates for all boxes if
they were at z=0.
This is accomplished by adding z to x and y. That is all.
(Isometric coordinates are useful for determining when boxes overlap on the
screen.)
3. Screen (2D)
Before drawing, we convert the isometric coordinates to the usual x,y screen
coordinates.
This is done by multiplying each isometric 2D coordinate by its respective
oblique axis vector and taking the sum.
We then multiply this position by "scale" value to implement zoom in/out
features for the camera.
Then we add to an "origin" to implement panning features for the camera.
*/
IsoBlock.Camera.prototype = {
// Determine if the given ranges are disjoint (i.e. do not overlap).
// For determining drawing order, this camera considers two
// ranges to be disjoint even if they share an endpoint.
// Thus, we use less-or-equal (<=) instead of strictly less (<).
areRangesDisjoint: function(amin,amax,bmin,bmax) {
return (amax <= bmin || bmax <= amin);
},
// Convert 3D space coordinates to flattened 2D isometric coordinates.
// x and y coordinates are oblique axes separated by 120 degrees.
// h,v are the horizontal and vertical distances from the origin.
spaceToIso: function(spacePos) {
var z = (spacePos.z == undefined) ? 0 : spacePos.z;
var x = spacePos.x + z;
var y = spacePos.y + z;
return {
x: x,
y: y,
h: (x-y)*Math.sqrt(3)/2, // Math.cos(Math.PI/6)
v: (x+y)/2, // Math.sin(Math.PI/6)
};
},
// Convert the given 2D isometric coordinates to 2D screen coordinates.
isoToScreen: function(isoPos) {
return {
x: isoPos.h * this.scale + this.origin.x,
y: -isoPos.v * this.scale + this.origin.y,
};
},
// Convert the given 3D space coordinates to 2D screen coordinates.
spaceToScreen: function(spacePos) {
return this.isoToScreen(this.spaceToIso(spacePos));
},
// Get a block's vertices with helpful aliases.
// Each vertex is named from its apparent position in an isometric view.
getIsoNamedSpaceVerts: function(block) {
var p = block.pos;
var s = block.size;
return {
rightDown: {x:p.x+s.x, y:p.y, z:p.z},
leftDown: {x:p.x, y:p.y+s.y, z:p.z},
backDown: {x:p.x+s.x, y:p.y+s.y, z:p.z},
frontDown: {x:p.x, y:p.y, z:p.z},
rightUp: {x:p.x+s.x, y:p.y, z:p.z+s.z},
leftUp: {x:p.x, y:p.y+s.y, z:p.z+s.z},
backUp: {x:p.x+s.x, y:p.y+s.y, z:p.z+s.z},
frontUp: {x:p.x, y:p.y, z:p.z+s.z},
};
},
// Get the given block's vertices in flattened 2D isometric coordinates.
getIsoVerts: function(block) {
var verts = this.getIsoNamedSpaceVerts(block);
return {
leftDown: this.spaceToIso(verts.leftDown),
rightDown: this.spaceToIso(verts.rightDown),
backDown: this.spaceToIso(verts.backDown),
frontDown: this.spaceToIso(verts.frontDown),
leftUp: this.spaceToIso(verts.leftUp),
rightUp: this.spaceToIso(verts.rightUp),
backUp: this.spaceToIso(verts.backUp),
frontUp: this.spaceToIso(verts.frontUp),
};
},
// For the given block, get the min and max values on each isometric axis.
getIsoBounds: function(block) {
var verts = this.getIsoVerts(block);
return {
xmin: verts.frontDown.x,
xmax: verts.backUp.x,
ymin: verts.frontDown.y,
ymax: verts.backUp.y,
hmin: verts.leftDown.h,
hmax: verts.rightDown.h,
};
},
// Try to find an axis in 2D isometric that separates the two given blocks.
// This helps identify if the the two blocks are overlap on the screen.
getIsoSepAxis: function(block_a, block_b) {
var a = this.getIsoBounds(block_a);
var b = this.getIsoBounds(block_b);
var sepAxis = null;
if (this.areRangesDisjoint(a.xmin,a.xmax,b.xmin,b.xmax)) {
sepAxis = 'x';
}
if (this.areRangesDisjoint(a.ymin,a.ymax,b.ymin,b.ymax)) {
sepAxis = 'y';
}
if (this.areRangesDisjoint(a.hmin,a.hmax,b.hmin,b.hmax)) {
sepAxis = 'h';
}
return sepAxis;
},
// Try to find an axis in 3D space that separates the two given blocks.
// This helps identify which block is in front of the other.
getSpaceSepAxis: function(block_a, block_b) {
var sepAxis = null;
var a = block_a.getBounds();
var b = block_b.getBounds();
if (this.areRangesDisjoint(a.xmin,a.xmax,b.xmin,b.xmax)) {
sepAxis = 'x';
}
else if (this.areRangesDisjoint(a.ymin,a.ymax,b.ymin,b.ymax)) {
sepAxis = 'y';
}
else if (this.areRangesDisjoint(a.zmin,a.zmax,b.zmin,b.zmax)) {
sepAxis = 'z';
}
return sepAxis;
},
// In an isometric perspective of the two given blocks, determine
// if they will overlap each other on the screen. If they do, then return
// the block that will appear in front.
getFrontBlock: function(block_a, block_b) {
// If no isometric separation axis is found,
// then the two blocks do not overlap on the screen.
// This means there is no "front" block to identify.
if (this.getIsoSepAxis(block_a, block_b)) {
return null;
}
// Find a 3D separation axis, and use it to determine
// which block is in front of the other.
var a = block_a.getBounds();
var b = block_b.getBounds();
switch(this.getSpaceSepAxis(block_a, block_b)) {
case 'x': return (a.xmin < b.xmin) ? block_a : block_b;
case 'y': return (a.ymin < b.ymin) ? block_a : block_b;
case 'z': return (a.zmin < b.zmin) ? block_b : block_a;
default: throw "blocks must be non-intersecting";
}
},
};
// Tango Color Palette
// http://en.wikipedia.org/wiki/Tango_Desktop_Project#Palette
IsoBlock.colors = {
yellow: {light:"#fce94f", medium:"#edd400", dark:"#c4a000"},
orange: {light:"#fcaf3e", medium:"#f57900", dark:"#ce5c00"},
brown: {light:"#e9b96e", medium:"#c17d11", dark:"#8f5902"},
green: {light:"#8ae234", medium:"#73d216", dark:"#4e9a06"},
blue: {light:"#729fcf", medium:"#3465a4", dark:"#204a87"},
purple: {light:"#ad7fa8", medium:"#75507b", dark:"#5c3566"},
red: {light:"#ef2929", medium:"#cc0000", dark:"#a40000"},
white: {light:"#eeeeec", medium:"#d3d7cf", dark:"#babdb6"},
black: {light:"#888a85", medium:"#555753", dark:"#2e3436"},
};
// from David at http://stackoverflow.com/a/11508164/142317
function hexToRgb(hex) {
// strip out "#" if present.
if (hex[0] == "#") {
hex = hex.substring(1);
}
var bigint = parseInt(hex, 16);
var r = (bigint >> 16) & 255;
var g = (bigint >> 8) & 255;
var b = bigint & 255;
return r + "," + g + "," + b;
}
<!doctype html>
<html>
<head>
<meta charset="utf8">
<style>
canvas {
background-color: #f5f5f5;
margin-right: 5px;
text-align: center;
}
</style>
<script src='main.js'></script>
<script src='colors.js'></script>
<script src='block.js'></script>
<script src='camera.js'></script>
<script src='painter.js'></script>
<script src='sortBlocks.js'></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<canvas id='figure1b' width=350 height=200></canvas>
<script>
var canvas = d3.select("#figure1b");
var context = canvas.node().getContext("2d");
var detachedContainer = document.createElement("custom");
var dataContainer = d3.select(detachedContainer);
var t = d3.timer(function(elapsed) {
if (elapsed > 1000) t.stop();
drawCanvas();
});
function drawCustom(data) {
var dataBinding = dataContainer.selectAll("custom.rect")
.data(data, function(d) { return d; });
dataBinding
.attr("z", 0)
.transition()
.duration(1000)
.attr("z", 10);
dataBinding.enter()
.append("custom")
.attr("class","rect")
.attr("x", function(d){return d.x})
.attr("y", function(d){return d.y})
.attr("z", function(d){return d.z})
.attr("dx", function(d){return d.dx})
.attr("dy", function(d){return d.dy})
.attr("color", function(d){return d.color})
.attr("dz", 0)
.transition()
.duration(1000)
.attr("dz", function(d){return d.dz});
dataBinding.exit()
.attr("size", 8)
.transition()
.duration(1000)
.attr("size", 5)
.attr("fillStyle", "lightgrey");
}
function drawCanvas(){
context.clearRect(0, 0, canvas.width, canvas.height);
var blocks = [];
var elements = dataContainer.selectAll("custom.rect");
elements.each(function(d) {
var node = d3.select(this);
blocks.push(new IsoBlock.Block({'x': parseFloat(node.attr("x")), 'y': parseFloat(node.attr("y")),'z': parseFloat(node.attr("z"))},
{'x':parseFloat(node.attr("dx")), 'y':parseFloat(node.attr("dy")), 'z': parseFloat(node.attr("dz"))}, IsoBlock.colors[node.attr("color")]));
});
IsoBlock.makeFigure({ canvas:'figure1b', blocks: blocks, drawPlane: false});
};
var ff = 'blue';
console.info(IsoBlock.colors[ff]);
var data = [{x:0,y:0,z:0,dx:1,dy:1,dz:3,color: 'blue'},
{x:1,y:0,z:0,dx:1,dy:1,dz:2,color:'red'},
{x:4,y:1,z:0,dx:2,dy:2,dz:3,color:'purple'}];
drawCustom(data);
</script>
</body>
</html>
var IsoBlock = IsoBlock || {};
IsoBlock.makeFigure = function(options) {
// extract options
var canvasId = options.canvas;
var blocks = options.blocks;
var shouldSortBlocks = (options.sortBlocks == undefined) ? true : options.sortBlocks;
var shouldDrawAxes = options.drawAxis;
var shouldDrawPlane = options.drawPlane;
var axisLen = options.axisLen;
var silhouette = options.silhouette;
// set canvas and context.
var canvas = document.getElementById(canvasId);
var ctx = canvas.getContext('2d');
// extract scale and origin (camera attributes)
var scale = (options.scale && options.scale(canvas.width,canvas.height)) || (canvas.height / 8);
var origin = (options.origin && options.origin(canvas.width,canvas.height)) || {x: canvas.width/2, y: canvas.height };
// compute appropriate axis length (assuming origin is horizontally centered)
if (!axisLen) {
// make sure the axis extends to the edge of the canvas
axisLen = Math.floor((canvas.width - origin.x) / scale / Math.cos(Math.PI/6)) - 0.5;
}
// Get horizontal axis' vertical displacement from origin.
var hAxisV = origin.y/scale - 1;
// create camera and painter.
var camera = new IsoBlock.Camera(origin, scale);
var painter = new IsoBlock.Painter(camera);
// draw the xy grid
function drawGrid() {
// grid step
var step = 1;
// grid range
var maxx = 15;
var maxy = 15;
// plot x lines
ctx.beginPath();
for (x=-maxx; x<=maxx; x+=step) {
painter.moveTo(ctx, {x:x, y:-maxy});
painter.lineTo(ctx, {x:x, y:maxy});
}
// plot y lines
for (y=-maxy; y<=maxy; y+=step) {
painter.moveTo(ctx, {x:-maxx, y:y});
painter.lineTo(ctx, {x:maxx, y:y});
}
// draw grid lines
ctx.strokeStyle = "#d7d7d7";
ctx.lineWidth = 1;
ctx.stroke();
};
// draw the xy axes and range bars for each block.
function drawAxes() {
var axisColor = "#444";
ctx.lineWidth = 1;
ctx.strokeStyle = axisColor;
ctx.fillStyle = axisColor;
var arrowSize = 0.3;
// draw x,y axes (and h axis if silhouette)
var xAxisStart = camera.spaceToIso({x:-axisLen, y:0});
var xAxisEnd = camera.spaceToIso({x:axisLen, y:0});
var yAxisStart = camera.spaceToIso({x:0, y:-axisLen});
var yAxisEnd = camera.spaceToIso({x:0, y:axisLen});
var hAxisStart = {h:yAxisEnd.h, v:hAxisV};
var hAxisEnd = {h:xAxisEnd.h, v:hAxisV};
ctx.beginPath();
painter.moveTo(ctx, xAxisStart);
painter.lineTo(ctx, xAxisEnd);
painter.moveTo(ctx, yAxisStart);
painter.lineTo(ctx, yAxisEnd);
if (silhouette) {
painter.moveTo(ctx, hAxisStart);
painter.lineTo(ctx, hAxisEnd);
}
ctx.stroke();
// draw x-axis arrow
ctx.beginPath();
painter.moveTo(ctx, {x:axisLen, y:0});
painter.lineTo(ctx, {x:axisLen-arrowSize, y:-arrowSize});
painter.lineTo(ctx, {x:axisLen-arrowSize, y:arrowSize});
ctx.closePath();
ctx.fill();
// draw y-axis arrow
ctx.beginPath();
painter.moveTo(ctx, {y:axisLen, x:0});
painter.lineTo(ctx, {y:axisLen-arrowSize, x:-arrowSize});
painter.lineTo(ctx, {y:axisLen-arrowSize, x:arrowSize});
ctx.closePath();
ctx.fill();
// draw h-axis arrow
if (silhouette) {
ctx.beginPath();
painter.moveTo(ctx, hAxisStart);
painter.lineTo(ctx, {h:hAxisStart.h+arrowSize, v:hAxisV+arrowSize});
painter.lineTo(ctx, {h:hAxisStart.h+arrowSize, v:hAxisV-arrowSize});
ctx.closePath();
ctx.fill();
ctx.beginPath();
painter.moveTo(ctx, hAxisEnd);
painter.lineTo(ctx, {h:hAxisEnd.h-arrowSize, v:hAxisV+arrowSize});
painter.lineTo(ctx, {h:hAxisEnd.h-arrowSize, v:hAxisV-arrowSize});
ctx.closePath();
ctx.fill();
}
// draw axis labels
var p = painter.transform({x:axisLen-1, y:-1});
ctx.font = "italic 1em serif";
ctx.textBaseline='middle';
ctx.textAlign='right';
ctx.fillText("x",p.x,p.y);
p = painter.transform({x:-1, y:axisLen-1});
ctx.textAlign='left';
ctx.fillText("y",p.x,p.y);
if (silhouette) {
p = painter.transform({h:hAxisEnd.h, v:hAxisV-1});
ctx.textAlign='right';
ctx.fillText("h",p.x,p.y);
}
// draw axis ranges for each block
var i,len,bounds,color,rgb,minp,maxp;
var s = 0.25;
for (i=0, len=blocks.length; i<len; i++) {
bounds = silhouette ? camera.getIsoBounds(blocks[i]) : blocks[i].getBounds();
color = blocks[i].color.medium;
rgb = hexToRgb(color);
tcolor = "rgba("+rgb+",0.7)";
// alternate which side of the axis the range bar is on.
s*=-1;
// draw x axis range
painter.fillQuad(ctx,
{x:bounds.xmin, y:0},
{x:bounds.xmin, y:s},
{x:bounds.xmax, y:s},
{x:bounds.xmax, y:0},
tcolor
);
// draw y axis range
painter.fillQuad(ctx,
{x:0, y:bounds.ymin},
{x:s, y:bounds.ymin},
{x:s, y:bounds.ymax},
{x:0, y:bounds.ymax},
tcolor
);
if (silhouette) {
painter.fillQuad(ctx,
{h:bounds.hmin, v:hAxisV+s},
{h:bounds.hmax, v:hAxisV+s},
{h:bounds.hmax, v:hAxisV},
{h:bounds.hmin, v:hAxisV},
tcolor
);
}
}
}
// draw a pseudo-shaded isometric block.
function drawBlock(block) {
var color = block.color;
// get aliases for each of the block's vertices relative to camera's perspective.
var b = camera.getIsoNamedSpaceVerts(block);
if (silhouette) {
var rgb = hexToRgb(color.medium);
var tcolor = "rgba("+rgb+",0.7)";
ctx.beginPath();
painter.moveTo(ctx, b.frontDown);
painter.lineTo(ctx, b.leftDown);
painter.lineTo(ctx, b.leftUp);
painter.lineTo(ctx, b.backUp);
painter.lineTo(ctx, b.rightUp);
painter.lineTo(ctx, b.rightDown);
ctx.fillStyle = tcolor;
ctx.fill();
}
else {
// fill in the grout for the inside edges
var lineWidth = 1;
var groutColor = color.medium;
painter.line(ctx, b.leftUp, b.frontUp, groutColor, lineWidth);
painter.line(ctx, b.rightUp, b.frontUp, groutColor, lineWidth);
painter.line(ctx, b.frontDown, b.frontUp, groutColor, lineWidth);
// Do not add line width when filling faces.
// This prevents a perimeter padding around the hexagon.
// Nonzero line width could cause the perimeter of another box
// to bleed over the edge of a box in front of it.
lineWidth = 0;
// fill each visible face of the block.
// left face
painter.fillQuad(ctx, b.frontDown, b.leftDown, b.leftUp, b.frontUp, !silhouette ? color.dark : color.medium, lineWidth);
// top face
painter.fillQuad(ctx, b.frontUp, b.leftUp, b.backUp, b.rightUp, !silhouette ? color.light : color.medium, lineWidth);
// right face
painter.fillQuad(ctx, b.frontDown, b.frontUp, b.rightUp, b.rightDown, color.medium, lineWidth);
}
};
// draw a plane to separate two isometric blocks.
function drawSeparationPlane(frontBlock, backBlock) {
// exit if back plane is not present
if (!backBlock) {
return;
}
var bounds = frontBlock.getBounds();
// get axis of separation
var aAxis = camera.getSpaceSepAxis(frontBlock, backBlock);
var bAxis,cAxis;
// aAxis, bAxis, cAxis are either 'x', 'y', or 'z'
// a, b, c are the values of its respective axis.
// determine what our abstract axes correspond to.
if (aAxis == 'x') {
a = bounds.xmax;
bAxis = 'y';
cAxis = 'z';
}
else if (aAxis == 'y') {
a = bounds.ymax;
bAxis = 'x';
cAxis = 'z';
}
else if (aAxis == 'z') {
a = bounds.zmin;
bAxis = 'x';
cAxis = 'y';
}
// the radius (read margin) of the separation plane).
var r = 0.7;
// the points of the separation plane in abstract coords.
var pts = [
{ a:a, b: bounds[bAxis+"min"]-r, c: bounds[cAxis+"min"]-r },
{ a:a, b: bounds[bAxis+"min"]-r, c: bounds[cAxis+"max"]+r },
{ a:a, b: bounds[bAxis+"max"]+r, c: bounds[cAxis+"max"]+r },
{ a:a, b: bounds[bAxis+"max"]+r, c: bounds[cAxis+"min"]-r },
];
// convert abstract coords to the real coords for this block.
var i;
var finalPts = [];
for (i=0; i<4; i++) {
var p = {};
p[aAxis] = pts[i].a;
p[bAxis] = pts[i].b;
p[cAxis] = pts[i].c;
finalPts.push(p);
}
// draw separation plane.
painter.fillQuad(ctx, finalPts[0], finalPts[1], finalPts[2], finalPts[3], "rgba(0,0,0,0.35)");
painter.strokeQuad(ctx, finalPts[0], finalPts[1], finalPts[2], finalPts[3], "rgba(0,0,0,0.9)", 1);
};
// draw grid
drawGrid();
// draw axes
if (shouldDrawAxes) {
drawAxes();
}
// sort blocks in drawing order.
if (shouldSortBlocks) {
blocks = IsoBlock.sortBlocks(blocks, camera);
}
// draw blocks and a separation plane if applicable.
var i,len;
for(i=0,len=blocks.length; i<len; i++) {
// only draw a separation plane for the last block
// and only if there is a block behind it.
if (shouldDrawPlane && i>0 && i==len-1) {
drawSeparationPlane(blocks[i], blocks[i-1]);
}
// draw block
drawBlock(blocks[i]);
}
};
// Allows us to paint shapes using isometric coordinates transformed by a given camera.
// It's basically a wrapper for the canvas context.
IsoBlock.Painter = function(camera) {
this.camera = camera;
};
IsoBlock.Painter.prototype = {
// This function allows us to draw using different coordinate systems.
// It accepts a nondescript position vector and tries to detect
// what coordinate system it is in by looking at its properties.
// (x,y,z) <- treated as a space coordinate
// (x,y) <- treated as a space coordinate at z=0
// (same as 2D isometric XY)
// (h,v) <- treated as a special 2D isometric coordinate
// (with horizontal and vertical distance from origin)
transform: function(pos) {
var x,y,z;
if (pos.x != undefined && pos.y != undefined) {
x = pos.x;
y = pos.y;
z = (pos.z == undefined) ? 0 : pos.z;
return this.camera.spaceToScreen({x:x, y:y, z:z});
}
else if (pos.h != undefined && pos.v != undefined) {
return this.camera.isoToScreen(pos);
}
else {
console.log("x",pos.x,"y",pos.y,"z",pos.z,"h",pos.h,"v",pos.v);
throw "painter.transform: Unable to detect coordinate system of given vector";
}
},
moveTo: function(ctx, pos) {
var v = this.transform(pos);
ctx.moveTo(v.x,v.y);
},
lineTo: function(ctx, pos) {
var v = this.transform(pos);
ctx.lineTo(v.x,v.y);
},
quad: function(ctx, p1, p2, p3, p4) {
this.moveTo(ctx, p1);
this.lineTo(ctx, p2);
this.lineTo(ctx, p3);
this.lineTo(ctx, p4);
},
fillQuad: function(ctx, p1, p2, p3, p4, color, lineWidth) {
ctx.beginPath();
this.quad(ctx,p1,p2,p3,p4);
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
if (lineWidth) {
ctx.lineWidth = lineWidth;
ctx.strokeStyle = color;
ctx.stroke();
}
},
fillQuadGradient: function(ctx, p1, p2, p3, p4, color1, color2) {
var v1 = this.transform(p1);
var v4 = this.transform(p4);
var v2 = this.transform(p2);
var dx = v4.x-v1.x;
var dy = v4.y-v1.y;
var dist = Math.sqrt(dx*dx+dy*dy);
dx /= dist;
dy /= dist;
var dx2 = v2.x-v1.x;
var dy2 = v2.y-v1.y;
dist = Math.sqrt(dx2*dx2+dy2*dy2);
dx *= dist;
dy *= dist;
//var grad = ctx.createLinearGradient(v1.x, v1.y, v2.x, v2.y);
var grad = ctx.createLinearGradient(v1.x,v1.y, v1.x-dy, v1.y+dx);
grad.addColorStop(0, color1);
grad.addColorStop(1, color2);
this.fillQuad(ctx, p1,p2,p3,p4, grad);
},
strokeQuad: function(ctx, p1, p2, p3, p4, color, lineWidth) {
ctx.beginPath();
this.quad(ctx,p1,p2,p3,p4);
ctx.closePath();
ctx.strokeStyle = color;
ctx.lineWidth = lineWidth;
ctx.lineJoin = "round";
ctx.stroke();
},
line: function(ctx, p1, p2, color, lineWidth) {
ctx.beginPath();
this.moveTo(ctx, p1);
this.lineTo(ctx, p2);
ctx.strokeStyle = color;
ctx.lineCap = 'butt';
ctx.lineWidth = lineWidth;
ctx.stroke();
},
fillCircle: function(ctx, p1, radius, color) {
var v = this.transform(p1);
ctx.beginPath();
ctx.arc(v.x,v.y,radius,0,2*Math.PI);
ctx.fillStyle = color;
ctx.fill();
},
strokeCircle: function(ctx, p1, radius, color) {
var v = this.transform(p1);
ctx.beginPath();
ctx.arc(v.x,v.y,radius,0,2*Math.PI);
ctx.fillStyle = color;
ctx.fill();
},
};
// From kennebec at http://stackoverflow.com/a/3955096/142317
// Add a remove value function to the Array class.
Array.prototype.remove = function() {
var what, a = arguments, L = a.length, ax;
while (L && this.length) {
what = a[--L];
while ((ax = this.indexOf(what)) !== -1) {
this.splice(ax, 1);
}
}
return this;
};
// Sort blocks in the order that they should be drawn for the given camera.
IsoBlock.sortBlocks = function(blocks, camera) {
var i, j, numBlocks=blocks.length;
// Initialize the list of blocks that each block is behind.
for (i=0; i<numBlocks; i++) {
blocks[i].blocksBehind = [];
blocks[i].blocksInFront = [];
}
// For each pair of blocks, determine which is in front and behind.
var a,b,frontBlock;
for (i=0; i<numBlocks; i++) {
a = blocks[i];
for (j=i+1; j<numBlocks; j++) {
b = blocks[j];
frontBlock = camera.getFrontBlock(a,b);
if (frontBlock) {
if (a == frontBlock) {
a.blocksBehind.push(b);
b.blocksInFront.push(a);
}
else {
b.blocksBehind.push(a);
a.blocksInFront.push(b);
}
}
}
}
// Get list of blocks we can safely draw right now.
// These are the blocks with nothing behind them.
var blocksToDraw = [];
for (i=0; i<numBlocks; i++) {
if (blocks[i].blocksBehind.length == 0) {
blocksToDraw.push(blocks[i]);
}
}
// While there are still blocks we can draw...
var blocksDrawn = [];
while (blocksToDraw.length > 0) {
// Draw block by removing one from "to draw" and adding
// it to the end of our "drawn" list.
var block = blocksToDraw.pop();
blocksDrawn.push(block);
// Tell blocks in front of the one we just drew
// that they can stop waiting on it.
for (j=0; j<block.blocksInFront.length; j++) {
var frontBlock = block.blocksInFront[j];
// Add this front block to our "to draw" list if there's
// nothing else behind it waiting to be drawn.
frontBlock.blocksBehind.remove(block);
if (frontBlock.blocksBehind.length == 0) {
blocksToDraw.push(frontBlock);
}
}
}
return blocksDrawn;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment