Саша Хмелёв
Саша Хмелёв

Reputation: 21

How do I use an NSImage as material of an SCNGeometry shape correctly?

I got a task in the university to add a picture as texture to the SCNGeometry Octahedron. It's my first project in Swift.

There are lot's of advices for the UIKit with UIImage class, but I'm using AppKit for macos and NSImage class. And none of the options I found in the web haven't worked for me yet. Probably I misunderstand something fundamental. Well, firstly I dragndroped a picture named "sims.jpg" to my project folder and to art.scnassets folder. And also added them with File → Add files to "art.scnassets" and general folder. And did nothing with Assets.xcassets.

Then here is how the shape is created:

func createOctahedron() {

   let vertices: [SCNVector3] = [
       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)
   ]

   let source = SCNGeometrySource(vertices: vertices)

   let indices: [UInt16] = [
       0, 1, 2,
       2, 3, 0,
       3, 4, 0,
       4, 1, 0,
       1, 5, 2,
       2, 5, 3,
       3, 5, 4,
       4, 5, 1
   ]

   let element = SCNGeometryElement(indices: indices, primitiveType: .triangles)
   let geometry = SCNGeometry(sources: [source], elements: [element])
   let node = SCNNode(geometry: geometry)

   node.geometry?.firstMaterial?.diffuse.contents = NSColor.green // назначаем цвет октаэдру

   let scnView = self.view as! SCNView
   scnView.scene?.rootNode.addChildNode(node)

   let rotateAction = SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: .pi, z: 0, duration: 5))
   node.runAction(rotateAction)
}

Just in case let me left a full code

So, I would add the image like this

let imageMaterial = SCNMaterial()
let image = NSImage.Name("sims")
imageMaterial.diffuse.contents = image
geometry.materials = [imageMaterial, imageMaterial, imageMaterial, imageMaterial, imageMaterial, imageMaterial, imageMaterial, imageMaterial]

Or maybe like that?

node.geometry?.firstMaterial?.diffuse.contents = NSImage.Name("sims")

Or should I additionally map it somehow? Help me please because I really do not get it. Xcode outputs just a rotating octahedron with no additional texture, no errors either

Upvotes: 1

Views: 326

Answers (2)

Саша Хмелёв
Саша Хмелёв

Reputation: 21

I found out what the deal was about. There was no texture appearing because of no UV mapping. I should have declared an array of 2d coordinates on the segment [0, 1] with the help of CGPoint and then add it to the source array.

Here is how it roughly looks like:

         let width:CGFloat = 1/8
         let coordinates: [CGPoint] = [
         CGPoint(x: 0,     y: 0),// BAC
         CGPoint(x: width, y: 0),
         CGPoint(x: width, y: 1),
         CGPoint(x: 0,     y: 1),// BAE
         CGPoint(x: width * 7, y: 0),
         CGPoint(x: 1,         y: 0),
         CGPoint(x: 1,         y: 1),// EDA
         CGPoint(x: width * 7, y: 1),
         CGPoint(x: width * 6, y: 0),
         CGPoint(x: width * 7, y: 0),// DAC
         CGPoint(x: width,     y: 0),
         CGPoint(x: width * 4, y: 0),
         CGPoint(x: width * 7, y: 1),// DFC
         CGPoint(x: width * 6, y: 1),
         CGPoint(x: width * 4, y: 1),
         CGPoint(x: width,     y: 1),// BFC
         CGPoint(x: width * 4, y: 0),
         CGPoint(x: width * 5, y: 0),
         CGPoint(x: width * 5, y: 1),// BFE
         CGPoint(x: width * 6, y: 1),
         CGPoint(x: width * 5, y: 0),
         CGPoint(x: width * 6, y: 0),// EFD
         CGPoint(x: width * 4, y: 1),
         CGPoint(x: width * 5, y: 1),
         ]

        let source = [
            SCNGeometrySource(vertices: vertices),
            SCNGeometrySource(textureCoordinates: coordinates)
        ]
        let element = SCNGeometryElement(indices: indices, primitiveType: .triangles)

        let octahedron = SCNGeometry(sources: source, elements: [element])

After we have specified the coordinates it's enough to wright the following:

        let image = NSImage(named: NSImage.Name("sims"))
        octahedron.firstMaterial?.diffuse.contents = image

And the image will be stretched on faces of our octahedron. The full code attaching

Upvotes: 1

Warren Burton
Warren Burton

Reputation: 17382

This line here:

let image = NSImage.Name("sims")

Only declares the name. You need:

let image = NSImage(named: NSImage.Name("sims"))

The reason your code compiles is that the contents property has a type of Any? so you can put any old goo in the property. It fails silently.

Upvotes: 1

Related Questions