Forum Archive

If, like me, your iDevice is too old for ARKit...

cvp

see here

SceneKit with, thanks to @jonB , the camera of your iDevice as background

from objc_util import *
import ctypes
import ui
import math
from ImageColor import getrgb

load_framework('SceneKit')

SCNView, SCNScene, SCNBox, SCNNode, SCNMaterial, SCNCamera, SCNLight, SCNAction, SCNLookAtConstraint = map(ObjCClass, ['SCNView', 'SCNScene', 'SCNBox', 'SCNNode', 'SCNMaterial', 'SCNCamera', 'SCNLight', 'SCNAction',  'SCNLookAtConstraint' ])

class SCNVector3(Structure):
    _fields_ = [('x', c_float), ('y', c_float), ('z', c_float)]

def camera_in_ui_view(ui_view):
      #https://github.com/jsbain/objc_hacks/blob/master/live_camera_view.p
      device = 0
      # 0 = back  camera
      # 1 = front camera
      ui_view._session=ObjCClass('AVCaptureSession').alloc().init()
      ui_view._session.setSessionPreset_('AVCaptureSessionPresetHigh');
      inputDevices=ObjCClass('AVCaptureDevice').devices()
      #print(inputDevices)
      ui_view._inputDevice=inputDevices[device]
      deviceInput=ObjCClass('AVCaptureDeviceInput').deviceInputWithDevice_error_(ui_view._inputDevice, None);
      if ui_view._session.canAddInput_(deviceInput):
         ui_view._session.addInput_(deviceInput)
      ui_view._previewLayer=ObjCClass('AVCaptureVideoPreviewLayer').alloc().initWithSession_(ui_view._session)
      ui_view._previewLayer.setVideoGravity_('AVLayerVideoGravityResizeAspectFill')

      rootLayer=ObjCInstance(ui_view).layer()
      rootLayer.setMasksToBounds_(True)
      ui_view._previewLayer.setFrame_(CGRect(CGPoint(-70, 0), CGSize(ui_view.height,ui_view.height)))
      rootLayer.insertSublayer_atIndex_(ui_view._previewLayer,0)
      ui_view._session.startRunning()

