4.0.0: MVC OpenGL Part 1

Thinking in MVC

Thus far, all of our code has been living in the view.  This was helpful for a time so we could see how the individual pieces of OpenGL interact in the simplest way possible; however, there is a limit to the readability and modularity of this C-like paradigm.  To fix this, we'll refactor the graphic engine into separate pieces in a way that conforms, as best we can, with Apple's MVC app design.

Start by creating a new target--completely new.  Click on the SwiftOpenGL project in the Project Navigator.  In the Editor view, beneath all of the targets listed, click on the “+“.  Name the new target OpenGLMVCPt1.  Copy the ViewController.swift and SwiftOpenGLView.swift code into new versions of these files as we have done before (remembering to adjust the compile sources so that the new versions are used instead of those from OnTheMove).  This time, however, we’re going to call the view controller SwiftOpenGLViewController because we want to show that this controller is actually going to be controlling aspects of the view.  Set up the OpenGL view in interface builder--don't forget to set the OpenGL view object's Class in the Identity Inspector.  Run the app to make sure it works and then proceed.

Referencing Interface Builder Objects

Last time, we started referencing the OpenGL view a lot to move around the screen.  Although we can continue to manually grab a reference during runtime, a more appropriate paradigm would be to create an outlet.  In Xcode, an outlet is a property that may be referenced as an object in the storyboard.  With the outlet, we can grab the view directly when we need it.  There are a couple ways to create the Outlet, one is by manually defining it in code and then, literally, connecting it.  The other is to "Ctrl + drag" from the view object in Interface Builder to a line in SwiftOpenGLViewController.swift.  This is the easiest and fastest method, so we will use this.  Click on the Assistant Editor next to the icon next to the Standard Editor.


Make sure that Main.storyboard is selected in the Project Navigator to the left and that Automatic->SwiftOpenGLViewController.swift is selected in the assistant view.


In Interface Builder, select the OpenGL view object.  Control+drag from the view object to the line above the  override definition of viewDidLoad().  You’ll see “Insert Outlet” and a blue line appear like the image below.


Release the mouse and a menu appears that asks for the name of property (we will call this view the “interactiveView”), the type of the property (it’s an instance of SwiftOpenGLView), and whether or not the reference to the property is strong or weak.  Generally, interface builder property references are held onto weakly by the controller.


Now delete the code in viewDidLoad() that creates an instance of interactiveView because we don’t need it anymore:  Xcode is setting up this property for us now.

Creating the Model

Now let's focus on the model.  SwiftOpenGLView’s .swift file is loaded with things that don’t belong there:  raw data such as vertices, computational functions such as calculating the camera matrices, Vector3 and Matrix4, CVDisplayLink setup, OpenGL Object setup... it's a mess and really hard to tell what a SwiftOpenGLView actually does.  We're going to create the Model part of our Model-View-Controller paradigm.  The Model is essentially the data crunching part of an app--it doesn't decide when to crunch data or how the user sees or interacts with the data it produces.  It just takes in data, processes it, and produces a result.

Vectors and Matrices

The first part of the model we'll make defines the Vector3 and Matrix4 related code.  We'll take this opportunity to redefine our implementations of vectors and matrices.  Vector3 worked fine, but what if we wanted to define a vector that used Double, Int16, or something else?  Let's fix that by defining Float2, Float3, and Float4:  2D, 3D, and 4D vectors that hold Float data.  Create a new file called GraphicMath.swift.  The code is essentially the same so I won't explain the details:  We've just renamed things.

//
//  GraphicMath.swift
//  SwiftOpenGLRefactor
//
//  Created by Myles Schultz on 1/17/18.
//  Copyright © 2018 MyKo. All rights reserved.
//

import Foundation


struct Float2: Hashable, Equatable {
    let x: Float
    let y: Float
    
    init() {
        x = 0.0
        y = 0.0
    }
    init(x: Float, y: Float) {
        self.x = x
        self.y = y
    }
    
    var hashValue: Int {
        return (x.hashValue ^ y.hashValue) &* 65_537
    }
    
    static func ==(lhs: Float2, rhs: Float2) -> Bool {
        return lhs.hashValue == rhs.hashValue
    }
    static func !=(lhs: Float2, rhs: Float2) -> Bool {
        return lhs.hashValue != rhs.hashValue
    }
    
    static func +(lhs: Float2, rhs: Float2) -> Float2 {
        return Float2(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
    }
    static func -(lhs: Float2, rhs: Float2) -> Float2 {
        return Float2(x: lhs.x - rhs.x, y: lhs.y - rhs.y)
    }
    static func *(lhs: Float2, rhs: Float) -> Float2 {
        return Float2(x: lhs.x * rhs, y: lhs.y * rhs)
    }
    static func *(lhs: Float, rhs: Float2) -> Float2 {
        return Float2(x: lhs * rhs.x, y: lhs * rhs.y)
    }
    func dotProduct(_ vector: Float2) -> Float {
        return (x * vector.x) + (y * vector.y)
    }
    
    func lenght() -> Float {
        return sqrtf((x * x) + (y * y))
    }
}

struct Float3: Hashable, Equatable {
    let x: Float
    let y: Float
    let z: Float
    
    init() {
        x = 0.0
        y = 0.0
        z = 0.0
    }
    init(x: Float, y: Float, z: Float) {
        self.x = x
        self.y = y
        self.z = z
    }
    
    var hashValue: Int {
        return (x.hashValue ^ y.hashValue ^ z.hashValue) &* 65_537
    }
    
    static func ==(lhs: Float3, rhs: Float3) -> Bool {
        return lhs.hashValue == rhs.hashValue
    }
    static func !=(lhs: Float3, rhs: Float3) -> Bool {
        return lhs.hashValue != rhs.hashValue
    }
    static func +(lhs: Float3, rhs: Float3) -> Float3 {
        return Float3(x: lhs.x + rhs.x, y: lhs.y + rhs.y, z: lhs.z + rhs.z)
    }
    static func -(lhs: Float3, rhs: Float3) -> Float3 {
        return Float3(x: lhs.x - rhs.x, y: lhs.y - rhs.y, z: lhs.z - rhs.z)
    }
    static func *(lhs: Float3, rhs: Float) -> Float3 {
        return Float3(x: lhs.x * rhs, y: lhs.y * rhs, z: lhs.z * rhs)
    }
    static func *(lhs: Float, rhs: Float3) -> Float3 {
        return Float3(x: lhs * rhs.x, y: lhs * rhs.y, z: lhs * rhs.z)
    }
    func dotProduct(_ vector: Float3) -> Float {
        return (x * vector.x) + (y * vector.y) + (z * vector.z)
    }
    func crossProduct(_ vector: Float3) -> Float3 {
        return Float3(x: (y * vector.z) - (z * vector.y),
                      y: (z * vector.x) - (x * vector.z),
                      z: (x * vector.y) - (y * vector.x))
    }
    
    func lenght() -> Float {
        return sqrtf((x * x) + (y * y) + (z * z))
    }
}

struct Float4: Hashable, Equatable {
    let x: Float
    let y: Float
    let z: Float
    let w: Float
    
    init() {
        x = 0.0
        y = 0.0
        z = 0.0
        w = 0.0
    }
    init(x: Float, y: Float, z: Float, w: Float) {
        self.x = x
        self.y = y
        self.z = z
        self.w = w
    }
    
    var hashValue: Int {
        return (x.hashValue ^ y.hashValue ^ z.hashValue ^ w.hashValue) &* 65_537
    }
    
    static func ==(lhs: Float4, rhs: Float4) -> Bool {
        return lhs.hashValue == rhs.hashValue
    }
    static func !=(lhs: Float4, rhs: Float4) -> Bool {
        return lhs.hashValue != rhs.hashValue
    }
    static func +(lhs: Float4, rhs: Float4) -> Float4 {
        return Float4(x: lhs.x + rhs.x, y: lhs.y + rhs.y, z: lhs.z + rhs.z, w: lhs.w + rhs.w)
    }
    static func -(lhs: Float4, rhs: Float4) -> Float4 {
        return Float4(x: lhs.x - rhs.x, y: lhs.y - rhs.y, z: lhs.z - rhs.z, w: lhs.w - rhs.w)
    }
    static func *(lhs: Float4, rhs: Float) -> Float4 {
        return Float4(x: lhs.x * rhs, y: lhs.y * rhs, z: lhs.z * rhs, w: lhs.w * rhs)
    }
    static func *(lhs: Float, rhs: Float4) -> Float4 {
        return Float4(x: lhs * rhs.x, y: lhs * rhs.y, z: lhs * rhs.z, w: lhs * rhs.w)
    }
    func dotProduct(_ vector: Float4) -> Float {
        return (x * vector.x) + (y * vector.y) + (z * vector.z) + (w * vector.w)
    }
    
