How to create a border for SCNNode to indicate its selection in iOS 11 ARKit-Scenekit? - scenekit

How to draw a border to highlight a SCNNode and indicate to user that the node is selected?
In my project user can place multiple virtual objects and user can select any object anytime. Upon selection i should show the user highlighted 3D object. Is there a way to directly achieve this or draw a border over SCNNode?

You need to add a tap gesture recognizer to the sceneView.
// add a tap gesture recognizer
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
scnView.addGestureRecognizer(tapGesture)
Then, handle the tap and highlight the node:
#objc
func handleTap(_ gestureRecognize: UIGestureRecognizer) {
// retrieve the SCNView
let scnView = self.view as! SCNView
// check what nodes are tapped
let p = gestureRecognize.location(in: scnView)
let hitResults = scnView.hitTest(p, options: [:])
// check that we clicked on at least one object
if hitResults.count > 0 {
// retrieved the first clicked object
let result = hitResults[0]
// get its material
let material = result.node.geometry!.firstMaterial!
// highlight it
SCNTransaction.begin()
SCNTransaction.animationDuration = 0.5
// on completion - unhighlight
SCNTransaction.completionBlock = {
SCNTransaction.begin()
SCNTransaction.animationDuration = 0.5
material.emission.contents = UIColor.black
SCNTransaction.commit()
}
material.emission.contents = UIColor.red
SCNTransaction.commit()
}
}
The snippet above highlights the whole node. You'd have to adjust it to highlight the borders only, if that's what you're looking for.
Disclaimer:
This code was taken directly from Xcode's template code created when opening a new game (SceneKit) project.

Related

F# WPF script, draw graphics on button click

I am tinkering with using F# scripts and I'm just wanting to draw lines on a blank Windows Form with a simple button click. Hopefully you can see what I'm trying to do here:
open System.Drawing
open System.Windows.Forms
let form = new Form(Width = 400, Height = 400, Text = "draw test")
let panel = new FlowLayoutPanel()
form.Controls.Add(panel)
let paint(e : PaintEventArgs) =
let pen = new Pen(Color.Black);
e.Graphics.DrawLine(pen, new PointF(100.0f, 100.0f), new PointF(200.0f, 200.0f))
let button = new Button()
button.Text <- "Click to draw"
button.AutoSize <- true
button.Click.Add(fun _ -> form.Paint.Add(paint)) // <- does not draw a line on click
panel.Controls.Add(button)
//form.Paint.Add(paint) <- here, if uncommented, it will draw a line when the script is run
form.Show()
If I take the form.Paint.Add(paint) uncomment it above form.Show(), then of course it will draw on the form, but I'm trying to do it with a button click. It's not exactly clear to me how to make this happen in a script like this, and I've been scouring all over for a similar example in F#. Any help would be appreciated.
If you add your Paint event handler before the form is drawn for the first time, then it will draw using that handler.
If you add it after, you need to make sure the form then redraws itself. You could for instance call Refresh or Invalidate on it.
Ex.:
button.Click.Add(fun _ -> form.Paint.Add(paint); form.Invalidate())
Originally an edit, I moved it to the answer section:
Okay, so I was confused about the difference between WPF and Winforms due to the fact that I have seen the terms used together various places... #Asik has added an answer for Winforms, but here I have slapped together a working .fsx script specifically for WPF based on several FSharp Snippets (as well as several Google searches) which can also be compiled if so desired. I'll update this as needed or requested. Also, just to point out, the whole motivation behind this is to be able to quickly test drawing graphics via FSI.
#r #"PresentationCore"
#r #"PresentationFramework"
#r #"WindowsBase"
#r #"System.Xaml"
#r #"UIAutomationTypes"
open System
open System.Windows
open System.Windows.Media
open System.Windows.Shapes
open System.Windows.Controls
let window = Window(Height = 400.0, Width = 400.0)
window.Title <- "Draw test"
let stackPanel = StackPanel()
window.Content <- stackPanel
stackPanel.Orientation <- Orientation.Vertical
let button1 = Button()
button1.Content <- "Click me to draw a blue ellipse"
stackPanel.Children.Add button1
let button2 = Button()
button2.Content <- "Click me to draw a red ellipse"
stackPanel.Children.Add button2
let clearButton = Button()
clearButton.Content <- "Click me to clear the canvas"
stackPanel.Children.Add clearButton
let canvas = Canvas()
canvas.Width <- window.Width
canvas.Height <- window.Height
stackPanel.Children.Add canvas
let buildEllipse height width fill stroke =
let ellipse = Ellipse()
ellipse.Height <- height
ellipse.Width <- width
ellipse.Fill <- fill
ellipse.Stroke <- stroke
ellipse
let ellipse1 = buildEllipse 100.0 200.0 Brushes.Aqua Brushes.Black
Canvas.SetLeft(ellipse1, canvas.Width / 10.0) //messy, will fix at some point!
Canvas.SetTop(ellipse1, canvas.Height / 10.0)
let ellipse2 = buildEllipse 200.0 100.0 Brushes.Red Brushes.DarkViolet
Canvas.SetLeft(ellipse2, canvas.Width / 4.0)
Canvas.SetTop(ellipse2, canvas.Height / 5.0)
let addEllipseToCanvas (canvas:Canvas) (ellipse:Ellipse) =
match canvas.Children with
| c when c.Contains ellipse ->
canvas.Children.Remove ellipse
canvas.Children.Add(ellipse) |> ignore //needs to be removed and readded or the canvas complains
| _ ->
canvas.Children.Add(ellipse) |> ignore
button1.Click.Add(fun _ -> addEllipseToCanvas canvas ellipse1)
button2.Click.Add(fun _ -> addEllipseToCanvas canvas ellipse2)
clearButton.Click.Add(fun _ -> canvas.Children.Clear())
#if INTERACTIVE
window.Show()
#else
[<EntryPoint; STAThread>]
let main argv =
let app = new Application()
app.Run(window)
#endif

