I'm using ilnumerics with C# on a simple WindowsFormsApplication and created some LinePlots in a PlotCube on an ILPanel. Like in:
How to find the 3D coordinates of a surface from the click location of the mouse on the ILNumerics surface plots?
i tried to get the translated coordinates while clicking into the PlotCube to create something similar to the data tips at MATLAB.
Instead of a ILSurface I'm using ILLinePlot and my x-Axis is of logarithmic scale.
So I adapted the method mentioned at the above link to my setting.
My problem is that I only get correct coordinates when i am clicking exactly at the LinePlot!
When clicking right next to the line or anywhere else at the PlotCube the above MouseClick method runs into a NullReferenceException.
My idea for that problem was not to use the MouseClick target to get the transformations if i didn't hit the LinePlot but to set the group to the LinePlot like as if I had clicked on it (see at my example).
But although I now get the same groups in the while-loop and no Exception debugging shows that the transformation matrices of the PlotsData group and the PlotCubeScale group differ in both cases.
If I don't hit the LinePlot all transformation matrices are just the identities.
Here comes my short example:
private void ilPanel1_Load(object sender, EventArgs e)
{
ILArray<double> A = new Double[,] {{1,4,0},{10,12,0},{100,10,0},{1000,18,0},{10000,15,0}};
var scene = new ILScene() {
new ILPlotCube(twoDMode: true){
new ILLinePlot(ILMath.tosingle(A))
}
};
scene.First<ILPlotCube>().ScaleModes.XAxisScale = AxisScale.Logarithmic;
scene.First<ILPlotCube>().MouseEnter += (_s, _a) =>
{
if (!_a.DirectionUp)
{
//onplot is a global flag which is true if the mouse is over the LinePlot
Text = "On Plot - Target: " + _a.Target.ToString();
onplot = true;
}
};
scene.First<ILPlotCube>().MouseLeave += (_s, _a) =>
{
if (!_a.DirectionUp)
{
Text = "Off Plot - Target: " + _a.Target.ToString();
onplot = false;
}
};
scene.First<ILPlotCube>().MouseClick += (s, arg) =>
{
if (!arg.DirectionUp)
return;
var group = arg.Target.Parent;
// *new part: group holds my LinePlot if I clicked on it
// else it holds the PlotCube
if (!onplot)
{
// so I search the LinePlot at set group to it
foreach (ILLineStrip strip in scene.Find<ILLineStrip>())
{
if (strip.Tag == "LinePlotLine")
{
group = strip.Parent;
}
}
}
if (group != null)
{
// walk up to the next camera node
Matrix4 trans = group.Transform;
while (!(group is ILCamera) && group != null)
{
group = group.Parent;
// collect all nodes on the path up
trans = group.Transform * trans;
}
if (group != null && (group is ILCamera))
{
// convert args.LocationF to world coords
// The Z coord is not provided by the mouse! -> choose arbitrary value
var pos = new Vector3(arg.LocationF.X * 2 - 1, arg.LocationF.Y * -2 + 1, 0);
// invert the matrix.
trans = Matrix4.Invert(trans);
// trans now converts from the world coord system (at the camera) to
// the local coord system in the 'target' group node (surface).
// In order to transform the mouse (viewport) position, we
// left multiply the transformation matrix.
pos = trans * pos;
// here I take care of the logarithmic scale of the xAxis
pos.X = (float)ILMath.pow(10.0, pos.X);
// view result in the window title
Text = "Model Position: " + pos.ToString();
}
}
};
ilPanel1.Scene = scene;
}
Why do the group.Transform-matrices differ depending on my clicking position? The while-loop is exactly the same in both cases.
I hope that anyone can understand my problem. Although searching for weeks I didn't find any answer or different ways to reach my goal which is just to show the user the Coordinates of the Points on the line where he is clicking.
Thank you very much in advance for any help.
One work around is to get the lineplot node from the mouse handler's target rather than from the scene directly. That way we always get the right transforms. Replace your logic of identifying the lineplot with the following
ILGroup plotcubeGroup = e.Target as ILGroup;
ILGroup group = plotcubeGroup.Children.Where(item => item.Tag == "LinePlot").First() as ILGroup;
if(group != null)
{
// continue with same logic as above
}
Hope it helps.
Related
I'm trying to use Navigation2D.GetSimplePath for my enemy to chase the player once discovered.
using this bit of code I'd expect my enemy to get a Vector2 array containing path info to nav to the player but, my enemy goes in a completely different direction than what I'd expect.
I've tried this:
var from = Enemy.Position;
var to = PlayerRef.Position;
//Nav is my Navigation2d
var paths = Nav.GetSimplePath(from, to);
Enemy.Status.NavPath = new Stack<Vector2>(paths);
for my from and to and but, I've also attempted a lot of conversions
My guess was that my locals need to be converted to the local of the Navigation2d so I tried this:
//Nav is my Navigation2d
var from = Nav.ToLocal(Enemy.GlobalPosition);
var to = Nav.ToLocal(PlayerRef.GlobalPosition);
Since then I've been just bashing my head against the way with converting different global positions to locals of others and using those for the from and to values with similarly off results. I've been looking at this for a really long time (multiple days in my spare time) and I think I'm overlooking something obvious. If possible could anyone provide a second set of eyes and tell me what I've been missing.
Additional note:
This issue only started occurring after my attempt at refactoring. I had this logic all smashed together in a single enemy class till it got to be a pain to maintain. I can provide the original as well.
Original Enemy3.cs Pre-Refactor
My current version of "Chase state" using the FSM pattern:
Enemy using chase state
Here is the full listing of the current file.
using Godot;
using ThemedHorrorJam5.Scripts.Enum;
using ThemedHorrorJam5.Scripts.Patterns.StateMachine;
using ThemedHorrorJam5.Scripts.GDUtils;
using System.Collections.Generic;
using System.Linq;
namespace ThemedHorrorJam5.Entities
{
public class ChaseEnemyState : State
{
private Navigation2D? GetLevelNavigation()
{
var nodeTuples = Enemy.GetTree().GetNavigation2dNodes();
if (nodeTuples.Item1) return nodeTuples.Item2[0];
return null;
}
private Navigation2D Nav { get; set; }
private EnemyV4 Enemy { get; set; }
private PlayerV2 PlayerRef { get; set; }
public ChaseEnemyState(EnemyV4 enemy)
{
this.Name = EnemyBehaviorStates.ChasePlayer.GetDescription();
Enemy = enemy;
(var hasPlayer, PlayerRef) = Enemy.GetTree().GetPlayerNode();
if (!hasPlayer)
{
Logger.Error("Player ref not found on scene tree");
}
(var hasNav, var navNodes) = Enemy.GetTree().GetNavigation2dNodes();
if (hasNav && navNodes != null)
{
Nav = navNodes[0];
}
this.OnEnter += () => this.Logger.Debug("ChaseEnemyState OnEnter called");
this.OnExit += () => this.Logger.Debug("ChaseEnemyState Exit called");
this.OnFrame += ChasePlayer;
}
private void ChasePlayer(float delta)
{
if (Enemy.IsDebugging && Enemy.HasNode("Line2D"))
{
Enemy.Status.Line = (Line2D)Enemy.GetNode("Line2D");
}
if (Nav!=null)
{
//Enemy.Status.Navigation2D = GetLevelNavigation();
//var nav = (Navigation2D)Enemy.Owner.GetNode("Navigation2D");
//Enemy.Status.Navigation2D = (Navigation2D)Enemy.Owner.GetNode("Navigation2D");
//var from = Enemy.Position;
var from = Nav.ToLocal(Enemy.GlobalPosition);
//var from = Enemy.GlobalPosition;
var to = Nav.ToLocal(PlayerRef.GlobalPosition);
//var to = PlayerRef.Position;
//var to = Enemy.Status.Target.Position;
//var to = Enemy.Status.Target.ToLocal(Enemy.Status.Target.Position);
//Enemy.DrawLine(from, to, new Color(255, 255, 255), 3);
//var paths = Enemy.Status.Navigation2D.GetSimplePath(from, to);
var paths = Nav.GetSimplePath(from, to);
Enemy.Status.NavPath = new Stack<Vector2>(paths);
if (Enemy.Status.Line != null)
{
Enemy.Status.Line.Points = Enemy.Status.NavPath.ToArray();
}
var distance_to_walk = Enemy.MoveSpeed * delta;
while (distance_to_walk > 0f && Enemy.Status.NavPath.Count > 0f)
{
var distance_to_next_point = Enemy.Position.DistanceTo(Enemy.Status.NavPath.Peek());
if (distance_to_walk <= distance_to_next_point)
{
var newPosition = Enemy.Position.DirectionTo(Enemy.Status.NavPath.Peek()) * distance_to_walk;
Enemy.Status.VisionManager.UpdateFacingDirection(newPosition.Normalized());
Enemy.Position += newPosition;
}
else
{
var newPosition = Enemy.Status.NavPath.Pop();
Enemy.Status.VisionManager.UpdateFacingDirection(newPosition.Normalized());
if (Enemy.GetSlideCount() > 0)
{
Enemy.HandleMovableObstacleCollision(newPosition);
}
Enemy.Position = newPosition;
}
distance_to_walk -= distance_to_next_point;
}
if (Enemy.IsDebugging)
{
Enemy.Status.DebugLabel.Text =
#$"
|-----------------------------------------------------------
| Enemy Global Position: {Enemy.GlobalPosition}
| Enemy Local Position: {Enemy.Position}
|----------------------------------------------------------
| Target Global Position: {Enemy.Status.Target.GlobalPosition}
| Target Local Position: {Enemy.Status.Target.Position}
|-----------------------------------------------------------
| From {from}
| To {to}
|-----------------------------------------------------------";
}
}
else
{
Logger.Error("Navigation2D not found");
}
if (Enemy.Status.CurrentCoolDownCounter > 0)
{
Enemy.Status.CurrentCoolDownCounter -= delta;
}
}
}
}
Some images to help show what I'm seeing. Player is green and the enemy is blue. From and To correspond to the values of the variables. The yellow is the sight cone for the enemy and is pointing out in the direction the enemy is walking. Target is an alias for player and the red line is a Line2d I've been using to draw the enemy's path.
//Nav is my Navigation2d
var from = Enemy.Position;
var to = PlayerRef.lPosition;
Second attempt results with :
//Nav is my Navigation2d
var from = Nav.ToLocal(Enemy.GlobalPosition);
var to = Nav.ToLocal(PlayerRef.GlobalPosition);
Any insight at all would be helpful as to what I'm doing wrong.
I can only guess this began failing due to the nodes being moved around. Either the order in the scene tree, or the Navigation2D position. I'm guessing that because the code was previously only using local positions. Meaning that their local coordinates used to match, but they no longer do.
Anyway, this code is correct:
var from = Nav.ToLocal(Enemy.GlobalPosition);
var to = Nav.ToLocal(PlayerRef.GlobalPosition);
And this is progress. You had fragile code before (it depended on the coincidence that the local coordinates matched). So this will ultimately be good.
I believe the issue you are facing comes from here:
Enemy.Position.DistanceTo(Enemy.Status.NavPath.Peek());
Which would be be correct if their local coordinates matched, but they don't. So we need to convert the result we get here.
If you are going to work on the enemy local coordinates, you need to convert again:
Enemy.Position.DistanceTo(Enemy.ToLocal(Nav.ToGlobal(Enemy.Status.NavPath.Peek())));
However, I'd suggest to work in global coordinates instead (since you can write to Enemy.GlobalPosition instead of Enemy.Position):
Enemy.GlobalPosition.DistanceTo(Nav.ToGlobal(Enemy.Status.NavPath.Peek()));
You would have to do the same change in other places in your code.
Let us see here:
var newPosition = Enemy.Position.DirectionTo(Enemy.Status.NavPath.Peek()) * distance_to_walk;
Enemy.Status.VisionManager.UpdateFacingDirection(newPosition.Normalized());
Wait, that isn't a position is it? That's a displacement. Don't mix things up.
Well, the question is what coordinates does UpdateFacingDirection expect.
You also have this code:
var newPosition = Enemy.Status.NavPath.Pop();
Enemy.Status.VisionManager.UpdateFacingDirection(newPosition.Normalized());
Sure a position normalized is a direction… From the origin. But which origin? In this case it is the origin of the Navigation2D…
So, I have searched for UpdateFacingDirection in your code. I found a couple implementations which look like this:
public void UpdateFacingDirection(Vector2 newVelocity)
{
this.Rotation = this.Position.AngleToPoint(newVelocity);
}
See? This makes no sense. The code claims to be taking the angle to go from a point to a velocity. I'll believe your instruction and not your naming. In which case UpdateFacingDirection takes a position in local coordinates.
I have also looked for HandleMovableObstacleCollision, and I found this implementation:
public void HandleMovableObstacleCollision(Vector2 motion)
{
this.PrintCaller();
motion = motion.Normalized();
if (GetSlideCollision(0).Collider is PushBlock box && box.CanBePushed)
{
box.Push(PushSpeed * motion);
}
}
So apparently this only cares about the direction of the argument. Now, this is the Push I found:
public void Push(Vector2 velocity)
{
MoveAndSlide(velocity);
}
So that is global coordinates. Meaning that HandleMovableObstacleCollision takes a direction in global coordinates.
Ok, I think I can rewrite:
var from = Nav.ToLocal(Enemy.GlobalPosition);
var to = Nav.ToLocal(PlayerRef.GlobalPosition);
var paths = Nav.GetSimplePath(from, to);
// …
var distance_to_walk = Enemy.MoveSpeed * delta;
while (distance_to_walk > 0f && paths.Count > 0f)
{
var next_point = Nav.ToGlobal(paths.Peek());
var global_direction = Enemy.GlobalPosition.DirectionTo(next_point);
var global_distance = Enemy.GlobalPosition.DistanceTo(next_point);
if (distance_to_walk <= global_distance)
{
var global_displacement = global_direction * distance_to_walk;
var global_new_position = Enemy.GlobalPosition + global_displacement;
var local_new_position = Enemy.ToLocal(global_new_position);
Enemy.Status.VisionManager.UpdateFacingDirection(local_new_position);
Enemy.GlobalPosition = global_new_position;
}
else
{
_ = paths.Pop();
// var global_displacement = next_point - Enemy.GlobalPosition;
var global_new_position = next_point;
var local_new_position = Enemy.ToLocal(global_new_position);
Enemy.Status.VisionManager.UpdateFacingDirection(local_new_position);
if (Enemy.GetSlideCount() > 0)
{
Enemy.HandleMovableObstacleCollision(global_direction);
}
Enemy.GlobalPosition = global_new_position;
}
distance_to_walk -= global_distance;
}
I don't know what are you storing Enemy.Status.NavPath for, if you are calling navigation each frame. So I removed that from the code above. If you do need it, add it back. Similarly, I don't know what to make of Enemy.Status.Line.Points. My guess is that is only for debug, and it should be in local coordinates of the Line2D, if you need it, perhaps you can use the global transforms to convert them (with Xform and XformInv).
About the line:
_ = paths.Pop();
Is a discard. We don't really need a discard. You can simply call Pop:
paths.Pop();
The discard - in this case - is just meant to indicate that we intentionally do not use the returned value. We don't need to, because we already got it from Peek.
By the way, in the second branch I added comment with global_displacement, you will see what you would need that line for below.
There is something else that bothers me:
if (Enemy.GetSlideCount() > 0)
{
Enemy.HandleMovableObstacleCollision(/*…*/);
}
What slide count? In fact, in HandleMovableObstacleCollision I see you use GetSlideCollision, but what slide collisions? If you don't use MoveAndSlide or similar on the Enemy? Instead I see you write Enemy.Position (which I changed to Enemy.GlobalPosition)?
Let us use MoveAndSlide. The catch is that it does not take a displacement, nor a position, it takes a velocity. So we will pass global_displacement / delta (remember that velocity is displacement divided by delta time). So instead of this:
Enemy.GlobalPosition = global_new_position;
Do this:
Enemy.MoveAndSlide(global_displacement / delta);
Also, don't you want to do that before checking Enemy.GetSlideCount()? Well, I don't know. You can finish figuring this out.
I've downloaded the ARKitSample project with the ship from - https://github.com/xamarin/ios-samples/blob/master/ios11/ARKitSample/ARKitSample/GameViewController.cs
After running the code, it works as expected until I tap on the screen, the gesture recognize event is called and it works fine the first time - I get a plane node with a snapshot of the screen. But when this is called the second time, instead of creating a new node at the location of the second tap, it overlaps the initial child node.
The code is a port of the original swift version from the ARKit keynote here - https://youtu.be/LLRweyZ1KpA?t=1380
private void HandleTap(UIGestureRecognizer gestureRecognize)
{
// Get current frame
var currentFrame = SceneView.Session.CurrentFrame;
if (currentFrame == null) return;
// Create an image plane using a snapshot of the view
var imagePlane = SCNPlane.Create(SceneView.Bounds.Width / 6000, SceneView.Bounds.Height / 6000);
imagePlane.FirstMaterial.Diffuse.Contents = SceneView.Snapshot();
imagePlane.FirstMaterial.LightingModelName = SCNLightingModel.Constant;
// Create a plane node and add it to the scene
var planeNode = SCNNode.FromGeometry(imagePlane);
SceneView.Scene.RootNode.AddChildNode(planeNode);
// Set transform of node to be 10cm in front of the camera
var translation = SCNMatrix4.CreateTranslation(0, 0, 0.1f);
var cameraTranslation = currentFrame.Camera.Transform.ToSCNMatrix4();
planeNode.Transform = SCNMatrix4.Mult(cameraTranslation, translation);
}
I've tried this on an iPhone XS and 7, am I missing something obvious?
The sample changes the node's transform for you. If you want to change the node's position, you can try the code below:
var tapLocation = gestureRecognize.LocationInView(SceneView);
var hitTestResults = SceneView.HitTest(tapLocation, ARHitTestResultType.EstimatedHorizontalPlane);
var hitTestResult = hitTestResults.FirstOrDefault();
if (hitTestResult == null) return;
var position = new SCNVector3(hitTestResult.WorldTransform.Column3.X, hitTestResult.WorldTransform.Column3.Y, hitTestResult.WorldTransform.Column3.Z);
planeNode.Position = position;
I am working on touchscreen app. This app is for a certain industry, but the general ideals are all that is needed to explain issue. To start, im doing a crash course on WPF and am allowing a user to add a new command (Shape - Rectangle) then will assign functions to it. Example, they can add one, then add second, then a third and so on positioning them where they want.
What i would like to find out is how to tell the API when inertia is occurring and say a user touches a shape then flings it across screen, for the event to not only complete when it reaches the edge of the screen (already does this), but also if it intersects or hits another shape. Below is the general code for a quick basic proof of concept.
void Window_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
try
{
// Get the Rectangle and its RenderTransform matrix.
Rectangle rectToMove = e.OriginalSource as Rectangle;
Matrix rectsMatrix = ((MatrixTransform)rectToMove.RenderTransform).Matrix;
// Rotate the Rectangle.
rectsMatrix.RotateAt(e.DeltaManipulation.Rotation,
e.ManipulationOrigin.X,
e.ManipulationOrigin.Y);
// Resize the Rectangle. Keep it square
// so use only the X value of Scale.
rectsMatrix.ScaleAt(e.DeltaManipulation.Scale.X,
e.DeltaManipulation.Scale.X,
e.ManipulationOrigin.X,
e.ManipulationOrigin.Y);
// Move the Rectangle.
rectsMatrix.Translate(e.DeltaManipulation.Translation.X,
e.DeltaManipulation.Translation.Y);
// Apply the changes to the Rectangle.
rectToMove.RenderTransform = new MatrixTransform(rectsMatrix);
Rect containingRect =
new Rect(((FrameworkElement)e.ManipulationContainer).RenderSize);
Rect shapeBounds =
rectToMove.RenderTransform.TransformBounds(
new Rect(rectToMove.RenderSize));
// Check if the rectangle is completely in the window.
// If it is not and intertia is occuring, stop the manipulation.
if (e.IsInertial && !containingRect.Contains(shapeBounds))
{
e.Complete();
}
if (e.IsInertial)
{
//convert current moving rect to new rect drawing type
var tempRect = new Rect(Canvas.GetLeft(rectToMove), Canvas.GetTop(rectToMove), rectToMove.Width, rectToMove.Height);
//we need to convert SytsemWindows.Shapes.Rectablge to SystemWindows.Drawing.Rect first
//List<System.Windows.Shapes.Rectangle> removeCurrent = Commands.Where(r => r != rectToMove).ToList();
//List<System.Windows.Rect> coordinates = Commands.Where(r => r.Name != rectToMove.Name).Select(r => new Rect(Canvas.GetLeft(r), Canvas.GetTop(r), r.Width, r.Height)).ToList();
List<System.Windows.Rect> recs = new List<Rect>();
List<Rectangle> test = Commands.Where(r => r.Name != rectToMove.Name).ToList();
foreach (System.Windows.Shapes.Rectangle re in test)
{
if (re != rectToMove)
{
recs.Add(new Rect(Canvas.GetLeft(re), Canvas.GetTop(re), re.Width, re.Height));
}
}
//if rect item is in motion inertia and it intersects with any of pre made command rects, then stop.
if (recs.Any(c => c.InteriorIntersectsWith(tempRect)))
{
//There is overlapping
e.Complete();
}
}
}
catch(Exception ex)
{
}
e.Handled = true;
}
The problem is inertia works fine. Once a second shape is then added to the canvas, inertia no longer works. The user can click and drag them but when trying to flick the shapes they stop instantly as if it reports from all positions that it is intersecting with another shape even when they are no where near each other. Any ideas on what i am doing wrong?
So i want to align 2 objects together on the left side of the object that is the furthest to the left. So i'll sketch out a scenario:
There are 2 images on random positions on the board. You select both images(with a selection tool that has been made) and you than click: "Align objects to the left"
The image that is the furthest to the RIGHT should than snap to the same position of the edge on the left side of the other image. So when clicking the button, my code should calculate both left sides(the edge of the image on the left) of the images position, than check which one if the furthest to the right on the canvas, and move that one to the same X axis as the other image.
This way the end result will be the images will be on the exact same X axis. So if image 1 is on -73 & Image 2 is on -50, image 2 should than also move to -73, regardless of the rotation of either images.
Currently i can only find out how to find the middle position of the image, my code looks like this atm:
using com.company.program.core.pageObjects;
using com.company.program.ui.colorPicker;
using UnityEngine;
namespace com.company.program.core.SelectionManager
{
public static class SelectionAlignment
{
public static void AlignLeft(PageObjectBase pageObject)
{
Debug.Log("Let's check if this is a group first!");
if (pageObject is PageObjectGroup)
Debug.Log("Now we can AlignLeft!");
PageObjectGroup group = (PageObjectGroup)pageObject;
foreach (PageObjectBase objectBase in group.Children)
{
//objectBase.transform.position
Debug.Log("Position is now" + objectBase.transform.position);
Debug.Log("Left Position is" + objectBase.transform.position + -objectBase.transform.right);
}
}
}
}
}
Note: I have no moving function yet as im first trying to figure out what the position of the most left side of the image is. The first Debug.log works and displays the normal position(middle point of image). The second one doesn't work, and displays the same. Both images get instantiated during runtime.
Hopefully this is enough information, i'm a long time lurker but have never posted anything myself, so be gentle on me if i forgot to add information.
From your question left means a smaller X value.
So in general the left edge of an image (assuming PageObjectBase somehow inherits from MonoBehaviour and you are speaking about Image component from Unity UI with a RectTransform)
is the most left of all four corners of the image. You can get all four corners by using GetWorldCorners
private float GetLeftEdge(PageObjectBase obj)
{
RectTransform rectTransformComponent = obj.gameObject.GetComponent<RectTransform>();
if(!rectTransformComponent)
{
Debug.LogError("No Image component found", this);
return 0;
}
Vector3[] v = new Vector3[4];
rectTransformComponent.GetWorldCorners(v);
float mostLeftCorner = float.MaxValue;
foreach(var pos in v)
{
mostLeftCorner = Mathf.Min(mostLeftCorner, pos.x);
}
return mostLeftCorner;
}
This should also work if the images are rotated.
If you are not using RectTransform you have to get the width another way somehow but the rest stays the same.
Than in your loop you first have to get the smallest (most left) edge so I would split it in two loops:
// Start with the max float value so any other value should be smaller
float mostLeftEdge = float.MaxValue;
PageObjectBase referenceToMostLeftObject;
// Get the most left position and object reference
foreach (PageObjectBase objectBase in group.Children)
{
float leftEdge = GetLeftEdge(objectBase);
if(leftEdge < mostLeftEdge)
{
referenceToMostLeftObject = objectBase;
mostLeftEdge = leftEdge;
}
}
// Now you have the most left edge value and the object which is your reference
// Just a little safety skip to not move to strange values
if(referenceToMostLeftObject == null || Mathf.Approximately(mostLeftEdge, float.MaxValue))
{
Debug.LogError("Ups, I think something went wrong getting the mostLeftEdge", this);
return;
}
// Move the other objects to match with that edge
foreach (PageObjectBase objectBase in group.Children)
{
// Skip the reference object
if(objectBase == referenceToMostLeftObject) continue;
// First get the difference
float leftEdge = GetLeftEdge(objectBase);
// Should always be negative
float difference = mostLeftEdge - leftEdge;
// Than move it there
var current = objectBase.transform.position;
// Since difference should be negative
// Adding it to the current position should result in the wanted position
var newPosition = new Vector3(current.x + difference, current.y, current.z);
objectBase.transform.position = newPosition;
}
I need to change the RenderTransformOrigin property of some annotation objects, so that they always rotate around the center (y) of the other side (x).
So let's say the user clicks on the right hand side of the object, then the rotation point would be (0.0, 0.5), but if the user wishes to change the rotation from the other side of the item, then he can click on the left hand side of it, in which case the rotation point becomes (1.0, 0.5).
The problem: the reason I am asking for help is that whenever this happens, the item jumps on the screen. How much it moves, depends on the angle of the item (if it is not rotated at all, then it won't move at all). The angle itself does not change during the movement, only the position. (I realise that it is a bit difficult to understand the problem without images, but due to stackoverflow's rules, I am not allowed to attach images to make things easier.) So this only happens when selecting the other side (e.g. clicking on the left hand side after the right hand side), and the movement/jump depends on the angle (the larger the angle, the larger the jump).
What I would like to do is to change the mentioned property for the rotation to happen around the selected edge of the object, without the object keep jumping up and down when changing side.
Thank you for your help!
Edit [2015-03-10_12-17-21]
class XYZ
{
...
private void RotateThumb_DragStarted(object sender, DragStartedEventArgs e) {
m_rotateTransform = m_designerItem.RenderTransform as RotateTransform;
if (m_rotateTransform == null) {
m_designerItem.RenderTransform = new RotateTransform(0);
m_rotateTransform = m_designerItem.RenderTransform as RotateTransform;
}
Point positionWithinAnnotation = Mouse.GetPosition(m_designerItem);
var selectedThumb = GetThumbPosition(m_designerItem, positionWithinAnnotation);
double relativeCenterX = ((selectedThumb == ThumbPosition.Left) ? (1.0D) : (0.0D));
double relativeCenterY = 0.5D;
m_designerItem.RenderTransformOrigin = new Point(relativeCenterX, relativeCenterY);
m_transformOrigin = m_designerItem.RenderTransformOrigin;
m_rotationPoint = m_designerItem.TranslatePoint(
new Point(relativeCenterX * m_designerItem.ActualWidth,
relativeCenterY * m_designerItem.ActualHeight),
m_canvas);
Point startPoint = Mouse.GetPosition(m_canvas);
m_startVector = Point.Subtract(startPoint, m_rotationPoint);
}
private void handleRotate(DragDeltaEventArgs e) {
Point currentPoint = Mouse.GetPosition(m_canvas);
Vector deltaVector = Point.Subtract(currentPoint, m_rotationPoint);
double angle = Vector.AngleBetween(m_startVector, deltaVector);
m_rotateTransform.Angle += Math.Round(angle, 0);
m_startVector = deltaVector;
m_designerItem.InvalidateMeasure();
}
...
}
Without a good, minimal, complete code example it's difficult to know for sure what you need to do to fix the code.
That said, from the description it sounds to me like you should be using the RotateTransform.CenterX and RotateTransform.CenterY properties, instead of setting RenderTransformOrigin.
For example:
private void RotateThumb_DragStarted(object sender, DragStartedEventArgs e) {
m_rotateTransform = m_designerItem.RenderTransform as RotateTransform;
if (m_rotateTransform == null) {
m_designerItem.RenderTransform = new RotateTransform();
m_rotateTransform = m_designerItem.RenderTransform as RotateTransform;
}
Point positionWithinAnnotation = Mouse.GetPosition(m_designerItem);
var selectedThumb = GetThumbPosition(m_designerItem, positionWithinAnnotation);
double relativeCenterX = ((selectedThumb == ThumbPosition.Left) ? (1.0D) : (0.0D));
double relativeCenterY = 0.5D;
m_rotateTransform.CenterX = relativeCenterX;
m_rotateTransform.CenterY = relativeCenterY;
// Without a complete example, I'm not sure I know what this part is
// supposed to be doing. I think it's fine, but maybe it needs to be
// fixed too?
m_rotationPoint = m_designerItem.TranslatePoint(
new Point(relativeCenterX * m_designerItem.ActualWidth,
relativeCenterY * m_designerItem.ActualHeight),
m_canvas);
Point startPoint = Mouse.GetPosition(m_canvas);
m_startVector = Point.Subtract(startPoint, m_rotationPoint);
}