player.bind('Move', function(from) {
if(this.hit('Tree')){
player.x = from._x
player.y = from._y
// Here player.x and player.y have right value but not renderered here.
}
}
)
I debugged that program enters here and from._x and from._y have good values.
But player remains "behind" the Tree.
I'm don't have a lot of CraftyJS experience yet, so don't take this as the best way to do it, but...
Some references I found in the API documentation:
Apply the Collision component to the player and tree
use onHit() for the player. When fired, this is passed a collection of hitDatas. Found definition of hitData in the description for hit()
Here's an example -- when the entity tagged with "Player" component collides with any component tagged "Tree", will back the player object's x position off by the amount it is overlapping with the tree.
<html>
<body>
<div id="game"></div>
<script type="text/javascript" src="https://rawgithub.com/craftyjs/Crafty/release/dist/crafty-min.js"></script>
<script>
Crafty.init(640,100, document.getElementById('game'));
Crafty.e('2D, Canvas, Color, Twoway, Player, Collision')
.attr({x: 0, y: 50, w: 50, h: 50})
.color('#00F')
.twoway(400)
.onHit('Tree', function(hitDatas) {
hitData = hitDatas[0];
this.x -= hitData.overlap * hitData.nx;
});
Crafty.e('2D, Canvas, Color, Tree, Collision')
.attr({x: 300, y:0, w: 40, h: 100})
.color('#873');
</script>
</body>
</html>
Related
Can't wrap my head around this and get it to work. Trying to transpose this sample on Konva, but can not get it to work with an image inside a layer, inside the stage.
The sample I am trying to reproduce is the "Zooming stage relative to pointer position" sample.
https://konvajs.org/docs/sandbox/Zooming_Relative_To_Pointer.html
Any help would generate kudos.
The trick with zooming from an arbitrary point such as the mouse position is to understand that you must relocate the stage to keep the point under the mouse consistent. I provide the working vanilla JS snippet below to illustrate the point. The guts are based on the original demo at the Konva docs site, just with more comments in the code. Translating into React should not be difficult once the basis of the technique is explained.
The gray area is the canvas, and the pink rect is the stage. Note that we start with the stage positioned into the canvas just to show that the stage can be moved like any other object.
The stage has a dot-pattern to help show the effects in the demo. The solution works for any layout of layers, shapes or images (as per OP question). Note that the shapes themselves do not need to be handled individually - the scaling is applied to the stage.
Move your mouse anywhere on the stage and scroll to see the zoom effect, noting how the top-left of the pink stage rectangle moves with the zoom. Now un-tick the checkbox to turn off the stage position adjustment - note now that the zoom is no longer respecting the mouse position because the stage position is not being moved.
Thus we have illustrated that it is necessary to move the stage sympathetically during the zoom but how do we know the amount to move by?
The tip to know is that you need to get the absolute distance of the mouse position over the stage before the new scale is applied, then apply the new scale to the stage, then use the computed distance * new scale to compute the new stage position. See the stage.on('wheel') event in the snippet for working code.
See also the codepen here.
// Making the stage
let stage = new Konva.Stage({
container: "container1",
width: $('#container1').width(),
height: $('#container1').height(),
draggable: true,
x: 40,
y: 60
});
// Make the layer
let layer = new Konva.Layer();
// make a rect to fill stage to show where it is.
let rect = new Konva.Rect({
x:0,
y: 0,
width: $('#container1').width(),
height: $('#container1').height(),
fill: 'magenta',
opacity: 0.3
})
layer.add(rect);
let grid = {w: $('#container1').width(), h: $('#container1').height()},
gridStep = 40,
mouseCircleRadius = 80,
circles = [],
dotRadius = 10;
// Make the grid of circles
for (let i = gridStep; i < grid.w; i = i + gridStep ){
for (let j = gridStep; j < grid.h; j = j + gridStep ){
let c = new Konva.Circle({ x: i, y: j, radius: dotRadius, fill: 'cyan'})
circles.push(c);
layer.add(c)
}
}
// Add layer to stage
stage.add(layer)
stage.on('mousemove', function (e) {
var pointer = stage.getPointerPosition();
// show the pointer and stage positions for illustration
$('#trace').html('Pointer.x = ' + pointer.x + ' stage.x() = ' + stage.x())
});
// this is the scale factor to be applied at each step.
var scaleBy = 1.01;
// this is the event that fires when the mouse wheel spins.
stage.on('wheel', (e) => {
e.evt.preventDefault();
// note the old scale to be used when deciding the current stage position
var oldScale = stage.scaleX();
// note the mouse pointer position relative to the stage at current scale
var pointer = stage.getPointerPosition();
// calculate the mouse pointer position at scale = 1.
// pointer.x is relative to the canvas - not affected by stage scale,
// stage.x() is the poistion of the stage on the canvas.
// This gives the distance from the pointer to the
var mousePointTo = {
x: (pointer.x - stage.x()) / oldScale,
y: (pointer.y - stage.y()) / oldScale,
};
// Calculate the new scale - slightly different calc for zoom in versus zoom out, as you would expect.
var newScale = (e.evt.deltaY > 0 ? oldScale * scaleBy : oldScale / scaleBy);
// Apply the new scale to the stage. Note that, assuming a zoom-in op, at this point in time the shapes on the stage would
// seem to have jumped down + right. We will shortly 'move' the stage left+up to counter this effect.
stage.scale({ x: newScale, y: newScale });
// To do that we have to calculate what the position of the stage must be relative to the mouse pointer position at the new scale.
// Note - remove the 'if' for your live code, the checkbox is for illustration only.
if ($('#fixstagepos').prop('checked')){
var newPos = {
x: pointer.x - mousePointTo.x * newScale,
y: pointer.y - mousePointTo.y * newScale,
};
// and apply the new position to the stage. Again in the case of a zoom-in op this has the effect of moving the stage left + up, countering the apparent movement
// caused by the change in scale.
stage.position(newPos);
}
});
.container {
width: 800px;
height: 400px;
background-color: silver;
margin: 5px;
}
#trace {
max-height: 200px;
overflow-y: scroll;
font-family: 'Courier'
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://unpkg.com/konva#8/konva.min.js"></script>
<p id="info">Grey is the canvas and the pink rect shows the stage position. The stage is intitially positioned at {x: 40, y: 60} and can be dragged.</p>
<p>Move mouse over stage and scroll to zoom in & out. Use checkbox to see what happens without stage pos correction. </p>
<p>Apply correction to stage position <input id='fixstagepos' type='checkbox' checked='1' /></p>
<div id='container1' class='container'>
</div>
<p id='trace'></p>
I'm developing a basic window frame configurator. I splitted glasses in function below. I want to change color when i clicked and get the position of clicked glass to draw openin direction lines.
I tried to understand and implement Lavrton's method(https://codesandbox.io/s/0xj7zml2zl?file=/src/index.js) but i couldn't succeed.
function glassDraw(piece, frameWidth, frameHeight) {
var glassGroup = new Konva.Group();
for (i = 0; i <piece; i++) {
var glass = new Konva.Rect({
name: 'Rect',
x: padding + (frameWidth / piece) * i,
y: padding,
width: frameWidth / piece,
height: frameHeight - padding * 2,
fill: 'lightblue',
id: 'glass'+i,
});
glassGroup.add(glass);
}
glassGroup.find("Rect").on('click', function (e) {
// get id of the cube i drag
var clickedId = e.target.attrs.id;
$('#theId').html(clickedId);
})
return glassGroup;
}
When i use getelementbyid method with id of konva element("glass"+i), it returns null. I think i need to get as a konva element.
You have to create a click listener for all of your rectangles.
for (let rect of glassGroup.children) {
rect.on('click', () => {
console.log(rect.x(), rect.y()); // current position of the object
console.log(rect.id()); // log id of the object
rect.fill('green'); // set color to green
layer.batchDraw(); // update layer (batchDraw just for better performance .draw() would work to)
})
}
Make sure you always update the stage by either call stage.draw() or layer.draw() (or batchDraw() for better performance) after changing something, otherwise your stage will not show anything of what you do.
If something with this code don't work feel free to ask.
Have a nice day.
Here's the scenario:
I have a 360ยบ panorama image of the world map, running on A-Frame
I would like each continent to be clickable, redirecting to a related Wikipedia page.
What I tried:
I tried using the <map> tag as I would in a normal project, but
with no results.
I also read the documentation, and found nothing
relevant about this.
My question:
Is my goal even possible? If so, how?
My code:
Note: So far, only the African continent has coordinates.
JSFiddle
<script src="https://aframe.io/releases/1.0.4/aframe.min.js"></script>
<a-scene>
<a-entity camera look-controls="reverseMouseDrag: true"></a-entity>
<a-sky id="image-360" radius="10" src="https://upload.wikimedia.org/wikipedia/commons/thumb/8/83/Equirectangular_projection_SW.jpg/1920px-Equirectangular_projection_SW.jpg" usemap="#image_map"></a-sky>
<map name="image_map">
<area alt="Africa" title="Africa" href="https://en.wikipedia.org/wiki/Africa" coords="920,296 871,351 873,424 916,461 979,449 1014,463 1005,490 1032,540 1020,579 1056,663 1081,667 1114,655 1146,600 1175,561 1165,504 1220,451 1228,422 1191,426 1124,314 1063,308 1054,324 1014,302 1016,284 963,290" shape="polygon">
</map>
</a-scene>
The <map> tag places the areas above an image. It won't project the areas onto a three dimentional object just like that.
I'd recommend an approach where you re-use the areas defined in the <map> tag within an a-frame custom component.
tldr:
I've made a component that seems to do the job, and is fairly simple in use:
<a-image material="src: #texture" asourcemap="#some-map"></a-image>
<!-- somewhere else -->
<map id="some-map">
<area shape="rect" alt="rectangle" coords="0,0, 50,50" href="rect.html">
<area shape="polygon" alt="poly" coords="0,0, 40,50, 25,0">
</map>
It should work well with the href attribute, but also the <a-image> will emit a signal with the clicked area name.
You can see it working with 3D planes and cylinders here.
end of tldr
0. Gathering <map> data
Simple parsing. Grab the <map> element, iterate through all children, and collect their data with getAttribute():
var map = document.querySelector(selector);
for (let area of map.children) {
// area.getAttribute("href") - href attribute
// area.getAttribute("alt") - alt name
// area.getAttribute("coords") - coordinates array.
}
Store them for later use. The coordinates are comma separated strings, so you may need to parseInt() them, manage the order (i.e. [[x1,x1], [x2,y2], [x3, y3]])
1. Make the a-frame entity interactable
React on clicks, and what's more important - check where the click occurred:
this.el.addEventListener("click", evt => {
var UVPoint = evt.detail.intersection.uv
})
UV mapping will help us determine which point on the texture was clicked. The UV ranges from <0, 1>, so we will need to re-scale the UVPoint:
// may need waiting for "model-loaded"
let mesh = this.el.getObject3D("mesh")
// this may not be available immidiately
let image = mesh.material.map.image
let x_on_image = UVPoint * image.width
// the y axis goes from <1, 0>
let y_on_image = image.height - UVPoint * image.heigth
So hey, we got the area coordinates and the point coordinates!
There is only one thing left:
2. Determining if an area was clicked
No need to re-invent the wheel here. This SO question on checking if a point is inside a polygon has a simple inside(point, polygon) function. Actually we have everything we need, so the last thing we do is:
iterate through the polygons
check if the clicked point is inside any of the polygons
if positive - do your thing
like this:
var point = [x_on_texture, y_on_texture]
for (var i = 0; i < polygons.length; i++) {
// polygons need to be [[x1, y1], [x2, y2],...[xn, yn]] here
if (inside(point, polygons[i]) {
console.log("polygon", i, "clicked!")
}
}
If you skipped the tldr section - the above steps are combined in this component and used in this example
3. Old, hacky try
Another way of doing this could be:
receive a click on the a-frame entity
grab the clicked coordinates like in 1
hide the scene
check out which <area> is at the coordinates with document.elementFromPoint(x, y);.
show the scene
create a mouse event with document.createEvent("MouseEvent");
dispatch it on the <area> element.
The hide / show trick works really good even on my mobile phone. I was really surprised that the scene wasn't flickering, freezing, even slowing down.
But document.elementFromPoint(x, y); didn't work with firefox, and probably any attempt to make it work would be way more time consuming than the 0-2 steps. Also I believe the trappings would become bigger and case-dependant.
Anyway, here's the old-answer component:
/* SETUP
<a-scene>
<a-image press-map>
</a-scene>
<image id="image" sourcemap="map">
<map name="map">
<area ...>
</map>
*/
AFRAME.registerComponent("press-map", {
init: function() {
// the underlying image
this.img = document.querySelector("#image")
// react on clicks
this.el.addEventListener("click", evt => {
// get the point on the UV
let uvPoint = evt.detail.intersection.uv
// the y is inverted
let pointOnImage = {
x: uvPoint.x * this.img.width,
y: this.img.height - uvPoint.y * this.img.height
}
// the ugly show-hide bits
this.el.sceneEl.style.display = "none";
this.img.style.display = "block";
// !! grab the <area> at the (x,y) position
var el = document.elementFromPoint(pointOnImage.x, pointOnImage.y);
this.el.sceneEl.style.display="block"
this.img.style.display="none"
// create and dispatch the event
var ev = document.createEvent("MouseEvent");
ev.initMouseEvent(
"click",
true /* bubble */, false /* cancelable */,
window, null,
x, y, 0, 0, /* coordinates */
false, false, false, false, /* modifier keys */
0 /*left*/, null
);
el.dispatchEvent(ev);
}
}
})
I created a standalone map to test this out for myself. I took a heap snapshot, using Chrome Developer Tools, of the page upon loading it and found it was using 882MB of memory. I'm looking to plot about an hours worth of lightning data and I would like for the user to be able to interact with it so Openlayers makes sense here. However its taking up a ton of memory and need a solution that is much more memory efficient.
Below is the code I used to do this:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
<script src="https://cdnjs.cloudflare.com/ajax/libs/ol3/3.6.0/ol.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/ol3/3.6.0/ol.css">
</head>
<body>
<div class="container">
<div class="row">
<div class="md-12">
<div id="map" class="map"></div>
</div>
</div>
<div id="span12">
</div>
</div>
<script>
var iconInfo = [{
points: 4,
radius: 3,
radius2: 0,
angle: 0
}, {
points: 4,
radius: 3,
radius2: 0,
angle: 0
}, {
points: 4,
radius: 3,
radius2: 0,
angle: 0
}, {
points: 4,
radius: 3,
radius2: 0,
angle: 0
}];
var i;
var iconCount = iconInfo.length;
var icons = new Array(iconCount);
for (i = 0; i < iconCount; ++i) {
var info = iconInfo[i];
icons[i] = new ol.style.RegularShape({
points: info.points,
radius: info.radius,
radius2: info.radius2,
angle: info.angle,
fill: new ol.style.Fill({color: 'rgba(0, 0, 0, 0.9)'}),
stroke: new ol.style.Stroke({width: 2, color: 'rgba(0, 0, 0, 0.9)'}),
});
}
var featureCount = 350000;
var features = new Array(featureCount);
var feature, geometry;
var e = 25000000;
for (i = 0; i < featureCount; ++i) {
geometry = new ol.geom.Point(
[2 * e * Math.random() - e, 2 * e * Math.random() - e]);
feature = new ol.Feature(geometry);
feature.setStyle(
new ol.style.Style({
image: icons[i % (iconCount - 1)]
})
);
features[i] = feature;
}
var vectorSource = new ol.source.Vector({
features: features
});
var vector = new ol.layer.Vector({
source: vectorSource
});
var map = new ol.Map({
layers: [vector],
target: document.getElementById('map'),
view: new ol.View({
center: [0, 0],
zoom: 5
})
});
var overlayFeatures = [];
for (i = 0; i < featureCount; i += 30) {
var clone = features[i].clone();
clone.setStyle(null);
overlayFeatures.push(clone);
}
var featureOverlay = new ol.layer.Vector({
map: map,
source: new ol.source.Vector({
features: overlayFeatures
}),
style: new ol.style.Style({
image: icons[iconCount - 1]
})
});
map.on('click', function(evt) {
var info = document.getElementById('info');
info.innerHTML =
'Hold on a second, while I catch those butterflies for you ...';
window.setTimeout(function() {
var features = [];
map.forEachFeatureAtPixel(evt.pixel, function(feature, layer) {
features.push(features);
return false;
});
if (features.length === 1) {
info.innerHTML = 'Got one butterfly';
} else if (features.length > 1) {
info.innerHTML = 'Got ' + features.length + ' butterflies';
} else {
info.innerHTML = 'Couldn\'t catch a single butterfly';
}
}, 1);
});
map.on('pointermove', function(evt) {
if (evt.dragging) {
return;
}
var pixel = map.getEventPixel(evt.originalEvent);
var hit = map.hasFeatureAtPixel(pixel);
map.getTarget().style.cursor = hit ? 'pointer' : '';
});
</script>
</body>
</html>
Any suggestions on how I could achieve better memory efficiency?
Short answer
OpenLayers 3 uses about 2 kB per Point feature (see below), so while there are some optimizations possible you have to keep the number of features down. 400 000 features will require about 800 MB of memory.
Load your features dynamically, or use MultiPoint geometries.
Move the style from the geometry to the layer.
Longer answer
Style
When i tested, removing the style from the feature and replacing it with a simple property reduced the memory footprint by 290 B per feature. See http://jsfiddle.net/vkm2rg46/3/:
var vector = new ol.layer.Vector({
source: vectorSource,
style: function (feature, resolution) {
var i = feature.getProperties().styleId;
return [new ol.style.Style({
image: icons[i]
})];
}
});
and to help the style function:
feature.set('styleId', i % (iconCount - 1));
Spatial index
You could set useSpatialIndex to false on the vector source. The source keep a spatial index to quickly retrieve features within a given extent, which seems to need about 200-250 bytes per feature. However, removing the index could have bad performance consequences with this amount of features.
Reduce feature count##
Your best bet is probably to load fewer features. There are several solutions to this.
Load on demand
It's most commonly solved by letting the server take care of the data, and dynamically load it when needed. You probably don't want to display 400 000 points at lower zoom levels, and the users wont pan everywhere.
This could be done by vector tiles or with a normal vector source using a bbox or tile.
It could also be done client side, by creating features/geometries from your own dataset in the vector source loader function.
Multipoints
A MultiPoint geometry with 10 or 100 points hardly take any more space than a single Point geometry. If you group you lightning strikes into MultiPoint geometries, memory could be a non-issue. You would however loose some semantics, and the possibility to attach metadata to each single point.
JsFiddle: http://jsfiddle.net/vkm2rg46/8/
Memory usage
I created http://jsfiddle.net/g7qduy1w/3/ to test the memory use of the geometry, features and source. You can take snapshot at the different stages (notably, the event listening data increases when adding a geometry to a feature, and a feature to a source).
With a simple point geometry added to a feature without extra properties, and added to a source, the memory use per feature is:
288 B geometry event listener
424 B rest of geometry data
752 B feature event listeners
184 B rest of feature data
261 B source (share of total memory using 100 000 features)
Have a look at this example from camptocamp
https://www.camptocamp.com/en/actualite/drawing-large-amounts-of-points-with-openlayers-3-and-webgl
OpenLayers Symbols with WebGL:
http://openlayers.org/en/master/examples/symbol-atlas-webgl.html
It displays 100k points very efficiently!
I want to rotate this image in cocos2d Java-script HTML5.
Upper and lower circles are two different sprites.
I am using this code:
var RotationAmount=0;
Top=cc.Sprite.create("Assets/Top.png");
Top.setPosition(MidX,MidY+100);
Top.schedule(function(){
if(RotationAmount>360)
RotationAmount=0;
});
this.addChild(Top);
Here's how my sprites look:
The above this causes my sprite starts to rotate around itself. I want to make it rotate around a point, so where am I going wrong?
You can do next:
var Top = cc.Sprite.create("Assets/Top.png");
var rotateAction = cc.RotateBy.create(0, 1);
Top.attr({
x: MidX,
y: MidY,
anchorX: 0.5,
anchorY: 0.5
});
Top.schedule(function() {
Top.runAction(rotateAction);
});
this.addChild(Top);