Scene Kit - Dragging an Object - macos

I’m trying to drag a chess piece with the mouse/trackpad using Scene Kit. All objects (board and pieces) are children of the root node, loaded from a Collada file.
I found a helpful description of the process elsewhere on Stack Overflow. Using that description I wrote the initial version of the code below. My problem is the disparity between the click coordinates and the piece node position — their coordinates are different orders of magnitude. I remain unclear on how to match them up — put them in the “same universe”. I’ve tried a number of suggestions from the Apple forums along with flights of fancy of my own.
Here’s my current attempt, mostly reverted back to the original version based on the link above, along with logged coordinate values along the way. The result is that dragging a chess piece causes it to abruptly jump off screen:
- (NSPoint)
viewPointForEvent: (NSEvent *) event_
{
NSPoint windowPoint = [event_ locationInWindow];
NSPoint viewPoint = [self.view convertPoint: windowPoint
fromView: nil];
return viewPoint;
}
- (SCNHitTestResult *)
hitTestResultForEvent: (NSEvent *) event_
{
NSPoint viewPoint = [self viewPointForEvent: event_];
CGPoint cgPoint = CGPointMake (viewPoint.x, viewPoint.y);
NSArray * points = [(SCNView *) self.view hitTest: cgPoint
options: #{}];
return points.firstObject;
}
- (void)
mouseDown: (NSEvent *) theEvent
{
SCNHitTestResult * result = [self hitTestResultForEvent: theEvent];
SCNVector3 clickWorldCoordinates = result.worldCoordinates;
log output: clickWorldCoordinates x 208.124578, y -12827.223365, z 3163.659073
SCNVector3 screenCoordinates = [(SCNView *) self.view projectPoint: clickWorldCoordinates];
log output: screenCoordinates x 245.128906, y 149.335938, z 0.985565
// save the z coordinate for use in mouseDragged
mouseDownClickOnObjectZCoordinate = screenCoordinates.z;
selectedPiece = result.node; // save selected piece for use in mouseDragged
SCNVector3 piecePosition = selectedPiece.position;
log output: piecePosition x -18.200000, y 6.483060, z 2.350000
offsetOfMouseClickFromPiece.x = clickWorldCoordinates.x - piecePosition.x;
offsetOfMouseClickFromPiece.y = clickWorldCoordinates.y - piecePosition.y;
offsetOfMouseClickFromPiece.z = clickWorldCoordinates.z - piecePosition.z;
log output: offsetOfMouseClickFromPiece x 226.324578, y -12833.706425, z 3161.309073
}
- (void)
mouseDragged: (NSEvent *) theEvent;
{
NSPoint viewClickPoint = [self viewPointForEvent: theEvent];
SCNVector3 clickCoordinates;
clickCoordinates.x = viewClickPoint.x;
clickCoordinates.y = viewClickPoint.y;
clickCoordinates.z = mouseDownClickOnObjectZCoordinate;
log output: clickCoordinates x 246.128906, y 0.000000, z 0.985565
log output: pieceWorldTransform =
m11 = 242.15889219510001, m12 = -0.000045609300002524833, m13 = -0.00000721691076126, m14 = 0,
m21 = 0.0000072168760805499971, m22 = -0.000039452697396149999, m23 = 242.15890446329999, m24 = 0,
m31 = -0.000045609300002524833, m32 = -242.15889219510001, m33 = -0.000039452676995750002, m34 = 0,
m41 = -4268.2349924762348, m42 = -12724.050221935429, m43 = 4852.6652710104272, m44 = 1)
SCNVector3 newPiecePosition;
newPiecePosition.x = offsetOfMouseClickFromPiece.x + clickCoordinates.x;
newPiecePosition.y = offsetOfMouseClickFromPiece.y + clickCoordinates.y;
newPiecePosition.z = offsetOfMouseClickFromPiece.z + clickCoordinates.z;
log output: newPiecePosition x 472.453484, y -12833.706425, z 3162.294639
selectedPiece.position = newPiecePosition;
}
Up to this point, I’ve gotten a lot of interesting and useful comments and advice. But I’ve realized that to move forward, I’m probably going to need a working code sample which shows the secret sauce which allows clicks and vectors to exist in the “same universe”.