    func lenght() -> Float {
        let normx = x / w
        let normy = y / w
        let normz = z / w
        
        return sqrtf((normx * normx) + (normy * normy) + (normz * normz))
    }
}

We're going to do the same thing with Matrix4 by defining FloatMatrix2, FloatMatrix3, and FloatMatrix4.  In addition, we'll also use our new Float2, Float3, and Float4 types to define the rows of a matrix.

struct FloatMatrix2: Hashable, Equatable {
    let vector1: Float2
    let vector2: Float2
    
    var hashValue: Int {
        return (vector1.hashValue ^ vector2.hashValue) &* 65_537
    }
    
    init() {
        vector1 = Float2(x: 1.0, y: 0.0)
        vector2 = Float2(x: 0.0, y: 1.0)
    }
    init(vector1: Float2, vector2: Float2) {
        self.vector1 = vector1
        self.vector2 = vector2
    }
    
    func transpose() -> FloatMatrix2 {
        return FloatMatrix2(vector1: Float2(x: vector1.xy: vector2.x),
                            vector2: Float2(x: vector1.y, y: vector2.y))
    }
    func determinant() -> Float {
        return (vector1.x * vector2.y) - (vector1.y * vector2.x)
    }
    func inverse() -> FloatMatrix2 {
        return FloatMatrix2(vector1: Float2(x: vector2.y / determinant(),
                                            y: -vector1.y / determinant()),
                            vector2: Float2(x: -vector2.x / determinant(),
                                            y: vector1.x / determinant()))
    }
    
    static func ==(lhs: FloatMatrix2, rhs: FloatMatrix2) -> Bool {
        return lhs.hashValue == rhs.hashValue
    }
    static func !=(lhs: FloatMatrix2, rhs: FloatMatrix2) -> Bool {
        return lhs.hashValue != rhs.hashValue
    }
    static func +(lhs: FloatMatrix2, rhs: FloatMatrix2) -> FloatMatrix2 {
        return FloatMatrix2(vector1: lhs.vector1 + rhs.vector1,
                            vector2: lhs.vector2 + rhs.vector2)
    }
    static func -(lhs: FloatMatrix2, rhs: FloatMatrix2) -> FloatMatrix2 {
        return FloatMatrix2(vector1: lhs.vector1 - rhs.vector1,
                            vector2: lhs.vector2 - rhs.vector2)
    }
    static func *(lhs: FloatMatrix2, rhs: Float) -> FloatMatrix2 {
        return FloatMatrix2(vector1: lhs.vector1 * rhs, vector2: lhs.vector2 * rhs)
    }
    static func *(lhs: Float, rhs: FloatMatrix2) -> FloatMatrix2 {
        return FloatMatrix2(vector1: lhs * rhs.vector1, vector2: lhs * rhs.vector2)
    }
    static func *(lhs: FloatMatrix2, rhs: FloatMatrix2) -> FloatMatrix2 {
        let transpose = rhs.transpose()
        return FloatMatrix2(vector1: Float2(x: lhs.vector1.dotProduct(transpose.vector1),
                                            y: lhs.vector1.dotProduct(transpose.vector2)),
                            vector2: Float2(x: lhs.vector2.dotProduct(transpose.vector1),
                                            y: lhs.vector2.dotProduct(transpose.vector2)))
    }
    static func /(lhs: FloatMatrix2, rhs: FloatMatrix2) -> FloatMatrix2 {
        return lhs * rhs.inverse()
    }
}
struct FloatMatrix3: Hashable, Equatable {
    let vector1: Float3
    let vector2: Float3
    let vector3: Float3
    
    var hashValue: Int {
        return (vector1.hashValue ^ vector2.hashValue ^ vector3.hashValue) &* 65_537
    }
    
    init() {
        vector1 = Float3(x: 1.0, y: 0.0, z: 0.0)
        vector2 = Float3(x: 0.0, y: 1.0, z: 0.0)
        vector3 = Float3(x: 0.0, y: 0.0, z: 1.0)
    }
    init(vector1: Float3, vector2: Float3, vector3: Float3) {
        self.vector1 = vector1
        self.vector2 = vector2
        self.vector3 = vector3
    }
    
    func transpose() -> FloatMatrix3 {
        return FloatMatrix3(vector1: Float3(x: vector1.x, y: vector2.x, z: vector3.x),
                            vector2: Float3(x: vector1.y, y: vector2.y, z: vector3.y),
                            vector3: Float3(x: vector1.z, y: vector2.z, z: vector3.z))
    }
    func cofactors() -> FloatMatrix3 {
        return FloatMatrix3(vector1: Float3(x: vector1.x, y: -(vector1.y), z: vector1.z),
                            vector2: Float3(x: -(vector2.x), y: vector2.y, z: -(vector2.z)),
                            vector3: Float3(x: vector3.x, y: -(vector3.y), z: vector3.z))
    }
    func inverse() -> FloatMatrix3 {
        let minors = FloatMatrix3(vector1: Float3(x: vector2.y * vector3.z - vector2.z
                                                    vector3.y,
                                                  y: vector2.x * vector3.z - vector2.z
                                                    vector3.x,
                                                  z: vector2.x * vector3.y - vector2.y
                                                    vector3.x),
                                  vector2: Float3(x: vector1.y * vector3.z - vector1.z
                                                    vector3.y,
                                                  y: vector1.x * vector3.z - vector1.z
                                                    vector3.x,
                                                  z: vector1.x * vector3.y - vector1.y
                                                    vector3.x),
                                  vector3: Float3(x: vector1.y * vector2.z - vector1.z
                                                    vector2.y,
                                                  y: vector1.x * vector2.z - vector1.z
                                                    vector2.x,
                                                  z: vector1.x * vector2.y - vector1.y
                                                    vector2.x))
        let adjugate = minors.cofactors().transpose()
        let inverseDeterminant = 1 / (vector1.x * minors.vector1.x - vector1.y * minors.vector1.y 
                                  + vector1.z * minors.vector1.z)
        return adjugate * inverseDeterminant
    }
    
    static func ==(lhs: FloatMatrix3, rhs: FloatMatrix3) -> Bool {
        return lhs.hashValue == rhs.hashValue
    }
    static func !=(lhs: FloatMatrix3, rhs: FloatMatrix3) -> Bool {
        return lhs.hashValue != rhs.hashValue
    }
    static func +(lhs: FloatMatrix3, rhs: FloatMatrix3) -> FloatMatrix3 {
        return FloatMatrix3(vector1: lhs.vector1 + rhs.vector1,
                            vector2: lhs.vector2 + rhs.vector2,
                            vector3: lhs.vector3 + rhs.vector3)
    }
    static func -(lhs: FloatMatrix3, rhs: FloatMatrix3) -> FloatMatrix3 {
        return FloatMatrix3(vector1: lhs.vector1 - rhs.vector1,
                            vector2: lhs.vector2 - rhs.vector2,
                            vector3: lhs.vector3 - rhs.vector3)
    }
    static func *(lhs: FloatMatrix3, rhs: Float) -> FloatMatrix3 {
        return FloatMatrix3(vector1: lhs.vector1 * rhs,
                            vector2: lhs.vector2 * rhs,
                            vector3: lhs.vector3 * rhs)
    }
    static func *(lhs: Float, rhs: FloatMatrix3) -> FloatMatrix3 {
        return FloatMatrix3(vector1: lhs * rhs.vector1,
                            vector2: lhs * rhs.vector2,
                            vector3: lhs * rhs.vector3)
    }
    static func *(lhs: FloatMatrix3, rhs: FloatMatrix3) -> FloatMatrix3 {
        let transpose = rhs.transpose()
        return FloatMatrix3(vector1: Float3(x: lhs.vector1.dotProduct(transpose.vector1),
                                            y: lhs.vector1.dotProduct(transpose.vector2),
                                            z: lhs.vector1.dotProduct(transpose.vector3)),
                            vector2: Float3(x: lhs.vector2.dotProduct(transpose.vector1),
                                            y: lhs.vector2.dotProduct(transpose.vector2),
                                            z: lhs.vector2.dotProduct(transpose.vector3)),
                            vector3: Float3(x: lhs.vector3.dotProduct(transpose.vector1),
                                            y: lhs.vector3.dotProduct(transpose.vector3),
                                            z: lhs.vector3.dotProduct(transpose.vector3)))
    }
    static func /(lhs: FloatMatrix3, rhs: FloatMatrix3) -> FloatMatrix3 {
        return lhs * rhs.inverse()
    }
}
struct FloatMatrix4: Hashable, Equatable {
    typealias StringLiteralType = String
    