@on_main_thread
def demo():
    main_view = ui.View()
    w, h = ui.get_screen_size()
    main_view.frame = (0,0,w,h)
    # background = image
    #bg = ui.ImageView()
    #w, h = ui.get_screen_size()
    #bg.frame = (0,0,w,h)
    #bg.image = ui.Image.named('test:Peppers')
    #bg.content_mode = ui.CONTENT_SCALE_ASPECT_FILL
    #main_view.add_subview(bg)
    camera_in_ui_view(main_view)    # background = camera

    main_view_objc = ObjCInstance(main_view)
    scene_view = SCNView.alloc().initWithFrame_options_(((0, 0),(w, h)), None).autorelease()

    # transparent scene_view background, thus we see main_view background 
    scene_view.setBackgroundColor_(ObjCClass('UIColor').clearColor())
    scene_view.setAutoresizingMask_(18)
    scene_view.setAllowsCameraControl_(True)
    main_view_objc.addSubview_(scene_view)
    main_view.name = 'SceneKit Demo'

    scene = SCNScene.scene()
    scene_view.setScene_(scene)

    root_node = scene.rootNode()

    camera = SCNCamera.camera()
    camera_node = SCNNode.node()
    camera_node.setCamera(camera)
    camera_node.setPosition((0,0,5))
    root_node.addChildNode_(camera_node)    
    #https://medium.com/@zxlee618/custom-geometry-in-scenekit-f91464297fd1
    verts = [
    SCNVector3(0, 1, 0),
    SCNVector3(-0.5, 0, 0.5),
    SCNVector3(0.5, 0, 0.5),
    SCNVector3(0.5, 0, -0.5),
    SCNVector3(-0.5, 0, -0.5),
    SCNVector3(0, -1, 0)]

    SCNVector3Array3 = SCNVector3 * len(verts)
    verts_array = SCNVector3Array3(*verts)
    SCNGeometrySource = ObjCClass('SCNGeometrySource')
    s = SCNGeometrySource.geometrySourceWithVertices_count_(byref(verts_array), len(verts), restype=c_void_p, argtypes=[POINTER(SCNVector3Array3), c_ulong],)

    indexes = [3,3,3,3,3,3,3,3,
    0, 1, 2,
    2, 3, 0,
    3, 4, 0,
    4, 1, 0,
    1, 5, 2,
    2, 5, 3,
    3, 5, 4,
    4, 5, 1]

    indexes_array = (c_int*len(indexes))(*indexes)
    datIndexes = ObjCClass('NSData').dataWithBytes_length_(indexes_array,sizeof(indexes_array))
    # primitiveType = 0 for SCNGeometryPrimitiveTypeTriangles
    #                                   4 for SCNGeometryPrimitiveTypePolygon
    # primitiveCount = 1
    e = ObjCClass('SCNGeometryElement').geometryElementWithData_primitiveType_primitiveCount_bytesPerIndex_(datIndexes,4,8,sizeof(c_int))

    geometry = ObjCClass('SCNGeometry').geometryWithSources_elements_([s],[e])  

    #print(geometry.geometryElements()[0].data())   # to check

    Material = SCNMaterial.material()
    Material.contents =             ObjCClass('UIColor').colorWithRed_green_blue_alpha_(1,0.9,0.9,1.0)
    geometry.setMaterials_([Material])

    geometry_node = SCNNode.nodeWithGeometry_(geometry)
    root_node.addChildNode_(geometry_node)

    e2 = []
    Materials = []
    colors = ['red','blue','green','yellow','orange','pink','cyan','orchid']
    for i in range(0,8):
      j = 8 + i*3
      indexes2 = [indexes[j],indexes[j+1],indexes[j+2]]
      indexes_array2 = (c_int*len(indexes2))(*indexes2)
      datIndexes2 = ObjCClass('NSData').dataWithBytes_length_(indexes_array2,sizeof(indexes_array2))
      e = ObjCClass('SCNGeometryElement').geometryElementWithData_primitiveType_primitiveCount_bytesPerIndex_(datIndexes2,0,1,sizeof(c_int))
      e2.append(e)
      rgb = getrgb(colors[i])
      r,g,b = tuple(c/255.0 for c in rgb)
      Material = SCNMaterial.material()
      Material.contents =           ObjCClass('UIColor').colorWithRed_green_blue_alpha_(r,g,b,1.0)
      Materials.append(Material)
    geometry2 = ObjCClass('SCNGeometry').geometryWithSources_elements_([s],e2)  
    geometry2.setMaterials_(Materials)
    geometry2_node = SCNNode.nodeWithGeometry_(geometry2)
    tx,ty,tz = (-1.5,0,0)
    x = (1,0,0,0, 0,1,0,0, 0,0,1,0, tx,ty,tz,1) # translation
    geometry2_node.setPivot_(x)
    root_node.addChildNode_(geometry2_node)

    # Add a constraint to the camera to keep it pointing to the target geometry
    constraint = SCNLookAtConstraint.lookAtConstraintWithTarget_(geometry_node)
    constraint.gimbalLockEnabled = True
    camera_node.constraints = [constraint]

    light_node = SCNNode.node()
    light_node.setPosition_((100, 0, -10))
    light = SCNLight.light()
    light.setType_('directional')
    light.setCastsShadow_(True)
    light.setColor_(UIColor.whiteColor().CGColor())
    light_node.setLight_(light)
    root_node.addChildNode_(light_node)

    rotate_action = SCNAction.repeatActionForever_(SCNAction.rotateByX_y_z_duration_(0, math.pi*2, 0, 10))
    geometry_node.runAction_(rotate_action)
    geometry2_node.runAction_(rotate_action)

    main_view.present(hide_title_bar=True)

demo()
ccc

The trailing triple backticks need to be on a line of their own.

cvp

@ccc Thanks, and happy new year 🍾

vignesh

In this part of the code:

   indexes = [3,3,3,3,3,3,3,3,
    0, 1, 2,
    2, 3, 0,
    3, 4, 0,
    4, 1, 0,
    1, 5, 2,
    2, 5, 3,
    3, 5, 4,
    4, 5, 1]

what does this part do:

[3,3,3,3,3,3,3,3,

I am trying to make a cube and I have four sides completed, but I can't cover the top and bottom because of these 3's I think.

JonB

See

https://developer.apple.com/documentation/scenekit/scngeometryprimitivetype/scngeometryprimitivetypepolygon?language=objc

@cvps code used polygon primatives -- so the data first has list of polygon lengths, then list of vertices making each face.

If you want to make your cube out of squares, you would need 8 vertices (cvp has 6), then if you ordered the vertices going clockwise in the first plane, then clockwise in the second plane, then you would have

[4,4,4,4,4,4, # six faces, each has 4 sides
0,3,2,1# bottom face
1,2,6,5,# right face
2,3,7,6# back face
3,0, 4,7# left face
0,1,5,4 # front face
4,5,6,7]

Depending on how your order your vertices, it might be a little different for you.

You'll want to make sure you provide indexes in a clockwise order for each face as viewed from outside, otherwise some of the faces will be considered inside faces, which might screw with lighting/shading or applying textures.

vignesh

@JonB said:

[4,4,4,4,4,4, # six faces, each has 4 sides
0,3,2,1# bottom face
1,2,6,5,# right face
2,3,7,6# back face
3,0, 4,7# left face
0,1,5,4 # front face
4,5,6,7]

So I used the new index array you provided on the cube defined by these vertices:

    verts = [
    SCNVector3(0, 0, 0),
    SCNVector3(0.05, 0, 0),
    SCNVector3(0.05, 0, 0.05),
    SCNVector3(0, 0, 0.05),
    SCNVector3(0, 0.05, 0),
    SCNVector3(0.05, 0.05, 0),
    SCNVector3(0.05, 0.05, 0.05),
    SCNVector3(0, 0.05, 0.05)]

and the result for me is triangles (covering half the face) instead of full square faces on the cube. Is there anything else I should change in cvp's original code to allow the new index array to work?

cvp

@vignesh said:

0,3,2,1# bottom face

It seems that some lines have a comma missing

0,3,2,1, # bottom face

cvp

@vignesh and

    #for i in range(0,8):
    #  j = 8 + i*3
    for i in range(0,6):
      j = 6 + i*4
      #indexes2 = [indexes[j],indexes[j+1],indexes[j+2]]
      indexes2 = [indexes[j],indexes[j+1],indexes[j+2],indexes[j+3]]
cvp

@vignesh and

    #e = ObjCClass('SCNGeometryElement').geometryElementWithData_primitiveType_primitiveCount_bytesPerIndex_(datIndexes,4,8,sizeof(c_int))
    e = ObjCClass('SCNGeometryElement').geometryElementWithData_primitiveType_primitiveCount_bytesPerIndex_(datIndexes,4,6,sizeof(c_int)) 
cvp

@vignesh but geometry2 is still composed of triangles due to

    e = ObjCClass('SCNGeometryElement').geometryElementWithData_primitiveType_primitiveCount_bytesPerIndex_(datIndexes,4,8,sizeof(c_int)) 
cvp

@vignesh it seems that if you want different colors per face, you have to play with triangles.
Then you could generate two triangles per square face, both with same color, like

    for i in range(0,6):
      j = 6 + i*4
      # first triangle of square face
      indexes2 = [indexes[j],indexes[j+1],indexes[j+2]]
      indexes_array2 = (c_int*len(indexes2))(*indexes2)
      datIndexes2 = ObjCClass('NSData').dataWithBytes_length_(indexes_array2,sizeof(indexes_array2))
      e = ObjCClass('SCNGeometryElement').geometryElementWithData_primitiveType_primitiveCount_bytesPerIndex_(datIndexes2,0,1,sizeof(c_int))
      e2.append(e)
      rgb = getrgb(colors[i])
      r,g,b = tuple(c/255.0 for c in rgb)
      Material = SCNMaterial.material()
      Material.contents =           ObjCClass('UIColor').colorWithRed_green_blue_alpha_(r,g,b,1.0)
      Materials.append(Material)

      # 2nd triangle to create a square face
      indexes2 = [indexes[j+2],indexes[j+3],indexes[j]]
      indexes_array2 = (c_int*len(indexes2))(*indexes2)
      datIndexes2 = ObjCClass('NSData').dataWithBytes_length_(indexes_array2,sizeof(indexes_array2))
      e = ObjCClass('SCNGeometryElement').geometryElementWithData_primitiveType_primitiveCount_bytesPerIndex_(datIndexes2,0,1,sizeof(c_int))
      e2.append(e)
      # with same color
      Materials.append(Material)

    geometry2 = ObjCClass('SCNGeometry').geometryWithSources_elements_([s],e2)  
    geometry2.setMaterials_(Materials)
    geometry2_node = SCNNode.nodeWithGeometry_(geometry2)

vignesh

Thanks so much, @cvp this makes sense and I just tested it myself and it works

pulbrich

@vignesh : if you want to use SceneKit in a more convenient way, there is a Pythonista wrapper with plenty of examples here:

vignesh

@pulbrich awesome, thank you I'll check it out!