Getting rotation of ARAnchor from didAdd anchors - scenekit

So I am using session(_ session: ARSession, didAdd anchors: [ARAnchor] to get all detected planes (I cannot use renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) Because I am using RealityKit instead of ARKit).
So my question is, How can I get the rotation of the detected anchor from this method? I want to create a plane Entity that has exactly the same position, size, orientation & rotation of the detected anchor. So far Im using the extent for the size, center for position but I can't figure out how to get the rotation. Because right now Its detected correctly (Right position and size)but with wrong rotation that the real plane.

You can get the position and orientation of the ARPlaneAnchor from its transform property, then use that to render the bounding plane in RealityKit.
extension float4x4 {
/// Returns the translation components of the matrix
func toTranslation() -> SIMD3<Float> {
return [self[3,0], self[3,1], self[3,2]]
}
/// Returns a quaternion representing the
/// rotation component of the matrix
func toQuaternion() -> simd_quatf {
return simd_quatf(self)
}
}
func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) {
guard let planeAnchor = anchors[0] as? ARPlaneAnchor else {
return
}
// NOTE: When the ARPlaneAnchor is first created its transform
// contains the position and the plane.center == [0,0,0]. On updates
// the plane.center will change as the extents of the plane change.
let position = planeAnchor.transform.toTranslation()
let orientation = planeAnchor.transform.toQuaternion()
// The center is a position before the orientation is taken in to
// account, so we need to rotate it to get the true position before
// we add it to the anchors position
let rotatedCenter = orientation.act(planeAnchor.center)
// You have a ModelEntity that you created earlier
// e.g. modelEntity
// Assuming you added the entity to an anchor that is just
// fixed at 0,0,0 in the world, or you created a custom entity
// with HasAnchor set to 0,0,0
modelEntity.transform.translation = position + rotatedCenter
modelEntity.transform.rotation = orientation
// Doesn't seem to be a way to update meshes in RealityKit so
// just create a new plane mesh for the updated dimensions
modelEntity.model?.mesh = MeshResource.generatePlane(
width: planeAnchor.extent.x,
depth: planeAnchor.extent.z
)
}

Related

Winforms pie chart legend text length affects label and chartarea size