    let vector1: Float4
    let vector2: Float4
    let vector3: Float4
    let vector4: Float4
    
    var hashValue: Int {
        return (vector1.hashValue ^ vector2.hashValue ^ vector3.hashValue ^ vector4.hashValue)
                 &* 65_537
    }
    
    init() {
        vector1 = Float4(x: 1.0, y: 0.0, z: 0.0, w: 0.0)
        vector2 = Float4(x: 0.0, y: 1.0, z: 0.0, w: 0.0)
        vector3 = Float4(x: 0.0, y: 0.0, z: 1.0, w: 0.0)
        vector4 = Float4(x: 0.0, y: 0.0, z: 0.0, w: 1.0)
    }
    init(vector1: Float4, vector2: Float4, vector3: Float4, vector4: Float4) {
        self.vector1 = vector1
        self.vector2 = vector2
        self.vector3 = vector3
        self.vector4 = vector4
    }
    
    func transpose() -> FloatMatrix4 {
        return FloatMatrix4(vector1: Float4(x: vector1.x,
                                            y: vector2.x,
                                            z: vector3.x,
                                            w: vector4.x),
                            vector2: Float4(x: vector1.y,
                                            y: vector2.y,
                                            z: vector3.y,
                                            w: vector4.y),
                            vector3: Float4(x: vector1.z,
                                            y: vector2.z,
                                            z: vector3.z,
                                            w: vector4.z),
                            vector4: Float4(x: vector1.w,
                                            y: vector2.w,
                                            z: vector3.w,
                                            w: vector4.w))
    }
    func cofactors() -> FloatMatrix4 {
        return FloatMatrix4(vector1: Float4(x: vector1.x,
                                            y: -(vector1.y),
                                            z: vector1.z,
                                            w: -(vector1.w)),
                            vector2: Float4(x: -(vector2.x),
                                            y: vector2.y,
                                            z: -(vector2.z),
                                            w: vector2.w),
                            vector3: Float4(x: vector3.x,
                                            y: -(vector3.y),
                                            z: vector3.z,
                                            w: -(vector3.w)),
                            vector4: Float4(x: -(vector4.x),
                                            y: vector4.y,
                                            z: -(vector4.z),
                                            w: vector4.w))
    }
    func inverse() -> FloatMatrix4 {
        let a = (vector3.z * vector4.w - vector3.w * vector4.z)
        let b = (vector3.y * vector4.w - vector3.w * vector4.y)
        let c = (vector3.y * vector4.z - vector3.z * vector4.y)
        let d = (vector3.x * vector4.w - vector3.w * vector4.x)
        let e = (vector3.x * vector4.z - vector3.z * vector4.x)
        let f = (vector3.x * vector4.y - vector3.y * vector4.x)
        let g = (vector2.z * vector4.w - vector2.w * vector4.z)
        let h = (vector2.y * vector4.w - vector2.w * vector4.y)
        let i = (vector2.y * vector4.z - vector2.z * vector4.y)
        let j = (vector2.x * vector4.w - vector2.w * vector4.x)
        let k = (vector2.x * vector4.z - vector2.z * vector4.x)
        let l = (vector2.x * vector4.y - vector2.y * vector4.x)
        let m = (vector2.z * vector3.w - vector2.w * vector3.z)
        let n = (vector2.y * vector3.w - vector2.w * vector3.y)
        let o = (vector2.y * vector3.z - vector2.z * vector3.y)
        let p = (vector2.x * vector3.w - vector2.w * vector3.x)
        let q = (vector2.x * vector3.z - vector2.z * vector3.x)
        let r = (vector2.x * vector3.y - vector2.y * vector3.x)
        
        let minors = FloatMatrix4(vector1: Float4(x: vector2.y * a - vector2.z * b +
                                                     vector2.w * c,
                                                  y: vector2.x * a - vector2.z * d + 
                                                     vector2.w * e,
                                                  z: vector2.x * b - vector2.y * d +
                                                     vector2.w * f,
                                                  w: vector2.x * c - vector2.y * e +
                                                     vector2.z * f),
                                  vector2: Float4(x: vector1.y * a - vector1.z * b +
                                                     vector1.w * c,
                                                  y: vector1.x * a - vector1.z * d +
                                                     vector1.w * e,
                                                  z: vector1.x * b - vector1.y * d +
                                                     vector1.w * f,
                                                  w: vector1.x * c - vector1.y * e +
                                                     vector1.z * f),
                                  vector3: Float4(x: vector1.y * g - vector1.z * h +
                                                     vector1.w * i,
                                                  y: vector1.x * g - vector1.z * j +
                                                     vector1.w * k,
                                                  z: vector1.x * h - vector1.y * j +
                                                     vector1.w * l,
                                                  w: vector1.x * i - vector1.y * k +
                                                     vector1.z * l),
                                  vector4: Float4(x: vector1.y * m - vector1.z * n +
                                                     vector1.w * o,
                                                  y: vector1.x * m - vector1.z * p +
                                                     vector1.w * q,
                                                  z: vector1.x * n - vector1.y * p +
                                                     vector1.w * r,
                                                  w: vector1.x * o - vector1.y * q +
                                                     vector1.z * r))
        
        let adjugate = minors.cofactors().transpose()
        let inverseDeterminant = 1 / (vector1.x * minors.vector1.x - vector1.y * minors.vector1.y 
                                 + vector1.z * minors.vector1.z - vector1.w * minors.vector1.w)
        return adjugate * inverseDeterminant
    }
    
    static func ==(lhs: FloatMatrix4, rhs: FloatMatrix4) -> Bool {
        return lhs.hashValue == rhs.hashValue
    }
    static func !=(lhs: FloatMatrix4, rhs: FloatMatrix4) -> Bool {
        return lhs.hashValue != rhs.hashValue
    }
    static func +(lhs: FloatMatrix4, rhs: FloatMatrix4) -> FloatMatrix4 {
        return FloatMatrix4(vector1: lhs.vector1 + rhs.vector1,
                            vector2: lhs.vector2 + rhs.vector2,
                            vector3: lhs.vector3 + rhs.vector3,
                            vector4: lhs.vector4 + rhs.vector4)
    }
    static func -(lhs: FloatMatrix4, rhs: FloatMatrix4) -> FloatMatrix4 {
        return FloatMatrix4(vector1: lhs.vector1 - rhs.vector1,
                            vector2: lhs.vector2 - rhs.vector2,
                            vector3: lhs.vector3 - rhs.vector3,
                            vector4: lhs.vector4 - rhs.vector4)
    }
    static func *(lhs: FloatMatrix4, rhs: Float) -> FloatMatrix4 {
        return FloatMatrix4(vector1: lhs.vector1 * rhs,
                            vector2: lhs.vector2 * rhs,
                            vector3: lhs.vector3 * rhs,
                            vector4: lhs.vector4 * rhs)
    }
    static func *(lhs: Float, rhs: FloatMatrix4) -> FloatMatrix4 {
        return FloatMatrix4(vector1: lhs * rhs.vector1,
                            vector2: lhs * rhs.vector2,
                            vector3: lhs * rhs.vector3,
                            vector4: lhs * rhs.vector4)
    }
    static func *(lhs: FloatMatrix4, rhs: FloatMatrix4) -> FloatMatrix4 {
        let transpose = rhs.transpose()
        return FloatMatrix4(vector1: Float4(x: lhs.vector1.dotProduct(transpose.vector1),
                                            y: lhs.vector1.dotProduct(transpose.vector2),
                                            z: lhs.vector1.dotProduct(transpose.vector3),
                                            w: lhs.vector1.dotProduct(transpose.vector4)),
                            vector2: Float4(x: lhs.vector2.dotProduct(transpose.vector1),
                                            y: lhs.vector2.dotProduct(transpose.vector2),
                                            z: lhs.vector2.dotProduct(transpose.vector3),
                                            w: lhs.vector2.dotProduct(transpose.vector4)),
                            vector3: Float4(x: lhs.vector3.dotProduct(transpose.vector1),
                                            y: lhs.vector3.dotProduct(transpose.vector2),
                                            z: lhs.vector3.dotProduct(transpose.vector3),
                                            w: lhs.vector3.dotProduct(transpose.vector4)),
                            vector4: Float4(x: lhs.vector4.dotProduct(transpose.vector1),
                                            y: lhs.vector4.dotProduct(transpose.vector2),
                                            z: lhs.vector4.dotProduct(transpose.vector3),
                                            w: lhs.vector4.dotProduct(transpose.vector4)))
    }
    static func /(lhs: FloatMatrix4, rhs: FloatMatrix4) -> FloatMatrix4 {
        return lhs * rhs.inverse()
    }
    
