I have my code in this JSFiddle. I can draw annotations with click-and-drag, using the plugins: user click and hold the mouse (drawing starts on mouse-down), start to drag around and drawing is completed when user releases the mouse (mouse-up event).
The issue here is: when we have a single-click in a certain point, an arrow symbol appears and this can not be selected nor draggable. In cases of single-click, will be ideal if mouse-up event to not complete the drawing and instead drawing to be completed on the next mouse-down event.
Here in this video we can see the behavior of how the single-click acts at the moment and also there I commented one condition to show how I expect this to behave. I am already successfully having the check if we have a single-click while drawing in the part with console.log("this is a single click");.
The two most important blocks of the code-snippet are given below, and what I don't understand is why this is not working as I described above, since I am already terminating the function in case single-click is detected (with return).
//on mouse-down event, if cursor is inside of the chart and if drawing tool is selected, set the
//flag "isAddingAnnotations" to true and store the coordinates from where the drawing started
navigation.eventsToUnbind.push(addEvent(chart.container, 'mousedown', function(e) {
if (!chart.cancelClick &&
chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop))
{
navigation.bindingsChartClick(chart, e);
if (!navigation.selectedButton) {
chart.isAddingAnnotations = false;
} else {
chart.isAddingAnnotations = true;
chart.addingAnnotationsX = e.clientX;
chart.addingAnnotationsY = e.clientY;
}
}
}));
//on mouse-up event, set the flag "isAddingAnnotations" to false (since drawing ended)
//additionally, if drawing was in progress and if ending coordinates are matching with
//starting coordinates, drawing should still be active
navigation.eventsToUnbind.push(addEvent(chart.container, 'mouseup', function(e) {
if (chart.isAddingAnnotations &&
chart.addingAnnotationsX == e.clientX &&
chart.addingAnnotationsY == e.clientY)
{
console.log("this is a single click");
return;
}
chart.pointer.normalize(e);
navigation.bindingsChartClick(chart, e);
chart.isAddingAnnotations = false;
}));
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 am looking for a way to clip a 3D object below a certain point in A-Frame with Ar.js. The clipping point would be 0,0,0 I suppose, the marker location. My idea is to have an object appear to come out of the marker from below, so below that point you wouldn't see it. Hopefully my diagram will explain what I mean.
I have tried using a C4D compositing tag but as expected that doesn't export as a gltf object.
There is a neat technique used to create an invisible cloak - disabling the colorWrite property of a material.
Lets say you want to hide your object within a box. You need to create a box, slightly bigger than your object, and set its material as described:
AFRAME.registerComponent('cloak', {
init: function() {
var geometry = new THREE.BoxGeometry( 1.1, 1.1, 1.1 );
var material = new THREE.MeshBasicMaterial( {colorWrite: false} );
var cube = new THREE.Mesh( geometry, material );
this.el.object3D.add( cube );
}
})
Then just make sure its rendered before the cloaked objects:
<a-marker>
<a-entity cloak></a-entity>
<a-box animation="property: position; to: 0 1.2 0; dur: 1500;
easing: linear; loop: true; dir: alternate"> </a-box>
</a-marker>
check it out in this glitch.
Is there an easy way to visualize a custom hit area shape?
As described here
https://konvajs.github.io/docs/events/Custom_Hit_Region.html
the hitFunc attribute can be set to a function that uses the supplied context to draw a custom hit area / region. Something like this:
var star = new Konva.Star({
...
hitFunc: function (context) {
context.beginPath()
context.arc(0, 0, this.getOuterRadius() + 10, 0, Math.PI * 2, true)
context.closePath()
context.fillStrokeShape(this)
}
})
For debugging purposes, I would like an easy way to toggle visual rendering of the shape (circle in this case), eg by filling it yellow.
Thanks :)
Currently, there is no public API for that. But you still can add hit canvas into the page somewhere and see how it looks:
const hitCanvas = layer.hitCanvas._canvas;
document.body.appendChild(hitCanvas);
// disable absolute position:
hitCanvas.style.position = '';
http://jsbin.com/mofocagupi/1/edit?js,output
Or you can add hit canvas on top of the stage and apply an opacity to make scene canvases visible:
const hitCanvas = layer.hitCanvas._canvas;
stage.getContainer().appendChild(hitCanvas);
hitCanvas.style.opacity = 0.5;
hitCanvas.style.pointerEvents = 'none';
http://jsbin.com/gelayolila/1/edit?js,output
I need to make a MovieClip rotate around it's center point to the left or right depending on mouse drag. I got some basic semblance of what i want going, but it's pretty hackish. Normally i calculate the angle and such but i only really need to use the distance the mouse travels and apply it to the movieclip and for flavor add some nice easing when you let go.
public function Main()
{
var wereld:MainScreen = new MainScreen();
addChild( wereld );
wereld.x = -510;
planet = wereld["planet"];
wereld.addEventListener( MouseEvent.MOUSE_DOWN, startRotatingWorld );
}
private function startRotatingWorld( e:MouseEvent ):void
{
m_mouseStartPos = stage.mouseX;
stage.addEventListener( MouseEvent.MOUSE_UP, stopRotatingWorld );
stage.addEventListener( Event.MOUSE_LEAVE, stopRotatingWorld);
}
private function applyRotationToWorld( e:MouseEvent ):void
{
//Calculate the rotation
var distance:Number = (m_mouseStartPos - stage.mouseX) / 10000;
//apply rotation
planet.rotation += -distance //* 180 / Math.PI;
}
private function stopRotatingWorld():void
{
stage.removeEventListener( MouseEvent.MOUSE_MOVE, applyRotationToWorld );
}
here is source and demo of a potential solution for you:
http://wonderfl.net/c/o8t0
I based the drag rotation detection on keith peter's minimalcomps, specifically the source for his knob component
In the Minimal Comp's Knob source, you can get rotational, horizontal, and vertical movement in the 'onMouseMoved' function.
Finally I used TweenLite to handle the easing back. In my code I Tween the 'this' object, but in practice you should create a public value in the object you are tweening and tween that item, so you can have more that one item tweening easily.
If this is for a public facing project, let me know when you've implemented it; wouldn't mind taking a look on what it is for ^_^