You don't need to "play around" to find the origin. You can do code like this:
let originZ = sceneView.projectPoint(SCNVector3Zero).z
let viewLocation = /* Location in SCNView coordinate system */
let viewLocationWithOriginZ = SCNVector3(
x: Float(viewLocation.x), // X & Y are in view coordinate system
y: Float(viewLocation.y),
z: originZ // Z is in scene coordinate system
)
var position = sceneView.unprojectPoint(viewLocationWithOriginZ)
/* "position" is in the scene's coordinate system with z of 0 */

You have to use the method unprojectPoint. The key is to play around with z-values to find the correct depth of your scene relative to your click point (at least that's how I understood it). I had a similar problem trying to do touch-dragging on iOS. This is what I did to solve it:
let convertedTranslation = scnView.unprojectPoint(SCNVector3Make(Float(translation.x), Float(translation.y), 1.0))
Notice that the z-value for the method is 1. From the documentation, you can see that it can be a value between -1 and 1. -1 and 0 did not work for me, but 1 did.

Related

Scenekit - Ploting the trajectory of an object

Ok, I was not very sure about the earlier conversion formula as someone pointed out (I just read it somewhere and tried out), but now I am trying out an approach where I have a tiny Box created at the edge of the ship and I keep on reading its positions as rotation is applied to the ship..then using these positions to plot the geometry..Below is the relevant code:
scene = [SCNScene sceneNamed:#"ship.scn"];
_viewpoint1.scene = scene;
box = [SCNNode node];
box.geometry = [SCNBox boxWithWidth:.1 height:.1 length:.1 chamferRadius:.1];
box.physicsBody = [SCNPhysicsBody staticBody];
// position the box to the end of the ship
box.position = SCNVector3Make(box.position.x, box.position.y, box.position.z +5.6);
[_viewpoint1.scene.rootNode addChildNode:box];
As the app receives quaternions, the ship rotates and so does the box at the tip, and I capture the positions of that box and store it in positions array.
_viewpoint1.scene.rootNode.orientation = SCNVector4Make(f_q0, f_q1, f_q2, f_q3);
SCNVector3 pt = [box convertPosition:SCNVector3Make(box.position.x, box.position.y, box.position.z) toNode:nil];
[positions addObject:[NSValue valueWithSCNVector3:SCNVector3Make(pt.x, pt.y, pt.z)]];
Later, using this positions array to create the geometry as below:
SCNVector3 positions2[pointCount];
for (int j = 0; j < pointCount; j++)
{
SCNVector3 value = [[positions objectAtIndex:j] SCNVector3Value];
SCNVector3 value1 = [_viewpoint2.scene.rootNode convertPosition:value fromNode:nil];
positions2[j] = value1;
};
SCNGeometrySource *vertexSource1 =
[SCNGeometrySource geometrySourceWithVertices:positions2 count:pointCount];
NSData *indexData1 = [NSData dataWithBytes:indices2
length:sizeof(indices2)];
SCNGeometryElement *element1 =
[SCNGeometryElement geometryElementWithData:indexData1
primitiveType:SCNGeometryPrimitiveTypeLine
primitiveCount:pointCount
bytesPerIndex:sizeof(int)];
SCNGeometry *geometry1 = [SCNGeometry geometryWithSources:#[vertexSource1]
elements:#[element1]];
SCNNode* lineNode1 = [SCNNode nodeWithGeometry:geometry1];
[_viewpoint2.scene.rootNode addChildNode:lineNode1];
What I observe is that the arc drawn is bigger than the ship rotation, so if the ship rotates by 30 deg, the arc drawn is about 60 deg or so..it should be the same as the ship rotation...wondering what I am doing incorrect here?

Making the x, y, x2, y2 axes constrained to the sides the plot area in core-plot

Project demonstrating the problem here: http://github.com/danieljfarrell/CorePlotBoxAxis
I want make a plot like the following where the we have a "box" axis, i.e. that axes are always on the outer edge of the plot area.
Here x and y axes are the bottom and left edges; x2 and y2 are the top and right edges, respectively.
In the past I have done with by setting the orthogonalCoordinateDecimal of the axis. However, this requires updating the coordinate manually when the plot ranges change.
Is it possible to implement the box axis with constraints system? I have tried the following and it correctly pins x,y. However, x2 is not visible (presumably it is being pinned somewhere outside of the range) and y2 has a 1 pixel offset (see below).
I have tried a few variations on the y2 constraint but nothing seems to help.
CPTXYAxis *x = [self _makeDefaultAxis];
x.coordinate = CPTCoordinateX;
x.axisConstraints = [CPTConstraints constraintWithLowerOffset:0.0];
x.tickDirection = CPTSignPositive;
// ...
CPTXYAxis *y = [self _makeDefaultAxis];
y.coordinate = CPTCoordinateY;
y.axisConstraints = [CPTConstraints constraintWithLowerOffset:0.0];
y.tickDirection = CPTSignPositive;
// ...
CPTXYAxis *y2 = [self _makeDefaultAxis];
y2.coordinate = CPTCoordinateY;
y2.axisConstraints = [CPTConstraints constraintWithUpperOffset:0.0];
y2.tickDirection = CPTSignNegative;
// ...
// Problem here ...
CPTXYAxis *x2 = [self _makeDefaultAxis];
x2.coordinate = CPTCoordinateX;
y2.axisConstraints = [CPTConstraints constraintWithUpperOffset:0.0];
x2.tickDirection = CPTSignNegative;
This code generates the following plot. Any ideas on how to mirror an axis in this way?
There's a typo in the x2 setup section. Change
y2.axisConstraints = …
to
x2.axisConstraints = …

draw uiimage along CGMutablePathRef

How do i draw a custom uiimage along a CGMutablePathRef ? I can get the points from CGMutablePathRef but it does not give the smooth points that create the path.
I want to know if i can extract all of them plus the one that creat the smooth path.
i've used CGPathApply but i only get the control points, and when i draw my image it does not stay smooth as the original CGMutablePathRef
void pathFunction(void *info, const CGPathElement *element){
if (element->type == kCGPathElementAddQuadCurveToPoint)
{
CGPoint firstPoint = element->points[1];
CGPoint lastPoint = element->points[0];
UIImage *tex = [UIImage imageNamed:#"myimage.png"];
CGPoint vector = CGPointMake(lastPoint.x - firstPoint.x, lastPoint.y - firstPoint.y);
CGFloat distance = hypotf(vector.x, vector.y);
vector.x /= distance;
vector.y /= distance;
for (CGFloat i = 0; i < distance; i += 1.0f) {
CGPoint p = CGPointMake(firstPoint.x + i * vector.x, firstPoint.y + i * vector.y);
[tex drawAtPoint:p blendMode:kCGBlendModeNormal alpha:1.0f];
}
}
}
It seems like you are looking for the function that is used to draw a cubic Bézier curve from a start point and an end point and two control points.
start⋅(1-t)^3 + 3⋅c1⋅t(1-t)^2 + 3⋅c2⋅t^2(1-t) + end⋅t^3
By setting a value for t between 0 and 1 you will get a point on the curve at a certain percentage of the curve length. I have a short description of how it works in the end of this blog post.
Update
To find the point to draw the image somewhere between the start and end points you pick a t (for example 0.36 and use it to calculate the x and y value of that points.
CGPoint start, end, c1, c2; // set to some value of course
CGFloat t = 0.36;
CGFloat x = start.x*pow((1-t),3) + 3*c1.x*t*pow((1-t),2) + 3*c2.x*pow(t,2)*(1-t) + end.x*pow(t,3);
CGFloat y = start.y*pow((1-t),3) + 3*c1.y*t*pow((1-t),2) + 3*c2.y*pow(t,2)*(1-t) + end.y*pow(t,3);
CGPoint point = CGPointMake(x,y); // this is 36% along the line of the curve
Which given the path in the image would correspond to the orange circle
If you do this for many points along the curve you will have many images positioned along the curve.
Update 2
You are missing that kCGPathElementAddQuadCurveToPoint (implicitly) has 3 points: start (the current/previous points, the control point (points[0]) and the end point (points[1]). For a quad curve both control points are the same so c1 = c2;. For kCGPathElementAddCurveToPoint you would get 2 different control points.

Bars not aligned with X-Axis ticks in CPTBarPlot of Core Plot

I have a problem with CPTBarPlot in Core Plot on Cocoa.
Namely, I cannot achieve to control step width of bars. X-Axis ticks are set normally as integer from 1 to infinite and drawing Scatter Plot on it works.
However, when bar plot is drawn, only first columns starts (with proper offset, of course) on first tick. Second is a bit further form its tick, so is third and all others as shown on screenshot:
I haven't found any property to control this, however, I found that data source method
-(NSNumber *)numberForPlot:(CPTPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)index
Behaves differently than when I draw Scatter Plot. namely, it enumerates only integer 3, which is CPTBarPlotBindingBarTips and doesn't enumerate any other constant, I am especially bothered with absence of CPTBarPlotBindingBarLocations.
If I start VerticalBarChart from Plot Gallery mac demo project I've checked and seen that same method enumerates 3,4 and 2 constants in same order.
I am using Core Plot 0.9 namely, I believe the same as in this demo. Still...
How to solve this?
Here is the complete code for labels and ticks on X axis;
x.labelRotation = M_PI/4;
x.labelingPolicy = CPTAxisLabelingPolicyNone;
x.labelOffset = 0.0;
CPTMutableTextStyle *labelStyle = [CPTMutableTextStyle textStyle];
labelStyle.fontSize = 9.0;
x.labelTextStyle = labelStyle;
// NSMutableArray *dateArray = [dataSeries objectAtIndex:0];
NSMutableArray *customTickLocations = [NSMutableArray array];
NSMutableArray *xAxisLabels = [NSMutableArray array];
for (int i=0;i<[dataSeries count];i++) {
[customTickLocations addObject:[NSDecimalNumber numberWithInt:i+1]];
[xAxisLabels addObject:[dataSeries objectAtIndex:i]];
}
// [customTickLocations addObject:[NSDecimalNumber numberWithInt:[dateArray count]]];
NSUInteger labelLocation = 0;
NSMutableArray *customLabels = [NSMutableArray arrayWithCapacity:[xAxisLabels count]];
for (NSNumber *tickLocation in customTickLocations) {
CPTAxisLabel *newLabel = [[CPTAxisLabel alloc] initWithText: [xAxisLabels objectAtIndex:labelLocation++] textStyle:x.labelTextStyle];
newLabel.tickLocation = [tickLocation decimalValue];
newLabel.offset = x.labelOffset + x.majorTickLength;
newLabel.rotation = M_PI/4;
[customLabels addObject:newLabel];
[newLabel release];
}
[x setMajorTickLocations:[NSSet setWithArray:customTickLocations]];
x.axisLabels = [NSSet setWithArray:customLabels];
I have to add that same ticks are being used when scatter plot is drawn on same plot space and it matches perfectly....
The field parameter will be one of the members of this enum:
typedef enum _CPTBarPlotField {
CPTBarPlotFieldBarLocation = 2, ///< Bar location on independent coordinate axis.
CPTBarPlotFieldBarTip = 3, ///< Bar tip value.
CPTBarPlotFieldBarBase = 4 ///< Bar base (used only if barBasesVary is YES).
} CPTBarPlotField;
If the plot's plotRange property is nil (the default), the plot will ask the datasource for locations and tips for each bar. If barBasesVary == YES, it will also ask for base values for each bar.

Precise pixel grid overlay in Core Graphics?

In my experiments with creating a pixel-centered image editor I've been trying to draw a precise grid overlay to help guide users when trying to access certain pixels. However, the grid I draw isn't very even, especially at smaller sizes. It's a regular pattern of one slightly larger column for every few normal columns, so I think it's a rounding issue, but I can't see it in my code. Here's my code:
- (void)drawRect:(NSRect)dirtyRect
{
context = [[NSGraphicsContext currentContext] graphicsPort];
CGContextAddRect(context, NSRectToCGRect(self.bounds));
CGContextSetRGBStrokeColor(context, 1.0f, 0.0f, 0.0f, 1.0f);
CGContextStrokePath(context);
CGContextSetInterpolationQuality(context, kCGInterpolationNone);
CGContextSetShouldAntialias(context, NO);
if (image)
{
NSRect imageRect = NSZeroRect;
imageRect.size = CGImageGetSize([image CGImage]);
drawRect = [self bounds];
NSRect viewRect = drawRect;
CGFloat aspectRatio = imageRect.size.width / imageRect.size.height;
if (viewRect.size.width / viewRect.size.height <= aspectRatio)
{
drawRect.size.width = viewRect.size.width;
drawRect.size.height = imageRect.size.height * (viewRect.size.width / imageRect.size.width);
}
else
{
drawRect.size.height = viewRect.size.height;
drawRect.size.width = imageRect.size.width * (viewRect.size.height / imageRect.size.height);
}
drawRect.origin.x += (viewRect.size.width - drawRect.size.width) / 2.0;
drawRect.origin.y += (viewRect.size.height - drawRect.size.height) / 2.0;
CGContextDrawImage(context, drawRect, [image CGImage]);
if (showPixelGrid)
{
//Draw grid by creating start and end points for vertical and horizontal lines.
//FIXME: Grid is uneven, especially at smaller sizes.
CGContextSetStrokeColorWithColor(context, CGColorGetConstantColor(kCGColorBlack));
CGContextAddRect(context, drawRect);
CGContextStrokePath(context);
NSUInteger numXPoints = (NSUInteger)imageRect.size.width * 2;
NSUInteger numYPoints = (NSUInteger)imageRect.size.height * 2;
CGPoint xPoints[numXPoints];
CGPoint yPoints[numYPoints];
CGPoint startPoint;
CGPoint endPoint;
CGFloat widthRatio = drawRect.size.width / imageRect.size.width;
CGFloat heightRatio = drawRect.size.height / imageRect.size.height;
startPoint.x = drawRect.origin.x;
startPoint.y = drawRect.origin.y;
endPoint.x = drawRect.origin.x;
endPoint.y = drawRect.size.height + drawRect.origin.y;
for (NSUInteger i = 0; i < numXPoints; i += 2)
{
startPoint.x += widthRatio;
endPoint.x += widthRatio;
xPoints[i] = startPoint;
xPoints[i + 1] = endPoint;
}
startPoint.x = drawRect.origin.x;
startPoint.y = drawRect.origin.y;
endPoint.x = drawRect.size.width + drawRect.origin.x;
endPoint.y = drawRect.origin.y;
for (NSUInteger i = 0; i < numYPoints; i += 2)
{
startPoint.y += heightRatio;
endPoint.y += heightRatio;
yPoints[i] = startPoint;
yPoints[i + 1] = endPoint;
}
CGContextStrokeLineSegments(context, xPoints, numXPoints);
CGContextStrokeLineSegments(context, yPoints, numYPoints);
}
}
}
Any ideas?
UPDATE: I managed to get your code running with a few tweaks - where did CGImageGetSize() come from? - and I can't really see the problem, other than columns aren't all exactly even at extremely small sizes. That's just how it has to work though. The only way around this is to either fix scaling to be integer multiples of the image size - in other words, get the largest integer multiple of the image size smaller than the view size -or reduce the number of lines drawn on the screen at very small sizes to get rid of this artefact. There's a reason the pixel grid only becomes visible when you zoom in a long way in most editors. Not to mention that if the grid is still visible at 3-4x resolution you're making the view just way too busy.
I couldn't run the code you provided because there's a bunch of class ivars in there, but from a cursory glance, I'd say it has something to do with drawing on pixel boundaries. After you round to an integer to get rid of fuzzy AA artefacts (I notice you turned AA off, but ideally you shouldn't have to do that), you then need to add 0.5 to your origin to get your line drawn in the center of the pixel rather than on the boundary.
Like this:
+---X---+---+---+---+---+
| | | | Y | | |
+---+---+---+---+---+---+
X : CGPoint (1, 1)
Y : CGPoint (3.5, 0.5)
You want to draw from the center of the pixel, because otherwise your line straddles two pixels.
In other words, where you're setting up xPoints and yPoints, make sure to floor() or round() your values, and then add 0.5.

Resources