I'm trying to catch a JSON object with a mouse click event. I use ray to identify the object, but for some reason, the objects are not always identified. I suspect that it is related to the fact that I move the camera, because when I click nearby the object, i is identified.
Can you help me figure out how to set the ray correctly, in accordance with the camera move?
Here is the code :
this is the part of the mouse down event *
document.addEventListener("mousemove", onDocumentMouseMove, false);
document.addEventListener("mouseup", onDocumentMouseUp, false);
document.addEventListener("mouseout", onDocumentMouseOut, false);
mouseXOnMouseDown = event.clientX - windowHalfX;
targetRotationOnMouseDown = targetRotation;
var ray, intersections;
_vector.set((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1, 0);
projector.unprojectVector(_vector, camera);
ray = new THREE.Ray(camera.position, _vector.subSelf(camera.position).normalize());
intersections = ray.intersectObjects(furniture);
if (intersections.length > 0) {
selected_block = intersections[0].object;
_vector.set(0, 0, 0);
selected_block.setAngularFactor(_vector);
selected_block.setAngularVelocity(_vector);
selected_block.setLinearFactor(_vector);
selected_block.setLinearVelocity(_vector);
mouse_position.copy(intersections[0].point);
block_offset.sub(selected_block.position, mouse_position);
intersect_plane.position.y = mouse_position.y;
}
}
this is the part of the camera move *
camera.position.x = (Math.cos(timer) * 10);
camera.position.z = (Math.sin(timer) * 10);
camera.lookAt(scene.position);
Hmmm, It is hard to say what your problem might be without seeing some kind of demonstration of how your program is actually acting. I would suggest looking at my demo that I have been working on today. I handle my camera, controls, and rays. I am using a JSON as well.
First you can view my demo: here to get an idea of what it is doing, what your describing sounds similar. You should be able to adapt my code if you can understand it.
--If you would like a direct link to the source code: main.js
I also have another you might find useful where I use rays and mouse collisions to spin a cube. --Source code: main.js
Finally I'll post the guts of my mouse events and how I handle it with the trackball camera in the first demo, hopefully some of this will lead you to a solution:
/** Event fired when the mouse button is pressed down */
function onDocumentMouseDown(event) {
event.preventDefault();
/** Calculate mouse position and project vector through camera and mouse3D */
mouse3D.x = mouse2D.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse3D.y = mouse2D.y = -(event.clientY / window.innerHeight) * 2 + 1;
mouse3D.z = 0.5;
projector.unprojectVector(mouse3D, camera);
var ray = new THREE.Ray(camera.position, mouse3D.subSelf(camera.position).normalize());
var intersects = ray.intersectObject(maskMesh);
if (intersects.length > 0) {
SELECTED = intersects[0].object;
var intersects = ray.intersectObject(plane);
offset.copy(intersects[0].point).subSelf(plane.position);
killControls = true;
}
else if (controls.enabled == false)
controls.enabled = true;
}
/** This event handler is only fired after the mouse down event and
before the mouse up event and only when the mouse moves */
function onDocumentMouseMove(event) {
event.preventDefault();
/** Calculate mouse position and project through camera and mouse3D */
mouse3D.x = mouse2D.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse3D.y = mouse2D.y = -(event.clientY / window.innerHeight) * 2 + 1;
mouse3D.z = 0.5;
projector.unprojectVector(mouse3D, camera);
var ray = new THREE.Ray(camera.position, mouse3D.subSelf(camera.position).normalize());
if (SELECTED) {
var intersects = ray.intersectObject(plane);
SELECTED.position.copy(intersects[0].point.subSelf(offset));
killControls = true;
return;
}
var intersects = ray.intersectObject(maskMesh);
if (intersects.length > 0) {
if (INTERSECTED != intersects[0].object) {
INTERSECTED = intersects[0].object;
INTERSECTED.currentHex = INTERSECTED.material.color.getHex();
plane.position.copy(INTERSECTED.position);
}
}
else {
INTERSECTED = null;
}
}
/** Removes event listeners when the mouse button is let go */
function onDocumentMouseUp(event) {
event.preventDefault();
if (INTERSECTED) {
plane.position.copy(INTERSECTED.position);
SELECTED = null;
killControls = false;
}
}
/** Removes event listeners if the mouse runs off the renderer */
function onDocumentMouseOut(event) {
event.preventDefault();
if (INTERSECTED) {
plane.position.copy(INTERSECTED.position);
SELECTED = null;
}
}
And in order to get the desired effect shown in my first demo that I wanted, I had to add this to my animation loop in order to use the killControls flag to selectively turn on and off the trackball camera controls based on the mouse collisions:
if (!killControls) controls.update(delta);
else controls.enabled = false;
Related
I have two styles of interactions, one highlights the feature, the second places a tooltop with the feature name. Commenting both out, they're very fast, leave either in, the map application slows in IE and Firefox (but not Chrome).
map.addInteraction(new ol.interaction.Select({
condition: ol.events.condition.pointerMove,
layers: [stationLayer],
style: null // this is actually a style function but even as null it slows
}));
$(map.getViewport()).on('mousemove', function(evt) {
if(!dragging) {
var pixel = map.getEventPixel(evt.originalEvent);
var feature = null;
// this block directly below is the offending function, comment it out and it works fine
map.forEachFeatureAtPixel(pixel, function(f, l) {
if(f.get("type") === "station") {
feature = f;
}
});
// commenting out just below (getting the feature but doing nothing with it, still slow
if(feature) {
target.css("cursor", "pointer");
$("#FeatureTooltip").html(feature.get("name"))
.css({
top: pixel[1]-10,
left: pixel[0]+15
}).show();
} else {
target.css("cursor", "");
$("#FeatureTooltip").hide();
}
}
});
I mean this seems like an issue with OpenLayers-3 but I just wanted to be sure I wasn't overlooking something else here.
Oh yeah, there's roughly 600+ points. Which is a lot, but not unreasonably so I would think. Zooming-in to limit the features in view definitely helps. So I guess this is a # of features issue.
This is a known bug and needs more investigation. You can track progress here: https://github.com/openlayers/ol3/issues/4232.
However, there is one thing you can do to make things faster: return a truthy value from map.forEachFeatureAtPixel to stop checking for features once one was found:
var feature = map.forEachFeatureAtPixel(pixel, function(f) {
if (f.get('type') == 'station') {
return feature;
}
});
i had same issue, solved a problem by setInterval, about this later
1) every mouse move to 1 pixel fires event, and you will have a quee of event till you stop moving, and the quee will run in calback function, and freezes
2) if you have an objects with difficult styles, all element shown in canvas will take time to calculate for if they hit the cursor
resolve:
1. use setInterval
2. check for pixels moved size from preview, if less than N, return
3. for layers where multiple styles, try to simplify them by dividing into multiple ones, and let only one layer by interactive for cursor move
function mouseMove(evt) {
clearTimeout(mm.sheduled);
function squareDist(coord1, coord2) {
var dx = coord1[0] - coord2[0];
var dy = coord1[1] - coord2[1];
return dx * dx + dy * dy;
}
if (mm.isActive === false) {
map.unByKey(mm.listener);
return;
}
//shedules FIFO, last pixel processed after 200msec last process
const elapsed = (performance.now() - mm.finishTime);
const pixel = evt.pixel;
const distance = squareDist(mm.lastP, pixel);
if (distance > 0) {
mm.lastP = pixel;
mm.finishTime = performance.now();
mm.sheduled = setTimeout(function () {
mouseMove(evt);
}, MIN_ELAPSE_MSEC);
return;
} else if (elapsed < MIN_ELAPSE_MSEC || mm.working === true) {
// console.log(`distance = ${distance} and elapsed = ${elapsed} mesc , it never should happen`);
mm.sheduled = setTimeout(function () {
mouseMove(evt);
}, MIN_ELAPSE_MSEC);
return;
}
//while multithreading is not working on browsers, this flag is unusable
mm.working = true;
let t = performance.now();
//region drag map
const vStyle = map.getViewport().style;
vStyle.cursor = 'default';
if (evt.dragging) {
vStyle.cursor = 'grabbing';
}//endregion
else {
//todo replace calback with cursor=wait,cursor=busy
UtGeo.doInCallback(function () {
checkPixel(pixel);
});
}
mm.finishTime = performance.now();
mm.working = false;
console.log('mm finished', performance.now() - t);
}
In addition to #ahocevar's answer, a possible optimization for you is to utilize the select interaction's select event.
It appears that both the select interaction and your mousemove listener are both checking for hits on the same layers, doing double work. The select interaction will trigger select events whenever the set of selected features changes. You could listen to it, and show the popup whenever some feature is selected and hide it when not.
This should reduce the work by half, assuming that forEachFeatureAtPixel is what's hogging the system.
I am learning Dart and I was trying to make a very simple drageable HTML element. I followed patterns I'm used to from javascript but it doesn't work as expected.
When making drageable object from scratch you usually do the following:
Listen on mouse down event on that object
Upon mouse down, remember mouse coordinates relative to the object's top-left corner
Listen for any mouse movement. For every move operation, move the object to cursor location minus the coordinates you remembered earlier.
Upon any mouse up event, stop following mouse movement.
So I produced this code:
class DrageableControl {
DivElement _elm;
DrageableControl(String txt, int x, int y) {
//Create element and set up styles
var elm = this.setupElement(x, y);
//Append element to document, add some text and start listeners
document.body.append(elm);
elm.text = txt;
setupEvents();
}
//This function creates all event necessary for drag operations
setupEvents() {
Point relativeMouseOffset = null;
_elm.onMouseDown.listen((MouseEvent e) {
Rectangle myPos = _elm.getBoundingClientRect();
relativeMouseOffset = new Point(e.offset.x-myPos.left, e.offset.y-myPos.top);
e.preventDefault();
return false;
});
//Of course this is completely wrong, the listener should only be added for the duration of dragging
document.onMouseMove.listen((MouseEvent e) {
if(relativeMouseOffset!=null) {
print("Clicked at: ${relativeMouseOffset}\nCurrent mouse position:${e.offset}");
_elm.style.top = "${(e.offset.y/*-relativeMouseOffset.y*/)}px";
_elm.style.left = "${(e.offset.x/*-relativeMouseOffset.x*/)}px";
}
});
document.onMouseUp.listen((MouseEvent e){
relativeMouseOffset = null;
});
}
setupElement(int x, int y) {
var elm = this._elm = new DivElement();
//TODO: Use css?
elm.style.position = "absolute";
elm.style.top = "${y}px";
elm.style.left = "${x}px";
elm.style.border = "1px solid red";
elm.style.backgroundColor = "#FFAAAA";
elm.style.cursor = "default";
//elm.style.transition = "top 1s";
return elm;
}
}
Problem is, that some coordinates delivered by MouseMove are complete nonsense. See the console:
Clicked at: Point(-76.0, -143.0)
Current mouse position:Point(1, 1)
Clicked at: Point(-76.0, -143.0)
Current mouse position:Point(374, 272)
Clicked at: Point(-76.0, -143.0)
Current mouse position:Point(1, 0)
Clicked at: Point(-76.0, -143.0)
Current mouse position:Point(376, 273)
Clicked at: Point(-76.0, -143.0)
Current mouse position:Point(0, 1)
As you can see every second mouse move event delivers broken data - coordinates right around [0, 0]. So how can I filter out this invalid data? Why does it happen?
So far I'm probably fixing this by adding:
if(e.offset.x+e.offset.y<5)
return;
Use e.client.x/e.client.y instead.
In my actionscript 3 i have a textfield, which i fill with the messages (get them from the webservice). Not all the messages are visible, so every (for Example) 5s i'm moving them vertically to the top.
To do that, i'm using the Tween effect, but the thing is, that it moves only the visible text and not the rest text together (the invisible one).
When scrolling - i can see the rest text.
Image to show the situation (Sorry for my skills):
Here are some code: Function to fill messages object. Plus count how many lines every message will take to display ( so i could easilly use the tween effect (From - to))
private function getNewMsg(e:ResultEvent):void
{
size = e.result.toString().split(" ").length - 1;
for (var i=0; i<size; i++)
{
smsList.push(new smsItem(e.result[i].Id, e.result[i].text));
}
summ = 0;
for (var d=0; d<size; d++)
{
textfield.appendText(smsList[d].Text.toUpperCase()+'\n');
if (d >= 1)
{
smsList[d].Lines = textfield.numLines - summ;
summ += smsList[d].Lines;
}
else
{
smsList[d].Lines = textfield.numLines-1;
summ += smsList[d].Lines + 1;
}
}
twEnd = smsList[1].Lines * (-30.5);
}
And on timer i'm starting the Tween:
function timerHandler(e:TimerEvent):void
{
myTweenX = new Tween(textfield, "y", None.easeIn, twStart, twEnd, 4, true)
myTweenX.addEventListener(TweenEvent.MOTION_FINISH, tweenCompleted);
}
function tweenCompleted(e:TweenEvent):void
{
twInd++;
twStart = twEnd;
twEnd += smsList[twInd].Lines * (-30.5);
}
Found the problem. Because of my fixed textfield height, text wasn't. After made the textfields height auto size, evrything got fixed.
I'm trying to build a simple Flash game where the user drags a sombrero onto a cactus. I've got it so that when you drag the sombrero anywhere but the cactus, it snaps back to it's original position. I had it so when you drag it onto the cactus, it stays there.
What I want is when the user drags the sombrero onto the cactus, it takes you to a screen that says "YAY! Play again?" I put a gotoAndPlay() inside my if statement:
if(dropTarget.parent.name == "cactus")
{
//scaleX = scaleY = 0.2;
//alpha = 0.2;
//y = stage.stageHeight - height - -100;
//buttonMode = false;
//removeEventListener(MouseEvent.MOUSE_DOWN, down);
gotoAndPlay("playAgain");
trace("dropped on cactus");
}
else
{
returnToOriginalPosition();
}
I labeled my second frame as "playAgain." I get an error saying:
ArgumentError: Error #2109: Frame label playAgain not found in scene playAgain.
at flash.display::MovieClip/gotoAndPlay()
at net.dndgtal.Cactus_Game::sombrero/stageUp()
I have Googled and checked and double checked all the suggestions, but cannot get it to work. I don't have a scene "playAgain," only "scene1." I've tried specifying both the scene and the frame- that doesn't work either. And I tried just putting in gotoAndPlay(2), for frame two, but that just does nothing.
Am I missing something? Any help would be appreciated. Here is all of my code if that helps:
package net.dndgtal.Cactus_Game
{
import flash.display.MovieClip;
import flash.events.MouseEvent;
import flash.geom.Point;
public class sombrero extends MovieClip
{
protected var OriginalPosition:Point;
public function sombrero ()
{
OriginalPosition = new Point(x, y);
buttonMode = true;
addEventListener ( MouseEvent.MOUSE_DOWN, down );
//trace("sombrero constructor");
}
protected function down (event:MouseEvent):void
{
parent.addChild(this);
startDrag();
stage.addEventListener(MouseEvent.MOUSE_UP, stageUp);
//trace("DOWN");
}
protected function stageUp(event:MouseEvent):void
{
stage.removeEventListener(MouseEvent.MOUSE_UP, stageUp);
stopDrag();
if (dropTarget)
{
if(dropTarget.parent.name == "cactus")
{
//scaleX = scaleY = 0.2;
//alpha = 0.2;
//y = stage.stageHeight - height - -100;
//buttonMode = false;
//removeEventListener(MouseEvent.MOUSE_DOWN, down);
gotoAndPlay("playAgain");
trace("dropped on cactus");
}
else
{
returnToOriginalPosition();
}
}
else
{
returnToOriginalPosition();
}
}
protected function returnToOriginalPosition(): void
{
x = OriginalPosition.x;
y = OriginalPosition.y;
}
}
}
Thanks! Let me know if you have any questions.
Your issue is likely one of scope. When you use gotoAndPlay("playAgain"), it will take the current timeline (scope), which is the sombrero class, and attempt to find a frame label or scene called "playAgain" on it's timeline.
Presumably, you want the main timeline to gotoAndPlay the frame label "playAgain", not the sombrero.
To access the main timeline, you can use MovieClip(root).gotoAndPlay("playAgain").
If it's not the main timeline, but the parent of sombrero, you can access it with MovieClip(parent).gotoAndPlay("playAgain")
If the relationship between the sombrero and whichever timeline your trying to advance is more complicated than that, then you could pass in a reference to it in your sombrero constructor:
private var targetTimeline:MovieClip;
public function sombrero (targetTimeline_:MovieClip)
{
targetTimeline = targetTimeline_;
//...rest of constructor code
And then:
if(dropTarget.parent.name == "cactus"){
targetTimeline.gotoAndPlay("playAgain");
And when you create the sombrero, pass in the target timeline:
var hat:sombrero = new sombrero(target);
I'm using the I'm currently using the tooltip formatter function to control the look of the tooltip, including adding some custom css to create an arrow on the side of the tooltip box facing the mouse. I am also using the positioner callback to not only determine the placement of the tooltip, but when it changes from one side of the mouse to the other I'm updating the formatter callback to switch the side of the tooltip the arrow lies on (see code below). Everything works fine, except for the very first point which causes the tooltip to switch sides of the mouse. Its clear that a tooltip's "formatter" function is called before the tooltips "positioner" function ( for reasons that are probably obvious ). However, it prevents me from correctly drawing the tooltip when it changes sides. What I really need to be able to do in the positioner callback is to update the formatter function, and then redraw the tooltip. Is that possible?
positioner: function (boxWidth, boxHeight, point) {
// Set up the variables
var chart = this.chart;
var plotLeft = chart.plotLeft;
var plotTop = chart.plotTop;
var plotWidth = chart.plotWidth;
var plotHeight = chart.plotHeight;
var distance = 40;
var pointX = point.plotX;
var pointY = point.plotY;
// Determine if we need to flip the tooltip from following on the left to
// following on the right
if ((pointX - boxWidth - distance) < plotLeft) {
x = pointX + distance;
chart.tooltip.options.formatter = function () {
// UPATE THE TOOLTIP FORMATTER HERE
};
}
}
Here is a js fiddle example of the issue
http://jsfiddle.net/bteWs/
If you go slowly, and notice the very first point where the switch happens the arrow will be point the wrong direction. After that it corrects ( as described in my post ). Just looking for a solution to get the correct behavior in this case.
You can always have enabled both classes for tooltip, and just remove inproper in positioner, see: http://jsfiddle.net/bteWs/3/
Default formatter:
formatter: function () {
var s = '<div id="custom-tooltip" class="tooltip-left tooltip-right">';
var chart = null;
s += "<div style='padding:5px;color:white'>Some Tooltip</div></div>";
return s;
},
And removing:
if ((pointX - boxWidth - distance) < plotLeft) {
x = pointX + 60;
$("#custom-tooltip").removeClass('tooltip-right');
}
else {
x = Math.max(plotLeft, pointX - boxWidth - 10);
$("#custom-tooltip").removeClass('tooltip-left');
}
I had the same issue. The problem is that positioner is begin called after the formatter. I made a fix to your jsfiddle. It's a huge hack but you can see how you can overcome your problem.
In the fix, I made use of a global. You don't need to but I hacked your fiddle in a hurry. I also got rid of some of your duplicate code.
The trick is to force a refresh on the tooltip after the tooltip switch sides.
positioner: function (boxWidth, boxHeight, point) {
// Set up the variables
var chart = this.chart;
var plotLeft = chart.plotLeft;
var plotTop = chart.plotTop;
var plotWidth = chart.plotWidth;
var plotHeight = chart.plotHeight;
var distance = 40;
var pointX = point.plotX;
var pointY = point.plotY;
var refreshTooltip = false;
var previousAlign = chart.align;
if ((pointX - boxWidth - distance) < plotLeft) {
x = pointX + 60;
chart.align = "left";
}
else {
x = Math.max(plotLeft, pointX - boxWidth - 10);
chart.align = "right";
}
y = Math.min(plotTop + plotHeight - boxHeight, Math.max(plotTop, pointY - boxHeight + plotTop + boxHeight / 2));
if (previousAlign != null && chart.align != previousAlign) {
chart.tooltip.refresh(activePoint);
}
return { x: x, y: y };
}
See the complete fiddle here:
http://jsfiddle.net/anber500/bteWs/1/