I have the following ChartArea Annotation settings set up:
private void chart1_PrePaint(object sender, ChartPaintEventArgs e)
{
if (e.ChartElement is ChartArea)
{
var ta = new TextAnnotation();
ta.IsMultiline = true;
ta.Text = "Results of Calculation\n%";
ta.Width = e.Position.Width;
ta.Height = e.Position.Height;
ta.X = e.Position.X;
ta.Y = e.Position.Y;
ta.Font = new Font("Candara", e.Position.Height / 10, FontStyle.Regular);
chart1.Annotations.Add(ta);
}
}
A few issues with this, and with the Legend in relation to my other posted question:
My other Pie Chart Legend/ChartArea question
With this PrePaint setup, I'm not sure if my position is correct for the TextAnnotation. I'm using the e.Position but it's coming out not "exactly" centered in the middle of the doughnut of the pie chart area. I'd like it to be centered perfectly. Not sure what other property to use here.
A second issue is that when Legend text length changes, it "pushes" and makes the ChartArea itself smaller so the pie chart gets smaller. I'd like it to be the other way around, where the ChartArea pie chart stays the same size but the Legend gets pushes aside.
Is this possible?
The following is the position setup of the pie chart:
Thanks
I'm sorry I couldn't help more, last time. I tested the centering of the TextAnnotation and in fact it has problems when the InnerPlotPosition is set to auto. Moreover, the answer found at link creates a new instance of the TextAnnotation at every PrePaint, causing the overlapping of TextAnnotations and the blurrying of the centered text.
I couldn't find a way to avoid the resizing of the doughnut (I'm not sure it's even possible, at this point...I'll wait for some other answers) but maybe this can work out as a workaround.
First I created a dictionary to store the centered TextAnnotations references (the key is the graph name, in case you have more than one), then in the PrePaint event I get the correct reference of the TextAnnotation used in the graph and update the coordinates of that one.
Second, I set the InnerPlotPosition manually, this seems to solve the problem of the centering of the TextAnnotation. Of course, you need to specify coordinates and size for the InnerPlot like I did with the line:
chart1.ChartAreas[0].InnerPlotPosition = new ElementPosition(0, 0, 60.65f, 94.99f);
Lastly, I set the position and the size of the legend manually and, with the extension method WrapAt I set a "line break" every _maxLegendTextBeforeWrap in the legend items text. Couldn't find a way to make it dynamically change with the width of the legend area, so it has to be set manually.
Below there's a GIF of the resulting effect. Don't know if this suits you as a solution (too much tweaking and code, for my taste), but anyway. Maybe this can trigger some new ideas on how to solve.
To do so I created these global variables:
/// <summary>
/// Saves the currently doughnut centered annotations per graph.
/// </summary>
private IDictionary<string, TextAnnotation> _annotationsByGraph;
/// <summary>
/// Number of characters
/// </summary>
private int _maxLegendTextBeforeWrap = 10;
/// <summary>
/// Legend area width.
/// </summary>
private int _legendWidth = 20;
/// <summary>
/// Legend area height.
/// </summary>
private int _legendHeight = 90;
This is the handler of the Load event:
private void ChartTest_Load(object sender, EventArgs e)
{
// ** Start of test data **
chart1.Series["Series1"].Points.AddXY("A", 33);
chart1.Series["Series1"].Points[0].LegendText = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
chart1.Series["Series1"].Points.AddXY("B", 33);
chart1.Series["Series1"].Points[1].LegendText = "BBBBBBBBBBBBBBBBBBBB";
chart1.Series["Series1"].Points.AddXY("C", 34);
chart1.Series["Series1"].Points[2].LegendText = "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC";
// ** End of test data **
// Creates a new instance of the dictionary storing the references to the annotations.
_annotationsByGraph = new Dictionary<string, TextAnnotation>();
// Createa a new instance of an annotation for the chart1 graph.
_annotationsByGraph.Add(chart1.Name, new TextAnnotation());
// Manually setting the position of the chart area prevents the imperfect positioning of the
// TextAnnotation at the center of the doughnut.
chart1.ChartAreas[0].Position.Auto = true;
// Manually set the position of the InnerPlotPosition area prevents the imperfect positioning of the
// TextAnnotation at the center of the doughnut.
chart1.ChartAreas[0].InnerPlotPosition.Auto = false;
chart1.ChartAreas[0].InnerPlotPosition = new ElementPosition(0, 0, 60.65f, 94.99f);
// Minimum size for the legend font.
chart1.Legends[0].AutoFitMinFontSize = 5;
// Set the legend style as column.
chart1.Legends[0].LegendStyle = LegendStyle.Column;
// Splits the legend texts with the space char every _maxLegendTextBeforeWrap characters.
chart1.Series["Series1"].Points.ToList().ForEach(p => p.LegendText = p.LegendText.WrapAt(_maxLegendTextBeforeWrap));
}
This is the handler of the PrePaint event:
private void chart1_PrePaint(object sender, ChartPaintEventArgs e)
{
if (e.ChartElement is ChartArea)
{
// Get the reference to the corresponding text annotation for this chart.
// We need this, otherwise we are creating and painting a new instance of a TextAnnotation
// at every PrePaint, with the resulting blurrying effect caused by the overlapping of the text.
var ta = _annotationsByGraph[e.Chart.Name];
// Check if the annotation has already been added to the chart.
if (!e.Chart.Annotations.Contains(ta))
e.Chart.Annotations.Add(ta);
// Set the properties of the centered TextAnnotation.
ta.IsMultiline = true;
ta.Text = "Results of Calculation\nx%";
ta.Font = new Font("Candara", e.Position.Height / 10, FontStyle.Regular);
ta.Width = e.Position.Width;
ta.Height = e.Position.Height;
ta.X = e.Position.X;
ta.Y = e.Position.Y;
// Move the legend manually to the right of the doughnut.
e.Chart.Legends[0].Position = new ElementPosition(e.Position.X + e.Position.Width, e.Position.Y, _legendWidth, _legendHeight);
}
}
This is what the button does:
private void BtnChangeLegendItemLength_Click(object sender, EventArgs e)
{
if (chart1.Series["Series1"].Points[1].LegendText.StartsWith("DD"))
chart1.Series["Series1"].Points[1].LegendText = "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB".WrapAt(_maxLegendTextBeforeWrap);
else
chart1.Series["Series1"].Points[1].LegendText = "DDDDDD".WrapAt(_maxLegendTextBeforeWrap);
}
This is the extension method definition:
internal static class ExtensionMethods
{
public static string WrapAt(this string legendText, int maxLengthBeforeWrap)
{
if (legendText.Length <= maxLengthBeforeWrap)
return legendText;
// Integer division to get how many times we have to insert a space.
var times = legendText.Length / maxLengthBeforeWrap;
// Counter of added spaces.
var spacesAdded = 0;
// Iterate for each space char needed.
for (var i = 1; i <= times; i++)
{
// Insert a space char every maxLengthBeforeWrap positions.
legendText = legendText.Insert(maxLengthBeforeWrap * i + spacesAdded, new string(' ', 1));
spacesAdded++;
}
return legendText;
}
}