Xcode SceneKit Shadows Will Not Render

I am creating a simple project using SceneKit and cannot get any shadows to appear in the Scene Editor or in the compiled application no matter what I try.
I have tried creating a simple box, placing it on a plane and adding a spot light. Code would be as follows:
// Create some properties
var scnView: SCNView!
var scnMasterScene: SCNScene!
var boxNode: SCNNode!
var cameraPerspNode: SCNNode!
var cameraOrthNode: SCNNode!
func applicationDidFinishLaunching(aNotification: NSNotification) {
// Insert code here to initialize your application
setupView()
createAndAddBoxScene()
setupCamera()
createAndAddSpotLight()
}
// Setup the view.
func setupView() {
scnView = self.sceneView as SCNView
// Set the scnView properties.
scnView.showsStatistics = true
scnView.autoenablesDefaultLighting = false
scnView.allowsCameraControl = true
// Create a Master scene.
scnMasterScene = SCNScene()
// Set the scene view's scene node to the master scene.
scnView.scene = scnMasterScene
}
// Setup the scene.
func createAndAddBoxScene() {
// Create a box of type SCNGeometry
let boxGeometry: SCNGeometry = SCNBox(width: 2000, height: 2000, length: 2000, chamferRadius: 100)
// Add a difuse colour to the box' first material.
boxGeometry.firstMaterial?.diffuse.contents = NSColor.redColor()
// Create a node of type SCNNode and attach the boxGeometry to it.
// Note: A node can only have 1 geometry object attached to it.
boxNode = SCNNode(geometry: boxGeometry)
// Add the new boxNode to the scene's root node.
scnMasterScene.rootNode.addChildNode(boxNode)
// Create a floor plane.
let floorGeometry: SCNGeometry = SCNPlane(width: 20000, height: 20000)
// Add a difuse colour to the floor's first material.
floorGeometry.firstMaterial?.diffuse.contents = NSColor.yellowColor()
// Create a floorPlaneNode and attach the floor plane to it.
let floorNode: SCNNode = SCNNode(geometry: floorGeometry)
// Tilt the floorPlaneNode in x.
let floorNodeTiltDegreesX: Double = -90
let floorNodeTiltRadiansX: Double = floorNodeTiltDegreesX * (π/180)
floorNode.rotation = SCNVector4Make(1, 0, 0, CGFloat(floorNodeTiltRadiansX))
// Add the floorPlaneNode to the master scene.
scnMasterScene.rootNode.addChildNode(floorNode)
}
// Create a camera, position it and add it to the scene.
func setupCamera() {
// Create a camera node which will be used to contain the camera.
cameraPerspNode = SCNNode()
// Create a new camera.
let cameraPersp: SCNCamera = SCNCamera()
// Set camera properties.
cameraPersp.name = "myPerspCamera"
cameraPersp.usesOrthographicProjection = false
cameraPersp.orthographicScale = 9
cameraPersp.xFov = 30
cameraPersp.zNear = 1
cameraPersp.zFar = 20000
// Assign the camera to the .camera property of the node.
cameraPerspNode.camera = cameraPersp
// Set the position and rotation of the camera node (NOT the camera).
cameraPerspNode.position = SCNVector3(x: 0, y: 4000, z: 6000)
let cameraPerspTiltDegrees: Double = -30
let cameraPerspTiltRadians: Double = cameraPerspTiltDegrees * (π/180)
cameraPerspNode.rotation = SCNVector4Make(1, 0, 0, CGFloat(cameraPerspTiltRadians))
// Add the new cameraNode to the scene's root.
scnMasterScene.rootNode.addChildNode(cameraPerspNode)
}
func createAndAddSpotLight() -> Void {
let spot = SCNLight()
spot.type = SCNLightTypeSpot
spot.castsShadow = true
spot.color = NSColor(hue: 1, saturation: 0, brightness: 0, alpha: 1)
spot.spotInnerAngle = 30
spot.spotOuterAngle = 60
let spotNode = SCNNode()
spotNode.light = spot
spotNode.position = SCNVector3(x: 0, y: 2000, z: 2000)
let lookAt = SCNLookAtConstraint(target: boxNode)
spotNode.constraints = [lookAt]
}
If I bring in a .dae filed add a spot light, or directional light, using the Scene Editor I can light the scene but there are no shadows when I set the Cast Shadows property in the Attributes Inspector.
Can anyone shine any light on my problem?
Thanks
The scene is large because the 3D model has been created at 1:1 and it depicts a large building. After much trial and error I finally found the solution - I changed the scale of the light to 10, 10, 10 and the shadows appeared.
the dimensions in your scene are huge (in SceneKit 1 unit = 1 meter). You'll want to change your light's zFar property (and probably zNear too) accordingly.

