I have a PNG in a UIImageView with alpha around the edges (let's say a circle). When I tap it, I want it to register as a tap for the circle if I'm touching the opaque bit, but a tap for the view behind if I touch the transparent bit.
(BTW: On another forum, someone said PNGs automatically do this, and a transparent PNG should pass the click on to the view below, but I've tested it and it doesn't, at least not in my case.)
Is there a flag I just haven't flipped, or do I need to create some kind of formula: "if tapped { get location; calculate distance from centre; if < r { touched circle } else { pass it on } }"?
-k.
I don't believe that PNGs automatically do this, but can't find any references that definitively say one way or the other.
Your radius calculation is probably simpler, but you could also manually check the alpha value of the touched pixel in your image to determine whether to count it as a hit. This code is targetted at OS X 10.5+, but with some minor modifications it should run on iPhone: Getting the pixel data from a CGImage object. Here is some related discussion on retrieving data from a UIImage: Getting data from an UIImage.
I figured it out...the PNG, bounding box transparency issue and being able to click through to another image behind:
var hitTestPoint1:Boolean = false;
var myHitTest1:Boolean = false;
var objects:Array;
clip.addEventListener(MouseEvent.MOUSE_DOWN, doHitTest);
clip.addEventListener(MouseEvent.MOUSE_UP, stopDragging);
clip.buttonMode = true;
clip.mouseEnabled = true;
clip.mouseChildren = true;
clip2.addEventListener(MouseEvent.MOUSE_DOWN, doHitTest);
clip2.addEventListener(MouseEvent.MOUSE_UP, stopDragging);
clip2.buttonMode = true;
clip2.mouseEnabled = true;
clip2.mouseChildren = true;
clip.rotation = 60;
function doHitTest(event:MouseEvent):void
{
objects = stage.getObjectsUnderPoint(new Point(event.stageX, event.stageY));
trace("Which one: " + event.target.name);
trace("What's under point: " + objects);
for(var i:int=0; i
function stopDragging(event:MouseEvent):void
{
event.target.stopDrag();
}
function realHitTest(object:DisplayObject, point:Point):Boolean
{
/* If we're already dealing with a BitmapData object then we just use the hitTest
* method of that BitmapData.
*/
if(object is BitmapData)
{
return (object as BitmapData).hitTest(new Point(0,0), 0, object.globalToLocal(point));
}
else {
/* First we check if the hitTestPoint method returns false. If it does, that
* means that we definitely do not have a hit, so we return false. But if this
* returns true, we still don't know 100% that we have a hit because it might
* be a transparent part of the image.
*/
if(!object.hitTestPoint(point.x, point.y, true))
{
return false;
}
else {
/* So now we make a new BitmapData object and draw the pixels of our object
* in there. Then we use the hitTest method of that BitmapData object to
* really find out of we have a hit or not.
*/
var bmapData:BitmapData = new BitmapData(object.width, object.height, true, 0x00000000);
bmapData.draw(object, new Matrix());
var returnVal:Boolean = bmapData.hitTest(new Point(0,0), 0, object.globalToLocal(point));
bmapData.dispose();
return returnVal;
}
}
}
Related
Below is a snippet of code thats running every update but when I log the local position of the image it still says 0,0,0 when it should be 10,10,10. What am I doing wrong??? Ultimately I am trying to understand how to programmatically move an image around on screen
public partial class MainCanvasSystem : SystemBase
{
protected override void OnUpdate()
{
if (MainGameObjectCanvas.Instance != null && SystemAPI.HasSingleton<MainEntityCanvas>())
{
Entity mainEntityCanvasEntity = SystemAPI.GetSingletonEntity<MainEntityCanvas>();
LocalToWorld targetLocalToWorld = SystemAPI.GetComponent<LocalToWorld>(mainEntityCanvasEntity);
Canvas canvas = MainGameObjectCanvas.Instance;
Image image = canvas.GetComponentInChildren<Image>();
var rect = image.GetComponent<RectTransform>();
rect.localScale.Set(10,10,10);
Debug.Log(rect.localPosition.x);
}
}
}
I think there is general misunderstanding here.
rect.localScale.Set(10,10,10);
does .. nothing!
Transform.localScale is a property and returns a COPY of a Vector3 struct.
You are calling Vector3.Set on it which replaces the values within that Vector3 copy, yes, but then you never actually apply it anywhere.
=> you need to actually set the property!
You rather would do e.g.
rect.locaScale = Vector3.one * 10;
or
rect.localScale = new Vector3(10,10,10);
However, this said, changing a localScale won't change the position at all. The RectTransform.anchoredPosition is probably rather the one to go with.
I'm trying to understand the part of this code below that uses SchedulerBinding.instance.scheduleFrameCallback(beginFrame);. beginFrame is listed in the other code block below.
The code comes from here, which is an animated timeline for Flutter. I don't expect anyone to read all this, obviously. But given some context, can you understand what for it is being used?
Context: this part of the code is inside a function called setViewport. The viewport of a timeline is simply the visible part of that timeline. So, once a viewport is set (a start and end point in the timeline are given), it ends animating something in the timeline. You can see that in the process of doing it, it calls SchedulerBinding.instance.scheduleFrameCallback, which is what I want to know what is used for. I obviously went into the page for SchedulerBinding but the explanation is so generic that I don't have an idea what it is used for.
if (!animate) {
_renderStart = start;
_renderEnd = end;
advance(0.0, false);
if (onNeedPaint != null) {
onNeedPaint();
}
} else if (!_isFrameScheduled) {
_isFrameScheduled = true;
_lastFrameTime = 0.0;
SchedulerBinding.instance.scheduleFrameCallback(beginFrame);
}
Here's beginFrame:
/// Make sure that all the visible assets are being rendered and advanced
/// according to the current state of the timeline.
void beginFrame(Duration timeStamp) {
_isFrameScheduled = false;
final double t =
timeStamp.inMicroseconds / Duration.microsecondsPerMillisecond / 1000.0;
if (_lastFrameTime == 0.0) {
_lastFrameTime = t;
_isFrameScheduled = true;
SchedulerBinding.instance.scheduleFrameCallback(beginFrame);
return;
}
double elapsed = t - _lastFrameTime;
_lastFrameTime = t;
if (!advance(elapsed, true) && !_isFrameScheduled) {
_isFrameScheduled = true;
SchedulerBinding.instance.scheduleFrameCallback(beginFrame);
}
if (onNeedPaint != null) {
onNeedPaint();
}
}
According to the project README, it's used to keep the Flare animations in sync:
"To have the animation reproduce correctly, it's also necessary to call advance(elapsed) on the current FlutterActor each frame. Moreover, the current ActorAnimation requires that the function apply(time) is called on it to display it's correct interpolated values.
This is all made possible by relying on Flutter's SchedulerBinding.scheduleFrameCallback()."
I would like to be able to zoom into an ILNumerics scene viewed by a camera (as in scene.Camera) with the center point of the zoom determined by where the mouse pointer is located when I start spinning the mouse scroll wheel. The default zoom behavior is for the zoom center to be at the scene.Camera.LookAt point. So I guess this would require the mouse to be tracked in (X,Y) continuously and for that point to be used as the new LookAt point? This seems to be like this post on getting the 3D coordinates from a mouse click, but in my case there's no click to indicate the location of the mouse.
Tips would be greatly appreciated!
BTW, this kind of zoom method is standard operating procedure in CAD software to zoom in and out on an assembly of parts. It's super convenient for the user.
One approach is to overload the MouseWheel event handler. The current coordinates of the mouse are available here, too.
Use the mouse screen coordinates to acquire (to "pick") the world
coordinate corresponding to the primitive under the mouse.
Adjust the Camera.Position and Camera.ZoomFactor to 'move' the camera closer to the point under the mouse and to achieve the required 'directional zoom' effect.
Here is a complete example from the ILNumerics website:
using System;
using System.Windows.Forms;
using ILNumerics;
using ILNumerics.Drawing;
using ILNumerics.Drawing.Plotting;
using static ILNumerics.Globals;
using static ILNumerics.ILMath;
namespace ILNumerics.Examples.DirectionalZoom {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void panel2_Load(object sender, EventArgs e) {
Array<float> X = 0, Y = 0, Z = CreateData(X, Y);
var surface = new Surface(Z, X, Y, colormap: Colormaps.Winter);
surface.UseLighting = true;
surface.Wireframe.Visible = false;
panel2.Scene.Camera.Add(surface);
// setup mouse handlers
panel2.Scene.Camera.Projection = Projection.Orthographic;
panel2.Scene.Camera.MouseDoubleClick += Camera_MouseDoubleClick;
panel2.Scene.Camera.MouseWheel += Camera_MouseWheel;
// initial zoom all
ShowAll(panel2.Scene.Camera);
}
private void Camera_MouseWheel(object sender, Drawing.MouseEventArgs e) {
// Update: added comments.
// the next conditionals help to sort out some calls not needed. Helpful for performance.
if (!e.DirectionUp) return;
if (!(e.Target is Triangles)) return;
// make sure to start with the SceneSyncRoot - the copy of the scene which receives
// user interaction and is eventually used for rendering. See: https://ilnumerics.net/scene-management.html
var cam = panel2.SceneSyncRoot.First<Camera>();
if (Equals(cam, null)) return; // TODO: error handling. (Should not happen in regular setup, though.)
// in case the user has configured limited interaction
if (!cam.AllowZoom) return;
if (!cam.AllowPan) return; // this kind of directional zoom "comprises" a pan operation, to some extent.
// find mouse coordinates. Works only if mouse is over a Triangles shape (surfaces, but not wireframes):
using (var pick = panel2.PickPrimitiveAt(e.Target as Drawable, e.Location)) {
if (pick.NextVertex.IsEmpty) return;
// acquire the target vertex coordinates (world coordinates) of the mouse
Array<float> vert = pick.VerticesWorld[pick.NextVertex[0], r(0, 2), 0];
// and transform them into a Vector3 for easier computations
var vertVec = new Vector3(vert.GetValue(0), vert.GetValue(1), vert.GetValue(2));
// perform zoom: we move the camera closer to the target
float scale = Math.Sign(e.Delta) * (e.ShiftPressed ? 0.01f : 0.2f); // adjust for faster / slower zoom
var offs = (cam.Position - vertVec) * scale; // direction on the line cam.Position -> target vertex
cam.Position += offs; // move the camera on that line
cam.LookAt += offs; // keep the camera orientation
cam.ZoomFactor *= (1 + scale);
// TODO: consider adding: the lookat point now moved away from the center / the surface due to our zoom.
// In order for better rotations it makes sense to place the lookat point back to the surface,
// by adjusting cam.LookAt appropriately. Otherwise, one could use cam.RotationCenter.
e.Cancel = true; // don't execute common mouse wheel handlers
e.Refresh = true; // immediate redraw at the end of event handling
}
}
private void Camera_MouseDoubleClick(object sender, Drawing.MouseEventArgs e) {
var cam = panel2.Scene.Camera;
ShowAll(cam);
e.Cancel = true;
e.Refresh = true;
}
// Some sample data. Replace this with your own data!
private static RetArray<float> CreateData(OutArray<float> Xout, OutArray<float> Yout) {
using (Scope.Enter()) {
Array<float> x_ = linspace<float>(0, 20, 100);
Array<float> y_ = linspace<float>(0, 18, 80);
Array<float> Y = 1, X = meshgrid(x_, y_, Y);
Array<float> Z = abs(sin(sin(X) + cos(Y))) + .01f * abs(sin(X * Y));
if (!isnull(Xout)) {
Xout.a = X;
}
if (!isnull(Yout)) {
Yout.a = Y;
}
return -Z;
}
}
// See: https://ilnumerics.net/examples.php?exid=7b0b4173d8f0125186aaa19ee8e09d2d
public static double ShowAll(Camera cam) {
// Update: adjusts the camera Position too.
// this example works only with orthographic projection. You will need to take the view frustum
// into account, if you want to make this method work with perspective projection also. however,
// the general functioning would be similar....
if (cam.Projection != Projection.Orthographic) {
throw new NotImplementedException();
}
// get the overall extend of the cameras scene content
var limits = cam.GetLimits();
// take the maximum of width/ height
var maxExt = limits.HeightF > limits.WidthF ? limits.HeightF : limits.WidthF;
// make sure the camera looks at the unrotated bounding box
cam.Reset();
// center the camera view
cam.LookAt = limits.CenterF;
cam.Position = cam.LookAt + Vector3.UnitZ * 10;
// apply the zoom factor: the zoom factor will scale the 'left', 'top', 'bottom', 'right' limits
// of the view. In order to fit exactly, we must take the "radius"
cam.ZoomFactor = maxExt * .50;
return cam.ZoomFactor;
}
}
}
Note, that the new handler performs the directional zoom only when the mouse is located over an object hold by this Camera! If, instead, the mouse is placed on the background of the scene or over some other Camera / plot cube object no effect will be visible and the common zoom feature is performed (zooming in/out to the look-at point).
I would like to associate the same script to different empty objects I just use as placeholders in the game. The aim is to exploit their positions so that when the user touch a point in the screen, close to one of these objects, a dedicate GUI appears. The problem is that though the two objects are different their scripts seem to influence each other so that when the game is running and I touch one of these two objects both the gui appears. What am I doing wrong?
....
private var check: boolean;
var topCamera : Camera;
var customSkin : GUISkin;
function Update () {
if (Input.GetMouseButtonDown(0)){
if(Input.mousePosition.x > this.transform.position.x - Screen.width*0.20 && Input.mousePosition.x < this.transform.position.x + Screen.width*20){
if(Input.mousePosition.y > this.transform.position.y - Screen.height*0.2 && Input.mousePosition.y < this.transform.position.y + Screen.height*0.2){
check = true;
}
}
}
if(check){
//the camera zooms forward
}else{
//the camera zooms backward
}
}
function OnGUI () {
if (this.check){
var w = Screen.width;
var h = Screen.height;
var bw = 0.083;
var bws = 0.001 *w;
GUI.skin = customSkin;
GUI.Box(new Rect(w*0.6,h*0.3,w*0.38,h*0.45), "Stuff");
customSkin.box.fontSize = 0.04*h;
customSkin.textField.fontSize = 0.08*h;
customSkin.button.fontSize = 0.04*h;
textFieldString = GUI.TextField (Rect (w*0.62, h*0.39, w*0.34, h*0.1), textFieldString);
if (GUI.Button (Rect (w*0.62,h*0.50, w*bw, h*0.1), "+")) {
if (this.check){
this.check=false;
}else{
this.check = true;
}
//...
}
//...
}
This is probably not working, because you are comparing apples with oranges in your Update() function. Input.mousePosition returns the the position in 2D pixel coordinates and transform.position returns the GameObject's position in 3D world coordinates.
To check if you clicked on an object, you need to attach a Collider to the game object in question and test for collisions using a Raycast in your script. Here is the relavant example from the documentation in JavaScript:
var ray = Camera.main.ScreenPointToRay (Input.mousePosition);
if (Physics.Raycast (ray, 100)) {
print ("Hit something");
}
The cool thing about this approach is that we are checking for collisions between the Collider and the ray. If you only want to see if you clicked near the GameObject, just make the Collider larger than the GameObject. No need for messing around with inequalities!
If your objective is to click somewhere close to the object and not only at the object, then you have some configurations (positions of those objects in space) where there are space that are close enough to both objects for their GUI to appear and therefore you need some script to decide which one is closer.
I suggest you to implement a monobehaviour that is a singleton that would track those clicks and measure the distance of all objects, to get the closest.
Reading again your post, I think you want to get the GUI just when you click at the object, and when you do this you get both GUIs. I think that's happening because wrong calculation of the area that makes check to go true.
Can you give more details? Is there some space where there shouldn't have GUI messages when clicked, or is everything filled by those objects?
I just starting coding with Google Earth using the GEPlugin control for .Net and still got a lot to learn.
What has got me puzzled is when I try to drag a polygon.
The method below is called whenever the mousemove event fires and should be moving each point of the polygon while retaining the orginal shape of the polygon. The lat / long for each point is changed but the polygon does not move position on the map.
Will moving a point in a polygon cause it to redraw, do I need to call a method to force a redraw or perhaps do something else entirely?
Thanks!
private void DoMouseMove(IKmlMouseEvent mouseEvent)
{
if (isDragging)
{
mouseEvent.preventDefault();
var placemark = mouseEvent.getTarget() as IKmlPlacemark;
if (placemark == null)
{
return;
}
IKmlPolygon polygon = placemark.getGeometry() as IKmlPolygon;
if (polygon != null)
{
float latOffset = startLatLong.Latitude - mouseEvent.getLatitude();
float longOffset = startLatLong.Longitude - mouseEvent.getLongitude();
KmlLinearRingCoClass outer = polygon.getOuterBoundary();
KmlCoordArrayCoClass coordsArray = outer.getCoordinates();
for(int i = 0; i < coordsArray.getLength(); i++)
{
KmlCoordCoClass currentPoint = coordsArray.get(i);
currentPoint.setLatLngAlt(currentPoint.getLatitude() + latOffset,
currentPoint.getLongitude() + longOffset, 0);
}
}
}
}
Consider voting for these issues to be resolved
http://code.google.com/p/earth-api-utility-library/issues/detail?id=33
http://code.google.com/p/earth-api-samples/issues/detail?id=167
You may find some hints at the following link:
http://earth-api-utility-library.googlecode.com/svn/trunk/extensions/examples/ruler.html
UPDATE:
I've released the extension library: https://bitbucket.org/mutopia/earth
See https://bitbucket.org/mutopia/earth/src/master/sample/index.html to run it.
See the drag() method in the sample code class, which calls setDragMode() and addDragEvent() to enable dragging of the KmlPolygon.
I successfully implemented this using takeOverCamera in the earth-api-utility-library and three events:
setDragMode: function (mode) {
// summary:
// Sets dragging mode on and off
if (mode == this.dragMode) {
Log.info('Drag mode is already', mode);
} else {
this.dragMode = mode;
Log.info('Drag mode set', mode);
if (mode) {
this.addEvent(this.ge.getGlobe(), 'mousemove', this.dragMouseMoveCallback);
this.addEvent(this.ge.getGlobe(), 'mouseup', this.dragMouseUpCallback);
this.addEvent(this.ge.getView(), 'viewchange', this.dragViewChange, false);
} else {
this.removeEvent(this.ge.getGlobe(), 'mousemove', this.dragMouseMoveCallback);
this.removeEvent(this.ge.getGlobe(), 'mouseup', this.dragMouseUpCallback);
this.removeEvent(this.ge.getView(), 'viewchange', this.dragViewChange, false);
}
}
},
This is in a utility library within a much larger project. dragMode is a boolean which adds and removes events. These three events control what happens when you drag. addEvent and removeEvent are my own wrapper functions:
addEvent: function (targetObject, eventID, listenerCallback, capture) {
// summary:
// Convenience method for google.earth.addEventListener
capture = setDefault(capture, true);
google.earth.addEventListener(targetObject, eventID, listenerCallback, capture);
},
removeEvent: function (targetObject, eventID, listenerCallback, capture) {
// summary:
// Convenience method for google.earth.removeEventListener
capture = setDefault(capture, true);
google.earth.removeEventListener(targetObject, eventID, listenerCallback, capture);
},
Ignoring the minor details, all the important stuff is in the callbacks to those events. The mousedown event locks the camera and sets the polygon I'm dragging as the dragObject (it's just a variable I'm using). It saves the original lat long coordinates.
this.dragMouseDownCallback = lang.hitch(this, function (event) {
var obj = event.getTarget();
this.lockCamera(true);
this.setSelected(obj);
this.dragObject = obj;
this.dragLatOrigin = this.dragLatLast = event.getLatitude();
this.dragLngOrigin = this.dragLngLast = event.getLongitude();
}
The mousemove callback updates to the latest lat long coordinates:
this.dragMouseMoveCallback = lang.hitch(this, function (event) {
if (this.dragObject) {
var lat = event.getLatitude();
var lng = event.getLongitude();
var latDiff = lat - this.dragLatLast;
var lngDiff = lng - this.dragLngLast;
if (Math.abs(latDiff) > this.dragSensitivity || Math.abs(lngDiff > this.dragSensitivity)) {
this.addPolyCoords(this.dragObject, [latDiff, lngDiff]);
this.dragLatLast = lat;
this.dragLngLast = lng;
}
}
});
Here I'm using some fancy sensitivity values to prevent updating this too often. Finally, addPolyCoords is also my own function which adds lat long values to the existing coordinates of the polygon - effectively moving it across the globe. I do this with the built in setLatitude() and setLongitude() functions for each coordinate. You can get the coordinates like so, where polygon is a KmlPolyon object:
polygon.getGeometry().getOuterBoundary().getCoordinates()
And of course, the mousedown callback turns off the drag mode so that moving the mouse doesn't continue to drag the polygon:
this.dragMouseUpCallback = lang.hitch(this, function (event) {
if (this.dragObject) {
Log.info('Stop drag', this.dragObject.getType());
setTimeout(lang.hitch(this, function () {
this.lockCamera(false);
this.setSelected(null);
}), 100);
this._dragEvent(event);
this.dragObject = this.dragLatOrigin = this.dragLngOrigin = this.dragLatLast = this.dragLngLast = null;
}
});
And finally, _dragEvent is called to ensure that the final coordinates are the actual coordinates the mouse event finished with (and not the latest mousemove call):
_dragEvent: function (event) {
// summary:
// Helper function for moving drag object
var latDiff = event.getLatitude() - this.dragLatLast;
var lngDiff = event.getLongitude() - this.dragLngLast;
if (!(latDiff == 0 && lngDiff == 0)) {
this.addPolyCoords(this.dragObject, [latDiff, lngDiff]);
Log.info('Moved ' + latDiff + ', ' + lngDiff);
}
},
The mousemove callback isn't too important and can actually be ignored - the only reason I use it is to show the polygon moving as the user moves their mouse. Removing it will result in the object being moved when they lift their mouse up.
Hopefully this incredibly long answer gives you some insights into how to implement dragging in the Google Earth API. And I also plan to release my library in the future when I've ironed out the kinks :)