Can move only the last object placed in Scene

Hy! I can move only the last virtual object placed in scene. When i add another object, the previous object added remains static and i cannot do any action on it. Can you give me please give me a solution to move, scale, rotate what object i want when I touch it?
Seeing as you haven't posted any code, its hard to provide an exact answer.
However, in order to keep track of the current SCNNode you wish to use, when you load a model etc you could store it as a variable e.g:
var currentNode: SCNNode!
You could also use the name property of the node when you are loading your model to keep a more specific reference to it.
Here is an example of doing this:
/// Example Of Loading An SCNScene & Setting It As The Current Node
func loadModel(){
let modelPath = "Character.scn"
//1. Get The Reference To Our SCNScene & Get The Model Root Node
guard let model = SCNScene(named: modelPath),
let modelObject = model.rootNode.childNode(withName: "Root", recursively: false) else { return }
//2. Scale It
modelObject.scale = SCNVector3(0.4, 0.4, 0.4)
//3. Add It To The Scene & Position It 1.5m Away From The Camera
augmentedRealityView.scene.rootNode.addChildNode(modelObject)
modelObject.position = SCNVector3(0, 0, -1.5)
//4. Set It As The Current Node & Assign A Name
currentNode = modelObject
currentNode.name = "Character"
}
You can also make use of an SCNHitTest which:
Looks for SCNGeometry objects along the ray you specify. For each
intersection between the ray and and a geometry, SceneKit creates a
hit-test result to provide information about both the SCNNode object
containing the geometry and the location of the intersection on the
geometry’s surface.
This can be used to differentiate between which node(s) have been hit e.g:
/// Detects A Tap On An SCNNode
///
/// - Parameter gesture: UITapGestureRecognizer
#objc func detectTap(_ gesture: UITapGestureRecognizer){
//1. Get The Current Touch Point
let currentTouchPoint = gesture.location(in: self.augmentedRealityView)
//2a. Perform An SCNHitTest To Detect If An SCNNode Has Been Touched
guard let nodeHitTest = self.augmentedRealityView.hitTest(currentTouchPoint, options: nil).first else { return }
if nodeHitTest.node == currentNode{
print("The Current Node Has Been Touched")
}
//Or To See Which Node Has Been Touched
if nodeHitTest.node.name == "The Model"{
print("The Model Has Been Touched")
}
}
Another way you can handle this is to add a UITapGestureRecognizer to your view, and enable interaction based on the result e.g. setting the currentNode that way e.g:
/// Detects A Tap On An SCNNode & Sets It As The Current Node
///
/// - Parameter gesture: UITapGestureRecognizer
#objc func detectTap(_ gesture: UITapGestureRecognizer){
//1. Get The Current Touch Point
let currentTouchPoint = gesture.location(in: self.augmentedRealityView)
//2a. Perform An SCNHitTest To Detect If An SCNNode Has Been Touched
guard let nodeHitTest = self.augmentedRealityView.hitTest(currentTouchPoint, options: nil).first else { return }
//2b. Set It As The Current Node
currentNode = nodeHitTest.node
}
Then you can apply logic to ensure that only the currentNode moves etc:
/// Moves An SCNNode
///
/// - Parameter gesture: UIPanGestureRecognizer
#objc func moveNode(_ gesture: UIPanGestureRecognizer) {
//1. Get The Current Touch Point
let currentTouchPoint = gesture.location(in: self.augmentedRealityView)
//2. If The Gesture State Has Begun Perform A Hit Test To Get The SCNNode At The Touch Location
if gesture.state == .began{
//2a. Perform An SCNHitTest To Detect If An SCNNode Has Been Touched
guard let nodeHitTest = self.augmentedRealityView.hitTest(currentTouchPoint, options: nil).first else { return }
//2b. Get The SCNNode Result
if nodeHitTest.node == currentNode{
//3a. If The Gesture State Has Changed Then Perform An ARSCNHitTest To Detect Any Existing Planes
if gesture.state == .changed{
//3b. Get The Next Feature Point Etc
guard let hitTest = self.augmentedRealityView.hitTest(currentTouchPoint, types: .existingPlane).first else { return }
//3c. Convert To World Coordinates
let worldTransform = hitTest.worldTransform
//3d. Set The New Position
let newPosition = SCNVector3(worldTransform.columns.3.x, worldTransform.columns.3.y, worldTransform.columns.3.z)
//3e. Apply To The Node
currentNode.simdPosition = float3(newPosition.x, newPosition.y, newPosition.z)
}
}
}
}
This should be more than enough to point you in the right direction...

