title: OpenGL 3.x .obj loader with ADS Phong "veiny-looking" sphere
body: <p>I've been trying to make my own basic .obj (Wavefront) renderer using the OpenGL 3.x core profile. I'm using the OpenGL SuperBible 5th ed. and the Swiftless tutorials as reference material.</p>
  
  <p>The geometry appears to load correctly, so now I'm trying to get an ADS Phong lighting model to work, but something is screwy, and I think it has something to do with either my OpenGL calls or else maybe the way I'm loading my normals, but I can't seem to figure out how to fix it. (It could be some other problem too I guess, since I'm not an expert in OpenGL).</p>
  
  <p>When rendering a simple cube, it almost looks right, but there's a weird point of light on one side:</p>
  
  <p><a href="http://imgur.com/8od38" rel="nofollow">picture of cube</a></p>
  
  <p>When rendering a sphere, the lighting shows up with "veins":</p>
  
  <p><a href="http://imgur.com/Tzla5" rel="nofollow">picture of sphere</a></p>
  
  <p>Something is obviously very wrong with my code. Here are the sections of code which I think might be relevant. I'll be happy to post more/all of the code if necessary... let me know if I should post more.</p>
  
  <p>file: objFileRenderer.cpp</p>
  
  <pre><code>#include "objFileRenderer.hpp"
  
  namespace def
  {
  
  objFileRenderer::objFileRenderer(const char* fileToRender, float objScale)
  {
      windowWidth = 800;
      windowHeight = 600;
      settings.majorVersion = 3;
      settings.minorVersion = 1;
      app = NULL;
      shader = NULL;
      vaoID = NULL;
      vboID = NULL;
      iboID = NULL;
      vaoID = new unsigned int[1];
      vboID = new unsigned int[3];
      iboID = new unsigned int[2];
      rotSpeed = 50.0f;
      rot = 0.0f;
  
      initSFML();
      initOpenGL();
  
      std::string objFilename(fileToRender);
      std::vector&lt; float &gt; vertices;
      std::vector&lt; unsigned short &gt; indices;
      std::vector&lt; float &gt; normals;
      std::vector&lt; unsigned short &gt; normalIndices;
  
      loadObj(objFilename, vertices, indices, normals, normalIndices);
  
  
      std::vector&lt; float &gt; colorA;
      std::vector&lt; float &gt; colorD;
      std::vector&lt; float &gt; colorS;
      loadMtl(objFilename, colorA, colorD, colorS);
  
  
      float* vertexArray = NULL;
      int numVertices = 0;
      unsigned short* indexArray = NULL;
      int numFaces = 0;
      vertexArray = vertexVectorToVertexArray(vertices, numVertices);
      indexArray = indexVectorToIndexArray(indices, numFaces);
  
      float* colorArrayA = NULL;
      float* colorArrayD = NULL;
      float* colorArrayS = NULL;
      int numColoredObjects = 0;
      colorVectorsToColorArrays(colorA, colorD, colorS, colorArrayA, colorArrayD, colorArrayS, numColoredObjects);
  
      float* normalArray = NULL;
      unsigned short* normalIndicesArray = NULL;
      int numNormals = 0;
      int numNormalIndices = 0;
      normalVectorsToNormalArrays(normals, normalIndices, normalArray, normalIndicesArray, numNormals, numNormalIndices);
  
      setupScene();
  
      putArraysIntoVAO(vertexArray, numVertices, indexArray, numFaces, normalArray, numNormals, normalIndicesArray, numNormalIndices);
  
      mainLoop(numVertices, numFaces, colorArrayA, colorArrayD, colorArrayD, normalArray, objScale);
  
      delete [] vertexArray;
      delete [] indexArray;
      delete [] colorArrayA;
      delete [] colorArrayD;
      delete [] colorArrayS;
      delete [] normalArray;
      delete [] normalIndicesArray;
  }
  
  objFileRenderer::~objFileRenderer()
  {
      shutdownSFML();
  }
  
  void objFileRenderer::loadObj(std::string&amp; objFilename, std::vector&lt; float &gt;&amp; vertices, std::vector&lt; unsigned short &gt;&amp; indices, std::vector&lt; float &gt;&amp; normals, std::vector&lt; unsigned short &gt;&amp; normalIndices)
  {
      std::ifstream objFile(objFilename.c_str());
  
      if (!objFile.is_open())
      {
          std::cerr &lt;&lt; "Error: unable to open .obj file: " &lt;&lt; objFilename &lt;&lt; std::endl;
          exit(1);
      }
  
      std::string line;
      while (objFile.good())
      {
          getline(objFile, line);
  
          // vertices
          if (line[0] == 'v' &amp;&amp; line[1] == ' ') // if line in .obj file contains vertices
          {
              std::vector&lt; std::string &gt; tmpStrVerts;
              std::string subline;
              subline = line.substr(2);
  
              boost::split(tmpStrVerts, subline, boost::is_any_of("\t "));
  
              std::vector&lt; std::string &gt;::iterator it;
              for (it = tmpStrVerts.begin(); it != tmpStrVerts.end(); it++)
              {
                  float vertex;
                  std::stringstream ss;
                  ss &lt;&lt; *it;
                  ss &gt;&gt; vertex;
                  vertices.push_back(vertex);
              }
          }
  
          // normals
          else if (line[0] == 'v' &amp;&amp; line[1] == 'n')
          {
              std::vector&lt; std::string &gt; tmpStrNorms;
              std::string subline;
              subline = line.substr(3);
  
              boost::split(tmpStrNorms, subline, boost::is_any_of("\t "));
  
              std::vector&lt; std::string &gt;::iterator it;
              for (it = tmpStrNorms.begin(); it != tmpStrNorms.end(); it++)
              {
                  float normal;
                  std::stringstream ss;
                  ss &lt;&lt; *it;
                  ss &gt;&gt; normal;
                  normals.push_back(normal);
                  //std::cout &lt;&lt; normal &lt;&lt; std::endl;
              }
          }
  
          // indices and normalIndices
          else if (line[0] == 'f' &amp;&amp; line[1] == ' ') // else if line in .obj file contains indices
          {
              std::vector&lt; std::string &gt; tmpStrIndices;
              std::string subline;
              subline = line.substr(2);
  
              // indices
              boost::split(tmpStrIndices, subline, boost::is_any_of("\t "));
  
              std::vector&lt; std::string &gt;::iterator it;
              for (it = tmpStrIndices.begin(); it != tmpStrIndices.end(); it++)
              {
                  unsigned short index;
                  std::stringstream ss;
                  ss &lt;&lt; *it;
                  ss &gt;&gt; index;
                  indices.push_back(index);
              }
  
              // normalIndices
              boost::split(tmpStrIndices, subline, boost::is_any_of("/"));
  
              int count = 0;
              std::vector&lt; std::string &gt;::iterator it2;
              for (it2 = tmpStrIndices.begin(); it2 != tmpStrIndices.end(); it2++)
              {
                  if (count == 2)
                  {
                      unsigned short index;
                      std::stringstream ss;
                      ss &lt;&lt; *it2;
                      ss &gt;&gt; index;
                      normalIndices.push_back(index);
                      count = 0;
                  }
                  count++;
              }
  
  
          }
      }
      objFile.close();
  
      return;
  }
  
  void objFileRenderer::loadMtl(std::string&amp; objFilename, std::vector&lt; float &gt;&amp; colorA, std::vector&lt; float &gt;&amp; colorD, std::vector&lt; float &gt;&amp; colorS)
  {
      int extpos = objFilename.find('.');
      std::string mtlFilename = objFilename.substr(0, extpos+1) + "mtl";
  
      std::ifstream mtlFile(mtlFilename.c_str());
  
      if (!mtlFile.is_open())
      {
          std::cerr &lt;&lt; "Error: unable to open .mtl file: " &lt;&lt; mtlFilename &lt;&lt; std::endl;
          exit(1);
      }
  
      std::string line;
      while (mtlFile.good())
      {
          getline(mtlFile, line);
  
          if (line[0] == 'K' &amp;&amp; line[1] == 'a')
          {
              std::vector&lt; std::string &gt; tmpStrColorA;
              std::string subline;
              subline = line.substr(3);
  
              boost::split(tmpStrColorA, subline, boost::is_any_of("\t "));
  
              std::vector&lt; std::string &gt;::iterator it;
              for (it = tmpStrColorA.begin(); it != tmpStrColorA.end(); it++)
              {
                  float rgbValue;
                  std::stringstream ss;
                  ss &lt;&lt; *it;
                  ss &gt;&gt; rgbValue;
                  colorA.push_back(rgbValue);
              }
          }
  
          if (line[0] == 'K' &amp;&amp; line[1] == 'd')
          {
              std::vector&lt; std::string &gt; tmpStrColorD;
              std::string subline;
              subline = line.substr(3);
  
              boost::split(tmpStrColorD, subline, boost::is_any_of("\t "));
  
              std::vector&lt; std::string &gt;::iterator it;
              for (it = tmpStrColorD.begin(); it != tmpStrColorD.end(); it++)
              {
                  float rgbValue;
                  std::stringstream ss;
                  ss &lt;&lt; *it;
                  ss &gt;&gt; rgbValue;
                  colorD.push_back(rgbValue);
              }
          }
  
          if (line[0] == 'K' &amp;&amp; line[1] == 's')
          {
              std::vector&lt; std::string &gt; tmpStrColorS;
              std::string subline;
              subline = line.substr(3);
  
              boost::split(tmpStrColorS, subline, boost::is_any_of("\t "));
  
              std::vector&lt; std::string &gt;::iterator it;
              for (it = tmpStrColorS.begin(); it != tmpStrColorS.end(); it++)
              {
                  float rgbValue;
                  std::stringstream ss;
                  ss &lt;&lt; *it;
                  ss &gt;&gt; rgbValue;
                  colorS.push_back(rgbValue);
              }
          }
  
      }
      mtlFile.close();
  
      return;
  }
  
  float* objFileRenderer::vertexVectorToVertexArray(std::vector&lt; float &gt;&amp; vertices, int&amp; numVertices)
  {
      numVertices = vertices.size() / 3;
      float* vertexArray = NULL;
      vertexArray = new float[vertices.size()];
  
      for (unsigned int i = 0; i &lt; vertices.size(); i++)
      {
          vertexArray[i] = vertices[i];
      }
  
      return vertexArray;
  }
  
  unsigned short* objFileRenderer::indexVectorToIndexArray(std::vector&lt; unsigned short &gt;&amp; indices, int&amp; numFaces)
  {
      numFaces = indices.size() / 3;
      unsigned short* indexArray = NULL;
      indexArray = new unsigned short[indices.size()];
  
      for (unsigned int i = 0; i &lt; indices.size(); i++)
      {
          indexArray[i] = indices[i]-1;
      }
  
      return indexArray;
  }
  
  void objFileRenderer::colorVectorsToColorArrays(std::vector&lt; float &gt;&amp; colorA, std::vector&lt; float &gt;&amp; colorD, std::vector&lt; float &gt;&amp; colorS, float*&amp; colorArrayA, float*&amp; colorArrayD, float*&amp; colorArrayS, int&amp; numColoredObjects)
  {
      numColoredObjects = colorA.size() / 3;
  
      colorArrayA = new float[numColoredObjects*3];
      colorArrayD = new float[numColoredObjects*3];
      colorArrayS = new float[numColoredObjects*3];
  
      for (int i = 0; i &lt; numColoredObjects; i+=3)
      {
          colorArrayA[i] = colorA[i]; colorArrayA[i+1] = colorA[i+1]; colorArrayA[i+2] = colorA[i+2];
          colorArrayD[i] = colorD[i]; colorArrayD[i+1] = colorD[i+1]; colorArrayD[i+2] = colorD[i+2];
          colorArrayS[i] = colorS[i]; colorArrayS[i+1] = colorS[i+1]; colorArrayS[i+2] = colorS[i+2];
      }
  
      return;
  }
  
  void objFileRenderer::normalVectorsToNormalArrays(std::vector&lt; float &gt;&amp; normals, std::vector&lt; unsigned short &gt;&amp; normalIndices, float*&amp; normalArray, unsigned short*&amp; normalIndicesArray, int&amp; numNormals, int&amp; numNormalIndices)
  {
      numNormals = normals.size() / 3;
      numNormalIndices = normalIndices.size();
  
      normalArray = new float[numNormalIndices];
      normalIndicesArray = new unsigned short[numNormalIndices];
  
      for (int i = 0; i &lt; numNormalIndices; i+=3)
      {
          normalIndicesArray[i] = normalIndices[i]-1;
          normalIndicesArray[i+1] = normalIndices[i+1]-1;
          normalIndicesArray[i+2] = normalIndices[i+2]-1;
      }
  
      // load normals in index order
      for (int i = 0; i &lt; numNormalIndices; i+=3)
      {
          int index = normalIndicesArray[i];
          normalArray[i] = normals[index];
          normalArray[i+1] = normals[index+1];
          normalArray[i+2] = normals[index+2];
      }
  
      return;
  }
  
  void objFileRenderer::putArraysIntoVAO(float* vertexArray, int&amp; numVertices, unsigned short* indexArray, int&amp; numFaces, float* normalArray, int&amp; numNormals, unsigned short* normalIndicesArray, int&amp; numNormalIndices)
  {
      glGenVertexArrays(1, &amp;vaoID[0]); // create our vertex array object
      glBindVertexArray(vaoID[0]); // bind our vertex array object so we can use it
  
      glGenBuffers(2, &amp;iboID[0]); // generate our index buffer object
      glGenBuffers(2, &amp;vboID[0]); // generate our vertex buffer object
  
  //  normalArray holds normals in index order, so I shouldn't use this
  //  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, iboID[1]); // bind our normal index buffer object
  //  glBufferData(GL_ELEMENT_ARRAY_BUFFER, (numNormalIndices) * sizeof(GLushort), normalIndicesArray, GL_STATIC_DRAW); // set the size and data of our IBO
  
      glBindBuffer(GL_ARRAY_BUFFER, vboID[1]); // bind our normal vertex buffer object
      glBufferData(GL_ARRAY_BUFFER, (numNormalIndices) * sizeof(GLfloat), normalArray, GL_STATIC_DRAW); // set the size and data of our VBO and set it to STATIC_DRAW
      glVertexAttribPointer((GLuint)1, 3, GL_FLOAT, GL_FALSE, 0, 0); // set up our vertex attributes pointer
      glEnableVertexAttribArray(1);
  
      glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, iboID[0]); // bind our index buffer object
      glBufferData(GL_ELEMENT_ARRAY_BUFFER, (numFaces*3) * sizeof(GLushort), indexArray, GL_STATIC_DRAW); // set the size and data of our IBO
  
      glBindBuffer(GL_ARRAY_BUFFER, vboID[0]); // bind our vertex buffer object
      glBufferData(GL_ARRAY_BUFFER, (numVertices*3) * sizeof(GLfloat), vertexArray, GL_STATIC_DRAW); // set the size and data of our VBO and set it to STATIC_DRAW
      glVertexAttribPointer((GLuint)0, 3, GL_FLOAT, GL_FALSE, 0, 0); // set up our vertex attributes pointer
      glEnableVertexAttribArray(0);
  
      glBindVertexArray(0);
      glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
  
      return;
  }
  
  void objFileRenderer::setupScene()
  {
      app-&gt;setFramerateLimit(60); // max 60 FPS
  
      glClearColor(0.4f, 0.6f, 0.9f, 0.0f);
      glEnable(GL_DEPTH_TEST);
      glDepthFunc(GL_LEQUAL);
      glEnable(GL_CULL_FACE);
  
      shader = new Shader("shader.vert", "shader.frag");
      projectionMatrix = glm::perspective(60.0f, (float)windowWidth / (float)windowHeight, 0.1f, 100.0f);
  
      return;
  }
  
  void objFileRenderer::renderScene(int&amp; numVertices, int&amp; numFaces, float*&amp; colorArrayA, float*&amp; colorArrayD, float*&amp; colorArrayS, float*&amp; normalArray, float objScale)
  {
      sf::Time elapsedTime = clock.getElapsedTime();
      clock.restart();
  
      if (rot &gt; 360.0f)
          rot = 0.0f;
      rot += rotSpeed * elapsedTime.asSeconds();
  
      float lightPosition[3] = { -100.0, -100.0, 100.0 };
  
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
      viewMatrix = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, -3.0f, -10.0f)); // move back by 5 units
      modelMatrix = glm::scale(glm::mat4(1.0f), glm::vec3(objScale)); // change last arg to 0.5f to shrink model by half
      modelMatrix *= glm::rotate&lt; float &gt;(glm::mat4(1.0f), rot, glm::vec3(0, 1, 0));
  
      shader-&gt;bind();
  
      int projectionMatrixLocation    = glGetUniformLocation(shader-&gt;id(), "projectionMatrix");
      int viewMatrixLocation          = glGetUniformLocation(shader-&gt;id(), "viewMatrix");
      int modelMatrixLocation         = glGetUniformLocation(shader-&gt;id(), "modelMatrix");
      int ambientLocation             = glGetUniformLocation(shader-&gt;id(), "ambientColor");
      int diffuseLocation             = glGetUniformLocation(shader-&gt;id(), "diffuseColor");
      int specularLocation            = glGetUniformLocation(shader-&gt;id(), "specularColor");
      int lightPositionLocation       = glGetUniformLocation(shader-&gt;id(), "lightPosition");
      int normalMatrixLocation        = glGetUniformLocation(shader-&gt;id(), "normalMatrix");
  
      glUniformMatrix4fv(projectionMatrixLocation, 1, GL_FALSE, &amp;projectionMatrix[0][0]);
      glUniformMatrix4fv(viewMatrixLocation, 1, GL_FALSE, &amp;viewMatrix[0][0]);
      glUniformMatrix4fv(modelMatrixLocation, 1, GL_FALSE, &amp;modelMatrix[0][0]);
      glUniform3fv(ambientLocation, 1, colorArrayA);
      glUniform3fv(diffuseLocation, 1, colorArrayD);
      glUniform3fv(specularLocation, 1, colorArrayS);
      glUniform3fv(lightPositionLocation, 1, lightPosition);
      glUniformMatrix3fv(normalMatrixLocation, 1, GL_FALSE, normalArray);
  
  
      glBindVertexArray(vaoID[0]);
  
      glDrawRangeElements(GL_TRIANGLES, 0, numFaces*3, numFaces*3, GL_UNSIGNED_SHORT, NULL);
  
      glBindVertexArray(0);
  
      shader-&gt;unbind();
  
      app-&gt;display();
  
      return;
  }
  
  void objFileRenderer::handleEvents()
  {
      sf::Event event;
  
      while (app-&gt;pollEvent(event))
      {
          if (event.type == sf::Event::Closed)
          {
              app-&gt;close();
          }
  
          if ((event.type == sf::Event::KeyPressed) &amp;&amp; (event.key.code == sf::Keyboard::Escape))
          {
              app-&gt;close();
          }
  
          if (event.type == sf::Event::Resized)
          {
              glViewport(0, 0, event.size.width, event.size.height);
          }
      }
  
      return;
  }
  
  void objFileRenderer::mainLoop(int&amp; numVertices, int&amp; numFaces, float*&amp; colorArrayA, float*&amp; colorArrayD, float*&amp; colorArrayS, float*&amp; normalArray, float objScale)
  {
      while (app-&gt;isOpen())
      {
          renderScene(numVertices, numFaces, colorArrayA, colorArrayD, colorArrayS, normalArray, objScale);
          handleEvents();
      }
  }
  
  
  }
  </code></pre>
  
  <p>file: shader.cpp</p>
  
  <pre><code>#include "shader.h"
  #include &lt;string.h&gt;
  #include &lt;iostream&gt;
  #include &lt;stdlib.h&gt;
  
  using namespace std;
  
  static char* textFileRead(const char *fileName) {
      char* text = NULL;
  
      if (fileName != NULL) {
          FILE *file = fopen(fileName, "rt");
  
          if (file != NULL) {
              fseek(file, 0, SEEK_END);
              int count = ftell(file);
              rewind(file);
  
              if (count &gt; 0) {
                  text = (char*)malloc(sizeof(char) * (count + 1));
                  count = fread(text, sizeof(char), count, file);
                  text[count] = '\0';
              }
              fclose(file);
          }
      }
      return text;
  }
  
  Shader::Shader() {
  
  }
  
  Shader::Shader(const char *vsFile, const char *fsFile) {
      init(vsFile, fsFile);
  }
  
  void Shader::init(const char *vsFile, const char *fsFile) {
      shader_vp = glCreateShader(GL_VERTEX_SHADER);
      shader_fp = glCreateShader(GL_FRAGMENT_SHADER);
  
      const char* vsText = NULL;
      const char* fsText = NULL;
      vsText = textFileRead(vsFile);
      fsText = textFileRead(fsFile);
  
      if (vsText == NULL)
      {
          cerr &lt;&lt; "Error: vertex shader file not found" &lt;&lt; endl;
      }
      if (fsText == NULL) {
          cerr &lt;&lt; "Error: fragment shader file not found." &lt;&lt; endl;
      }
      if (vsText == NULL || fsText == NULL)
          return;
  
      glShaderSource(shader_vp, 1, &amp;vsText, 0);
      glShaderSource(shader_fp, 1, &amp;fsText, 0);
  
      glCompileShader(shader_vp);
      glCompileShader(shader_fp);
  
      shader_id = glCreateProgram();
      glAttachShader(shader_id, shader_fp);
      glAttachShader(shader_id, shader_vp);
      glLinkProgram(shader_id);
  
      glBindAttribLocation(shader_id, 0, "in_Position");
      //glBindAttribLocation(shader_id, 1, "in_Color");
      glBindAttribLocation(shader_id, 1, "in_Normal");
  }
  
  Shader::~Shader() {
      glDetachShader(shader_id, shader_fp);
      glDetachShader(shader_id, shader_vp);
  
      glDeleteShader(shader_fp);
      glDeleteShader(shader_vp);
      glDeleteProgram(shader_id);
  }
  
  unsigned int Shader::id() {
      return shader_id;
  }
  
  void Shader::bind() {
      glUseProgram(shader_id);
  }
  
  void Shader::unbind() {
      glUseProgram(0);
  }
  </code></pre>
  
  <p>file: shader.vert</p>
  
  <pre><code>#version 150 core
  
  in vec3 in_Position;
  in vec3 in_Normal;
  
  uniform mat4 projectionMatrix;
  uniform mat4 viewMatrix;
  uniform mat4 modelMatrix;
  uniform vec3 lightPosition;
  uniform mat3 normalMatrix;
  
  smooth out vec3 vVaryingNormal;
  smooth out vec3 vVaryingLightDir;
  
  void main()
  {       
      // derive MVP and MV matrices
      mat4 modelViewProjectionMatrix = projectionMatrix * viewMatrix * modelMatrix;
      mat4 modelViewMatrix = viewMatrix * modelMatrix;
  
      // get surface normal in eye coordinates
      vVaryingNormal = normalMatrix * in_Normal;
  
      // get vertex position in eye coordinates
      vec4 vPosition4 = modelViewMatrix * vec4(in_Position, 1.0);
      vec3 vPosition3 = vPosition4.xyz / vPosition4.w;
  
      // get vector to light source
      vVaryingLightDir = normalize(lightPosition - vPosition3);
  
  
      // Set the position of the current vertex 
      gl_Position = modelViewProjectionMatrix * vec4(in_Position, 1.0);
  
  }
  </code></pre>
  
  <p>file: shader.frag</p>
  
  <pre><code>#version 150 core
  
  out vec4 out_Color;
  
  uniform vec3 ambientColor;
  uniform vec3 diffuseColor;
  uniform vec3 specularColor;
  
  smooth in vec3 vVaryingNormal;
  smooth in vec3 vVaryingLightDir;
  
  void main()
  {
      // dot product gives us diffuse intensity
      float diff = max(0.0, dot(normalize(vVaryingNormal), normalize(vVaryingLightDir)));
  
      // multiply intensity by diffuse color, force alpha to 1.0
      out_Color = vec4(diff * diffuseColor, 1.0);
  
      // add in ambient light
      out_Color += vec4(ambientColor, 1.0);
  
      // specular light
      vec3 vReflection = normalize(reflect(-normalize(vVaryingLightDir), normalize(vVaryingNormal)));
      float spec = max(0.0, dot(normalize(vVaryingNormal), vReflection));
  
      if (diff != 0)
      {
          float fSpec = pow(spec, 128.0);
          // Set the output color of our current pixel
          out_Color.rgb += vec3(fSpec, fSpec, fSpec);
      }
  }
  </code></pre>
  
  <p>I know it's a lot to look through, but I would be very happy to get some help with getting to the bottom of this problem, so thanks in advance to anyone who has some time to help me figure this out!</p>