Adjust SCNPlane size to a specific screen size

1.2k Views Asked by At

In my application, the user draws a 2D rectangle on the screen using Core Graphics. Once they release their mouse I need to create a SCNPlane and place it into my SceneKit scene at the same screen size and position. I plan on setting the z position of the plane to a constant value away from the camera (lets say 10 units away from the camera).

The part I'm having trouble with is how to position and scale the plane so it looks exactly like what they drew in Core Graphics. BTW the Core Graphics view is on top of the SceneKit view at the same size.

I plan on using the billboard constraint so the plane is facing the camera properly.

I'm sure there is some math to accomplish this using the camera's properties, but I have no clue how to do it.

2

There are 2 best solutions below

1
Kukulski On BEST ANSWER

Best bet is to make a screen-aligned container with its origin and scale set to give you pixel-alignment and your choice of zero-zero location.

your building blocks are sceneView.unprojectPoint and sceneView.frame

import Cocoa        // (or UIKit for iOS)
import SceneKit
import PlaygroundSupport;

// create a scene view with an empty scene
var sceneView = SCNView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
var scene = SCNScene()
sceneView.scene = scene

// have it show in your playground
PlaygroundPage.current.liveView = sceneView;

// a camera
var cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(0, 0, 3)
scene.rootNode.addChildNode(cameraNode)

var origin = sceneView.unprojectPoint(SCNVector3(0,0,0))
var viewWidth = sceneView.frame.width;
var topRight = sceneView.unprojectPoint(SCNVector3(sceneView.frame.width, 0, 0));
var scale = 2 * (topRight.x - origin.x) / sceneView.frame.width

var container = SCNNode()
origin.z *= -1
origin.x = -2 * topRight.x;
origin.y = -2 * topRight.y;

container.position = origin;
container.scale = SCNVector3(scale, -scale, scale)
cameraNode.addChildNode(container);

func addBox(w:CGFloat, h:CGFloat, x:CGFloat, y:CGFloat) {
    let box = SCNNode(geometry: SCNPlane(width: w, height: h))
    box.geometry?.firstMaterial?.diffuse.contents  = NSColor.red  
    //SCNPlane geometry is still center-origin    
    box.position = SCNVector3(x + w / 2, y + h / 2,0)
    container.addChildNode(box)
}

addBox(w: 10, h:10, x: 290, y:290)
addBox(w: 10, h:10, x: 0, y:0)
addBox(w: 10, h:10, x: 0, y:290)
addBox(w: 10, h:10, x: 290, y:0)

result: 10-pixel blocks at each corner of the view

0
user3069232 On

I stand corrected, running on iOS 16 and made a few minor changes

import UIKit        // (or UIKit for iOS)
import SceneKit
import PlaygroundSupport;

// create a scene view with an empty scene
var sceneView = SCNView(frame: CGRect(x: 0, y: 0, width: 512, height: 512))
var scene = SCNScene()
sceneView.scene = scene

// have it show in your playground
PlaygroundPage.current.liveView = sceneView;

// a camera
var cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(0, 0, 3)
scene.rootNode.addChildNode(cameraNode)

var origin = sceneView.unprojectPoint(SCNVector3(0,0,0))
var viewWidth = sceneView.frame.width;
var topRight = sceneView.unprojectPoint(SCNVector3(sceneView.frame.width, 0, 0));
var scale = 2 * CGFloat((topRight.x - origin.x)) / sceneView.frame.width

var container = SCNNode()
origin.z *= -1
origin.x = -2 * topRight.x;
origin.y = -2 * topRight.y;

container.position = origin;
container.scale = SCNVector3(scale, scale, scale)
cameraNode.addChildNode(container);

func addBox(w:CGFloat, h:CGFloat, x:CGFloat, y:CGFloat, color:UIColor)     {
    let box = SCNNode(geometry: SCNPlane(width: w, height: h))
    box.geometry?.firstMaterial?.diffuse.contents  = color
    //SCNPlane geometry is still center-origin
    box.position = SCNVector3(x + w / 2, y + h / 2,0)
    container.addChildNode(box)
}


addBox(w: 10, h:10, x: 0, y:0, color: .blue)
addBox(w: 10, h:10, x: 490, y:0, color: .orange)

addBox(w: 10, h:10, x: 490, y:490, color: .red)
addBox(w: 10, h:10, x: 0, y:490, color: .green)

scene.rootNode.addChildNode(container)

And now it works...