Generalizing the ScaleTransform (WPF) when image is not aligned to x-y axes

My linear algebra is weak. WPF is a great system for Rendering different transformations upon an image. However, the standard ScaleTransform will only scale the image's along the x-y axes. When the edges have first been rotated, the result of applying the ScaleTransform will result in a skewed transformation (as shown below) since the edges are no longer aligned.
So, if I have an image that has undergone several different transforms with the result being shown by the WPF rendering system, how do I calculate the correct matrix transform to take the (final rotated image) and scale it along the axes of the rendered image?
Any help or suggestions will be most appreciated.
TIA
(For the complete code, please see my previous question.)
Edit #1: To see the above effect:
Drop image onto Inkcavas. -- no skewing seen.
Rotate image counterclockwise (to about 45deg) -- no skewing seen.
Make the image larger (about twice its prescaled size -- no skewing seen.
Rotate the image clockwise (about back to where it started) -- skewing is
immediately seen during and after the rotation.
If step 3 is skipped, simple rotation -- no matter how many times done -- will not cause the skewing effect. Actually, this makes sense. The ScaleTransform preserves the distance from center from the edges of the image. If the image is at an angle, the x-y distance from the edges of the transform are no longer constant through the width and length of the rendered image. So the edges get appropriately scaled, but the angles are changed.
Here is the most relevant code:
private ImageResizing(Image image)
{
if (image == null)
throw new ArgumentNullException("image");
_image = image;
TransformGroup tg = new TransformGroup();
image.RenderTransformOrigin = new Point(0.5, 0.5); // All transforms will be based on the center of the rendered element.
tg.Children.Add(image.RenderTransform); // Keeps whatever transforms have already been applied.
image.RenderTransform = tg;
_adorner = new MyImageAdorner(image); // Create the adorner.
InstallAdorner(); // Get the Adorner Layer and add the Adorner.
}
Note: The image.RenderTransformOrigin = new Point(0.5, 0.5) is set to the center
of the rendered image. All transforms will be based on the center of the image at the time it is seem by the transform.
public MyImageAdorner(UIElement adornedElement)
: base(adornedElement)
{
visualChildren = new VisualCollection(this);
// Initialize the Movement and Rotation thumbs.
BuildAdornerRotate(ref moveHandle, Cursors.SizeAll);
BuildAdornerRotate(ref rotateHandle, Cursors.Hand);
// Add handlers for move and rotate.
moveHandle.DragDelta += new DragDeltaEventHandler(moveHandle_DragDelta);
moveHandle.DragCompleted += new DragCompletedEventHandler(moveHandle_DragCompleted);
rotateHandle.DragDelta += new DragDeltaEventHandler(rotateHandle_DragDelta);
rotateHandle.DragCompleted += new DragCompletedEventHandler(rotateHandle_DragCompleted);
// Initialize the Resizing (i.e., corner) thumbs with specialized cursors.
BuildAdornerCorner(ref topLeft, Cursors.SizeNWSE);
// Add handlers for resizing.
topLeft.DragDelta += new DragDeltaEventHandler(TopLeft_DragDelta);
topLeft.DragCompleted += TopLeft_DragCompleted;
// Put the outline border arround the image. The outline will be moved by the DragDelta's
BorderTheImage();
}
#region [Rotate]
/// <summary>
/// Rotate the Adorner Outline about its center point. The Outline rotation will be applied to the image
/// in the DragCompleted event.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void rotateHandle_DragDelta(object sender, DragDeltaEventArgs e)
{
// Get the position of the mouse relative to the Thumb. (All cooridnates in Render Space)
Point pos = Mouse.GetPosition(this);
// Render origin is set at center of the adorned element. (all coordinates are in rendering space).
double CenterX = AdornedElement.RenderSize.Width / 2;
double CenterY = AdornedElement.RenderSize.Height / 2;
double deltaX = pos.X - CenterX;
double deltaY = pos.Y - CenterY;
double angle;
if (deltaY.Equals(0))
{
if (!deltaX.Equals(0))
angle = 90;
else
return;
}
else
{
double tan = deltaX / deltaY;
angle = Math.Atan(tan); angle = angle * 180 / Math.PI;
}
// If the mouse crosses the vertical center,
// find the complementary angle.
if (deltaY > 0)
angle = 180 - Math.Abs(angle);
// Rotate left if the mouse moves left and right
// if the mouse moves right.
if (deltaX < 0)
angle = -Math.Abs(angle);
else
angle = Math.Abs(angle);
if (double.IsNaN(angle))
return;
// Apply the rotation to the outline. All Transforms are set to Render Center.
rotation.Angle = angle;
rotation.CenterX = CenterX;
rotation.CenterY = CenterY;
outline.RenderTransform = rotation;
}
/// Rotates image to the same angle as outline arround the render origin.
void rotateHandle_DragCompleted(object sender, DragCompletedEventArgs e)
{
// Get Rotation Angle from outline. All element rendering is set to rendering center.
RotateTransform _rt = outline.RenderTransform as RotateTransform;
// Add RotateTransform to the adorned element.
TransformGroup gT = AdornedElement.RenderTransform as TransformGroup;
RotateTransform rT = new RotateTransform(_rt.Angle);
gT.Children.Insert(0, rT);
AdornedElement.RenderTransform = gT;
outline.RenderTransform = Transform.Identity; // clear transform from outline.
}
#endregion //Rotate
#region [TopLeft Corner
// Top Left Corner is being dragged. Anchor is Bottom Right.
void TopLeft_DragDelta(object sender, DragDeltaEventArgs e)
{
ScaleTransform sT = new ScaleTransform(1 - e.HorizontalChange / outline.ActualWidth, 1 - e.VerticalChange / outline.ActualHeight,
outline.ActualWidth, outline.ActualHeight);
outline.RenderTransform = sT; // This will immediately show the new outline without changing the Image.
}
/// The resizing outline for the TopLeft is based on the bottom right-corner. The resizing transform for the
/// element, however, is based on the render origin being in the center. Therefore, the Scale transform
/// received from the outling must be recalculated to have the same effect--only from the rendering center.
///
/// TopLeft_DragCompleted resize the element rendering.
private void TopLeft_DragCompleted(object sender, DragCompletedEventArgs e)
{
// Get new scaling from the Outline.
ScaleTransform _sT = outline.RenderTransform as ScaleTransform;
scale.ScaleX *= _sT.ScaleX; scale.ScaleY *= _sT.ScaleY;
Point Center = new Point(AdornedElement.RenderSize.Width/2, AdornedElement.RenderSize.Height/2);
TransformGroup gT = AdornedElement.RenderTransform as TransformGroup;
ScaleTransform sT = new ScaleTransform( _sT.ScaleX, _sT.ScaleY, Center.X, Center.Y);
gT.Children.Insert(0, sT);
AdornedElement.RenderTransform = gT;
outline.RenderTransform = Transform.Identity; // Clear outline transforms. (Same as null).
}
#endregion
Note: I am adding each new transform to the first of the children list. This makes calculations on the image easier.
I could not find with Google or in text all the elements needed to answer this question completely. So, for all other newbies like my self, I will post this (very long) answer. (Editors and Gurus please feel free to correct).
A word toward setup. I have an inkcanvas onto which an image is dropped and added as a child of the inkcanvas. At the time of the drop, an adorner containing a Thumb on each corner for resizing, a Top-Middle thumb for rotating, and a middle thumb for translation is added for final positioning of the image. Along with a "outline" designed as a path element, the Thumbs and outline complete the Adorner and create a kind of wire frame around the adorned element.
There are multiple key points:
WPF first uses a layout pass to position elements within their parent container, followed by a rendering pass to arrange the element. Transforms can be applied to either or both the layout and rendering passes. However, it needs to be noted that the layout pass uses an x-y coordinate system with the origin on the top left of the parent, where as the rendering system inherently references the top left of the child element. If the layout position of the dropped element is not specifically defined, it will by default be added to the "origin" of the parent container.
The RenderTransform is by default a MatrixTransform but can be replaced by a TransformGroup. Using either or both of these allows for Matrices (in the MatrixTransform) or Transforms (in the TransformGroup) to be applied in any order. My preference was to use the MatrixTransforms to better see the relationship between scaling, rotation, and translation.
The rendering of the adorner follows the element it adorns. That is, the element's rendering will also be applied to the Adorner. This behavior can be overriden by use of
public override GeneralTransform GetDesiredTransform(GeneralTransform transform)
As noted in the initial question, I had avoided using SetTop() and SetLeft() as they messed up my other matrices. In hindsight, the reason my matrices failed was because SetTop() and SetLeft() apparently work during the layout phase--so all my coordinates for rendering were off. (I was using a TransalateTransform to position the image upon drag-drop.) However, using SetTop() and SetLeft() apparently act during the Layout Phase. Using this greatly simplified the calculations needed for the Rendering Phase since all coordinates could refer to the image without regard to the position on the canvas.
private void IC_Drop(object sender, DragEventArgs e)
{
InkCanvas ic = sender as InkCanvas;
// Setting InkCanvasEditingMode.None is necessary to capture DrawingLayer_MouseDown.
ic.EditingMode = InkCanvasEditingMode.None;
ImageInfo image_Info = e.Data.GetData(typeof(ImageInfo)) as ImageInfo;
if (image_Info != null)
{
// Display enlarged image on ImageLayer
// This is the expected format for the Uri:
// ImageLayer.Source = new BitmapImage(new Uri("/Images/Female - Front.png", UriKind.Relative));
// Source = new BitmapImage(image_Info.Uri);
Image image = new Image();
image.Width = image_Info.Width * 4;
// Stretch.Uniform keeps the Aspect Ratio but totally screws up resizing the image.
// Stretch.Fill allows for resizing the Image without keeping the Aspect Ratio.
image.Stretch = Stretch.Fill;
image.Source = new BitmapImage(image_Info.Uri);
// Position the drop. Note that SetLeft and SetTop are active during the Layout phase of the image drop and will
// be applied before the Image hits its Rendering stage.
Point position = e.GetPosition(ic);
InkCanvas.SetLeft(image, position.X);
InkCanvas.SetTop(image, position.Y);
ic.Children.Add(image);
ImageResizing imgResize = ImageResizing.Create(image);
}
}
Since I want to be able to resize the image from any direction, the image is set with Stretch.Fill. When Stretch.Uniform was used, the image appeared to first be resized then jump back to its initial size.
Since I am using MatrixTransform, the order of the Matrices is important. So when applying the Matrices, for my use
// Make new render transform. The Matrix order of multiplication is extremely important.
// Scaling should be done first, followed by (skewing), rotation and translation -- in
// that order.
MatrixTransform gT = new MatrixTransform
{
Matrix = sM * rM * tM
};
ele.RenderTransform = gT;
Scaling (sM), is performed before Rotation (rM). Translation is applied last. (C# does matrix multiplication from left to right).
In review of the matrices, it is apparent that the Rotation Matrix also involves skewing elements. (Which makes sense since apparently the RotationTransform is intended to keep the angles at the edges constant). Thus, the Rotation Matrix depends on the size of the image.
In my case, the reason scaling after rotation was causing skewing is because the Scaling transform multiplies the distance between points of the image and the x-y axes. So if the edge of the image is not of constant distance to the x-y axes, scaling will distort (i.e., skew) the image.
Putting this together, results in the following method to resize the image:
Action<Matrix, Vector> DragCompleted = (growthMatrix, v) =>
{
var ele = AdornedElement;
// Get the change vector. Transform (i.e, Rotate) change vector into x-y axes.
// The Horizontal and Vertical changes give the distance between the the current cursor position
// and the Thumb.
Matrix m = new Matrix();
m.Rotate(-AngleDeg);
Vector v1 = v * m;
// Calculate Growth Vector.
var gv = v1 * growthMatrix;
// Apply new scaling along the x-y axes to obtain the rendered size.
// Use the current Image size as the reference to calculate the new scaling factors.
var scaleX = sM.M11; var scaleY = sM.M22;
var W = ele.RenderSize.Width * scaleX; var H = ele.RenderSize.Height * scaleY;
var sx = 1 + gv.X/ W; var sy = 1 + gv.Y / H;
// Change ScalingTransform by applying the new scaling factors to the existing scaling transform.
// Do not add offsets to the scaling transform matrix as they will be included in future scalings.
// With RenderTransformOrigin set to the image center (0.5, 0.5), scalling occurs from the center out.
// Move the new center of the new resized image to its correct position such that the image's thumb stays
// underneath the cursor.
sM.Scale(sx, sy);
tM.Translate(v.X / 2, v.Y / 2);
// New render transform. The order of the transform's is extremely important.
MatrixTransform gT = new MatrixTransform
{
Matrix = sM * rM * tM
};
ele.RenderTransform = gT;
outline.RenderTransform = Transform.Identity; // clear this transform from the outline.
};
Just to be clear, my "Growth Matrix" is defined in such a manner as to result in "Positive" growth as the cursor is moved away from the center of the image. For Example, the TopLeft corner will "grow" the image when moved to the left and up. Hence
growth matrix = new Matrix(-1, 0, 0, -1, 0, 0) for top-left corner.
The last problem is that of correctly calculating the rotation center (i.e., I want to spin, not orbit). This becomes greatly simplified by using
// All transforms will be based on the center of the rendered element.
AdornedElement.RenderTransformOrigin = new Point(0.5, 0.5);
Lastly, since I am scaling from a corner, the center of the image needs to be translated to keep the corner underneath the cursor.
Sorry for the length of this answer, but there is much to cover (and learn :) ). Hope this helps somebody.

Use SKSpriteNode(s) Multiple Times From An Array

The app I'm working on has multiple nodes which are added repeatedly to the game. What I have been doing is creating new SKSpriteNodes using an array of texture, and once the node has been added, I assign that node with a specific physics body. But what I would like to do is instead of a having an array of textures, have an array of SKSpriteNodes with the physics body pre-assigned.
I believe I have figured out how to do this, the problem I have is when I try to add that SKSpriteNode more then once to the scene.
For example, below is I'm been playing around with, which works, but if I add another line of addChild(arraySegment_Type1[0] as SKSpriteNode), I get an error and the app crashes.
override func didMoveToView(view: SKView) {
anchorPoint = CGPointMake(0.5, 0.5)
var arraySegment_Type1:[SKSpriteNode] = []
var sprite = SKSpriteNode(imageNamed: "myimage")
sprite.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(sprite.size.width, sprite.size.height))
sprite.physicsBody?.dynamic = false
sprite.name = "mysprite"
arraySegment_Type1.append(sprite)
addChild(arraySegment_Type1[0] as SKSpriteNode)
}
Does anybody know how I would be able to load SKSpriteNodes multiple times from an array?
What your code does at the moment is the following:
Creates an empty array of the type [SKSpriteNode]
Creates a single SKSpriteNode instance
Ads a physicsBody to the sprite
Ads this single spriteNode to an array
Picks out the first object in the array (index 0) and ads it to the parentNode.
Your app crashes because you are trying to add the same SKSpriteNode instance to the scene several times over.
Here is a reworked version of your code that actually ads several sprites to the scene, it technically works but does not make a lot of sense just yet:
override func didMoveToView(view: SKView) {
// your ARRAY is not an array-segment it is a collection of sprites
var sprites:[SKSpriteNode] = []
let numberOfSprites = 16 // or any other number you'd like
// Creates 16 instances of a sprite an add them to the sprites array
for __ in 0..<numberOfSprites {
var sprite = SKSpriteNode(imageNamed: "myimage")
sprite.physicsBody = SKPhysicsBody(rectangleOfSize: sprite.size)
sprite.physicsBody?.dynamic = false
sprite.name = "mysprite" // not sure what your plans are here...
sprites.append(sprite)
}
// picks out each sprite in the sprites array
for sprite in sprites {
addChild(sprite)
}
}
Now as I said this doesn't make all that much sense. As you've probably noticed the sprites all appear on top of each other for one thing. More importantly, doing this all in the didMoveToView means it makes very little sense going through the whole array-dance, you could simply do the following instead:
override func didMoveToView(view: SKView) {
let numberOfSprites = 16 // or any other number you'd like
for __ in 0..<numberOfSprites {
var sprite = SKSpriteNode(imageNamed: "myimage")
// skipping the physicsBody stuff for now as it is not part of the question
addChild(sprite)
}
}
An improvement obviously, but all sprites are still covering each other, and didMoveToView might not be the best place to handle all this anyway, so perhaps something like this to illustrate the point:
override func didMoveToView(view: SKView) {
let sprites = spritesCollection(count: 16)
for sprite in sprites {
addChild(sprite)
}
}
private func spritesCollection(#count: Int) -> [SKSpriteNode] {
var sprites = [SKSpriteNode]()
for __ in 0..<count {
var sprite = SKSpriteNode(imageNamed: "myimage")
// skipping the physicsBody stuff for now as it is not part of the question
// giving the sprites a random position
let x = CGFloat(arc4random() % UInt32(size.width))
let y = CGFloat(arc4random() % UInt32(size.height))
sprite.position = CGPointMake(x, y)
sprites.append(sprite)
}
return sprites
}
Now, long-wound as this is it is still just a starting point towards a working and more sensible code-structure...