Scroll to selection in Angular ui-grid (not ng-grid)

For my Angular JS grid work, I'm using ui-grid rather than ng-grid as ui-grid is meant to be the new version which is purer Angular.
I've got a grid that I'm populating with a http response, and I'm able to select a row (based on finding the record matching a $scope variable value) using the api.selection.selectRow method call.
What I need to do next is scroll the grid to that record.
There's an existing stack overflow question along the same lines that is for ng-grid and the answer to that refers to undocumented features which are not present in ui-grid so I can't use that approach.
The closest I've got is finding $scope.gridApi.grid to get a reference to the actual grid itself but looking through the properties and methods in the Chrome debugger doesn't show anything that sounds like it could work.
You can use the cellNav plugin. You should already have a reference to your row entity from the selection. The documentation is here.
gridApi.cellNav.scrollTo(grid, $scope, rowEntity, null);
I managed to hack together something that works pretty well but it's a bit dodgy and could probably be cleaner with a bit more Angular/jquery understanding.
I used the browser dom explorer to find that the scrollbars have a css class that we can detect to find them and then set the scroll properties on them to have the grid scroll (the grid and scrollbars are separate divs but their properties are bound so changing one updates the other).
It doesn't completely work for scrolling to the last row of the grid. This could be a timing issue, I've noticed when using breakpoints that the grid comes on screen a little larger and then shrinks down to it's final size. This could be messing with the scrolling values.
The first loop finds the height of the grid by adding up the rows, and the y position of the row for my data object (project), then we find the scrollbar and set it's scrollTop, trying to centre the row on screen without going out of bounds.
var grid = $scope.projectsGridApi.grid;
// var row = grid.rowHashMap.get(project.$$hashKey);
var found = false;
var y = 0;
var totalY = 0;
var rowHeight = 0;
for (var rowIdx in grid.rows)
{
var row = grid.rows[rowIdx];
if (row.entity.$$hashKey == project.$$hashKey)
{
found = true;
rowHeight = row.height;
}
if (!found)
{
y += row.height;
}
totalY += row.height;
}
// now find the scroll bar div and set it's scroll-top
// (todo: checking if we're at the end of the list - setting scrollTop > max means it doesn't work properly
var grid = $scope.projectsGridApi.grid;
// annoyingly this is nastily coded to find the scrollbar and isn't completely right
// I think the grid is a little taller when this is called, then shrinks
// which affects what the maximum is (so we might not always be able to put the selected item on screen if it is the last one).
var holderDiv = $('#projectsGridHolder');
if (holderDiv)
{
var scrollBarDivs = holderDiv.find('.ui-grid-native-scrollbar');
if (scrollBarDivs)
{
for (var scrollBarDivIdx in scrollBarDivs)
{
var scrollBarDiv = scrollBarDivs[scrollBarDivIdx];
var scrollBarDivClass = scrollBarDiv.className;
if (scrollBarDivClass)
{
if (scrollBarDivClass.indexOf('vertical') != -1)
{
var scrollHeight = scrollBarDiv.scrollHeight;
var clientHeight = scrollBarDiv.clientHeight;
if (rowHeight > 0)
{
y -= (clientHeight - rowHeight) / 2; // center on screen be scrolling slightly higher up
}
if (y < 0) y = 0;
else if (y > totalY - clientHeight) y = totalY - clientHeight;
scrollBarDiv.scrollTop = y;
}
}
}
}
}

