I'm writing a simple 2D game engine because I want to brush up my school math knowledge.
I know that maybe a lot of people already answered similar questions (I read a lot of questions and answers about this topic), but I can't manage why my method doesn't work.
In this example we can see a "room" with a camera and two rays that change direction depending on the direction of the camera.
These rays should represent left and right border of the field of view of the camera and should intersect the walls in front of the camera.
The problem is that when the camera moves and rotates (using "UP","DOWN","LEFT","RIGHT"), sometimes the rays disappear i.e. the intersection function fails.
Can someone help me find the solution?
Here there are the relevant parts of code:
function Player(x,y,angle) {
this.x = x;
this.y = y;
this.z = 30;
this.fieldOfView = {
degrees : 60,
rads : Game.utils.degree2Rad(60),
set : function (deg) {
var self = this;
self.fieldOfView.degrees = deg;
self.fieldOfView.rads = Game.utils.degree2Rad(rads);
}
};
this.angleDeg = angle;
this.angleRads = Game.utils.degree2Rad(angle);
this.rotate = function () {
/* Handle rotation and position
of the camera depending on the button pressed */
}
this.draw = function (canvas1,canvas2) {
var self = this;
var ctx1 = canvas1.getContext('2d');
var ctx2 = canvas2.getContext('2d');
/*Draw a circle on canvas1 at Game.player.x Game.player.y*/
// CODE
/*Draw a circle on canvas2 at the center of canvas*/
// CODE
/*Angles of the two rays in radians (FOV is 60°).
Add 30° to player's angle and find one,
Substract 30° to player's angle and find the other
*/
var rad1 = Game.utils.degree2Rad(Game.player.angleDeg+30);
var rad2 = Game.utils.degree2Rad(Game.player.angleDeg-30);
/*
The two Rays, defined with a point (player.x and player.y)
and a vector
*/
var _rad1 = new Ray2D(self.x,self.y,new Vector2D(Math.cos(rad1),Math.sin(rad1)));
var _rad2 = new Ray2D(self.x,self.y,new Vector2D(Math.cos(rad2),Math.sin(rad2)));
var _w = Game.walls;
var ps = [];
for(var i=0;i<_w.length;i++)
{
//FIND THE INTERSECTION POINTS
var j = _w[i];
var p =_rad1.intersectionWall(j);
if(p) {
ps.push(p);
}
var p2 = _rad2.intersectionWall(j);
if(p2) {
ps.push(p2);
}
}
if(ps.length>1)
{
// DRAW THE TWO INTERSECTION POINTS
ctx1.beginPath();
ctx1.moveTo(self.x,self.y);
ctx1.lineTo(ps[0].x,ps[0].y);
ctx1.stroke();
ctx1.beginPath();
ctx1.moveTo(self.x,self.y);
ctx1.lineTo(ps[1].x,ps[1].y);
ctx1.stroke();
//CODE
}
else {
console.log(_rad1,_rad2);
//console.log('non-p',ps[0]);
}
/*ctx1.beginPath();
ctx1.arc(self.x,self.y,2,0,Game.GLOBAL.CIRCLE);
ctx1.stroke();
ctx1.closePath();*/
},
this.update = function () {
this.rotate();
}
}
function Wall (x1,y1,x2,y2) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
this.update = function (context1,context2,player) {
};
this.draw = function (context1,context2,player) {
//CODE
}
}
function Vector2D (x,y) {
this.x = x;
this.y = y;
this.dot = function (v) {
//Dot product
return v.x*self.x+v.y*self.y;
}
this.cross = function (v) {
//cross product
return self.x*v.y-self.y*v.x;
}
}
function StraightLine2D (xo,yo,v) {
if(!(v instanceof(Vector2D)))
{
throw new Error('Invalid Argument supplied for constructor "StraightLine2D"');
}
this.xo = xo;
this.yo = yo;
this.v = v;
this.calc = function (t) {
var self = this;
return {
x : t*self.v.x + self.xo,
y : t*self.v.y + self.yo
}
}
this.intersectionLine = function (R) {
var self = this;
var x1 = R.xo;
var y1 = R.yo;
var v1 = R.v;
var _cross = self.v.cross(v1);
if(_cross == 0)
{
return null;
}
switch(true) {
case self.v.x == 0:
var t = (self.xo-x1)/v1.x;
return R.calc(t);
break;
case self.v.y == 0:
var t = (self.yo-y1)/v1.y;
return R.calc(t);
break;
default:
var t = (self.v.y*(self.xo-x1)+self.v.x*(y1-self.yo))/(-_cross);
//var t = (t1*(v1.x)+x1-self.xo)/self.v.x;
return R.calc(t);
break;
}
}
}
function Ray2D (xo,yo,v) {
if(!(v instanceof(Vector2D)))
{
throw new Error('Invalid Argument supplied for constructor "StraightLine2D"');
}
this.xo = xo;
this.yo = yo;
this.v = v;
this.calc = function (t) {
var self = this;
if(t<0) {
return null;
}
return {
x : t*self.v.x + self.xo,
y : t*self.v.y + self.yo
}
}
this.intersectionLine = function (R) {
var self = this;
var x1 = R.xo;
var y1 = R.yo;
var v1 = R.v;
var _cross = self.v.cross(v1);
if(_cross == 0.0)
{
return null;
}
switch(true) {
case self.v.x == 0:
var t = (self.xo-x1)/v1.x;
return t > 0 ? R.calc(t) : null;
break;
case self.v.y == 0:
var t = (self.yo-y1)/v1.y;
return t > 0 ? R.calc(t) : null;
break;
default:
var t1 = ((y1-self.yo)*self.v.x+self.v.y*(self.xo-x1))/(v1.x*self.v.y-v1.y*self.v.x);
var t = (t1*R.v.x+R.xo-self.xo)/self.v.x;
return t >= 0 ? self.calc(t) : null;
break;
}
}
this.intersectionWall = function (W) {
var self = this;
var R = new StraightLine2D(W.x1,W.y1,new Vector2D(W.x1-W.x2,W.y1-W.y2));
var point = self.intersectionLine(R);
if(point &&
point.x <= Math.max(W.x1,W.x2) && point.x >= Math.min(W.x1,W.x2) &&
point.y <= Math.max(W.y1,W.y2) && point.y >= Math.min(W.y1,W.y2))
{
return point;
}
return null;
}
}
EDIT: I tried to cut out the non-relevant part of the code. Synthesis is not one of my qualities, I hope now is more readable.
Related
I want to have onclick and hover functionality simultaneously but if you have clicked somewhere then hover should not work until I click somewhere else. I have tried alot but I didn't find any working code. Kindly help
canvas.addEventListener('mousedown', function(evt) {
}, false);
canvas.onmousemove = function(evt) {
};
Well, I am not quite sure what you need and why you need it. But, in that short piece of code you wrote I saw the word "canvas" and thought "what the heck, that could be fun!". I have not much experience with the canvas element since earlier, so I realize there may be better ways of writing this code.
But, I hope what I wrote in the below example is at least close to what you were looking for. Otherwise, go nuts and change and adapt the way you like... and while you do that, try to learn something of it.
var Canvas = function() {
this.$canvas = $('canvas');
this.$currPos = $('#currPos');
this.$currClick = $('#currClick');
this.$clickInfo = $('#clickInfo');
this.canvsWidth = 150;
this.cavasHeight = 150;
this.ctx = ctx = this.$canvas[0].getContext('2d');
this.rect = this.$canvas[0].getBoundingClientRect();
this.squares = [];
this.sqm = 50;
this.tracker = 0;
this.latestHover = {};
this._events();
this._prepare();
};
Canvas.prototype._events = function() {
var self = this;
this.$canvas.on('mousemove', function(e) {
var posX = e.clientX - self.rect.left,
posY = e.clientY - self.rect.top,
newX = Math.floor(posX / self.sqm),
newY = Math.floor(posY / self.sqm);
if($.isEmptyObject(self.latestHover) || (self.latestHover.x !== newX || self.latestHover.y !== newY)) {
self.latestHover.x = newX;
self.latestHover.y = newY;
self.squares.map(function(k, v) {
let obj = self.squares[v];
if(!obj.fixedBackground) obj.reverseBackgroundColor();
});
var square = self.findObject(newX, newY)[0];
if(square) {
square.setBackgroundColor('#ff0000');
self.$currPos.html(newX +'x'+ newY);
self._redraw();
}
}
});
this.$canvas.on('click', function() {
if(self.tracker === 2) {
return self._reset();
}
if(!($.isEmptyObject(self.latestHover))) {
var x = self.latestHover.x,
y = self.latestHover.y;
var square = self.findObject(x, y)[0];
square.setFixedBackground();
self.$currClick.html(x +'x'+ y);
self.setTracker();
}
});
};
Canvas.prototype._prepare = function() {
for(var row = 0; row < 3; row++) {
for(var col = 0; col < 3; col++) {
this.squares.push(new Square(row, col, this.ctx, this.sqm));
}
}
};
Canvas.prototype._redraw = function() {
var self = this;
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
this.squares.filter(function(k, v) {
self.squares[v].draw();
});
};
Canvas.prototype.setTracker = function() {
this.tracker++;
if(this.tracker === 2) this.$clickInfo.html('Click one more time to start over');
};
Canvas.prototype.findObject = function(x, y) {
var self = this;
return square = self.squares.filter(function(k, v) {
var obj = self.squares[v];
if(obj.posX === x && obj.posY === y) return obj;
});
};
Canvas.prototype._reset = function() {
var self = this;
this.squares.map(function(k, v) {
let obj = self.squares[v];
obj.reverseBackgroundColor();
obj.unsetFixedBackground();
});
this.$currClick.html('');
this.$clickInfo.html('');
this.tracker = 0;
this._redraw();
};
var Square = function(x, y, ctx, sqm) {
this.ctx = ctx;
this.sqm = sqm;
this.posX = x;
this.posY = y;
this.background = '#fff';
this.strokeThickness = 1;
this.fixedBackground = false;
this.draw();
};
Square.prototype.setBackgroundColor = function(color) {
return this.background = color;
};
Square.prototype.reverseBackgroundColor = function() {
return this.background = '#fff';
};
Square.prototype.setFixedBackground = function() {
return this.fixedBackground = true;
};
Square.prototype.unsetFixedBackground = function() {
return this.fixedBackground = false;
};
Square.prototype.draw = function() {
this.ctx.fillStyle = this.background;
this.ctx.fillRect(this.posX * this.sqm, this.posY * this.sqm, this.sqm, this.sqm);
this.ctx.strokeRect(this.posX * this.sqm, this.posY * this.sqm, this.sqm, this.sqm);
};
window.Canvas = new Canvas();
canvas {
border: 1px solid #ccc;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas width="150" height="150"></canvas>
<div>
Current position: <span id="currPos"></span> <br/>
Last click: <span id="currClick"></span> <span id="clickInfo"></span>
</div>
I am recently creating a Game using html5 canvas .The player have multiple state it can walk jump kick and push and multiple other states my question is simple but after some deep research i couldn't find the best way to deal with those multiple states
this is my jsfiddle : http://jsfiddle.net/Z7a5h/5/
i managed to do one animation but i started my code in a messy way ,can anyone show me a way to deal with multiple state animation for one sprite image or just give a useful link to follow and understand the concept of it please .I appreciate your help
if (!this.IsWaiting) {
this.IsWaiting = true;
this.lastRenderTime = now;
this.Pos = 1 + (this.Pos + 1) % 3;
}
else {
if (now - this.lastRenderTime >= this.RenderRate) this.IsWaiting = false;
}
This is my animation class, which let's you set an animation and create the animation as object. I personally like to place the animations in an array such as playerAnimations[], and run the animations according to what the player does.
var toPix = function(n) {
return n*TILE; //tile is basically the same as sh or sw, but I used Tilesizes to draw things.
};
// Animations
var Sprite = function(image, sx, sy, sw, sh) {
this.img = image;
this.sx = sx;
this.sy = sy;
this.sw = sw;
this.sh = sh;
Sprite.prototype.draw = function(ctx, x, y) {
this.x = x;
this.y = y;
this.ctx = ctx;
this.ctx.drawImage(this.img, this.sx, this.sy, this.sw, this.sh, this.x, this.y, this.sw, this.sh);
};
};
var Animation = function(url, ctx, startingRow, rows, columns, sw, sh) {
this.ctx = ctx;
this.url = url;
this.startRow = toPix(startingRow - 1);
this.rows = rows;
this.columns = columns;
this.sprites = [];
animImg = new Image();
animImg.addEventListener('load', function() {});
animImg.src = this.url;
for(var i = 0; i < columns; i++) {
sprite = new Sprite(animImg, i*sw, this.startRow, sw, sh);
this.sprites.push(sprite);
}
this.spriteToDraw = 0;
this.drawSprite = 0;
this.drawSpriteTime = 10;
Animation.prototype.start = function() {
this.stopAnimation = false;
};
Animation.prototype.stop = function() {
this.stopAnimation = true;
};
Animation.prototype.draw = function(x, y) {
if(!this.stopAnimation) {
if(this.spriteToDraw < this.sprites.length) {
var sprite = this.sprites[this.spriteToDraw];
} else {
this.spriteToDraw = 0;
var sprite = this.sprites[this.spriteToDraw];
}
sprite.draw(this.ctx, x, y);
if(this.drawSprite > this.drawSpriteTime) {
this.spriteToDraw++;
this.drawSprite = 0;
} else {
this.drawSprite += 1;
}
}
};
};
//var animation = new Animation('theSprite.png', 5, 5, 45, 45);
//playerAnimations.push(animation);
And then this would be a sample player.draw() function.action.
What it does is: it checks which state the player is in, stops all other animations and runs the correct animation for that state.
player.prototype.draw = function() {
//player.draw function
if(this.playerRight) {
if (this.playerAnimation = playerAnimations[0]) {
this.playerAnimation.stop();
}
if (this.playerAnimation = playerAnimations[2]) {
this.playerAnimation.stop();
}
this.playerAnimation = playerAnimations[1];
this.playerAnimation.start();
this.playerAnimation.draw(this.x, this.y);
} else if(!this.playerRight && !this.playerLeft) {
if (this.playerAnimation = playerAnimations[1]) {
this.playerAnimation.stop();
}
if (this.playerAnimation = playerAnimations[2]) {
this.playerAnimation.stop();
}
this.playerAnimation = playerAnimations[0];
this.playerAnimation.start();
this.playerAnimation.draw(this.x, this.y);
} else {
if(this.playerLeft) {
if (this.playerAnimation = playerAnimations[0]) {
this.playerAnimation.stop();
}
if (this.playerAnimation = playerAnimations[1]) {
this.playerAnimation.stop();
}
this.playerAnimation = playerAnimations[2];
this.playerAnimation.start();
this.playerAnimation.draw(this.x, this.y);
}
};
I hope this is able to help you. This is my way of doing these kind of animations and it works for me, good luck!
I need to allow user to rotate bitmap features on map with OpenLayers.Control.ModifyFeature or another way as it work for Polygons or other geometry objects, except Point, but only for Point I can set "externalGraphic" with my bitmap. Example of ModifyFeature to rotation as I expected here: ModifyFeature example
When I add Vector with Point geometry and activate ModifyFeature there is no rotation tool showing - only drag-drop. I know what is a point, but I need to have tool for rotate bitmap features. It may be image on any another geometry object, but with custom image.
After long research I found an example in gis.stackexchange.com, and fix it.
Here is a code which works for me:
OpenLayers.Control.RotateGraphicFeature = OpenLayers.Class(OpenLayers.Control.ModifyFeature, {
rotateHandleStyle: null,
initialize: function(layer, options) {
OpenLayers.Control.ModifyFeature.prototype.initialize.apply(this, arguments);
this.mode = OpenLayers.Control.ModifyFeature.ROTATE; // This control can only be used to rotate the feature
this.geometryTypes = ['OpenLayers.Geometry.Point'] // This control can only be used to rotate point because the 'exteralGraphic' is a point style property
var init_style = OpenLayers.Util.extend({}, OpenLayers.Feature.Vector.style.select);
this.rotateHandleStyle = OpenLayers.Util.extend(init_style, {
externalGraphic: "./static/resources/images/cross.png",
graphicWidth: 12,
graphicHeight: 12,
fillOpacity: 1
});
},
resetVertices: function() {
// You need to set yours renderIntent or use "vertex"
if (this.feature && this.feature.renderIntent == "vertex") {
var vertex = this.feature;
this.feature = this.backup_feature;
this.layer.destroyFeatures([this.radiusHandle], {silent: true});
this.collectRadiusHandle();
return;
}
if (this.dragControl.feature) {
this.dragControl.outFeature(this.dragControl.feature);
}
if (this.vertices.length > 0) {
this.layer.removeFeatures(this.vertices, {silent: true});
this.vertices = [];
}
if (this.virtualVertices.length > 0) {
this.layer.removeFeatures(this.virtualVertices, {silent: true});
this.virtualVertices = [];
}
if (this.dragHandle) {
this.layer.destroyFeatures([this.dragHandle], {silent: true});
this.dragHandle = null;
}
if (this.radiusHandle) {
this.layer.destroyFeatures([this.radiusHandle], {silent: true});
this.radiusHandle = null;
}
if (this.feature && this.feature.geometry &&
this.feature.geometry.CLASS_NAME != "OpenLayers.Geometry.Point") {
if ((this.mode & OpenLayers.Control.ModifyFeature.DRAG)) {
this.collectDragHandle();
}
if ((this.mode & (OpenLayers.Control.ModifyFeature.ROTATE |
OpenLayers.Control.ModifyFeature.RESIZE))) {
this.collectRadiusHandle();
}
if (this.mode & OpenLayers.Control.ModifyFeature.RESHAPE) {
if (!(this.mode & OpenLayers.Control.ModifyFeature.RESIZE)) {
this.collectVertices();
}
}
}
this.collectRadiusHandle();
},
collectRadiusHandle: function() {
var scope = this,
feature = this.feature,
geometry = this.feature.geometry || this.backup_feature.geometry,
centroid = geometry.getCentroid().transform(this.WGS84_google_mercator, this.WGS84),
lon = centroid.x, lat = centroid.y;
if (this.feature.geometry) {
this.backup_feature = this.feature;
} else {
this.feature.geometry = this.backup_feature.geometry;
}
var originGeometry = new OpenLayers.Geometry.Point(lon, lat);
// radius geometry position.
var pixel_dis_x = 10,
pixel_dis_y = -10;
var rotationFeatureGeometry = new OpenLayers.Geometry.Point(lon+pixel_dis_x, lat+pixel_dis_y);
var rotationFeature = new OpenLayers.Feature.Vector(rotationFeatureGeometry, null, this.rotateHandleStyle);
var resize = (this.mode & OpenLayers.Control.ModifyFeature.RESIZE);
var reshape = (this.mode & OpenLayers.Control.ModifyFeature.RESHAPE);
var rotate = (this.mode & OpenLayers.Control.ModifyFeature.ROTATE);
rotationFeatureGeometry.move = function(x, y) {
OpenLayers.Geometry.Point.prototype.move.call(this, x, y);
var dx1 = this.x - originGeometry.x;
var dy1 = this.y - originGeometry.y;
var dx0 = dx1 - x;
var dy0 = dy1 - y;
if (rotate) {
var a0 = Math.atan2(dy0, dx0);
var a1 = Math.atan2(dy1, dx1);
var angle = a1 - a0;
angle *= 180 / Math.PI;
var old_angle = feature.attributes.angle;
var new_angle = old_angle - angle;
feature.attributes.angle = new_angle;
// redraw the feature
scope.feature.layer.redraw.call(scope.feature.layer);
}
};
rotationFeature._sketch = true;
this.radiusHandle = rotationFeature;
this.radiusHandle.renderIntent = this.vertexRenderIntent;
this.layer.addFeatures([this.radiusHandle], {silent: true});
},
CLASS_NAME: "OpenLayers.Control.RotateGraphicFeature"
});
Style of your Vector may be as:
new OpenLayers.StyleMap({
"default": new OpenLayers.Style({
externalGraphic: "link/to/icon",
graphicHeight: "32px",
graphicWidth: "25px",
fillOpacity: 1,
rotation: "${angle}",
graphicZIndex: 1
})
})
UPD: I fixed it for OpenLayers 2.13.1
OpenLayers.Control.RotateGraphicFeature = OpenLayers.Class(OpenLayers.Control.ModifyFeature, {
rotateHandleStyle: null,
initialize: function (layer, options) {
OpenLayers.Control.ModifyFeature.prototype.initialize.apply(this, arguments);
this.mode = OpenLayers.Control.ModifyFeature.ROTATE; // This control can only be used to rotate the feature
this.geometryTypes = ['OpenLayers.Geometry.Point'] // This control can only be used to rotate point because the 'exteralGraphic' is a point style property
var init_style = OpenLayers.Util.extend({}, OpenLayers.Feature.Vector.style.select);
this.rotateHandleStyle = OpenLayers.Util.extend(init_style, {
externalGraphic: "./static/resources/images/cross.png",
graphicWidth: 12,
graphicHeight: 12,
fillOpacity: 1
});
},
resetVertices: function () {
// You need to set yours renderIntent or use "vertex"
if (this.feature && this.feature.renderIntent == "vertex") {
var vertex = this.feature;
this.feature = this.backup_feature;
if (this.dragControl.feature) {
this.dragControl.outFeature(this.dragControl.feature);
}
this.layer.destroyFeatures([this.radiusHandle], {silent: true});
delete this.radiusHandle;
this.collectRadiusHandle();
return;
}
if (this.vertices.length > 0) {
this.layer.removeFeatures(this.vertices, {silent: true});
this.vertices = [];
}
if (this.virtualVertices.length > 0) {
this.layer.removeFeatures(this.virtualVertices, {silent: true});
this.virtualVertices = [];
}
if (this.dragHandle) {
this.layer.destroyFeatures([this.dragHandle], {silent: true});
this.dragHandle = null;
}
if (this.radiusHandle) {
this.layer.destroyFeatures([this.radiusHandle], {silent: true});
this.radiusHandle = null;
}
if (this.feature && this.feature.geometry &&
this.feature.geometry.CLASS_NAME != "OpenLayers.Geometry.Point") {
if ((this.mode & OpenLayers.Control.ModifyFeature.DRAG)) {
this.collectDragHandle();
}
if ((this.mode & (OpenLayers.Control.ModifyFeature.ROTATE |
OpenLayers.Control.ModifyFeature.RESIZE))) {
this.collectRadiusHandle();
}
if (this.mode & OpenLayers.Control.ModifyFeature.RESHAPE) {
if (!(this.mode & OpenLayers.Control.ModifyFeature.RESIZE)) {
this.collectVertices();
}
}
}
this.collectRadiusHandle();
},
collectRadiusHandle: function () {
var scope = this,
feature = this.feature,
data = feature.attributes,
geometry = this.feature.geometry || this.backup_feature.geometry,
center = this.feature.geometry.bounds.getCenterLonLat();
centroid = geometry.getCentroid().transform(this.WGS84_google_mercator, this.WGS84),
lon = centroid.x, lat = centroid.y;
if (data.type && Tms.settings.roadObjectTypeSettings[data.type].NoAzimuth) {
return;
}
if (this.feature.geometry) {
this.backup_feature = this.feature;
} else {
this.feature.geometry = this.backup_feature.geometry;
}
var originGeometry = new OpenLayers.Geometry.Point(lon, lat);
var center_px = this.map.getPixelFromLonLat(center);
// you can change this two values to get best radius geometry position.
var pixel_dis_x = 20,
pixel_dis_y = 20;
var radius_px = center_px.add(pixel_dis_x, pixel_dis_y);
var rotation_lonlat = this.map.getLonLatFromPixel(radius_px);
var rotationFeatureGeometry = new OpenLayers.Geometry.Point(
rotation_lonlat.lon, rotation_lonlat.lat
);
var rotationFeature = new OpenLayers.Feature.Vector(rotationFeatureGeometry, null, this.rotateHandleStyle);
var resize = (this.mode & OpenLayers.Control.ModifyFeature.RESIZE);
var reshape = (this.mode & OpenLayers.Control.ModifyFeature.RESHAPE);
var rotate = (this.mode & OpenLayers.Control.ModifyFeature.ROTATE);
rotationFeatureGeometry.move = function(x, y) {
OpenLayers.Geometry.Point.prototype.move.call(this, x, y);
var dx1 = this.x - originGeometry.x;
var dy1 = this.y - originGeometry.y;
var dx0 = dx1 - x;
var dy0 = dy1 - y;
if (rotate) {
var a0 = Math.atan2(dy0, dx0);
var a1 = Math.atan2(dy1, dx1);
var angle = a1 - a0;
angle *= 180 / Math.PI;
var old_angle = feature.attributes.angle;
var new_angle = old_angle - angle;
feature.attributes.angle = new_angle;
// redraw the feature
scope.feature.layer.redraw.call(scope.feature.layer);
}
};
rotationFeature._sketch = true;
this.radiusHandle = rotationFeature;
this.radiusHandle.renderIntent = this.vertexRenderIntent;
this.layer.addFeatures([this.radiusHandle], {silent: true});
},
CLASS_NAME: "OpenLayers.Control.RotateGraphicFeature"
});
The Issue
I am using the following example code to draw arrows on google map.
Here is the main overlay function that creates the svg files for the arrow.
// A small directable Arrow overlay for GMaps V3
// Bill Chadwick Feb 2012 after my previous V2 API version
// Free for any use as far as I am concerned
// Some public domain code VML/SVG utility code of Ben Appleton's is reused at the end of this file
var arrowOverlayMarkerCounter; //unique id counter for SVG arrow head markers
function ArrowOverlay(map, location, rotation, color, opacity, tooltip) {
this.map_ = map;
this.location_ = location;
this.rotation_ = rotation || 0.0;
var r = this.rotation_ + 90; //compass to math
this.dx_ = 20 * Math.cos(r * Math.PI / 180); //other end of arrow line to point
this.dy_ = 20 * Math.sin(r * Math.PI / 180);
this.color_ = color || "#0000FF";
this.opacity_ = opacity || 0.7;
this.tooltip_ = tooltip || "";
this.div_ = null;
this.setMap(map);
this.handle_ = null;//click event handler handle
if (arrowOverlayMarkerCounter == null)
arrowOverlayMarkerCounter = 0;
else
arrowOverlayMarkerCounter += 1;
this.svgId_ = "ArrowOverlay" + arrowOverlayMarkerCounter.toString();
}
ArrowOverlay.prototype = new google.maps.OverlayView();
ArrowOverlay.prototype.onAdd = function() {
// Create the DIV and set some basic attributes.
var div = document.createElement('DIV');
div.title = this.tooltip_;
div.style.cursor = "help";
var obj = this;
this.handle_ = google.maps.event.addDomListener(div, 'click', function() { google.maps.event.trigger(obj, "click") });
//set up arrow invariants
if (supportsVML()) {
var l = createVmlElement('v:line', div);
l.strokeweight = "3px";
l.strokecolor = this.color_;
l.style.position = 'absolute';
var s = createVmlElement("v:stroke", l);
s.opacity = this.opacity_;
s.startarrow = "classic"; // or "block", "open" etc see VML spec
this.vmlLine_ = l;
}
else {
// make a 40x40 pixel space centered on the arrow
var svgNS = "http://www.w3.org/2000/svg";
var svgRoot = document.createElementNS(svgNS, "svg");
svgRoot.setAttribute("width", 40);
svgRoot.setAttribute("height", 40);
svgRoot.setAttribute("stroke", this.color_);
svgRoot.setAttribute("fill", this.color_);
svgRoot.setAttribute("stroke-opacity", this.opacity_);
svgRoot.setAttribute("fill-opacity", this.opacity_);
div.appendChild(svgRoot);
var svgNode = document.createElementNS(svgNS, "line");
svgNode.setAttribute("stroke-width", 3);
svgNode.setAttribute("x1", 20);
svgNode.setAttribute("y1", 20);
svgNode.setAttribute("x2", 20 + this.dx_);
svgNode.setAttribute("y2", 20 + this.dy_);
//make a solid arrow head, can't share these, as in SVG1.1 they can't get color from the referencing object, only their parent
//a bit more involved than the VML
if (this.rotation_ >= 0) {
var svgM = document.createElementNS(svgNS, "marker");
svgM.id = this.svgId_;
svgM.setAttribute("viewBox", "0 0 10 10");
svgM.setAttribute("refX", 0);
svgM.setAttribute("refY", 5);
svgM.setAttribute("markerWidth", 4);
svgM.setAttribute("markerHeight", 3);
svgM.setAttribute("orient", "auto");
var svgPath = document.createElementNS(svgNS, "path"); //could share this with 'def' and 'use' but hardly worth it
svgPath.setAttribute("d", "M 10 0 L 0 5 L 10 10 z");
svgM.appendChild(svgPath);
svgRoot.appendChild(svgM);
svgNode.setAttribute("marker-start", "url(#" + this.svgId_ + ")");
}
svgRoot.appendChild(svgNode);
this.svgRoot_ = svgRoot;
this.svgNode_ = svgNode;
}
// Set the overlay's div_ property to this DIV
this.div_ = div;
var panes = this.getPanes();
panes.overlayImage.appendChild(this.div_);
}
ArrowOverlay.prototype.draw = function() {
var overlayProjection = this.getProjection();
var p = overlayProjection.fromLatLngToDivPixel(this.location_);
var div = this.div_;
if (!div)
return;
if (!div.style)
return;
// Calculate the DIV coordinates of the ref point of our arrow
var x2 = p.x + this.dx_;
var y2 = p.y + this.dy_;
if (supportsVML()) {
this.vmlLine_.from = p.x + "px, " + p.y + "px";
this.vmlLine_.to = x2 + "px, " + y2 + "px";
}
else {
this.svgRoot_.setAttribute("style", "position:absolute; top:" + (p.y - 20) + "px; left:" + (p.x - 20) + "px");
}
}
ArrowOverlay.prototype.onRemove = function() {
if (this.handle_ != null) {
google.maps.eventclear.removeListener(this.handle_);
}
this.div_.parentNode.removeChild(this.div_);
}
ArrowOverlay.prototype.setVisible = function(v) {
if (v)
this.show();
else
this.hide();
}
ArrowOverlay.prototype.getVisible = function(v) {
if (this.div_) {
return (this.div_.style.display == "");
}
return false;
}
ArrowOverlay.prototype.hide = function() {
if (this.div_) {
this.div_.style.display = "none";
}
}
ArrowOverlay.prototype.show = function() {
if (this.div_) {
this.div_.style.display = "";
}
}
ArrowOverlay.prototype.setPosition = function(l) {
this.location_ = l;
this.draw();
}
ArrowOverlay.prototype.getPosition = function() {
return this.location_;
}
ArrowOverlay.prototype.setHeading = function(h) {
this.rotation_ = h || 0.0;
var r = this.rotation_ + 90; //compass to math
this.dx_ = 20 * Math.cos(r * Math.PI / 180); //other end of arrow line to point
this.dy_ = 20 * Math.sin(r * Math.PI / 180);
if (!supportsVML()) {
this.svgNode_.setAttribute("x2", 20 + this.dx_);
this.svgNode_.setAttribute("y2", 20 + this.dy_);
}
this.draw();
}
ArrowOverlay.prototype.getHeading = function() {
return this.rotation_;
}
ArrowOverlay.prototype.setTooltip = function(t) {
this.tooltip_ = t;
}
ArrowOverlay.prototype.getTooltip = function() {
return this.tooltip_;
}
ArrowOverlay.prototype.toggle = function() {
if (this.div_) {
if (this.div_.style.visibility == "hidden") {
this.show();
} else {
this.hide();
}
}
}
ArrowOverlay.prototype.fromDivPixelToLatLng = function(x, y) {
var overlayProjection = this.getProjection();
return overlayProjection.fromDivPixelToLatLng(new google.maps.Point(x, y));
}
ArrowOverlay.prototype.fromLatLngToContainerPixel = function(p) {
var overlayProjection = this.getProjection();
return overlayProjection.fromLatLngToContainerPixel(p);
}
// SVG utils from here http://appleton-static.appspot.com/static/simple_poly.js
// by Ben Appleton of Google
var SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
function supportsSVG() {
return document.implementation.hasFeature(
'http://www.w3.org/TR/SVG11/feature#Shape',
'1.1');
}
// VML utils from here http://appleton-static.appspot.com/static/simple_poly.js
// by Ben Appleton of Google
var VML_NAMESPACE = 'urn:schemas-microsoft-com:vml';
function createVmlElement(tagName, parent) {
var element = document.createElement(tagName);
parent.appendChild(element);
element.style['behavior'] = 'url(#default#VML)';
return element;
}
function supportsVML() {
if (supportsVML.result_ == null) {
if (!maybeCreateVmlNamespace()) {
return supportsVML.result_ = false;
}
// Create some VML. Its 'adj' property will be an object only when VML
// is enabled.
var div = document.createElement('DIV');
document.body.appendChild(div);
div.innerHtml = '<v:shape id="vml_flag1" adj="1" />';
var child = div.firstChild;
if (child) child.style['behavior'] = 'url(#default#VML)';
supportsVML.result_ = !child || (typeof child['adj'] == 'object');
div.parentNode.removeChild(div);
}
return supportsVML.result_;
}
function maybeCreateVmlNamespace() {
var hasVmlNamespace = false;
if (document.namespaces) {
for (var x = 0; x < document.namespaces.length; x++) {
var ns = document.namespaces(x);
if (ns.name == 'v') {
if (ns.urn == VML_NAMESPACE) {
hasVmlNamespace = true;
} else {
throw new Error('document namespace v: is required for VML ' +
'but has been reserved for ' + ns.urn);
}
}
}
if (!hasVmlNamespace) {
// Import namespace
hasVmlNamespace = true;
document.namespaces.add('v', VML_NAMESPACE);
}
}
return hasVmlNamespace;
}
Question
I can not figure out how to change the length of those arrow? Can I somehow add a length parameter to the function ArrowOverlay?
I would do something like this (scale also works with x, y):
<svg>
<g id="idG" transform="scale(1, 1)">
..your arrow
</g>
</svg>
document.getElementById("idG").setAttribute("transform", "scale(" + horizontalScale + "," + verticalScale + ")");
Example: http://k8.no-ip.org/stackoverflow/13540341.htm
I am trying to get the distance between my character and the ground, I have found something that looks like it should do what I want but it has been written for another version of box2d.
Original:
float targetHeight = 3;
float springConstant = 100;
//make the ray at least as long as the target distance
b2Vec2 startOfRay = m_hovercarBody->GetPosition();
b2Vec2 endOfRay = m_hovercarBody->GetWorldPoint( b2Vec2(0,-5) );
overcarRayCastClosestCallback callback;
m_world->RayCast(&callback, startOfRay, endOfRay);
if ( callback.m_hit ) {
float distanceAboveGround = (startOfRay - callback.m_point).Length();
//dont do anything if too far above ground
if ( distanceAboveGround < targetHeight ) {
float distanceAwayFromTargetHeight = targetHeight - distanceAboveGround;
m_hovercarBody->ApplyForce( b2Vec2(0,springConstant*distanceAwayFromTargetHeight),
m_hovercarBody->GetWorldCenter() );
}
}
I have tried to change it to what I think it should be but it doesn't even call the callback.
var targetHeight = 3;
var springConstant = 100;
//make the ray at least as long as the target distance
startOfRay = new b2Vec2(m_hovercarBody.GetPosition());
endOfRay = new b2Vec(m_hovercarBody.GetWorldPoint( b2Vec2(0,-5)));
function callback(raycast){
if ( raycast.m_hit ) {
var distanceAboveGround = (startOfRay - raycast.m_point).Length();
//dont do anything if too far above ground
if ( distanceAboveGround < targetHeight ) {
var distanceAwayFromTargetHeight = targetHeight - distanceAboveGround;
m_hovercarBody.ApplyForce( b2Vec2(0,springConstant*distanceAwayFromTargetHeight),
m_hovercarBody.GetWorldCenter() );
}
}
}
m_world.RayCast(callback, startOfRay, endOfRay);
Any idea how to convert it to work with box2dweb?
Thanks
It might be that the original bit of code was written for a platform where the coordinate system works differently.
In a Canvas element, the coordinate system starts from the top left corner, meaning that m_hovercarBody.GetWorldPoint( b2Vec2(0,-5)) is checking for a point above the character, rather than below.
I'm not sure about the rest of the code but try changing that to m_hovercarBody.GetWorldPoint( b2Vec2(0,5)) and see what happens.
EDIT:
I think actually the problem is with the way you've structured your callback. Looking up the reference for the Raycast function would reveal more.
(The Javascript version of Box2D you're using is an automatic port of the Actionscript one. Given the two have fairly similar syntax, you can use the reference for Flash.)
The original code you posted seems to be C++, but I don't know much about its syntax. It seems there's some sort of class that does the raycasting (overcarRayCastClosestCallback). You can either look for that, or try and build your own callback function according to the first link I posted. It would be something along the lines of:
function customRaycastCallback(fixture, normal, fraction) {
// you can, for instance, check if fixture belongs to the ground
// or something else, then handle things accordingly
if( /* fixture belongs to ground */ ) {
// you've got the fraction of the original length of the raycast!
// you can use this to determine the distance
// between the character and the ground
return fraction;
}
else {
// continue looking
return 1;
}
}
Try running this on your browser. I coded this when I was learning sensor and RAYCAST in Box2Dweb. Hope it helps.
<html>
<head>
<title>Box2dWeb Demo</title>
</head>
<body>
<canvas id="canvas" width="600" height="420" style="background-color:#333333;" ></canvas>
<div id="cc" style="position:absolute; right:0; top:100px; width:500px; height:50px; margin:0;"></div>
</body>
<script type="text/javascript" src="Box2dWeb-2.1.a.3.js"></script>
<script type="text/javascript" src="jquery-1.7.2.js"></script>
<script type="text/javascript">
var b2Vec2 = Box2D.Common.Math.b2Vec2
, b2BodyDef = Box2D.Dynamics.b2BodyDef
, b2Body = Box2D.Dynamics.b2Body
, b2FixtureDef = Box2D.Dynamics.b2FixtureDef
, b2World = Box2D.Dynamics.b2World
, b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape
, b2CircleShape = Box2D.Collision.Shapes.b2CircleShape
, b2ContactFilter = Box2D.Dynamics.b2ContactFilter
, b2MouseJointDef = Box2D.Dynamics.Joints.b2MouseJointDef
, b2DebugDraw = Box2D.Dynamics.b2DebugDraw
, b2Fixture = Box2D.Dynamics.b2Fixture
, b2AABB = Box2D.Collision.b2AABB
, b2WorldManifold = Box2D.Collision.b2WorldManifold
, b2ManifoldPoint = Box2D.Collision.b2ManifoldPoint
, b2RayCastInput = Box2D.Collision.b2RayCastInput
, b2RayCastOutput = Box2D.Collision.b2RayCastOutput
, b2Color = Box2D.Common.b2Color;
var world = new b2World(new b2Vec2(0,10), true);
var canvas = $('#canvas');
var context = canvas.get(0).getContext('2d');
//box
var bodyDef = new b2BodyDef;
bodyDef.type = b2Body.b2_dynamicBody;
bodyDef.position.Set(9,7);
bodyDef.userData = 'box';
var fixDef = new b2FixtureDef;
fixDef.filter.categoryBits = 1;
fixDef.density = 10.0;
fixDef.friction = 0.5;
fixDef.restitution = .5;
fixDef.shape = new b2PolygonShape;
fixDef.shape.SetAsBox(1,5);
var box1 = world.CreateBody(bodyDef);
box1.CreateFixture(fixDef);
//circle
var bodyDef2 = new b2BodyDef;
bodyDef2.type = b2Body.b2_dynamicBody;
bodyDef2.position.Set(4,8);
bodyDef2.userData = 'obj';
var fixDef2 = new b2FixtureDef;
fixDef2.filter.categoryBits = 2;
fixDef2.filter.maskBits = 13;
fixDef2.density = 10.0;
fixDef2.friction = 0.5;
fixDef2.restitution = .2;
fixDef2.shape = new b2CircleShape(1);
//circlesensor
var cc = new b2FixtureDef;
cc.shape = new b2CircleShape(2);
cc.shape.SetLocalPosition(new b2Vec2(0 ,0));
cc.density = 0;
cc.isSensor = true;
cc.filter.categoryBits = 8;
var wheel = world.CreateBody(bodyDef2);
wheel.CreateFixture(fixDef2);
wheel.CreateFixture(cc);
//create a ground
var holderDef = new b2BodyDef;
holderDef.type = b2Body.b2_staticBody;
holderDef.userData = "ground";
holderDef.position.Set(10, 14);
var fd = new b2FixtureDef;
fd.filter.categoryBits = 4;
fd.shape = new b2PolygonShape;
fd.shape.SetAsBox(10,1);
var ground = world.CreateBody(holderDef);
ground.CreateFixture(fd);
//create another static body
var holderDef = new b2BodyDef;
holderDef.type = b2Body.b2_staticBody;
holderDef.position.Set(10, 20);
var temp = world.CreateBody(holderDef);
temp.CreateFixture(fd);
var c=0;
$(window).keydown(function(e) {
$('#aa').html(++c);
code = e.keyCode;
if(c==1) {
if(code == 38 && onground)
wheel.SetLinearVelocity(new b2Vec2(0,-10));
if(code == 39)
wheel.ApplyForce(new b2Vec2(1000,0), box1.GetWorldPoint(new b2Vec2(0,0)));
if(code == 37)
wheel.ApplyForce(new b2Vec2(-1000,0), box1.GetWorldPoint(new b2Vec2(0,0)));
}
});
$(window).keyup(function(e) {
c=0;
});
var listener = new Box2D.Dynamics.b2ContactListener;
listener.BeginContact = function(contact) {
if(contact.GetFixtureA().GetBody().GetUserData()== 'obj' || contact.GetFixtureB().GetBody().GetUserData()== 'obj' ) // think about why we don't use fixture's userData directly.
onground = true;// don't put 'var' here!
fxA=contact.GetFixtureA();
fxB=contact.GetFixtureB();
sA=fxA.IsSensor();
sB=fxB.IsSensor();
if((sA && !sB) || (sB && !sA)) {
if(sA) {
$('#cc').prepend(contact.GetFixtureB().GetBody().GetUserData() + ' is in the viscinity of body '+contact.GetFixtureA().GetBody().GetUserData()+'<br>');
}
else {
$('#cc').prepend(contact.GetFixtureA().GetBody().GetUserData() + ' is in the viscinity of body '+contact.GetFixtureB().GetBody().GetUserData()+'<br>');
}
}
}
listener.EndContact = function(contact) {
if (contact.GetFixtureA().GetBody().GetUserData()== 'obj' || contact.GetFixtureB().GetBody().GetUserData()== 'obj' )
onground = false;
}
var debugDraw = new b2DebugDraw();
debugDraw.SetSprite ( document.getElementById ("canvas").getContext ("2d"));
debugDraw.SetDrawScale(30); //define scale
debugDraw.SetAlpha(1);
debugDraw.SetFillAlpha(.3); //define transparency
debugDraw.SetLineThickness(1.0);
debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
world.SetDebugDraw(debugDraw);
window.setInterval(update,1000/60);
//mouse
var mouseX, mouseY, mousePVec, isMouseDown, selectedBody, mouseJoint;
var canvasPosition = getElementPosition(document.getElementById("canvas"));
document.addEventListener("mousedown", function(e) {
isMouseDown = true;
handleMouseMove(e);
document.addEventListener("mousemove", handleMouseMove, true);
}, true);
document.addEventListener("mouseup", function() {
document.removeEventListener("mousemove", handleMouseMove, true);
isMouseDown = false;
mouseX = undefined;
mouseY = undefined;
}, true);
function handleMouseMove(e) {
mouseX = (e.clientX - canvasPosition.x) / 30;
mouseY = (e.clientY - canvasPosition.y) / 30;
};
function getBodyAtMouse() {
mousePVec = new b2Vec2(mouseX, mouseY);
var aabb = new b2AABB();
aabb.lowerBound.Set(mouseX - 0.001, mouseY - 0.001);
aabb.upperBound.Set(mouseX + 0.001, mouseY + 0.001);
// Query the world for overlapping shapes.
selectedBody = null;
world.QueryAABB(getBodyCB, aabb);
return selectedBody;
}
function getBodyCB(fixture) {
if(fixture.GetBody().GetType() != b2Body.b2_staticBody) {
if(fixture.GetShape().TestPoint(fixture.GetBody().GetTransform(), mousePVec)) {
selectedBody = fixture.GetBody();
return false;
}
}
return true;
}
//at global scope
var currentRayAngle = 0;
var input = new b2RayCastInput();
var output = new b2RayCastOutput();
var b = new b2BodyDef();
var f = new b2FixtureDef();
var closestFraction = 1;
var intersectionNormal = new b2Vec2(0,0);
var intersectionPoint = new b2Vec2();
rayLength = 25; //long enough to hit the walls
var p1 = new b2Vec2( 11, 7 ); //center of scene
var p2 = new b2Vec2();
var normalEnd = new b2Vec2();
function update() {
if(isMouseDown && (!mouseJoint)) {
var body = getBodyAtMouse();
if(body) {
var md = new b2MouseJointDef();
md.bodyA = world.GetGroundBody();
md.bodyB = body;
md.target.Set(mouseX, mouseY);
md.collideConnected = true;
md.maxForce = 300.0 * body.GetMass();
mouseJoint = world.CreateJoint(md);
body.SetAwake(true);
}
}
if(mouseJoint) {
if(isMouseDown) {
mouseJoint.SetTarget(new b2Vec2(mouseX, mouseY));
} else {
world.DestroyJoint(mouseJoint);
mouseJoint = null;
}
}
world.Step(1 / 60, 10, 10);
world.DrawDebugData();
world.ClearForces();
world.SetContactListener(listener);
ray();
};
function ray() {
//in Step() function
var k = 360/20;
var t = k/60;
var DEGTORAD = Math.PI/180;
currentRayAngle += t * DEGTORAD; //one revolution every 20 seconds
//console.log(currentRayAngle*(180/Math.PI));
//calculate points of ray
p2.x = p1.x + rayLength * Math.sin(currentRayAngle);
p2.y = p1.y + rayLength * Math.cos(currentRayAngle);
input.p1 = p1;
input.p2 = p2;
input.maxFraction = 1;
closestFraction = 1;
var b = new b2BodyDef();
var f = new b2FixtureDef();
for(b = world.GetBodyList(); b; b = b.GetNext()) {
for(f = b.GetFixtureList(); f; f = f.GetNext()) {
if(!f.RayCast(output, input))
continue;
else if(output.fraction < closestFraction) {
closestFraction = output.fraction;
intersectionNormal = output.normal;
}
}
}
intersectionPoint.x = p1.x + closestFraction * (p2.x - p1.x);
intersectionPoint.y = p1.y + closestFraction * (p2.y - p1.y);
normalEnd.x = intersectionPoint.x + intersectionNormal.x;
normalEnd.y = intersectionPoint.y + intersectionNormal.y;
context.strokeStyle = "rgb(255, 255, 255)";
context.beginPath(); // Start the path
context.moveTo(p1.x*30,p1.y*30); // Set the path origin
context.lineTo(intersectionPoint.x*30, intersectionPoint.y*30); // Set the path destination
context.closePath(); // Close the path
context.stroke();
context.beginPath(); // Start the path
context.moveTo(intersectionPoint.x*30, intersectionPoint.y*30); // Set the path origin
context.lineTo(normalEnd.x*30, normalEnd.y*30); // Set the path destination
context.closePath(); // Close the path
context.stroke(); // Outline the path
}
//helpers
//http://js-tut.aardon.de/js-tut/tutorial/position.html
function getElementPosition(element) {
var elem=element, tagname="", x=0, y=0;
while((typeof(elem) == "object") && (typeof(elem.tagName) != "undefined")) {
y += elem.offsetTop;
x += elem.offsetLeft;
tagname = elem.tagName.toUpperCase();
if(tagname == "BODY")
elem=0;
if(typeof(elem) == "object") {
if(typeof(elem.offsetParent) == "object")
elem = elem.offsetParent;
}
}
return {x: x, y: y};
}
</script>
</html>
Here's a Raycast in Box2dWeb that I got working:
var p1 = new b2Vec2(body.GetPosition().x, body.GetPosition().y); //center of scene
var p2 = new b2Vec2(body.GetPosition().x, body.GetPosition().y + 5); //center of scene
world.RayCast(function(x){
console.log("You've got something under you");
}, p1,p2);