Xcode6 Swift. arc4random using array with textures

I have a spriteNode which has a default texture of a black circle and I have placed it in the center of the screen. I also have an array which contains 4 textures. What I want to do is when I click on the screen the black circle in the center randomly picks a SKTexture from the array and changes into set texture. I was thinking a long the lines of the code in the didBeginTouches but I'm stuck on how to truly execute this idea. Thanks for any help. :)
var array = [SKTexture(imageNamed: "GreenBall"), SKTexture(imageNamed: "RedBall"), SKTexture(imageNamed: "YellowBall"), SKTexture(imageNamed: "BlueBall")]
override func didMoveToView(view: SKView) {
var choiceBallImg = SKTexture(imageNamed: "BlackBall")
choiceBall = SKSpriteNode(texture: choiceBallImg)
choiceBall.position = CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2)
self.addChild(choiceBall)
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
choiceBall.texture = SKTexture(imageNamed: arc4random(array))
//error: Cannot assign a value of type 'SKTexture!' to a value of type 'SKTexture?'
}
Almost there, change your touchesBegan to this:
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
let randomIndex = Int(arc4random_uniform(UInt32(array.count)))
choiceBall.texture = array[randomIndex]
}
First line generates a random number from 0 to the size of your array-1 using the arc4random_uniform function. We also need to convert the size of your array to an Unsigned Integer because Swift is very strict (and rightfully so). We then cast it back to an Integer and use it to access the textures that you already created in your array.

Resources