    func rowMajorArray() -> [Float] {
        return [vector1.x, vector1.y, vector1.z, vector1.w,
                vector2.x, vector2.y, vector2.z, vector2.w,
                vector3.x, vector3.y, vector3.z, vector3.w,
                vector4.x, vector4.y, vector4.z, vector4.w]
    }
    func columnMajorArray() -> [Float] {
        return [vector1.x, vector2.x, vector3.x, vector4.x,
                vector1.y, vector2.y, vector3.y, vector4.y,
                vector1.z, vector2.z, vector3.z, vector4.z,
                vector1.w, vector2.w, vector3.w, vector4.w]
    }
    
    static func projection(angeOfView theta: Float = 35, aspect: Float
                           distanceToNearClippingPlane nearZ: Float = 0.1
                           distanceToFarClippingPlane farZ: Float = 1000) -> FloatMatrix4 {
        let scale = 1 / tanf(theta * 0.5 * Float.pi / 180)
        return FloatMatrix4(vector1: Float4(x: scale / aspect,
                                            y: 0.0,
                                            z: 0.0,
                                            w: 0.0),
                            vector2: Float4(x: 0.0,
                                            y: scale,
                                            z: 0.0,
                                            w: 0.0),
                            vector3: Float4(x: 0.0,
                                            y: 0.0,
                                            z: (farZ + nearZ) / (nearZ - farZ),
                                            w: (2 * farZ * nearZ) / (nearZ - farZ)),
                            vector4: Float4(x: 0.0,
                                            y: 0.0,
                                            z: -1.0,
                                            w: 0.0))
    }
    static func orthographic(width: Float, height: Float, nearZ: Float = 0.001,
                             farZ: Float = 1000) -> FloatMatrix4 {
        let right = width * 0.5
        let left = -right
        let top = height * 0.5
        let bottom = -top
        return FloatMatrix4(vector1: Float4(x: 2 / (right - left),
                                            y: 0.0,
                                            z: 0.0,
                                            w: -((right + left) / (right - left))),
                            vector2: Float4(x: 0.0,
                                            y: 2 / (top - bottom),
                                            z: 0.0,
                                            w: -((top + bottom) / (top - bottom))),
                            vector3: Float4(x: 0.0,
                                            y: 0.0,
                                            z: -2 / (farZ - nearZ),
                                            w: -(farZ + nearZ) / (farZ - nearZ)),
                            vector4: Float4(x: 0.0,
                                            y: 0.0,
                                            z: 0.0,
                                            w: 1.0))
    }
    
    func translate(x: Float, y: Float, z: Float) -> FloatMatrix4 {
        return self * FloatMatrix4(vector1: Float4(x: 1.0, y: 0.0, z: 0.0, w: x),
                                   vector2: Float4(x: 0.0, y: 1.0, z: 0.0, w: y),
                                   vector3: Float4(x: 0.0, y: 0.0, z: 1.0, w: z),
                                   vector4: Float4(x: 0.0, y: 0.0, z: 0.0, w: 1.0))
    }
    
    func rotateXAxis(_ radians: Float) -> FloatMatrix4 {
        return self * FloatMatrix4(vector1: Float4(x: 1.0,
                                                   y: 0.0,
                                                   z: 0.0,
                                                   w: 0.0),
                                   vector2: Float4(x: 0.0,
                                                   y: cos(radians),
                                                   z: -sin(radians),
                                                   w: 0.0),
                                   vector3: Float4(x: 0.0,
                                                   y: sin(radians),
                                                   z: cos(radians),
                                                   w: 0.0),
                                   vector4: Float4(x: 0.0,
                                                   y: 0.0,
                                                   z: 0.0,
                                                   w: 1.0))
    }
    func rotateYAxis(_ radians: Float) -> FloatMatrix4 {
        return self * FloatMatrix4(vector1: Float4(x: cos(radians),
                                                   y: 0.0,
                                                   z: sin(radians),
                                                   w: 0.0),
                                   vector2: Float4(x: 0.0,
                                                   y: 1.0,
                                                   z: 0.0,
                                                   w: 0.0),
                                   vector3: Float4(x: -sin(radians),
                                                   y: 0.0,
                                                   z: cos(radians),
                                                   w: 0.0),
                                   vector4: Float4(x: 0.0, y: 0.0, z: 0.0, w: 1.0))
    }
    func rotateZAxis(_ radians: Float) -> FloatMatrix4 {
        return self * FloatMatrix4(vector1: Float4(x: cos(radians),
                                                   y: -sin(radians),
                                                   z: 0.0,
                                                   w: 0.0),
                                   vector2: Float4(x: sin(radians),
                                                   y: cos(radians),
                                                   z: 0.0,
                                                   w: 0.0),
                                   vector3: Float4(x: 0.0,
                                                   y: 0.0,
                                                   z: 1.0,
                                                   w: 0.0),
                                   vector4: Float4(x: 0.0,
                                                   y: 0.0,
                                                   z: 0.0,
                                                   w: 1.0))
    }
    func rotate(_ angle: Float, along axis: Float3) -> FloatMatrix4 {
        let cosine = cos(angle)
        let inverseCosine = 1.0 - cosine
        let sine = sin(angle)
        
        return self * FloatMatrix4(vector1: Float4(x: cosine + inverseCosine * axis.x * axis.x,
                                                   y: inverseCosine * axis.x * axis.y + axis.z
                                                      * sine,
                                                   z: inverseCosine * axis.x * axis.z - axis.y
                                                      * sine,
                                                   w: 0.0),
                                   vector2: Float4(x: inverseCosine * axis.x * axis.y - axis.z
                                                      * sine,
                                                   y: cosine + inverseCosine * axis.y * axis.y,
                                                   z: inverseCosine * axis.y * axis.z + axis.x
                                                      * sine,
                                                                                                                           w: 0.0),
                                   vector3: Float4(x: inverseCosine * axis.x * axis.z + axis.y
                                                      * sine,
                                                   y: inverseCosine * axis.y * axis.z - axis.x
                                                      * sine,
                                                   z: cosine + inverseCosine * axis.z * axis.z,
                                                   w: 0.0),
                                   vector4: Float4(x: 0.0,
                                                   y: 0.0,
                                                   z: 0.0,
                                                   w: 1.0))
    }
    func scale(x: Float, y: Float, z: Float) -> FloatMatrix4 {
        return self * FloatMatrix4(vector1: Float4(x: x, y: 0.0, z: 0.0, w: 0.0),
                                   vector2: Float4(x: 0.0, y: y, z: 0.0, w: 0.0),
                                   vector3: Float4(x: 0.0, y: 0.0, z: z, w: 0.0),
                                   vector4: Float4(x: 0.0, y: 0.0, z: 0.0, w: 1.0))
    }
    func uniformScale(by value: Float) -> FloatMatrix4 {
        return self.scale(x: value, y: value, z: value)
    }
}

Now that we have that defined, we can delete all of the math code we put at the top of SwiftOpenGLView.swift.  You can do that now, but you'll get a lot of errors in those camera functions we put in SwiftOpenGLView.  We're really going to work on those later, but not quite yet.  First we're going to refactor the OpenGL objects and CVDisplayLink code.  Create a new file and call it OpenGLObjects.swift.

Vertex Buffer Object

First define a protocol called OpenGLObject.

protocol OpenGLObject {
    var id: GLuint { get set }
    
    func bind()
    func unbind()
    mutating func delete()
}

With this, we ensure that each of the OpenGL Objects were going to defines an id, and implements this set of functions.  When we think about how we used a VBO, we defined an empty id variable, and then later reset that id when we actually created and filled the VBO with our data.  This is really important, but we need to remember that when we set up our new type, we need to know that Type initialization and the OpenGL code for object initialization are two separate tasks.  Meaning that if we try to do both at the same time, we are likely to going to cause an error.  The reason is that the environment has to be right in order to create OpenGL objects:  namely, a context needs to be bound. It's automatically bound for us in prepareOpenGL, but the time that an instance of SwiftOpenGLView is initializing it's properties, for example, this is not true and we have to bind the context ourselves before we may start calling OpenGL code.  To solve this problem, we'll initialize our type's properties to some initial value to make type initialization simple, and then define a "load" method.


struct VertexBufferObject: OpenGLObject {
    var id: GLuint = 0
    let type: GLenum = GLenum(GL_ARRAY_BUFFER)
    var vertexCount: Int32 {
        return Int32(data.count)
    }
    var data: [Vertex] = []
    