Content not rotated when printing in wpf

I am printing an XPS document using the System.Windows.Controls.PrintDialog. When I choose landscape orientation in the print dialog the resulting page is rotated to landscape but the actual content stays in portrait mode and is clipped.
This is the way I print. I also tried to use the AddJob method on PrintDialog.PrintQueue and the overloads on PrintQueue.CreateXpsDocumentWriter(...).Write(...) all with the same or worse result. And I tried to set DocumentPaginator.PageSize, the printDialog.PrintTicket.PageMediaSize and the width and height of th first FixedPage to the correct lanscape size with no result. PrintDialog.PrintTicket.PageOrientation is on landscape and PrintDialog.PrintableAreaWidth and PrintDialog.PrintableAreaHeight is as it should be when lanscape is selected after the PrintDialog was shown.
var printDialog = new PrintDialog
{
MaxPage = (uint)pageCount,
MinPage = 1,
PageRange = new PageRange(1, pageCount),
UserPageRangeEnabled = true
};
if (printDialog.ShowDialog() != true) return;
using (var doc = new XpsDocument(filename, FileAccess.Read))
{
var paginator = doc.GetFixedDocumentSequence().DocumentPaginator;
printDialog.PrintDocument(fds.paginator , "myPrintJob");
}

Settings TranslateX or Canvas.SetLeft Property programmatically in Silverlight

So Here is the Problem, I am trying to get that circle to align on the number. When I do that in blend it shows me I have a Left (23), I try to do that programmaticly Canvas.SetLeft(thePanel,23) it overshoots. Better yet, if anyone knows of a control like this in silverlight let me know. What this does is when the user clicks on a number the green circle is suppose to go to that number so it looks like the user has selected it.
On your Circle object you have to set the Radius of the circle and the TranslateTransform attribute. Lets say your Circle has a radius of 15:
private const double Radious = 15.0;
private double _x = Radious;
private double _y = Radious;
private TranslateTransform _translation = new TranslateTransform();
and properties to handle the Circle's X and Y coordinates,
public double X
{
get { return this._x; }
set
{
this._x = value;
_translation.X = this._x - Radious;
}
}
public double Y
{
get { return this._y; }
set
{
this._y = value;
_translation.Y = this._y - Radious;
}
}
and in Silverlight you can get where the user has clicked on a Canvas, setting this code on the Click Event of the panel, and set the center of the circle to where the user has clicked:
//Get the points where it was clicked
Point clickPoint = e.GetPosition(Canvas);
MyCircle.X = clickPoint.X;
MyCircle.Y = clickPoint.Y;
Now, if you want them to always fall in fixed positions, you can set conditions that, if a user clicks around a number, then set the center of the circle to the center of the number, or just change the X value of your circle to move to the desired position.

Resources