Smooth drag scrolling of an Isometric map - Html5 - html
I have implemented a basic Isometric tile engine that can be explored by dragging the map with the mouse. Please see the fiddle below and drag away:
http://jsfiddle.net/kHydg/14/
The code broken down is (CoffeeScript):
The draw function
draw = ->
requestAnimFrame draw
canvas.width = canvas.width
for row in [0..width]
for col in [0..height]
xpos = (row - col) * tileHeight + width
xpos += (canvas.width / 2) - (tileWidth / 2) + scrollPosition.x
ypos = (row + col) * (tileHeight / 2) + height + scrollPosition.y
context.drawImage(defaultTile, Math.round(xpos), Math.round(ypos), tileWidth, tileHeight)
Mouse drag-scrolling control
scrollPosition =
x: 0
y: 0
dragHelper =
active: false
x: 0
y: 0
window.addEventListener 'mousedown', (e) =>
handleMouseDown(e)
, false
window.addEventListener 'mousemove', (e) =>
handleDrag(e)
, false
window.addEventListener 'mouseup', (e) =>
handleMouseUp(e)
, false
handleDrag = (e) =>
e.preventDefault()
if dragHelper.active
x = e.clientX
y = e.clientY
scrollPosition.x -= Math.round((dragHelper.x - x) / 28)
scrollPosition.y -= Math.round((dragHelper.y - y) / 28)
handleMouseUp = (e) =>
e.preventDefault()
dragHelper.active = false
handleMouseDown = (e) =>
e.preventDefault()
x = e.clientX
y = e.clientY
dragHelper.active = true
dragHelper.x = x
dragHelper.y = y
The Problem
As you can see from the fiddle the dragging action is ok but not perfect. How would I change the code to make the dragging action more smooth? What I would like is the point of the map you click on to stay under the mouse point whilst you drag; the same as they have done here: http://craftyjs.com/demos/isometric/
Lots of libraries help with things like this. I would recommend using the data manipulation abilities of d3 to help, for several reasons.
First, in d3, there is a drag behavior where the origin of the object is stored and a mouse position relative to the origin is computed when the drag starts. Then, you can use the absolute position of the mouse to determine where the object should be and avoid the incremental errors that occur when you use relative changes - which get far worse when you start rounding them, as above.
dragMap = (d) ->
d.x = d3.event.x # d3.event.x, y are computed relative to the origin for you!
d.y = d3.event.y
dragBehavior = d3.behavior.drag()
.origin(Object) # equivalent to (d) -> d
.on("drag", dragMap)
d3.select(canvas)
.datum(x: 0, y: 0) # Load your canvas with an arbitary origin
.call(dragBehavior) # And now you can drag it!
Second, by using d3's linear or other numerical scales you can avoid doing typical drawing math yourself which is error-prone especially when you have to do it all over the place. Before you were scaling the drag by 28. In my current approach it's unnecessary, but if you change your drawing algorithm to use tiles instead of pixels, you can change this scale which will automatically convert mouse pixels into tile sizes.
pixelToTile = d3.scale.linear()
.domain([0, 1])
.range([0, 1])
Here's your fiddle re-done with d3 help. No dragHelper and all that extraneous code necessary. All the Math.round calls are also unnecessary except the one for canvas draw, which prevents antialiasing.
http://jsfiddle.net/kHydg/23/
Isn't that much shorter and sweeter?
P.S. Isometric real-time browser games are an awesome idea. I will definitely try making one when I have time.
Related
Autodesk forge Markup extension markers position not matching x y z values
I am using the markup extension that can be found at https://github.com/wallabyway/markupExt and have not changed the code within the extension except setting this.size to a bigger value to make the marker easier to find the actual rendering of the points works fine I just can't seem to place them in a specific position on the model, they just appear floating off in the middle of empty space. Code to generate the point var dummyData = []; dummyData.push({ icon: Math.round(Math.random() * 3), x: 129597.054373, y: -27184.841094, z: 44514.362733 }); window.dispatchEvent(new CustomEvent('newData', { 'detail': dummyData })); I have tried feeding it values taken straight from a selected items properties and have also tried feeding it values taken from the following code (I have tried with and without the nominalise step) function onMouseClick(event) { var screenPoint = { x: event.clientX, y: event.clientY }; var n = normalizeCoords(screenPoint) var hitTest = viewer.impl.hitTest(n.x, n.y, true); if (hitTest) { alert(hitTest.intersectPoint.x + ' ' + ' ' + hitTest.intersectPoint.y + ' ' + hitTest.intersectPoint.z) } } function normalizeCoords(screenPoint) { var viewport = viewer.navigation.getScreenViewport(); var n = { x: (screenPoint.x - viewport.left) / viewport.width, y: (screenPoint.y - viewport.top) / viewport.height }; return n; } The point moves when i change the x y z values but never to where I want them and am not sure where I am going wrong
Rather than the code found in the other extensions that uses the viewport to offset the point I seem to have fixed it by adding viewer.model.getData().globalOffset to the hit test intersection values
Alignment issue between BPMN.IO and Heatmap.js
I'm trying to overlay a bpmn flow using bpmn.io with a heatmap using heatmap.js. If the canvas is set to the first and only element in the dom it actually works, but once you add anything else like in my sample a header, the coordinate system between both gets off. I've prepared a fiddle that shows exactly what I mean. https://jsfiddle.net/rafaturtle/qt8Ly4ez/16/ const rect = canvas.getGraphics(element).getBoundingClientRect(); const x = (rect.x + rect.width / 2); const y = (rect.y + rect.height / 2); data.push({ x: x.toFixed(0), y: y.toFixed(0), value: stats[element.id] }); I believe its on the calculation of x,y of each element, off setting it by a factor but after trying every combination I could think of I didn't manage. thanks
I am not sure if I misunderstood what you aim for, but laying the heatmap over the elements can be easily achieved by using the dimensions of the current element, element.x, element.y, element.width, element.height. The following code places the heatmap at the exact position of the elements: var registry = bpmnJS.get('elementRegistry'); for (var i in registry.getAll()) { var element = registry.getAll()[i]; if (stats[element.id] != null) { const centerx = element.x + (element.width / 2 ) const centery = element.y + (element.height / 2 ) data.push({ x: centerx, y: centery, value: stats[element.id] }); } } Working example: https://jsfiddle.net/cr8Lg53a/
const x = (rect.x + rect.width / 2) - canvas.getContainer().getBoundingClientRect().x; const y = (rect.y + rect.height / 2) - canvas.getContainer().getBoundingClientRect().y; try this
Translate a rectangle in canvas
I am unable to clear a rectangle completely using clearRect method which is being translated on canvas across X direction. The problem can be seen live at JS Bin - Link to demo JS code (function() { var canvas = document.getElementById('canvas'); var context = canvas.getContext('2d'); context.fillStyle = '#ccc'; context.fillRect(0, 0, 100, 50); //x,y,w,h translate(canvas, context, 0, 0, 100, 50, 'x', 5); function translate(canvas, context, x, y, w, h, direction, interval) { context.fillRect(x, y, w, h); if (direction == 'x') { if ((x + interval + w >= canvas.width) || (x + interval < 0)) interval = -1 * interval; setTimeout(function() { context.clearRect(x, y, w, h); translate(canvas, context, x + interval, y, w, h, direction, interval); }, 1000); } } }()); It is leaving traces before moving forward/backward. And I have used the same dimensions to clear the rectangle which were used to draw it. Please observe the complete path for observing the problem.
IE10 and FF21 work fine, Chrome gives the "remnants". Even then, I can't consistently reproduce the misbehaving effect. Also notice that when you scroll your jsbin results panel the remnants go away. So Chrome becomes aware that these remnant pixels shouldn't be there. Looks like yet another canvas bug in Chrome (possibly, but not necessarily, related to anti-aliasing). You could hack at it by extending your clear area to erase the remnants. Alternatively you could leave out the translate and increment x by interval to move your rect. Hacky fix: context.clearRect(x+((interval>0)?-.5:.5), y, w, h);
HTML5 Canvas Clip or Crop Everything but an S shape
How would I clip everything in the following drawing except for the S stroke? In other words get rid of all transparent space and only keep black S shape... thanks in advance! === PNG RENDERED IMAGE OF CANVAS === image located at --> http://buildasearch.com/ant/s.png === ACTUAL COORDINATES OF CANVAS DRAWING === var x = '68,67,66,65,64,63,62,61,60,59,57,56,55,54,53,52,51,51,51,51,51,51,51,52,55,56,52,58,60,59,61,62,64,65,66,68,70,71,72,74,75,76,77,78,78,79,79,79,79,79,79,79,79,79,79,79,79,78,76,74,71,67,59,56,54,52,49,47,46,45,43,42,41,40,39'; var y = '11,11,11,11,11,12,12,12,12,12,13,14,14,15,17,18,20,21,23,24,27,30,32,32,34,34,33,34,34,34,34,35,35,35,35,35,35,36,36,37,38,38,39,40,41,42,43,44,45,47,48,49,50,51,52,53,54,55,56,57,58,59,59,59,60,60,60,60,60,60,60,60,60,60,60';
It would be easier if your x and y were of the form [6, 3, 19] instead of '6,3,19'. For the purposes of this answer, I will assume that you have it done this way as it makes the code a bit easier. It's quite possible to calculate afterwards, but it would get messy with getting the bit that you want and then resizing the canvas. It will end up easier and faster to calculate it before-hand if you can. Something like this should work: // Data specification var x = [68,67,66,65,64,63,62,61,60,59,57,56,55,54,53,52,51,51,51,51,51,51,51,52,55,56,52,58,60,59,61,62,64,65,66,68,70,71,72,74,75,76,77,78,78,79,79,79,79,79,79,79,79,79,79,79,79,78,76,74,71,67,59,56,54,52,49,47,46,45,43,42,41,40,39], y = [11,11,11,11,11,12,12,12,12,12,13,14,14,15,17,18,20,21,23,24,27,30,32,32,34,34,33,34,34,34,34,35,35,35,35,35,35,36,36,37,38,38,39,40,41,42,43,44,45,47,48,49,50,51,52,53,54,55,56,57,58,59,59,59,60,60,60,60,60,60,60,60,60,60,60]; // Work out the extreme points in both dimensions var xs = x.slice().sort(), ys = y.slice().sort(), minX = xs[0], maxX = xs[xs.length-1], minY = ys[0], maxY = ys[ys.length-1]; // Resize the canvas canvas.width = maxX - minX + 1; canvas.height = maxY - minY + 1; // Shift the points to fit on the shrunk canvas x.forEach(function(v, i) { x[i] = v - minX; }); y.forEach(function(v, i) { y[i] = v - minY; });
Zooming an object based on mouse position
I've got a large large Sprite, and I want to zoom in and out keeping mouse position as the pivot point, exactly how google maps or the photoshop zoom works, when you rotate the mouse wheel. To archieve the zoom effect I tween the scaleX and scaleY properties, but the fixed point is (0,0), while i need to be the mouse position (that changes everytime, of course). How can I change this? thanks.
I just did a Google search for "zoom about an arbitrary point" (minus the quotes) and the first result looks promising. You have to take the original offset out the scale and then reapply it at the new scale. I've done this myself but don't have the code to hand right now, so I'll see if I can dig it out and post it later. The pseudo code for this is something like this (from memory): float dir = UP ? 1 : -1; float oldXscale = Xscale; float oldYscale = Yscale; Xscale += dir * increment; Yscale += dir * increment; newX = (oldX - Xoffset) / Xscale; newY = (oldY - Yoffset) / Yscale; Xoffset += (newX * oldXscale) - (newX * Xscale); Yoffset += (newY * oldYscale) - (newY * Yscale); Anything not declared is a "global"
Found out by searching the right words on google ... This link explains the Affine Transformations http://gasi.ch/blog/zooming-in-flash-flex/ and so I can Tween the transformation Matrix: var affineTransform:Matrix = board.transform.matrix; affineTransform.translate( -mouseX, -mouseY ); affineTransform.scale( 0.8, 0.8 ); affineTransform.translate( mouseX, mouseY ); var originalMatrix:Matrix = board.transform.matrix; TweenLite.to(originalMatrix, 0.7, {a:affineTransform.a, b:affineTransform.b, c:affineTransform.c, d:affineTransform.d, tx:affineTransform.tx, ty:affineTransform.ty, onUpdate:applyMatrix, onUpdateParams:[originalMatrix]}); :-)
private static function onMouseWheel(event:MouseEvent):void { var zoomAmount:Number = 0.03; if (event.delta < 0) zoomAmount *= -1; var x:int = largeLargeSprite.mouseX; var y:int = largeLargeSprite.mouseY; largeLargeSprite.scaleX += zoomAmount; largeLargeSprite.scaleY += zoomAmount; largeLargeSprite.x -= x * zoomAmount; largeLargeSprite.y -= y * zoomAmount; }