    mutating func load(_ data: [Vertex]) {
        self.data = data
        
        glGenBuffers(1, &id)
        bind()
        glBufferData(GLenum(GL_ARRAY_BUFFER), data.count * MemoryLayout<Vertex>.size, data, GLenum(GL_STATIC_DRAW))
    }
    
    func bind() {
        glBindBuffer(type, id)
    }
    
    func unbind() {
        glBindBuffer(type, id)
    }
    
    mutating func delete() {
        glDeleteBuffers(1, &id)
    }

}

Notice that we have declared VertexBufferObject as type that conforms to our protocol OpenGLObject.  Now look carefully at our data property and load method.  They are using another new type that we'll define now:  Vertex.  This just helps us when defining a model to know which piece of data goes with what.  Place this definition just above VertexBufferObject.


struct Vertex {
    var position: Float3
    var normal: Float3
    var textureCoordinate: Float2
    var color: Float3

}

Take note that I also reordered things just a little.  Instead of having the color second and normal last, I have switched them.  This wasn't strictly necessary, but in my mind this feels better as an order of importance.  The other thing I have done, it make the position a three dimensional vector!  Now we can define a truly three dimensional object to draw.  These changes require us to adjust our vertex layout definition in the VAO as well so let's do that now.

Vertex Array Object

Just as we did for VertexBufferObject, we are going to declare this object as conforming to OpenGLObject, and we are going to separate type initialization from OpenGL initialization.

struct VertexArrayObject: OpenGLObject {
    var id: GLuint = 0
    
    mutating func layoutVertexPattern() {
        glGenVertexArrays(1, &id)
        bind()
        
        /* Position */
        glVertexAttribPointer(0, 3, GLenum(GL_FLOAT), GLboolean(GL_FALSE), 44
                              UnsafePointer<GLuint>(bitPattern: 0))
        glEnableVertexAttribArray(0)
        
        /* Normal */
        glVertexAttribPointer(1, 3, GLenum(GL_FLOAT), GLboolean(GL_FALSE), 44
                              UnsafePointer<GLuint>(bitPattern: 12))
        glEnableVertexAttribArray(1)
        
        /* Texture Coordinate */
        glVertexAttribPointer(2, 2, GLenum(GL_FLOAT), GLboolean(GL_FALSE), 44
                              UnsafePointer<GLuint>(bitPattern: 24))
        glEnableVertexAttribArray(2)
        
        /* Color */
        glVertexAttribPointer(3, 3, GLenum(GL_FLOAT), GLboolean(GL_FALSE), 44
                              UnsafePointer<GLuint>(bitPattern:32))
        glEnableVertexAttribArray(3)
    }
    
    func bind() {
        glBindVertexArray(id)
    }
    func unbind() {
        glBindVertexArray(id)
    }
    
    mutating func delete() {
        glDeleteVertexArrays(1, &id)
    }
}

The key changes here are to the layout.  When we defined a Vertex, we define an order of properties:  position, normal, textureCoordinate, and color.  That translates to a specific order of Float's:  Float3, Float3, Float2, Float3.  A Float contains 4 bytes making a Float3 12 bytes long.  The position is still starting the starting point, so it's index and pointer are still 0, but the pointer to the following Float3 (the normal) is 12 as you see above.  This also means the overall size of the type were sending has increase from 40 to 44, which is why the stride parameter is now 44 above.  The other change, as mentioned earlier, is that the normal and color have switched places (normal is second and color is last).

Texture Buffer Object

A TBO is much the same: conforms to OpenGLObject, and initialization is separated into two pieces.  However, we need to change our module import from OpenGL.GL3 to Quartz so we can access the NSImage initializer.

import Foundation
import OpenGL.GL3
import Quartz

...

struct TextureBufferObject: OpenGLObject {
    var id: GLuint = 0
    var textureSlot: GLintGLenum(GL_TEXTURE0)
    
    mutating func loadTexture(named name: String) {
        guard let textureData = NSImage(named: NSImage.Name(rawValue: name))?.tiffRepresentation 
        else {
            Swift.print("Image name not located in Image Asset Catalog")
            return
        }
        
        glGenTextures(1, &id)
        bind()
        
        glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MIN_FILTER), GL_LINEAR)
        glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MAG_FILTER), GL_LINEAR)
        glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_S), GL_REPEAT)
        glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_T), GL_REPEAT)
        
        glTexImage2D(GLenum(GL_TEXTURE_2D), 0, GL_RGBA, 256, 256, 0, GLenum(GL_RGBA),
                            GLenum(GL_UNSIGNED_BYTE), (textureData as NSData).bytes)
    }
    
    func bind() {
        glBindTexture(GLenum(GL_TEXTURE_2D), id)
    }
    func unbind() {
        glBindTexture(GLenum(GL_TEXTURE_2D), 0)
    }
    
    mutating func delete() {
        glDeleteTextures(1, &id)
    }
}

There's nothing to crazy to discuss with this one, so we'll move on to the shader.

Shader

We're going to try to condense some of these code.  Instead of going through compiling, linking, and checking for errors during that process for both there vertex and fragment shader, we're going to obstruct those two into functions so we only have to type them out one.  We could even condense them further to reduce even more boilerplate, but it may not be as clear what we are doing because compile and link would be the same method, but doing different things.  We'll keep them separate for now (we have to make separate glShader* and glProgram* calls anyway).

struct Shader: OpenGLObject {
    var id: GLuint = 0
    
    mutating func create(withVertex vertexSource: String, andFragment fragmentSource: String) {
        id = glCreateProgram()
        
        let vertex = compile(shaderType: GLenum(GL_VERTEX_SHADER), withSource: vertexSource)
        let fragment = compile(shaderType: GLenum(GL_FRAGMENT_SHADER), withSource: fragmentSource)
        
        link(vertexShader: vertex, fragmentShader: fragment)
    }
    
    func compile(shaderType type: GLenum, withSource source: String) -> GLuint {
        let shader = glCreateShader(type)
        var pointerToShader = UnsafePointer<GLchar>(source.cString(using: String.Encoding.ascii))
        glShaderSource(shader, 1, &pointerToShader, nil)
        glCompileShader(shader)
        var compiled: GLint = 0
        glGetShaderiv(shader, GLbitfield(GL_COMPILE_STATUS), &compiled)
        if compiled <= 0 {
            print("Could not compile shader type: \(type), getting log...")
            var logLength: GLint = 0
            print("Log length: \(logLength)")
            glGetShaderiv(shader, GLenum(GL_INFO_LOG_LENGTH), &logLength)
            if logLength > 0 {
                let cLog = UnsafeMutablePointer<CChar>.allocate(capacity: Int(logLength))
                glGetShaderInfoLog(shader, GLsizei(logLength), &logLength, cLog)
                print("\n\t\(String.init(cString: cLog))")
                free(cLog)
            }
        }
        
        return shader
    }
    
    func link(vertexShader vertex: GLuint, fragmentShader fragment: GLuint) {
        glAttachShader(id, vertex)
        glAttachShader(id, fragment)
        glLinkProgram(id)
        var linked: GLint = 0
        glGetProgramiv(id, UInt32(GL_LINK_STATUS), &linked)
        if linked <= 0 {
            print("Could not link, getting log")
            var logLength: GLint = 0
            glGetProgramiv(id, UInt32(GL_INFO_LOG_LENGTH), &logLength)
            print(" logLength = \(logLength)")
            if logLength > 0 {
                let cLog = UnsafeMutablePointer<CChar>.allocate(capacity: Int(logLength))
                glGetProgramInfoLog(id, GLsizei(logLength), &logLength, cLog)
                print("log: \(String.init(cString:cLog))")
                free(cLog)
            }
        }
        
        glDeleteShader(vertex)
        glDeleteShader(fragment)
    }
    
