@Override
  public JPanel getControls() {

    blankPanel.add(new JLabel(" "));
    blankPanel.add(
        new JLabel(
            "No custom shader is active, use the keys 1-4 to choose a shader.",
            SwingConstants.CENTER));
    blankPanel.add(new JLabel(" "));
    blankPanel.add(new JLabel("Shader choices:", SwingConstants.CENTER));
    blankPanel.add(new JLabel(" "));
    blankPanel.add(new JLabel("1) Blinn-Phong Fragment Shader", SwingConstants.CENTER));
    blankPanel.add(new JLabel(" "));
    blankPanel.add(new JLabel("2) Checkerbooard Shader", SwingConstants.CENTER));
    blankPanel.add(new JLabel(" "));
    blankPanel.add(new JLabel("3) Woodcut (Hashing) Shader", SwingConstants.CENTER));
    blankPanel.add(new JLabel(" "));
    blankPanel.add(new JLabel("4) Toon Shader", SwingConstants.CENTER));
    blankPanel.add(new JLabel(" "));

    lightControls.setBorder(new TitledBorder("Light Controls"));
    lightControls.add(lightPos);
    VerticalFlowPanel lightColorPanel = new VerticalFlowPanel();
    lightColorPanel.setBorder(new TitledBorder("Light Colour"));
    lightColorPanel.add(lightColR.getSliderControls(false));
    lightColorPanel.add(lightColG.getSliderControls(false));
    lightColorPanel.add(lightColB.getSliderControls(false));
    lightControls.add(lightColorPanel.getPanel());
    lightControls.add(ambient.getSliderControls(false));

    blankPanel.add(lightControls.getPanel());
    return blankPanel.getPanel();
  }
  public void display(GLAutoDrawable drawable) {
    GL2 gl = drawable.getGL().getGL2();

    gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL2.GL_AMBIENT_AND_DIFFUSE, color, 0);
    gl.glRotated(rotateX.getValue(), 1, 0, 0);
    gl.glRotated(rotateY.getValue(), 0, 1, 0);
    gl.glRotated(rotateZ.getValue(), 0, 0, 1);
    gl.glPushMatrix();
    gl.glTranslated(translateX.getValue(), translateY.getValue(), translateZ.getValue());
    gl.glPushMatrix();
    gl.glRotated(90, 1, 0, 0);
    gl.glTranslated(0, 0, -height / 2);
    glut.glutSolidCylinder(radius, height, 32, 2);
    gl.glPopMatrix();
    super.display(drawable);
    gl.glPopMatrix();
  }
  private void addControls() {
    // Blinn-Phong tab
    VerticalFlowPanel blinnPhongSettings = new VerticalFlowPanel();
    blinnPhongSettings.setBorder(new TitledBorder("Shader Settings"));
    blinnPhongSettings.add(shininess.getSliderControls(false));
    blinnPhongControls.add(blinnPhongSettings.getPanel());
    ev.controlFrame.add(blinnPhongTabName, blinnPhongControls.getPanel());

    // Checkerboard tab
    VerticalFlowPanel checkerboardSettings = new VerticalFlowPanel();
    checkerboardSettings.setBorder(new TitledBorder("Shader Settings"));
    VerticalFlowPanel color1Panel = new VerticalFlowPanel();
    color1Panel.setBorder(new TitledBorder("Colour 1"));
    color1Panel.add(col1R.getSliderControls(false));
    color1Panel.add(col1G.getSliderControls(false));
    color1Panel.add(col1B.getSliderControls(false));
    checkerboardSettings.add(color1Panel.getPanel());
    VerticalFlowPanel color2Panel = new VerticalFlowPanel();
    color2Panel.setBorder(new TitledBorder("Colour 2"));
    color2Panel.add(col2R.getSliderControls(false));
    color2Panel.add(col2G.getSliderControls(false));
    color2Panel.add(col2B.getSliderControls(false));
    checkerboardSettings.add(color2Panel.getPanel());
    checkerboardSettings.add(frequency.getSliderControls(false));
    checkerboardSettings.add(useIntFrequency.getControls());
    checkerboardSettings.add(useAveraging.getControls());
    checkerboardSettings.add(useSmoothStep.getControls());
    checkerboardControls.add(checkerboardSettings.getPanel());
    ev.controlFrame.add(checkerboardTabName, checkerboardControls.getPanel());

    // Woodcut tab
    VerticalFlowPanel woodcutSettings = new VerticalFlowPanel();
    woodcutSettings.setBorder(new TitledBorder("Shader Settings"));
    woodcutSettings.add(time.getSliderControls(false));
    woodcutControls.add(woodcutSettings.getPanel());
    ev.controlFrame.add(woodcutTabName, woodcutControls.getPanel());

    // Toon tab
    VerticalFlowPanel toonSettings = new VerticalFlowPanel();
    toonSettings.setBorder(new TitledBorder("Shader Settings"));
    toonSettings.add(thresholdHigh.getSliderControls(false));
    toonSettings.add(thresholdMedium.getSliderControls(false));
    toonSettings.add(thresholdLow.getSliderControls(false));
    toonControls.add(toonSettings.getPanel());
    ev.controlFrame.add(toonTabName, toonControls.getPanel());
  }
  @Override
  public void display(GLAutoDrawable drawable) {
    GL2 gl = drawable.getGL().getGL2();
    gl.glClear(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_DEPTH_BUFFER_BIT);

    gl.glShadeModel(GL2.GL_SMOOTH); // Enable Smooth Shading (Gouraud)

    // Display the scene according to the current trackball orientation, scale the scene to fit
    // better
    tbc.prepareForDisplay(drawable);
    gl.glScaled(3, 3, 3);

    // Add a light
    int lightNumber = 0;
    float[] position = {lightPos.x, lightPos.y, lightPos.z, 1};
    float[] colour = {
      lightColR.getFloatValue(), lightColG.getFloatValue(), lightColB.getFloatValue(), 1
    };
    float[] acolour = {
      colour[0] * ambient.getFloatValue(),
      colour[1] * ambient.getFloatValue(),
      colour[2] * ambient.getFloatValue()
    };
    gl.glLightfv(GL2.GL_LIGHT0 + lightNumber, GL2.GL_SPECULAR, colour, 0);
    gl.glLightfv(GL2.GL_LIGHT0 + lightNumber, GL2.GL_DIFFUSE, colour, 0);
    gl.glLightfv(GL2.GL_LIGHT0 + lightNumber, GL2.GL_AMBIENT, acolour, 0);
    gl.glLightfv(
        GL2.GL_LIGHT0 + lightNumber,
        GL2.GL_POSITION,
        position,
        0); // transformed by the modelview matrix when glLight is called
    gl.glEnable(GL2.GL_LIGHT0 + lightNumber);

    // Determine which shader to display the scene with

    ShaderState customShader = null;

    if (viewingMode == 1) {
      stateCheckerboard.useProgram(gl, false);
      stateWoodcut.useProgram(gl, false);
      stateToon.useProgram(gl, false);
      statePerFragment.useProgram(gl, usePerFragment);
      if (usePerFragment) customShader = statePerFragment;
      else customShader = null;
    } else if (viewingMode == 2) {
      statePerFragment.useProgram(gl, false);
      stateWoodcut.useProgram(gl, false);
      stateToon.useProgram(gl, false);
      stateCheckerboard.useProgram(gl, useCheckerboard);
      if (useCheckerboard) customShader = stateCheckerboard;
      else customShader = null;
    } else if (viewingMode == 3) {
      statePerFragment.useProgram(gl, false);
      stateCheckerboard.useProgram(gl, false);
      stateToon.useProgram(gl, false);
      stateWoodcut.useProgram(gl, useWoodcut);
      if (useWoodcut) customShader = stateWoodcut;
      else customShader = null;
    } else if (viewingMode == 4) {
      statePerFragment.useProgram(gl, false);
      stateCheckerboard.useProgram(gl, false);
      stateWoodcut.useProgram(gl, false);
      stateToon.useProgram(gl, useToon);
      if (useToon) customShader = stateToon;
      else customShader = null;
    }

    // Setup the uniform values that may be used within the current shader
    if (customShader != null) {
      // Blinn-Phong uniforms
      int specExponentUniformLocation = customShader.getUniformLocation(gl, "shininess");
      gl.glUniform1f(specExponentUniformLocation, shininess.getFloatValue());
      int ambientUniformLocation = customShader.getUniformLocation(gl, "ambient");
      gl.glUniform1f(ambientUniformLocation, ambient.getFloatValue());

      // Checkerboard uniforms
      int colour1UniformLocation = customShader.getUniformLocation(gl, "Color1");
      float[] colour1 = {col1R.getFloatValue(), col1G.getFloatValue(), col1B.getFloatValue()};
      gl.glUniform3fv(colour1UniformLocation, 1, colour1, 0);
      int colour2UniformLocation = customShader.getUniformLocation(gl, "Color2");
      float[] colour2 = {col2R.getFloatValue(), col2G.getFloatValue(), col2B.getFloatValue()};
      gl.glUniform3fv(colour2UniformLocation, 1, colour2, 0);
      int avgColourUniformLocation = customShader.getUniformLocation(gl, "AvgColor");
      float[] avgColour = {
        (colour1[0] + colour2[0]) / 2, (colour1[1] + colour2[1]) / 2, (colour1[2] + colour2[2]) / 2
      };
      gl.glUniform3fv(avgColourUniformLocation, 1, avgColour, 0);
      int frequencyUniformLocation = customShader.getUniformLocation(gl, "Frequency");
      double frequencyValue = frequency.getValue();
      frequencyValue = useIntFrequency.getValue() ? Math.round(frequencyValue) : frequencyValue;
      gl.glUniform1f(frequencyUniformLocation, (float) frequencyValue);
      int useAveragingUniformLocation = customShader.getUniformLocation(gl, "UseAveraging");
      gl.glUniform1i(useAveragingUniformLocation, useAveraging.getValue() ? 1 : 0);
      int useSmoothStepUniformLocation = customShader.getUniformLocation(gl, "UseSmoothStep");
      gl.glUniform1i(useSmoothStepUniformLocation, useSmoothStep.getValue() ? 1 : 0);

      // Woodcut uniforms
      int timeUniformLocation = customShader.getUniformLocation(gl, "Time");
      gl.glUniform1f(timeUniformLocation, time.getFloatValue());
      int lightPositionUniformLocation = customShader.getUniformLocation(gl, "LightPosition");
      float[] lightPosition = {lightPos.x, lightPos.y, lightPos.z};
      gl.glUniform3fv(lightPositionUniformLocation, 1, lightPosition, 0);

      // Toon uniforms
      int threshHighUniformLocation = customShader.getUniformLocation(gl, "ThresholdHigh");
      gl.glUniform1f(threshHighUniformLocation, thresholdHigh.getFloatValue());
      int threshMedUniformLocation = customShader.getUniformLocation(gl, "ThresholdMedium");
      gl.glUniform1f(threshMedUniformLocation, thresholdMedium.getFloatValue());
      int threshLowUniformLocation = customShader.getUniformLocation(gl, "ThresholdLow");
      gl.glUniform1f(threshLowUniformLocation, thresholdLow.getFloatValue());
    }

    // Draw teapot
    gl.glMaterialfv(GL.GL_FRONT, GL2.GL_AMBIENT_AND_DIFFUSE, new float[] {1, 1, 0, 1}, 0);
    gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL2.GL_SPECULAR, new float[] {1, 1, 1, 1}, 0);
    gl.glMaterialf(GL.GL_FRONT_AND_BACK, GL2.GL_SHININESS, 50);
    glut.glutSolidTeapot(1);

    // Draw table
    gl.glMaterialfv(GL.GL_FRONT, GL2.GL_AMBIENT_AND_DIFFUSE, new float[] {0, 1, 1, 1}, 0);
    gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL2.GL_SPECULAR, new float[] {1, 1, 1, 1}, 0);
    gl.glMaterialf(GL.GL_FRONT_AND_BACK, GL2.GL_SHININESS, 50);
    gl.glPushMatrix();
    gl.glTranslated(0, -0.8, 0);
    gl.glBegin(GL2.GL_TRIANGLE_FAN);
    gl.glNormal3d(0, 1, 0);
    gl.glTexCoord2d(0, 0);
    gl.glVertex3d(-10, 0, -10);
    gl.glTexCoord2d(0, 1);
    gl.glVertex3d(-10, 0, 10);
    gl.glTexCoord2d(1, 1);
    gl.glVertex3d(10, 0, 10);
    gl.glTexCoord2d(1, 0);
    gl.glVertex3d(10, 0, -10);
    gl.glEnd();
    gl.glPopMatrix();
  }