I am using ol.interaction.Transform to rotate one image on the map after selecting it. The final goal is to have the possibility to scale, stretch, rotate and translate (drag&drop) the image.
My problems are that:
when I rotate the image, it does not keep the aspect ratio (the size of the image change furthermore it stretches)
the red dotted line that appears along the image perimeter when the image is selected does not follow the movement of the rotation, e.g this is how it looks before I start the rotation:
and this is how it looks meanwhile I am doing the rotation:
I would aspect indeed something like this (black dotted line):
How can I fix it?
This is my code:
var styleCache = {};
function getStyle(img, scaleX, scaleY) {
var view = map.getView();
var resolutionAtEquator = view.getResolution();
var y = Math.round(img.height * scaleY);
var x = Math.round(img.width * scaleX);
var key = img.src + ',' + x + ',' + y;
var style = styleCache[key];
if (!style) {
var canvas = document.createElement('canvas');
canvas.width = x;
canvas.height = y;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, x, y);
var resizeImageUrl = canvas.toDataURL();
canvas.remove();
var keys = Object.keys(styleCache);
if (keys.length >= 100) {
// delete an old entry to limit the cache size
delete styleCache[keys[0]];
}
var style = new ol.style.Style({
image: new ol.style.Icon({
src: resizeImageUrl,
opacity: imageOpacity,
})
});
styleCache[key] = style;
}
return style;
}
styles = [
new ol.style.Style({
fill: new ol.style.Fill({
color: transparent
})
}),
new ol.style.Style({
stroke: new ol.style.Stroke({
color: transparent,
width: width + 2
})
}),
new ol.style.Style({
stroke: new ol.style.Stroke({
color: transparent,
width: width
})
}),
new ol.style.Style({
image: new ol.style.Circle({
radius: width * 2,
fill: new ol.style.Fill({
color: blue
}),
stroke: new ol.style.Stroke({
color: transparent,
width: width / 2
})
}),
zIndex: Infinity
})
];
var florplanStyle = new ol.style.Style({
image: new ol.style.Icon({
src: img.src,
opacity: imageOpacity,
})
});
styleFunction = function(feature, resolution) {
var rayDrawValueX = img.width/2;
var resAdjustX = rayDrawValueX * resolution;
var rayDrawValueY = img.height/2;
var resAdjustY = rayDrawValueY * resolution;
var rotation = feature.get('rotation');
if (rotation !== undefined) {
var extent = feature.getGeometry().getExtent();
var coordinates = feature.getGeometry().getCoordinates()[0];
var getBottomLeft = ol.extent.getBottomLeft(extent);
var getBottomRight = ol.extent.getBottomRight(extent);
var getTopLeft = ol.extent.getTopLeft(extent);
var getTopRight = ol.extent.getTopRight(extent);
var center = ol.extent.getCenter(extent);
var dx = center[0] - getBottomLeft[0];
var dy = 0;
var scaleX = Math.sqrt(dx * dx + dy * dy)/resAdjustX;
var dx = 0;
var dy = getTopRight[1] - center[1];
var scaleY = Math.sqrt(dx * dx + dy * dy)/resAdjustY;
var florplanStyle2 = getStyle(img, scaleX, scaleY);
florplanStyle2.setGeometry(new ol.geom.Point(center));
florplanStyle2.getImage().setRotation(rotation);
return debug ? styles.concat([florplanStyle2]) : florplanStyle2;
} else if (feature.getGeometry().getCenter) {
//scrolling map case
florplanStyle.setGeometry(new ol.geom.Point(feature.getGeometry().getCenter()));
// get rotation from drawn feature or geometry
florplanStyle.getImage().setRotation(feature.getGeometry().get('rotation'));
florplanStyle.getImage().setScale(feature.getGeometry().getRadius()/resAdjustX);
return florplanStyle;
} else {
return styles;
}
};
if ( this.nord && this.sud && this.est && this.ovest && this.floorplanImage && this.opacity) {
var extent = ol.proj.transformExtent([this.ovest, this.sud, this.est, this.nord], 'EPSG:4326', 'EPSG:3857');
var center = ol.extent.getCenter(extent);
var size = ol.extent.getSize(extent);
var view = map.getView();
var resolutionAtEquator = view.getResolution();
var width = ol.extent.getWidth(extent);
var height = ol.extent.getHeight(extent);
var radius = width/2;
var rotation = 0;
var circle = circle || new ol.geom.Circle(center, radius);
var circleFeature = new ol.Feature(circle);
circleFeature.set('rotation', rotation);
var geom = ol.geom.Polygon.fromExtent(extent);
circleFeature.setGeometry(geom);
this.features.push(circleFeature);
this.mapView.fit(geom, {minResolution: 0.05});
} else {
this.controller.fireEvent('mapstaterequest');
}
var raster = new ol.layer.Tile({
source: new ol.source.OSM()
});
var source = new ol.source.Vector({
wrapX: false,
features: this.features
});
var vector = new ol.layer.Vector({
source: source,
style: styleFunction
});
vector.setMap(map);
var draw = new ol.interaction.Draw({
source: source,
type: 'Circle',
geometryFunction: function(coordinates, geometry) {
var center = coordinates[0];
var last = coordinates[1];
var dx = center[0] - last[0];
var dy = center[1] - last[1];
var radius = dx;
var rotation = Math.PI - Math.atan2(dy, dx);
geometry = geometry || new ol.geom.Circle(center, radius);
geometry.setCenterAndRadius(center, radius);
geometry.set('rotation', rotation);
return geometry;
},
style: styleFunction,
handler: 'onSaveClick'
});
draw.on('drawstart', function () {
source.clear();
});
draw.on('drawend', function (evt) {
// move rotation from geometry to drawn feature
var rotation = evt.feature.getGeometry().get('rotation');
evt.feature.set('rotation', rotation);
var extent = evt.feature.getGeometry().getExtent();
var geom = ol.geom.Polygon.fromExtent(extent);
if(img.width!==img.height){
scaleY = img.height/img.width
geom.scale(1,scaleY);
}
evt.feature.setGeometry(geom);
});
this.map.addInteraction(draw);
var isCorner = true; // use opposite corner to scale/stretch, (false = use center);
var transform = new ol.interaction.Transform({
features: this.features,
translateFeature: false,
// flip wouldn't be compatible with rotation
noFlip: true,
rotate: true,
modifyCenter: function(){ return isCorner; }
});
var startangle = 0;
transform.on('select', function(e) {
draw.setActive(e.features.length == 0 );
});
transform.on('rotatestart', function(e) {
startangle = e.feature.get('rotation') || 0;
});
transform.on('rotating', function (e) {
// Set angle attribute to be used on style !
e.feature.set('rotation', startangle - e.angle);
});
this.map.addInteraction(transform);
This is the part of the code where I have the feeling I am doing something wrong, but I don't understand what:
if (rotation !== undefined) {
var extent = feature.getGeometry().getExtent();
var coordinates = feature.getGeometry().getCoordinates()[0];
var getBottomLeft = ol.extent.getBottomLeft(extent);
var getBottomRight = ol.extent.getBottomRight(extent);
var getTopLeft = ol.extent.getTopLeft(extent);
var getTopRight = ol.extent.getTopRight(extent);
var center = ol.extent.getCenter(extent);
var dx = center[0] - getBottomLeft[0];
var dy = 0;
var scaleX = Math.sqrt(dx * dx + dy * dy)/resAdjustX;
var dx = 0;
var dy = getTopRight[1] - center[1];
var scaleY = Math.sqrt(dx * dx + dy * dy)/resAdjustY;
var florplanStyle2 = getStyle(img, scaleX, scaleY);
florplanStyle2.setGeometry(new ol.geom.Point(center));
florplanStyle2.getImage().setRotation(rotation);
return debug ? styles.concat([florplanStyle2]) : florplanStyle2;
}
I found a solution for my 2 problems. I felt right about the part of the code I mentioned at the bottom of my question. I was calculating scaleX and scaleY in the wrong way.
This is the correct code that fixed my first problem
when I rotate the image, it does not keep the aspect ratio
if (rotation !== undefined) {
//style during rotation
var extent = feature.getGeometry().getExtent();
var coordinates = feature.getGeometry().getCoordinates()[0];
var getTopLeft = coordinates[0];
var getBottomLeft = coordinates[1];
var getBottomRight = coordinates[2];
var getTopRight = coordinates[3];
var center = ol.extent.getCenter(extent);
var top = new ol.geom.LineString([getTopLeft, getTopRight]).getClosestPoint(center);
var left = new ol.geom.LineString([getTopLeft, getBottomLeft]).getClosestPoint(center);
var dx = center[0] - left[0];
var dy = center[1] - left[1];
var scaleX = Math.sqrt(dx * dx + dy * dy) / resAdjustX;
var dx = top[0] - center[0];
var dy = top[1] - center[1];
var scaleY = Math.sqrt(dx * dx + dy * dy) / resAdjustY;
var floorplanStyle2 = getStyle(img, scaleX, scaleY);
floorplanStyle2.setGeometry(new ol.geom.Point(center));
floorplanStyle2.getImage().setRotation(rotation);
return debug ? styles.concat([floorplanStyle2]) : floorplanStyle2;
}
About my second problem
the red dotted line that appears along the image perimeter when the
image is selected does not follow the movement of the rotation
In order to have this feature, I had to add keepRectangle: true to my ol.interaction.Transform variable. This feature was not initially working on my app because I was using ol-ext v3.1.2, so I had to change it first to the ol-ext v3.2.24 in order to use it.
This is my complete code:
var iconURL = '/media/floorplans/' + uploadedImage;
var transparent = [255, 255, 255, 0];
var blue = [0, 153, 255, 1];
var width = 3;
img.crossOrigin = 'anonymous';
img.src = iconURL;
this.features = new ol.Collection();
var styleCache = {};
function getStyle(img, scaleX, scaleY) {
var view = map.getView();
var resolutionAtEquator = view.getResolution();
var y = Math.round(img.height * scaleY);
var x = Math.round(img.width * scaleX);
var key = img.src + ',' + x + ',' + y;
var style = styleCache[key];
if (!style) {
var canvas = document.createElement('canvas');
canvas.width = x;
canvas.height = y;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, x, y);
var resizeImageUrl = canvas.toDataURL();
canvas.remove();
var keys = Object.keys(styleCache);
if (keys.length >= 100) {
// delete an old entry to limit the cache size
delete styleCache[keys[0]];
}
var style = new ol.style.Style({
image: new ol.style.Icon({
src: resizeImageUrl,
opacity: imageOpacity,
})
});
styleCache[key] = style;
}
return style;
}
// Patch because ol-ext is changing extent of circle during rotation
ol.geom.Circle.prototype.rotate = function(angle, anchor) {
var point = new ol.geom.Point(this.getCenter());
point.rotate(angle, anchor);
this.setCenter(point.getCoordinates());
};
styles = [
new ol.style.Style({
stroke: new ol.style.Stroke({
color: transparent,
width: width
})
}),
//pointer used to draw the image
new ol.style.Style({
image: new ol.style.Circle({
radius: width * 2,
fill: new ol.style.Fill({
color: blue
}),
}),
zIndex: Infinity
})
];
var floorplanStyle = new ol.style.Style({
image: new ol.style.Icon({
src: img.src,
opacity: imageOpacity,
})
});
styleFunction = function(feature, resolution) {
var rayDrawValueX = img.width / 2;
var resAdjustX = rayDrawValueX * resolution;
var rayDrawValueY = img.height / 2;
var resAdjustY = rayDrawValueY * resolution;
var rotation = feature.get('rotation');
if (rotation !== undefined) {
//style during rotation
var extent = feature.getGeometry().getExtent();
var coordinates = feature.getGeometry().getCoordinates()[0];
var getTopLeft = coordinates[0];
var getBottomLeft = coordinates[1];
var getBottomRight = coordinates[2];
var getTopRight = coordinates[3];
var center = ol.extent.getCenter(extent);
var top = new ol.geom.LineString([getTopLeft, getTopRight]).getClosestPoint(center);
var left = new ol.geom.LineString([getTopLeft, getBottomLeft]).getClosestPoint(center);
var dx = center[0] - left[0];
var dy = center[1] - left[1];
var scaleX = Math.sqrt(dx * dx + dy * dy) / resAdjustX;
var dx = top[0] - center[0];
var dy = top[1] - center[1];
var scaleY = Math.sqrt(dx * dx + dy * dy) / resAdjustY;
var floorplanStyle2 = getStyle(img, scaleX, scaleY);
floorplanStyle2.setGeometry(new ol.geom.Point(center));
floorplanStyle2.getImage().setRotation(rotation);
return debug ? styles.concat([floorplanStyle2]) : floorplanStyle2;
} else if (feature.getGeometry().getCenter) {
//style meanwhile drawing
floorplanStyle.setGeometry(new ol.geom.Point(feature.getGeometry().getCenter()));
// get rotation from drawn feature or geometry
floorplanStyle.getImage().setRotation(feature.getGeometry().get('rotation'));
floorplanStyle.getImage().setScale(feature.getGeometry().getRadius() / resAdjustX);
return floorplanStyle;
} else {
return styles;
}
};
if (this.nord && this.sud && this.est && this.ovest && this.floorplanImage && this.opacity) {
//floorplan when floorplan position map is opened
var extent = ol.proj.transformExtent([this.ovest, this.sud, this.est, this.nord], 'EPSG:4326', 'EPSG:3857');
var center = ol.extent.getCenter(extent);
var size = ol.extent.getSize(extent);
var view = map.getView();
var resolutionAtEquator = view.getResolution();
var width = ol.extent.getWidth(extent);
var height = ol.extent.getHeight(extent);
var radius = width / 2;
var rotation = this.rotation;
var rotationInRadian = rotation * Math.PI / 180;
var circle = circle || new ol.geom.Circle(center, radius);
var circleFeature = new ol.Feature(circle);
circleFeature.set('rotation', rotationInRadian);
var geom = ol.geom.Polygon.fromExtent(extent);
geom.rotate(-rotationInRadian, ol.extent.getCenter(geom.getExtent()));
circleFeature.setGeometry(geom);
this.features.push(circleFeature);
this.mapView.fit(geom, {
minResolution: 0.05
});
} else {
this.controller.fireEvent('mapstaterequest');
}
var raster = new ol.layer.Tile({
source: new ol.source.OSM()
});
var source = new ol.source.Vector({
features: this.features
});
var vector = new ol.layer.Vector({
source: source,
style: styleFunction,
updateWhileAnimating: true,
});
vector.setMap(map);
var draw = new ol.interaction.Draw({
source: source,
type: 'Circle',
geometryFunction: function(coordinates, geometry) {
var center = coordinates[0];
var last = coordinates[1];
var dx = center[0] - last[0];
var dy = center[1] - last[1];
var radius = dx;
var rotation = Math.PI - Math.atan2(dy, dx);
geometry = geometry || new ol.geom.Circle(center, radius);
geometry.setCenterAndRadius(center, radius);
geometry.set('rotation', rotation);
return geometry;
},
style: styleFunction,
});
draw.on('drawstart', function() {
source.clear();
});
draw.on('drawend', function(evt) {
var rotation = evt.feature.getGeometry().get('rotation');
evt.feature.set('rotation', rotation);
var extent = evt.feature.getGeometry().getExtent();
var geom = ol.geom.Polygon.fromExtent(extent);
if (img.width !== img.height) {
scaleY = img.height / img.width;
geom.scale(1, scaleY);
}
geom.rotate(-rotation, ol.extent.getCenter(geom.getExtent()));
evt.feature.setGeometry(geom);
});
this.map.addInteraction(draw);
var isCorner = true; // use opposite corner to scale/stretch, (false = use center);
var transform = new ol.interaction.Transform({
features: this.features,
keepRectangle: true,
// flip wouldn't be compatible with rotation
noFlip: true,
translateFeature: false,
rotate: true,
handler: 'onSaveClick',
modifyCenter: function() {
return isCorner;
}
});
var startangle;
transform.on('select', function(e) {
if (e.feature != undefined) {
draw.setActive(e.feature.length == 0);
} else {
draw.setActive(true);
}
});
transform.on(['rotatestart', 'scalestart'], function(e) {
startangle = e.feature.get('rotation') || 0;
});
transform.on('rotating', function(e) {
// Set angle attribute to be used on style !
e.feature.set('rotation', startangle - e.angle);
});
this.map.addInteraction(transform);
Related
I am using a north,south, west, est coordinate system and I want to draw on my map an image in the right place and with a specific size.
I tried first to draw just one point, and it appeared on the right position (so the coordinate stuff seems work), but if I try to draw the image it doesn't appear on the map.I know that I can not directly give to the icon an height and a width, so I first created a canvas to use after. This is my code:
if ( this.nord && this.sud && this.est && this.ovest && this.opacityPercentage) {
var extent = ol.proj.transformExtent([this.nord, this.ovest, this.sud, this.est], 'EPSG:4326', 'EPSG:3857');
var height = ol.extent.getHeight(extent);
var width = ol.extent.getWidth(extent);
var center = ol.extent.getCenter(extent);
var sourceImage = new Image();
var canvas = document.createElement('canvas');
sourceImage.src = 'https://www.mikenunn.net/data/oak-tree-icon-png-17.png';
canvas.width = width;
canvas.height = height;
canvas.getContext("2d").drawImage(sourceImage, 0, 0, width, height);
var resizedImageURL = canvas.toDataURL();
var treePoint = new ol.geom.Point(center);
var featureTree = new ol.Feature(treePoint);
featureTree.setStyle(new ol.style.Style({
image: new ol.style.Icon({
src: resizedImageURL,
opacity: this.opacityPercentage,
})
}));
this.features.push(featureTree);
this.mapView.fit(treePoint, {minResolution: 0.05});
} else {
this.controller.fireEvent('mapstaterequest');
}
In case, there is another way I could use to achieve my purpose?
If you cannot preload sourceImage (e.g. you don't know the url until you need it) you need to process it in an onload function similar to xhr
var sourceImage = new Image();
sourceImage.onload = function() {
var canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
canvas.getContext("2d").drawImage(sourceImage, 0, 0, width, height);
var resizedImageURL = canvas.toDataURL();
var treePoint = new ol.geom.Point(center);
var featureTree = new ol.Feature(treePoint);
featureTree.setStyle(new ol.style.Style({
image: new ol.style.Icon({
src: resizedImageURL,
opacity: this.opacityPercentage,
})
}));
this.features.push(featureTree);
this.mapView.fit(treePoint, {minResolution: 0.05});
}
sourceImage.src = 'https://www.mikenunn.net/data/oak-tree-icon-png-17.png';
I solved in this way:
if ( this.nord && this.sud && this.est && this.ovest && this.image && this.opacity) {
var extent = ol.proj.transformExtent([this.ovest, this.sud, this.est, this.nord], 'EPSG:4326', 'EPSG:3857');
var center = ol.extent.getCenter(extent);
var size = ol.extent.getSize(extent)
var view = map.getView();
var resolutionAtEquator = view.getResolution();
var width = ol.extent.getWidth(extent);
var height = ol.extent.getHeight(extent);
var radius = width/2;
var rotation = 0;
var circle = circle || new ol.geom.Circle(center, radius);
var circleFeature = new ol.Feature(circle);
circleFeature.set('rotation', rotation);
var geom = ol.geom.Polygon.fromExtent(extent);
circleFeature.setGeometry(geom);
this.features.push(circleFeature);
this.mapView.fit(geom, {minResolution: 0.05});
} else {
this.controller.fireEvent('mapstaterequest');
}
and I edited the style function like that:
styleFunction = function(feature, resolution) {
var rayDrawValue = img.width/2;
var resAdjust = rayDrawValue * resolution;
var rotation = feature.get('rotation');
if (rotation !== undefined) {
var extent = feature.getGeometry().getExtent();
var coordinates = feature.getGeometry().getCoordinates()[0];
var getBottomLeft = ol.extent.getBottomLeft(extent);
var getBottomRight = ol.extent.getBottomRight(extent);
var getTopLeft = ol.extent.getTopLeft(extent);
var getTopRight = ol.extent.getTopRight(extent);
var center = ol.extent.getCenter(extent);
var dx = center[0] - getBottomLeft[0];
var dy = 0;
var scaleX = Math.sqrt(dx * dx + dy * dy)/resAdjust;
var dx = 0;
var dy = getTopRight[1] - center[1];
var scaleY = Math.sqrt(dx * dx + dy * dy)/resAdjust;
var treeStyle2 = getStyle(img, scaleX, scaleY);
treeStyle2.setGeometry(new ol.geom.Point(center));
treeStyle2.getImage().setRotation(rotation);
return debug ? styles.concat([treeStyle2]) : treeStyle2;
} else if (feature.getGeometry().getCenter) {
treeStyle.setGeometry(new ol.geom.Point(feature.getGeometry().getCenter()));
treeStyle.getImage().setRotation(feature.getGeometry().get('rotation'));
treeStyle.getImage().setScale(feature.getGeometry().getRadius()/resAdjust);
return treeStyle;
} else {
return styles;
}
};
I have a canvas where I use "fillText" with a string, saying for example "stackoverflow". Then I read the imagedata of the canvas in order to pick out each pixel of that text.
I want to pick the following from the pixel: x position, y position and its color. Then I would like to loop over that array with those pixels so I can draw back the text pixel by pixel so I have full control of each pixel, and can for example animate them.
However, I dont get it as smooth as I want. Look at my attach image, and you see the difference between the top text and then the text I've plotted out using fillRect for each pixel. Any help on how to make the new text look like the "fillText" text does?
Thanks
UPDATE: Added my code
var _particles = [];
var _canvas, _ctx, _width, _height;
(function(){
init();
})();
function init(){
setupParticles(getTextCanvasData());
}
function getTextCanvasData(){
// var w = 300, h = 150, ratio = 2;
_canvas = document.getElementById("textCanvas");
// _canvas.width = w * ratio;
// _canvas.height = h * ratio;
// _canvas.style.width = w + "px";
// _canvas.style.height = h + "px";
_ctx = _canvas.getContext("2d");
_ctx.fillStyle = "rgb(0, 154, 253)";
// _ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
var str = "stackoverflow";
_ctx.font = "32px EB Garamond";
_ctx.fillText(str,0,23);
_width = _canvas.width;
_height = _canvas.height;
var data32 = new Uint32Array(_ctx.getImageData(0, 0, _width, _height).data.buffer);
var positions = [];
for(i = 0; i < data32.length; i++) {
if (data32[i] & 0xffff0000) {
positions.push({
x: (i % _width),
y: ((i / _width)|0),
});
}
}
return positions;
}
function setupParticles(positions){
var i = positions.length;
var particles = [];
while(i--){
var p = new Particle();
p.init(positions[i]);
_particles.push(p);
drawParticle(p);
}
}
function drawParticle(particle){
var x = particle.x;
var y = particle.y;
_ctx.beginPath();
_ctx.fillRect(x, y, 1, 1);
_ctx.fillStyle = 'green';
}
function Particle(){
this.init = function(pos){
this.x = pos.x;
this.y = pos.y + 30;
this.x0 = this.x;
this.y0 = this.y;
this.xDelta = 0;
this.yDelta = 0;
}
}
Here is an update to your code that reuses the alpha component of each pixel. There will still be some detail lost because we do not keep the antialiasing of the pixels (which in effect alters the actual color printed), but for this example the alpha is enough.
var _particles = [];
var _canvas, _ctx, _width, _height;
(function(){
init();
})();
function init(){
setupParticles(getTextCanvasData());
}
function getTextCanvasData(){
// var w = 300, h = 150, ratio = 2;
_canvas = document.getElementById("textCanvas");
// _canvas.width = w * ratio;
// _canvas.height = h * ratio;
// _canvas.style.width = w + "px";
// _canvas.style.height = h + "px";
_ctx = _canvas.getContext("2d");
_ctx.imageSmoothingEnabled= false;
_ctx.fillStyle = "rgb(0, 154, 253)";
// _ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
var str = "stackoverflow";
_ctx.font = "32px EB Garamond";
_ctx.fillText(str,0,23);
_width = _canvas.width;
_height = _canvas.height;
var pixels = _ctx.getImageData(0, 0, _width, _height).data;
var data32 = new Uint32Array(pixels.buffer);
var positions = [];
for(i = 0; i < data32.length; i++) {
if (data32[i] & 0xffff0000) {
positions.push({
x: (i % _width),
y: ((i / _width)|0),
a: pixels[i*4 + 3] / 255
});
}
}
return positions;
}
function setupParticles(positions){
var i = positions.length;
var particles = [];
while(i--){
var p = new Particle();
p.init(positions[i]);
_particles.push(p);
drawParticle(p);
}
}
function drawParticle(particle){
var x = particle.x;
var y = particle.y;
_ctx.beginPath();
_ctx.fillStyle = `rgba(0,128,0,${particle.alpha})`;
_ctx.fillRect(x, y, 1, 1);
}
function Particle(){
this.init = function(pos){
this.x = pos.x;
this.y = pos.y + 30;
this.x0 = this.x;
this.y0 = this.y;
this.xDelta = 0;
this.yDelta = 0;
this.alpha = pos.a;
}
}
<canvas id="textCanvas"></canvas>
1.I want to be able to animated shapes at the same time using canvas, but each to one side.
2.Then when the mouse was placed on each circle appears around it with a text.My canvas knowledge isn't amazing, Here is an image to display what i want.
anyone shed some light on how to do it? Here is a fiddle of what I've managed
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var canvas01 = document.getElementById("canvas01");
var ctx01 = canvas01.getContext("2d");
canvas.width = 600;
canvas.height = 600;
canvas01.width = 600;
canvas01.height = 600;
var centerX = canvas01.width / 2;
var centerY = canvas01.height / 2;
var cw = canvas.width;
var ch = canvas.height;
var nextTime = 0;
var duration = 2000;
var start = Date.now();
var end = start + duration;
var endingPct = 100;
var endingPct1 = 510;
var pct = 0;
var pct1 = 0;
var i = 0;
var increment = duration;
var angle = 0;
var background = new Image();
var img = new Image();
img.src = "http://uupload.ir/files/2fhw_adur-d-01.jpg";
//http://uupload.ir/files/2fhw_adur-d-01.jpg
background.src = "http://uupload.ir/files/9a2q_adur-d-00.jpg";
//http://uupload.ir/files/9a2q_adur-d-00.jpg
Math.inOutQuart = function(n) {
n *= 2;
if (n < 1)
return 0.5 * n * n * n * n;
return -0.5 * ((n -= 2) * n * n * n - 2);
};
background.onload = function() {
ctx.drawImage(background, 0, 0);
};
function animate() {
var now = Date.now();
var p = (now - start) / duration;
val = Math.inOutQuart(p);
pct = 101 * val;
draw(pct);
if (pct >= (endingPct )) {
start = Date.now();
return animate1();
}
if (pct < (endingPct )) {
requestAnimationFrame(animate);
}
}
function animate1() {
var now1 = Date.now();
var p1 = (now1 - start) / duration;
val = Math.inOutQuart(p1);
pct1 = centerY + 211 * val;
SmallCircle(pct1);
if (pct1 < (endingPct1 )) {
requestAnimationFrame(animate1);
}
}
function draw(pct) {
var endRadians = -Math.PI / 2 + Math.PI * 2 * pct / 100;
ctx.beginPath();
ctx.arc(canvas.width / 2, canvas.height / 2, 180, -Math.PI / 2, endRadians);
ctx.lineTo(canvas.width / 2, canvas.height / 2);
ctx.fillStyle = 'white';
ctx.fill();
ctx.save();
ctx.clip();
ctx.drawImage(img, 0, 0);
ctx.restore();
}
animate();
function SmallCircle(pctt) {
ctx01.clearRect(0, 0, canvas01.width, canvas01.height);
ctx01.beginPath();
ctx01.arc(centerX, pctt, 7, 0, 2 * Math.PI, false);
ctx01.closePath();
ctx01.fillStyle = 'green';
ctx01.fill();
}
You can use transformations to draw your small circles extending at a radius from the logo center.
Here is example code and a Demo:
The smallCircle function let you specify these settings:
X & Y of the logo center: cx,cy,
The current radius which the small circle is from the logo center: pctt,
The angle of the smallCircle vs the logo center: angle,
The text to draw: text,
The smallCircle fill color: circlecolor,
The arc-circle stroke color: arccolor (if you don't want the arc-circle to appear you can specify transparent as the arccolor),
The text color: textcolor (if you don't want the text to appear you can specify transparent as the textcolor),
var canvas=document.getElementById("canvas01");
var ctx01=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
var cx=canvas.width/2;
var cy=canvas.height/2;
var PI2=Math.PI*2;
var smallCount=8;
var pctt=0;
var chars=['A','B','C','D','E','F','G','H'];
var circleFill='green';
var arcStroke='lawngreen';
var textFill='white';
ctx01.textAlign='center';
ctx01.textBaseline='middle';
animate(performance.now());
function animate(time){
ctx01.clearRect(0, 0, canvas01.width, canvas01.height);
for(var i=0;i<smallCount;i++){
smallCircle(
cx,cy,pctt,PI2/smallCount*i,
chars[i],circleFill,'transparent','transparent');
}
pctt+=1;
if(pctt<100){
requestAnimationFrame(animate);
}else{
for(var i=0;i<smallCount;i++){
smallCircle(
cx,cy,pctt,PI2/smallCount*i,
chars[i],circleFill,arcStroke,textFill);
}
}
}
function hilightCircle(n){}
function smallCircle(cx,cy,pctt,angle,text,circlecolor,arccolor,textcolor){
// move to center canvas
ctx01.translate(cw/2,ch/2);
// rotate by the specified angle
ctx01.rotate(angle);
// move to the center of the circle
ctx01.translate(pctt,0);
// draw the filled small circle
ctx01.beginPath();
ctx01.arc(0,0,7,0,PI2);
ctx01.closePath();
ctx01.fillStyle = circlecolor;
ctx01.fill();
// stroke the outside circle
ctx01.beginPath();
ctx01.arc(0,0,7+5,0,PI2);
ctx01.closePath();
ctx01.strokeStyle=arccolor;
ctx01.stroke();
// unrotate so the text is upright
ctx01.rotate(-angle);
// draw the text
ctx01.fillStyle=textcolor;
ctx01.fillText(text,0,0);
// reset all transforms to default
ctx01.setTransform(1,0,0,1,0,0);
}
body{ background-color:gray; }
canvas{border:1px solid red; margin:0 auto; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>After animation, click the mouse.</h4>
<canvas id="canvas01" width=300 height=300></canvas>
I started my first Three.js project. A solar system which you can see here.
I have a function addCelestrialObject() where I create the planets and I want this function to automatically create the orbit circles what it does for the planets but I want it also for moons.
So every Planet (mesh) becomes a name and I want to access this object to get its center position so I can add a circle to this position if I have a moon.
My problem is that the function scene.getObjectByName(parent,true); always delivers an undefined. You can see the console.log(scene) on my example when you inspect the site.
function addCelestrialObject(name, type, parent, surface, bump, specular,
positionX, positionY, positionZ, size, clouds, drawcircle
) {
var loader = new THREE.TextureLoader();
var group = new THREE.Group();
loader.load(surface, function (texture) {
var geometry = new THREE.SphereGeometry(size, 32, 32);
if (type == "sun") {
var material = new THREE.MeshBasicMaterial({ map: texture });
material.shading = true;
} else {
var material = new THREE.MeshPhongMaterial({ map: texture, overdraw: 0.5 });
material.shading = true;
if (bump) {
material.bumpMap = THREE.ImageUtils.loadTexture(bump);
material.bumpScale = 0.5;
}
if (specular) {
material.specularMap = THREE.ImageUtils.loadTexture(specular);
material.specular = new THREE.Color(0x222222);
}
}
var mesh = new THREE.Mesh(geometry, material);
mesh.name = name;
mesh.position.x = positionX;
mesh.position.y = positionY;
mesh.position.z = positionZ;
objectControls.add(mesh);
mesh.select = function () {
var position = { x: controls.target.x, y: controls.target.y, z: controls.target.z };
var target = { x: this.position.x, y: this.position.y, z: this.position.z };
var tween = new TWEEN.Tween(position).to(target, 500);
tween.easing(TWEEN.Easing.Exponential.InOut)
tween.onUpdate(function () {
controls.target.x = position.x;
controls.target.y = position.y;
controls.target.z = position.z;
controls.dollyIn(2);
});
tween.start();
controls.minDistance = size * 5;
}
onRenderFcts.push(function (delta, now) {
mesh.rotateY(1 / 32 * delta)
});
group.add(mesh);
});
if (clouds == true) {
var canvasResult = document.createElement('canvas')
canvasResult.width = 1024
canvasResult.height = 512
var contextResult = canvasResult.getContext('2d')
// load earthcloudmap
var imageMap = new Image();
imageMap.addEventListener("load", function () {
// create dataMap ImageData for earthcloudmap
var canvasMap = document.createElement('canvas')
canvasMap.width = imageMap.width
canvasMap.height = imageMap.height
var contextMap = canvasMap.getContext('2d')
contextMap.drawImage(imageMap, 0, 0)
var dataMap = contextMap.getImageData(0, 0, canvasMap.width, canvasMap.height)
// load earthcloudmaptrans
var imageTrans = new Image();
imageTrans.addEventListener("load", function () {
// create dataTrans ImageData for earthcloudmaptrans
var canvasTrans = document.createElement('canvas')
canvasTrans.width = imageTrans.width
canvasTrans.height = imageTrans.height
var contextTrans = canvasTrans.getContext('2d')
contextTrans.drawImage(imageTrans, 0, 0)
var dataTrans = contextTrans.getImageData(0, 0, canvasTrans.width, canvasTrans.height)
// merge dataMap + dataTrans into dataResult
var dataResult = contextMap.createImageData(canvasMap.width, canvasMap.height)
for (var y = 0, offset = 0; y < imageMap.height; y++) {
for (var x = 0; x < imageMap.width; x++, offset += 4) {
dataResult.data[offset + 0] = dataMap.data[offset + 0]
dataResult.data[offset + 1] = dataMap.data[offset + 1]
dataResult.data[offset + 2] = dataMap.data[offset + 2]
dataResult.data[offset + 3] = 255 - dataTrans.data[offset + 0]
}
}
// update texture with result
contextResult.putImageData(dataResult, 0, 0)
material.map.needsUpdate = true;
})
imageTrans.src = 'textures/earthcloudmaptrans.jpg';
}, false);
imageMap.src = 'textures/earthcloudmap.jpg';
var geometry = new THREE.SphereGeometry(size + 0.5, 32, 32)
var material = new THREE.MeshPhongMaterial({
map: new THREE.Texture(canvasResult),
side: THREE.DoubleSide,
transparent: true,
opacity: 1,
shading: true,
})
var cloudMesh = new THREE.Mesh(geometry, material);
cloudMesh.position.x = positionX;
cloudMesh.position.y = positionY;
cloudMesh.position.z = positionZ;
group.add(cloudMesh);
onRenderFcts.push(function (delta, now) {
cloudMesh.rotateY(1 / 16 * delta)
});
}
if (drawcircle == true) {
//circle
var radius = Math.abs(distance(0, positionX, 0, positionZ));
segments = 64;
materialLine = new THREE.LineBasicMaterial({ color: 0x00a8ff });
geometry = new THREE.CircleGeometry(radius, segments);
// Remove center vertex
geometry.vertices.shift();
circle = new THREE.Line(geometry, materialLine);
circle.rotation.x = 1.571;
if (parent) {
var object = scene.getObjectByName(parent, true);
//circle.position.x=object.position.x;
//circle.position.y=object.position.y;
//circle.position.z=object.position.z;
}
group.add(circle);
}
scene.add(group);
}
I want to calculate the radius of an inverted circle.
I managed to implement everything but, after hours of struggle, I could not find a formula to calculate the correct inverted radius.
More info about circle inversion:
http://en.wikipedia.org/wiki/Inversive_geometry
https://www.youtube.com/watch?v=sG_6nlMZ8f4
My code so far: http://codepen.io/rafaelcastrocouto/pen/Mwjdga
It seems to be working but you can easily tell it's totally wrong.
var c = $('#c'),
b = $('body'),
canvas = c[0],
ctx = canvas.getContext('2d'),
pi = Math.PI,
r = 100,
mr = 30,
width, height, hw, hh;
var setup = function() {
width = b.width();
height = b.height();
hw = width/2;
hh = height/2;
canvas.width = width;
canvas.height = height;
mid();
};
var mid = function() {
circle(hw,hh,0.25);
circle(hw,hh,r);
}
var circle = function(x,y,r) {
ctx.beginPath();
ctx.arc(x,y,r,0,pi*2);
ctx.stroke();
ctx.closePath();
};
var move = function(evt) {
var x = evt.clientX,
y = evt.clientY;
ctx.clearRect(0,0,width,height);
mid();
circle(x,y,mr);
var dx = x-hw,
dy = y-hh,
d = dist(dx,dy),
nd = r*r/d,
nx = dx*nd/d,
ny = dy*nd/d,
nr = mr*mr*pi/d; // whats the correct formula?
console.log(nr);
circle(nx+hw, ny+hh, nr);
};
var dist = function(x,y) {
return Math.pow(x*x + y*y, 1/2);
};
$(setup);
$(window).resize(setup);
$(window).mousemove(move);
Need help from the math experts!
As you said, inverting the centre of a circle doesn't give you the centre of the other one. Likewise if we invert two oposite points of one circle, it doesn't mean they'll be opposing points on the inverted circle.
Since three points describe a unique circle we can use these to find the equation for the inverse circle. That gives us the centre of the inverse circle. We can then find the distance from the centre to one of the inverted points, that's the radius.
The following c++ code gives the centre. (I don't know javascript). The function v.norm2() gives the squared norm of the vector v.
Vector2D getcircle(Vector2D p1, Vector2D p2, Vector2D p3){
Vector2D result;
long double div = 2*(p1.x*(p2.y-p3.y)-p1.y*(p2.x-p3.x)+p2.x*p3.y-p3.x*p2.y);
result.x = (p1.norm2()*(p2.y-p3.y)+p2.norm2()*(p3.y-p1.y)+p3.norm2()*(p1.y-p2.y))/div;
result.y = (p1.norm2()*(p3.x-p2.x)+p2.norm2()*(p1.x-p3.x)+p3.norm2()*(p2.x-p1.x))/div;
return result;
}
So if you have a circle c of radius r, and you are inverting respect to another circle C and radius R, you could do something like
float getRadius(Vector2D C, float R, Vector2D c, float r){
Vector2D p1 = Vector2D(c.x + r, c.y).invert(C, R);
Vector2D p2 = Vector2D(c.x - r, c.y).invert(C, R);
Vector2D p3 = Vector2D(c.x, c.y + r).invert(C, R);
return (getcircle(p1, p2, p3) - p1).norm();
}
Here is an image of a circle with centre (130, -130) and radius 128, and it's inversion respect to another circle (not shown) of centre (0, 0) and radius 40.
The red points on the big circle are polar opposites. They are then inverted and shown on the little circle where you can see they are not polar opposites.
My error was that I was assuming that the center of the inverted circle also respected OP x OP' = r2, but as the image below shows, it clearly does not. The solution was to calculate two points on the circle and reflect each one, then use half the distance between this points to find the radius.
So this is the correct code:
var c = $('#c'),
b = $('body'),
canvas = c[0],
ctx = canvas.getContext('2d'),
fixedRadius = 100,
saved = [],
width, height,
half = {
w: 0,
h: 0
},
mouse = {
r: 31,
x: 0,
y: 0
},
reflect = {
x: 0,
y: 0,
r: 0
};
var setup = function() {
width = b.width();
height = b.height();
half.w = width/2;
half.h = height/2;
canvas.width = width;
canvas.height = height;
move();
};
var mid = function() {
circle(half.w,half.h,1.5);
circle(half.w,half.h,fixedRadius);
};
var circle = function(x,y,r,c) {
ctx.strokeStyle = c || 'black';
ctx.beginPath();
ctx.arc(x,y,r,0,Math.PI*2);
ctx.stroke();
ctx.closePath();
};
var line = function(x1,y1,x2,y2,c) {
ctx.strokeStyle = c || 'black';
ctx.beginPath();
ctx.moveTo(x1,y1);
ctx.lineTo(x2,y2);
ctx.stroke();
ctx.closePath();
};
var axis = function () {
line(half.w,0,half.w,height,'#ccc');
line(0,half.h,width,half.h,'#ccc');
};
var move = function(evt) {
mouse.x = evt ? evt.clientX : half.w;
mouse.y = evt ? evt.clientY : half.h + 11;
ctx.clearRect(0,0,width,height);
axis();
mid();
circle(mouse.x,mouse.y,mouse.r);
circle(mouse.x,mouse.y,1,'grey');
var di = {
x: mouse.x - half.w, // orange
y: mouse.y - half.h // green
}
di.v = dist(di.x,di.y);
var a = Math.atan2(di.y,di.x); // angle
line(mouse.x - di.x,mouse.y,mouse.x,mouse.y,'orange');
line(mouse.x,mouse.y - di.y,mouse.x,mouse.y,'green');
var p1 = {
v: di.v + mouse.r // cyan
};
p1.x = half.w + (Math.cos(a) * p1.v);
p1.y = half.h + (Math.sin(a) * p1.v);
circle(p1.x,p1.y,1.5,'cyan');
var p2 = {
v: di.v - mouse.r // red
};
p2.x = half.w+Math.cos(a)*p2.v;
p2.y = half.h+Math.sin(a)*p2.v;
circle(p2.x,p2.y,1.5,'red');
var rp1 = {
v: Math.pow(fixedRadius,2) / p1.v // cyan
};
rp1.x = Math.cos(a) * rp1.v,
rp1.y = Math.sin(a) * rp1.v;
circle(rp1.x+half.w,rp1.y+half.h,1.5,'cyan');
var rp2 = {
v: Math.pow(fixedRadius,2) / p2.v // red
};
rp2.x = Math.cos(a) * rp2.v,
rp2.y = Math.sin(a) * rp2.v;
circle(rp2.x+half.w,rp2.y+half.h,1.5,'red');
var newDi = {
v: dist(rp1.x - rp2.x, rp1.y - rp2.y)
};
newDi.r = newDi.v/2,
newDi.x = rp1.x + (Math.cos(a) * newDi.r), // yellow
newDi.y = rp1.y + (Math.sin(a) * newDi.r); // purple
if (p2.v < 0) {
newDi.x = rp1.x - (Math.cos(a) * newDi.r),
newDi.y = rp1.y - (Math.sin(a) * newDi.r);
}
reflect.x = half.w+newDi.x;
reflect.y = half.h+newDi.y
// reflected lines
if (di.v<fixedRadius) line(rp1.x+half.w,rp1.y+half.h,p1.x,p1.y,'cyan');
else line(rp2.x+half.w,rp2.y+half.h,p2.x,p2.y,'red');
line(p1.x,p1.y,half.w,half.h,'#ccc');
line(rp2.x+half.w,rp2.y+half.h,half.w,half.h,'#ccc');
line(reflect.x-newDi.x,reflect.y,reflect.x,reflect.y,'yellow');
line(reflect.x,reflect.y-newDi.y,reflect.x,reflect.y,'purple');
// reflected circle
circle(reflect.x, reflect.y, newDi.r);
circle(reflect.x,reflect.y,1,'grey');
circles(); // saved circles
reflect.r = newDi.r;
};
var dist = function(x,y) {
return Math.pow(x*x + y*y, 1/2);
};
var scroll = function(evt) {
if(evt.originalEvent.wheelDelta > 0) {
mouse.r++;
} else {
mouse.r--;
}
move(evt);
};
var click = function(evt) {
saved.push(['c',mouse.x,mouse.y,mouse.r]);
saved.push(['c',reflect.x,reflect.y,reflect.r]);
saved.push(['l',mouse.x,mouse.y,reflect.x,reflect.y]);
};
var circles = function() {
for(var i = 0; i < saved.length; i++) {
var s = saved[i];
if (s[0]=='c') circle(s[1],s[2],s[3],'grey');
if (s[0]=='l') line(s[1],s[2],s[3],s[4],'grey');
}
};
$(setup);
$(window)
.on('resize', setup)
.on('mousemove', move)
.on('mousewheel', scroll)
.on('click', click);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="c"></canvas>