    func setInitialUniforms() {
        let location = glGetUniformLocation(id, "sample")
        glUniform1i(location, GLint(GL_TEXTURE0))
        
        bind()
        glUniform3fv(glGetUniformLocation(id, "light.color"), 1, [1.0, 1.0, 1.0])
        glUniform3fv(glGetUniformLocation(id, "light.position"), 1, [0.0, 2.0, 2.0])
        glUniform1f(glGetUniformLocation(id, "light.ambient"), 0.25)
        glUniform1f(glGetUniformLocation(id, "light.specStrength"), 3.0)
        glUniform1f(glGetUniformLocation(id, "light.specHardness"), 32)
    }
    func update(view: FloatMatrix4, projection: FloatMatrix4) {
        glUniformMatrix4fv(glGetUniformLocation(id, "view"), 1, GLboolean(GL_FALSE),
                           view.columnMajorArray())
        glUniformMatrix4fv(glGetUniformLocation(id, "projection"), 1, GLboolean(GL_FALSE),
                           projection.columnMajorArray())
    }
    
    func bind() {
        glUseProgram(id)
    }
    func unbind() {
        glUseProgram(0)
    }
    
    func delete() {
        glDeleteProgram(id)
    }
}

The other key thing we did here was place the uniform updates in the shader.  This will also really clean up our code.  We'll keep it simple and hard wired for the most part for now, but this is an area that we could really extend so that no matter the shader, we could update all or any of the uniform variables.

That's it for new types.  Back in SwiftOpenGLView, we can swap out our old types and put in the new ones.  This is a rather large undertaking, so I'll place the new definition of SwiftOpenGLView below comment annotations.


final class SwiftOpenGLView: NSOpenGLView {
    // Replace the previous properties with out Shader, VertexArrayObject
    // VertexBufferObject, and TextureBufferObject types.  We don't have
    // to worry about crashing the system if we initialize them right
    // away because the OpenGL code will be initiated later in
    // `prepareOpenGL`.
    // Also decalre the data array to be of type [Vertex]
    fileprivate var shader = Shader()
    fileprivate var vao = VertexArrayObject()
    fileprivate var vbo = VertexBufferObject()
    fileprivate var tbo = TextureBufferObject()
    fileprivate var data = [Vertex]()
    
    fileprivate var previousTime = CFTimeInterval()
    
    // Make the view and projection matrices of type `FloatMatrix4`
    fileprivate var view = FloatMatrix4()
    fileprivate var projection = FloatMatrix4()
    
    fileprivate var displayLink: CVDisplayLink?
    
    override var acceptsFirstResponder: Bool { return true }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        
        //  We'll use double buffering this time (one buffer is displayed while the other is
        //  calculated, then we swap them.
        let attrs: [NSOpenGLPixelFormatAttribute] = [
            NSOpenGLPixelFormatAttribute(NSOpenGLPFAAccelerated),
            NSOpenGLPixelFormatAttribute(NSOpenGLPFADoubleBuffer),
            NSOpenGLPixelFormatAttribute(NSOpenGLPFAColorSize), 32,
            NSOpenGLPixelFormatAttribute(NSOpenGLPFAOpenGLProfile), 
            NSOpenGLPixelFormatAttribute(NSOpenGLProfileVersion3_2Core),
            0
        ]
        guard let pixelFormat = NSOpenGLPixelFormat(attributes: attrs) else {
            Swift.print("pixelFormat could not be constructed")
            return
        }
        self.pixelFormat = pixelFormat
        guard let context = NSOpenGLContext(format: pixelFormat, share: nil) else {
            Swift.print("context could not be constructed")
            return
        }
        self.openGLContext = context
        
        //  Set the context's swap interval parameter to 60Hz (i.e. 1 frame per swamp)
        self.openGLContext?.setValues([1], for: .swapInterval)
    }
    
    override func prepareOpenGL() {
        super.prepareOpenGL()
        
        glClearColor(0.0, 0.0, 0.0, 1.0)
        
        // Fill the data array with our new `Vertex` type.  We'll also take
        // this opportunity to make a new 3D mesh.
        data = [
            Vertex(position: Float3(x: -1.0, y: -1.0, z: 1.0),  /* Front face 1 */
                normal: Float3(x: 0.0, y: 0.0, z: 1.0),
                textureCoordinate: Float2(x: 0.0, y: 0.0),
                color: Float3(x: 1.0, y: 0.0, z: 0.0)),
            Vertex(position: Float3(x: 1.0, y: -1.0, z: 1.0),
                   normal: Float3(x: 0.0, y: 0.0, z: 1.0),
                   textureCoordinate: Float2(x: 1.0, y: 0.0),
                   color: Float3(x: 0.0, y: 0.0, z: 1.0)),
            Vertex(position: Float3(x: 1.0, y: 1.0, z: 1.0),
                   normal: Float3(x: 0.0, y: 0.0, z: 1.0),
                   textureCoordinate: Float2(x: 1.0, y: 1.0),
                   color: Float3(x: 0.0, y: 1.0, z: 0.0)),
            
            Vertex(position: Float3(x: 1.0, y: 1.0, z: 1.0),    /* Front face 2 */
                normal: Float3(x: 0.0, y: 0.0, z: 1.0),
                textureCoordinate: Float2(x: 1.0, y: 1.0),
                color: Float3(x: 0.0, y: 1.0, z: 0.0)),
            Vertex(position: Float3(x: -1.0, y: 1.0, z: 1.0),
                   normal: Float3(x: 0.0, y: 0.0, z: 1.0),
                   textureCoordinate: Float2(x: 0.0, y: 1.0),
                   color: Float3(x: 1.0, y: 1.0, z: 1.0)),
            Vertex(position: Float3(x: -1.0, y: -1.0, z: 1.0),
                   normal: Float3(x: 0.0, y: 0.0, z: 1.0),
                   textureCoordinate: Float2(x: 0.0, y: 0.0),
                   color: Float3(x: 1.0, y: 0.0, z: 0.0)),
            
            Vertex(position: Float3(x: 1.0, y: -1.0, z: 1.0),   /* Right face 1 */
                normal: Float3(x: 1.0, y: 0.0, z: 0.0),
                textureCoordinate: Float2(x: 0.0, y: 0.0),
                color: Float3(x: 0.0, y: 0.0, z: 1.0)),
            Vertex(position: Float3(x: 1.0, y: -1.0, z: -1.0),
                   normal: Float3(x: 1.0, y: 0.0, z: 0.0),
                   textureCoordinate: Float2(x: 1.0, y: 0.0),
                   color: Float3(x: 1.0, y: 1.0, z: 0.0)),
            Vertex(position: Float3(x: 1.0, y: 1.0, z: -1.0),
                   normal: Float3(x: 1.0, y: 0.0, z: 0.0),
                   textureCoordinate: Float2(x: 1.0, y: 1.0),
                   color: Float3(x: 0.0, y: 1.0, z: 1.0)),
            
            Vertex(position: Float3(x: 1.0, y: 1.0, z: -1.0),   /* Right face 2 */
                normal: Float3(x: 1.0, y: 0.0, z: 0.0),
                textureCoordinate: Float2(x: 1.0, y: 1.0),
                color: Float3(x: 0.0, y: 1.0, z: 1.0)),
            Vertex(position: Float3(x: 1.0, y: 1.0, z: 1.0),
                   normal: Float3(x: 1.0, y: 0.0, z: 0.0),
                   textureCoordinate: Float2(x: 0.0, y: 1.0),
                   color: Float3(x: 0.0, y: 1.0, z: 0.0)),
            Vertex(position: Float3(x: 1.0, y: -1.0, z: 1.0),
                   normal: Float3(x: 1.0, y: 0.0, z: 0.0),
                   textureCoordinate: Float2(x: 0.0, y: 0.0),
                   color: Float3(x: 0.0, y: 0.0, z: 1.0)),
            
            Vertex(position: Float3(x: 1.0, y: -1.0, z: -1.0),  /* Back face 1 */
                normal: Float3(x: 0.0, y: 0.0, z: -1.0),
                textureCoordinate: Float2(x: 0.0, y: 0.0),
                color: Float3(x: 1.0, y: 1.0, z: 0.0)),
            Vertex(position: Float3(x: -1.0, y: -1.0, z: -1.0),
                   normal: Float3(x: 0.0, y: 0.0, z: -1.0),
                   textureCoordinate: Float2(x: 1.0, y: 0.0),
                   color: Float3(x: 0.0, y: 0.0, z: 0.0)),
            Vertex(position: Float3(x: -1.0, y: 1.0, z: -1.0),
                   normal: Float3(x: 0.0, y: 0.0, z: -1.0),
                   textureCoordinate: Float2(x: 1.0, y: 1.0),
                   color: Float3(x: 1.0, y: 0.0, z: 1.0)),
            
            Vertex(position: Float3(x: -1.0, y: 1.0, z: -1.0),  /* Back face 2 */
                normal: Float3(x: 0.0, y: 0.0, z: -1.0),
                textureCoordinate: Float2(x: 1.0, y: 1.0),
                color: Float3(x: 1.0, y: 0.0, z: 1.0)),
            Vertex(position: Float3(x: 1.0, y: 1.0, z: -1.0),
                   normal: Float3(x: 0.0, y: 0.0, z: -1.0),
                   textureCoordinate: Float2(x: 0.0, y: 1.0),
                   color: Float3(x: 0.0, y: 1.0, z: 1.0)),
            Vertex(position: Float3(x: 1.0, y: -1.0, z: -1.0),
                   normal: Float3(x: 0.0, y: 0.0, z: -1.0),
                   textureCoordinate: Float2(x: 0.0, y: 0.0),
                   color: Float3(x: 1.0, y: 1.0, z: 0.0)),
            
            Vertex(position: Float3(x: -1.0, y: -1.0, z: -1.0), /* Left face 1 */
                normal: Float3(x: -1.0, y: 0.0, z: 0.0),
                textureCoordinate: Float2(x: 0.0, y: 0.0),
                color: Float3(x: 0.0, y: 0.0, z: 0.0)),
            Vertex(position: Float3(x: -1.0, y: -1.0, z: 1.0),
                   normal: Float3(x: -1.0, y: 0.0, z: 0.0),
                   textureCoordinate: Float2(x: 1.0, y: 0.0),
                   color: Float3(x: 1.0, y: 0.0, z: 0.0)),
            Vertex(position: Float3(x: -1.0, y: 1.0, z: 1.0),
                   normal: Float3(x: -1.0, y: 0.0, z: 0.0),
                   textureCoordinate: Float2(x: 1.0, y: 1.0),
                   color: Float3(x: 1.0, y: 1.0, z: 1.0)),
            
            Vertex(position: Float3(x: -1.0, y: 1.0, z: 1.0),   /* Left face 2 */
                normal: Float3(x: -1.0, y: 0.0, z: 0.0),
                textureCoordinate: Float2(x: 1.0, y: 1.0),
                color: Float3(x: 1.0, y: 1.0, z: 1.0)),
            Vertex(position: Float3(x: -1.0, y: 1.0, z: -1.0),
                   normal: Float3(x: -1.0, y: 0.0, z: 0.0),
                   textureCoordinate: Float2(x: 0.0, y: 1.0),
                   color: Float3(x: 1.0, y: 0.0, z: 1.0)),
            Vertex(position: Float3(x: -1.0, y: -1.0, z: -1.0),
                   normal: Float3(x: -1.0, y: 0.0, z: 0.0),
                   textureCoordinate: Float2(x: 0.0, y: 0.0),
                   color: Float3(x: 0.0, y: 0.0, z: 0.0)),
            
            Vertex(position: Float3(x: -1.0, y: -1.0, z: 1.0),  /* Bottom face 1 */
                normal: Float3(x: 0.0, y: -1.0, z: 0.0),
                textureCoordinate: Float2(x: 0.0, y: 0.0),
                color: Float3(x: 1.0, y: 0.0, z: 0.0)),
            Vertex(position: Float3(x: -1.0, y: -1.0, z: -1.0),
                   normal: Float3(x: 0.0, y: -1.0, z: 0.0),
                   textureCoordinate: Float2(x: 1.0, y: 0.0),
                   color: Float3(x: 0.0, y: 0.0, z: 0.0)),
            Vertex(position: Float3(x: 1.0, y: -1.0, z: -1.0),
                   normal: Float3(x: 0.0, y: -1.0, z: 0.0),
                   textureCoordinate: Float2(x: 1.0, y: 1.0),
                   color: Float3(x: 1.0, y: 1.0, z: 0.0)),
            
            Vertex(position: Float3(x: 1.0, y: -1.0, z: -1.0),  /* Bottom face 2 */
                normal: Float3(x: 0.0, y: -1.0, z: 0.0),
                textureCoordinate: Float2(x: 1.0, y: 1.0),
                color: Float3(x: 1.0, y: 1.0, z: 0.0)),
            Vertex(position: Float3(x: 1.0, y: -1.0, z: 1.0),
                   normal: Float3(x: 0.0, y: -1.0, z: 0.0),
                   textureCoordinate: Float2(x: 0.0, y: 1.0),
                   color: Float3(x: 0.0, y: 0.0, z: 1.0)),
            Vertex(position: Float3(x: -1.0, y: -1.0, z: 1.0),
                   normal: Float3(x: 0.0, y: -1.0, z: 0.0),
                   textureCoordinate: Float2(x: 0.0, y: 0.0),
                   color: Float3(x: 1.0, y: 0.0, z: 0.0)),
            
            Vertex(position: Float3(x: -1.0, y: 1.0, z: 1.0),   /* Top face 1 */
                normal: Float3(x: 0.0, y: 1.0, z: 0.0),
                textureCoordinate: Float2(x: 0.0, y: 0.0),
                color: Float3(x: 1.0, y: 1.0, z: 1.0)),
            Vertex(position: Float3(x: 1.0, y: 1.0, z: 1.0),
                   normal: Float3(x: 0.0, y: 1.0, z: 0.0),
                   textureCoordinate: Float2(x: 0.0, y: 1.0),
                   color: Float3(x: 0.0, y: 1.0, z: 0.0)),
            Vertex(position: Float3(x: 1.0, y: 1.0, z: -1.0),
                   normal: Float3(x: 0.0, y: 1.0, z: 0.0),
                   textureCoordinate: Float2(x: 1.0, y: 1.0),
                   color: Float3(x: 0.0, y: 1.0, z: 1.0)),
            
            Vertex(position: Float3(x: 1.0, y: 1.0, z: -1.0),   /* Top face 2 */
                normal: Float3(x: 0.0, y: 1.0, z: 0.0),
                textureCoordinate: Float2(x: 1.0, y: 1.0),
                color: Float3(x: 0.0, y: 1.0, z: 1.0)),
            Vertex(position: Float3(x: -1.0, y: 1.0, z: -1.0),
                   normal: Float3(x: 0.0, y: 1.0, z: 0.0),
                   textureCoordinate: Float2(x: 0.0, y: 1.0),
                   color: Float3(x: 1.0, y: 0.0, z: 1.0)),
            Vertex(position: Float3(x: -1.0, y: 1.0, z: 1.0),
                   normal: Float3(x: 0.0, y: 1.0, z: 0.0),
                   textureCoordinate: Float2(x: 0.0, y: 0.0),
                   color: Float3(x: 1.0, y: 1.0, z: 1.0))
        ]
        
        // This is where OpenGL initialization is going to happen.  Also of our
        // OpenGLObject's may now safely run their initialization code here.
        
        // Load the texture--make sure you have a texture named "Texture" in your
        // Assets.xcassets folder.
        tbo.loadTexture(named: "Texture")
        
        // Load our new data into the VBO.
        vbo.load(data)
        
        // Now we tell OpenGL what our vertex layout looks like.
        vao.layoutVertexPattern()
        
        // Define our view and projection matrices
        view = FloatMatrix4().translate(x: 0.0, y: 0.0, z: -5.0)
        projection = FloatMatrix4.projection(aspect: Float(bounds.size.width /
                                             bounds.size.height))
        
        // Declare our Vertex and Fragment shader source code.
        let vertexSource = "#version 330 core                                  \n" +
            "layout (location = 0) in vec3 position;                           \n" +
            "layout (location = 1) in vec3 normal;                             \n" +
            "layout (location = 2) in vec2 texturePosition;                    \n" +
            "layout (location = 3) in vec3 color;                              \n" +
            "out vec3 passPosition;                                            \n" +
            "out vec3 passNormal;                                              \n" +
            "out vec2 passTexturePosition;                                     \n" +
            "out vec3 passColor;                                               \n" +
            "uniform mat4 view;                                                \n" +
            "uniform mat4 projection;                                          \n" +
            "void main()                                                       \n" +
            "{                                                                 \n" +
            "    gl_Position = projection * view * vec4(position, 1.0);        \n" +
            "    passPosition = position;                                      \n" +
            "    passNormal = vec4(view * vec4(normal, 1.0).xyz;               \n" +
            "    passTexturePosition = texturePosition;                        \n" +
            "    passColor = color;                                            \n" +
            "}                                                                 \n"
        let fragmentSource = "#version 330 core                                              \n" +
            "uniform sampler2D sample;                                                       \n" +
            "uniform struct Light {                                                          \n" +
            "   vec3 color;                                                                  \n" +
            "   vec3 position;                                                               \n" +
            "   float ambient;                                                               \n" +
            "   float specStrength;                                                          \n" +
            "   float specHardness;                                                          \n" +
            "} light;                                                                        \n" +
            "in vec3 passPosition;                                                           \n" +
            "in vec3 passNormal;                                                             \n" +
            "in vec2 passTexturePosition;                                                    \n" +
            "in vec3 passColor;                                                              \n" +
            "out vec4 outColor;                                                              \n" +
            "void main()                                                                     \n" +
            "{                                                                               \n" +
            "    vec3 normal = normalize(passNormal);                                        \n" +
            "    vec3 lightRay = normalize(light.position - passPosition);                   \n" +
            "    float intensity = dot(normal, lightRay);                                    \n" +
            "    intensity = clamp(intensity, 0, 1);                                         \n" +
            "    vec3 viewer = normalize(vec3(0.0, 0.0, 0.2) - passPosition);                \n" +
            "    vec3 reflection = reflect(lightRay, normal);                                \n" +
            "    float specular = pow(max(dot(viewer, reflection), 0.0), light.specHardness);\n" +
            "    vec3 light = light.ambient + light.color * intensity + light.specStrength * 
                              specular * light.color;   \n" +
            "    vec3 surface = texture(sample, passTexturePosition).rgb * passColor;        \n" +
            "    vec3 rgb = surface * light;                                                 \n" +
            "    outColor = vec4(rgb, 1.0);                                                  \n" +
            "}                                                                               \n"
        
        // Pass in the source code to create our shader object and then set
        // it's uniforms.
        shader.create(withVertex: vertexSource, andFragment: fragmentSource)
        shader.setInitialUniforms()
        
        // We'll deal with this guy soon, but for now no changes here.
        let displayLinkOutputCallback: CVDisplayLinkOutputCallback = {(displayLink: CVDisplayLink,
               inNow: UnsafePointer<CVTimeStamp>, inOutputTime: UnsafePointer<CVTimeStamp>,
               flagsIn: CVOptionFlags, flagsOut: UnsafeMutablePointer<CVOptionFlags>,
               displayLinkContext: UnsafeMutableRawPointer?) -> CVReturn in
            unsafeBitCast(displayLinkContext, to: SwiftOpenGLView.self).drawView()
            
            return kCVReturnSuccess
        }
        
        CVDisplayLinkCreateWithActiveCGDisplays(&displayLink)
        CVDisplayLinkSetOutputCallback(displayLink!, displayLinkOutputCallback, 
               UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()))
        CVDisplayLinkStart(displayLink!)
    }
    
    override func draw(_ dirtyRect: NSRect) {
        drawView()
    }
    
    fileprivate func drawView() {
        guard let context = self.openGLContext else {
            Swift.print("oops")
            return
        }
        
        context.makeCurrentContext()
        context.lock()
        
        let time = CACurrentMediaTime()
        
        let value = Float(sin(time))
        previousTime = time
        
        glClearColor(GLfloat(value), GLfloat(value), GLfloat(value), 1.0)
        
        glClear(GLbitfield(GL_COLOR_BUFFER_BIT))
        
        // Bind the shader and VAO
        shader.bind()
        vao.bind()
        
        // reset any uniforms we want to update.  We're going to fix this later,
        // but for fight now, we'll leave the camera out so we can adjust it's position
        // We'll also place the light a little higher up and away from the mesh so it shines
        // on it and not in it.
        glUniform3fv(glGetUniformLocation(shader.id, "light.position"), 1, [value, 2.0, 2.0])
        shader.update(view: view, projection: projection)
        
        // "The moment we've all been waiting for", was ask OpenGL to draw our
        // scene.  Make sure that you adjust the `count` parameter or else you
        // won't see anything more than one triangle.  We'll use our `data`
        // property's `count` property, but cast it into an Int32 which is
        // what glDraw* is expecting.
        glDrawArrays(GLenum(GL_TRIANGLES), 0, Int32(data.count))
        
        // Unbind the shader.
        shader.unbind()
        
        context.flushBuffer()
        context.unlock()
    }
    
    deinit {
        CVDisplayLinkStop(displayLink!)
        // Don't forget to be a good memory manager and delete what we don't
        // need anymore.
        shader.delete()
        vao.delete()
        vbo.delete()
        tbo.delete()
    }

}

Also, back in the SwiftOpenGLViewController, remove the keyboard events.  We aren't worried about moving around right now, we need to make sure we can still draw.  Try running the program now and you should see a brand new shape in your view.




Ta-da!... sort of... You'll notice that the cube isn't quite what you were expecting.  That's because of the way OpenGL draws by default:  a painter's model.  The first face of the cube we drew was the front, then right, back, left, etc.  As each vertex was drawn, it replaced the last previous one such that vertices that were behind replace those in the front and then the same thing with the fragments.  The result is that we can see into a box instead of the face of a cube.  To stop this from happening, we need to enable face culling.  You could just draw the thing closet to you last, but that would mean changing the ordering of our vertices in data every time we moved so we avoided artifacts like this. Face culling lets us leave data alone and allow OpenGL to decide whether or not to remove (cull) surfaces that are facing us or facing away from us which means we need to thinking about "winding".  Faces that face the viewer are, by default, defined as having counterclockwise winding.  So for our cube, that means bottom left corner, bottom right corner, top right corner would be the definition of the first front facing triangle.  The second would be top right corner, top left corner, bottom left corner.  You could actually start each triangle anywhere you like, but this is the convention I've used because it forms a square in my mind.  You have to enable culling by using the glEnable command like this:


fileprivate func drawView() {
        ...
        
        glClear(GLbitfield(GL_COLOR_BUFFER_BIT))

        glEnable(GLenum(GL_CULL_FACE))

        ...
}

Now when you run the app, you get what you were probably expecting to see.


Next time we'll work on the CVDisplayLink and the view lifecycle.  We'll also introduce some new types like Scene!

Hope your enjoying the tutorials, and that you're able to follow along.  If at any time, there is confusing or confounding material, let me know in the comments below.  If you have suggestions to make the tutorials better, or advice on any of the coding concepts or implementation, I would greatly appreciate any help.  Thank you for helping me make these tutorials better!

Find the project target related files for OpenGLMVCPt1 here.  When you're ready, click here to move on to Part 2 in which we'll implement a delegate and data source that will allow our view controller to take actually "control" some stuff.

Update

If you spin the cube, you may notice, the light doesn't illuminate the cube quite as expected.  The reason for this is that the surface normals were not being appropriately updated when the camera moved.  To fix this, for now, is to multiply the normals by the view matrix in the vertex shader before passing the normal to the fragment shader.  In other words,

passNormal = vec4(view * vec4(normal, 1.0)).xyz;

is the missing line of code.  I've made the adjustment above already, but if you hadn't noticed it, you'd still see the artifact.

Comments

  1. Myles,

    I'm working my way through this excellent series but one thing that makes it difficult is the lack of a downloadable repository of the code. Like all programmers, I've added my own twists and turns to your code. In each tutorial you start with modifications to the existing code but in my case (and perhaps others) my code isn't exactly the same as yours.

    I think I'll go back to the beginning and create a project that exactly mimics yours but it would be easier if I could just download the code for a specific tutorial, look it over and then incorporate your latest ideas into my code.

    Please keep up the good work but a gitHub repo would be helpful to me.

    Doug

    ReplyDelete
    Replies
    1. My apologies about the GitHub repository. I've been putting it off until I implement loading from files. I have those targets in the project, but they are based off a much older version of the project so they would make a lot of sense with the current project. I'm not very practiced at GitHub so I don't trust my skills in setting up an exception file to keep those targets from being part of repository commits. I understand that many people are likely using these tutorials as guidelines for one of their own projects instead of implementing line by line so having a repository would, of course, be very useful. I'll do my best to get it out there. I've been working hard on how I want to complete MVC part II. I think there a couple things I might change to make the thought process and progression make more sense.

      I would love to hear about some of your "twists"! Im by no means an Apple expert, I just know that the code I'm working with works when I run it. I'm always looking for ways to improve! I'm very glad that you are enjoying the tutorials and I really appreciate your feedback. I've been thinking of moving these tutorials to YouTube to see if I can get more feedback.

      In the mean time, I'll see if I can get GitHub set correctly.

      Delete

Post a Comment

Popular posts from this blog

Using Swift with OpenGL

0.0: Swift OpenGL Setup