<|start_filename|>web/lesson10.dart<|end_filename|>
// Copyright (c) 2013, <NAME>.
/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
part of learn_gl;

/// Load a world!
class Lesson10 extends Lesson {
  late GlProgram program;
  Texture? texture;
  JsonObject? world;

  bool get isLoaded => world != null && texture != null;

  Lesson10() {
    JsonObject.fromUrl('world.json').then((object) {
      world = object;
      print('world loaded with ${world?._itemSize}');
    });

    loadTexture('mcdole.gif', (Texture texture, ImageElement ele) {
      gl.pixelStorei(WebGL.UNPACK_FLIP_Y_WEBGL, 1);
      gl.bindTexture(WebGL.TEXTURE_2D, texture);
      gl.texImage2D(
        WebGL.TEXTURE_2D,
        0,
        WebGL.RGBA,
        WebGL.RGBA,
        WebGL.UNSIGNED_BYTE,
        ele,
      );
      gl.texParameteri(
        WebGL.TEXTURE_2D,
        WebGL.TEXTURE_MAG_FILTER,
        WebGL.LINEAR,
      );
      gl.texParameteri(
        WebGL.TEXTURE_2D,
        WebGL.TEXTURE_MIN_FILTER,
        WebGL.LINEAR,
      );
      this.texture = texture;
      print('texture loaded');
    });

    final attributes = ['aVertexPosition', 'aTextureCoord'];
    final uniforms = ['uMVMatrix', 'uPMatrix', 'uSampler'];
    program = GlProgram(
      '''
          precision mediump float;

          varying vec2 vTextureCoord;

          uniform sampler2D uSampler;

          void main(void) {
              gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
          }
        ''',
      '''
          attribute vec3 aVertexPosition;
          attribute vec2 aTextureCoord;

          uniform mat4 uMVMatrix;
          uniform mat4 uPMatrix;

          varying vec2 vTextureCoord;

          void main(void) {
              gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
              vTextureCoord = aTextureCoord;
          }
        ''',
      attributes,
      uniforms,
    );
    gl.useProgram(program.program);
  }

  int? get aTextureCoord => program.attributes['aTextureCoord'];
  int? get aVertexPosition => program.attributes['aVertexPosition'];

  UniformLocation? get uSampler => program.uniforms['uSampler'];
  UniformLocation? get uPMatrix => program.uniforms['uPMatrix'];
  UniformLocation? get uMVMatrix => program.uniforms['uMVMatrix'];

  @override
  void drawScene(int viewWidth, int viewHeight, double aspect) {
    if (!isLoaded) return;

    gl.disable(WebGL.BLEND);
    gl.enable(WebGL.DEPTH_TEST);

    // Basic viewport setup and clearing of the screen
    gl.viewport(0, 0, viewWidth, viewHeight);
    gl.clear(WebGL.COLOR_BUFFER_BIT | WebGL.DEPTH_BUFFER_BIT);

    // Setup the perspective - you might be wondering why we do this every
    // time, and that will become clear in much later lessons. Just know, you
    // are not crazy for thinking of caching this.
    pMatrix = Matrix4.perspective(45.0, aspect, 0.1, 100.0);

    mvPushMatrix();

    mvMatrix
      ..rotateX(radians(-pitch))
      ..rotateY(radians(-yaw))
      ..translate([-xPos, -yPos, -zPos]);

    gl.activeTexture(WebGL.TEXTURE0);
    gl.bindTexture(WebGL.TEXTURE_2D, texture);
    gl.uniform1i(uSampler, 0);

    world?.draw(
        vertex: aVertexPosition,
        coord: aTextureCoord,
        setUniforms: () {
          gl.uniformMatrix4fv(uPMatrix, false, pMatrix.buf);
          gl.uniformMatrix4fv(uMVMatrix, false, mvMatrix.buf);
        });

    mvPopMatrix();
  }

  double pitch = 0.0, yaw = 0.0;
  double pitchRate = 0.0, yawRate = 0.0;
  double xPos = 0.0, yPos = 0.4, zPos = 0.0;
  double speed = 0.0;

  @override
  void animate(double now) {
    if (lastTime != 0) {
      final elapsed = now - lastTime;

      if (speed != 0) {
        xPos -= sin(radians(yaw)) * speed * elapsed;
        zPos -= cos(radians(yaw)) * speed * elapsed;
      }
      yaw += yawRate * elapsed;
      pitch += pitchRate * elapsed;
    }
    lastTime = now;
  }

  @override
  void handleKeys() {
    if (anyActive([KeyCode.UP, KeyCode.W])) {
      speed = 0.003;
    } else if (anyActive([KeyCode.DOWN, KeyCode.S])) {
      speed = -0.003;
    } else {
      speed = 0.0;
    }
    if (anyActive([KeyCode.LEFT, KeyCode.A])) {
      yawRate = 0.1;
    } else if (anyActive([KeyCode.RIGHT, KeyCode.D])) {
      yawRate = -0.1;
    } else {
      yawRate = 0.0;
    }
    if (anyActive([KeyCode.PAGE_UP, KeyCode.NINE])) {
      pitchRate = 0.1;
    } else if (anyActive([KeyCode.PAGE_DOWN, KeyCode.THREE])) {
      pitchRate = -0.1;
    } else {
      pitchRate = 0.0;
    }
  }

  @override
  void initHtml(DivElement hook) {
    hook.setInnerHtml(
      '''
    Use the cursor keys or WASD to run around, and <code>Page Up</code>/<code>Page Down</code> to
    look up and down.
    ''',
      treeSanitizer: NullTreeSanitizer(),
    );
  }
}


<|start_filename|>web/lesson5.dart<|end_filename|>
// Copyright (c) 2013, <NAME>.
/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
part of learn_gl;

/// Introducing Textures!
class Lesson5 extends Lesson {
  late GlProgram program;
  Texture? neheTexture;
  late Cube cube;

  bool get isLoaded => neheTexture != null;

  Lesson5() {
    cube = Cube();
    loadTexture('nehe.gif', (Texture texture, ImageElement element) {
      gl.bindTexture(WebGL.TEXTURE_2D, texture);
      gl.pixelStorei(WebGL.UNPACK_FLIP_Y_WEBGL, 1);
      gl.texImage2D(
        WebGL.TEXTURE_2D,
        0,
        WebGL.RGBA,
        WebGL.RGBA,
        WebGL.UNSIGNED_BYTE,
        element,
      );
      gl.texParameteri(
        WebGL.TEXTURE_2D,
        WebGL.TEXTURE_MAG_FILTER,
        WebGL.NEAREST,
      );
      gl.texParameteri(
        WebGL.TEXTURE_2D,
        WebGL.TEXTURE_MIN_FILTER,
        WebGL.NEAREST,
      );
      gl.bindTexture(WebGL.TEXTURE_2D, null);
      neheTexture = texture;
    });

    final attributes = ['aVertexPosition', 'aTextureCoord'];
    final uniforms = ['uPMatrix', 'uMVMatrix', 'uSampler'];

    program = GlProgram(
      '''
          precision mediump float;

          varying vec2 vTextureCoord;

          uniform sampler2D uSampler;

          void main(void) {
              gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
          }
        ''',
      '''
          attribute vec3 aVertexPosition;
          attribute vec2 aTextureCoord;

          uniform mat4 uMVMatrix;
          uniform mat4 uPMatrix;

          varying vec2 vTextureCoord;

          void main(void) {
              gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
              vTextureCoord = aTextureCoord;
          }
        ''',
      attributes,
      uniforms,
    );

    gl.useProgram(program.program);
  }

  @override
  void drawScene(int viewWidth, int viewHeight, double aspect) {
    if (!isLoaded) return;

    // Basic viewport setup and clearing of the screen
    gl.viewport(0, 0, viewWidth, viewHeight);
    gl.clear(WebGL.COLOR_BUFFER_BIT | WebGL.DEPTH_BUFFER_BIT);
    gl.enable(WebGL.DEPTH_TEST);
    gl.disable(WebGL.BLEND);

    // Setup the perspective - you might be wondering why we do this every
    // time, and that will become clear in much later lessons. Just know, you
    // are not crazy for thinking of caching this.
    pMatrix = Matrix4.perspective(45.0, aspect, 0.1, 100.0);

    // First stash the current model view matrix before we start moving around.
    mvPushMatrix();

    mvMatrix
      ..translate([0.0, 0.0, -5.0])
      ..rotateX(radians(xRot))
      ..rotateY(radians(yRot))
      ..rotateZ(radians(zRot));

    gl.activeTexture(WebGL.TEXTURE0);
    gl.bindTexture(WebGL.TEXTURE_2D, neheTexture);
    gl.uniform1i(uSampler, 0);
    cube.draw(
        setUniforms: setMatrixUniforms,
        vertex: program.attributes['aVertexPosition'],
        coord: program.attributes['aTextureCoord']);

    // Finally, reset the matrix back to what it was before we moved around.
    mvPopMatrix();
  }

  UniformLocation? get uPMatrix => program.uniforms['uPMatrix'];
  UniformLocation? get uMVMatrix => program.uniforms['uMVMatrix'];
  UniformLocation? get uSampler => program.uniforms['uSampler'];

  void setMatrixUniforms() {
    gl.uniformMatrix4fv(uPMatrix, false, pMatrix.buf);
    gl.uniformMatrix4fv(uMVMatrix, false, mvMatrix.buf);
  }

  double xRot = 0.0, yRot = 0.0, zRot = 0.0;

  @override
  void animate(double now) {
    if (lastTime != 0) {
      final elapsed = now - lastTime;

      xRot += (90 * elapsed) / 1000.0;
      yRot += (90 * elapsed) / 1000.0;
      zRot += (90 * elapsed) / 1000.0;
    }
    lastTime = now;
  }

  @override
  void handleKeys() {
    handleDirection(up: () => yRot -= 0.5, down: () => yRot += 0.5, left: () => xRot -= 0.5, right: () => xRot += 0.5);
  }
}


<|start_filename|>web/lesson13.dart<|end_filename|>
// Copyright (c) 2013, <NAME>.
/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
part of learn_gl;

/// Handle custom shaders, animation, etc for Lesson 12 - Point Lighting.
/// In this lesson, we spin a sphere and a cube around a central axis with at its core is a
/// point of directional lighting.
///
/// In the original lesson, the moon and box are tidal locked (always showing the same face),
/// lets play around with that.
class Lesson13 extends Lesson {
  late Cube cube;
  late Sphere moon;

  late GlProgram perVertexProgram;
  late GlProgram perFragmentProgram;

  late GlProgram currentProgram;

  Texture? moonTexture, cubeTexture;
  bool get isLoaded => moonTexture != null && cubeTexture != null;

  Lesson13() {
    moon = Sphere(lats: 30, lons: 30, radius: 1);
    cube = Cube();

    final attributes = ['aVertexPosition', 'aVertexNormal', 'aTextureCoord'];
    final uniforms = [
      'uPMatrix',
      'uMVMatrix',
      'uNMatrix',
      'uSampler',
      'uUseTextures',
      'uUseLighting',
      'uAmbientColor',
      'uPointLightingLocation',
      'uPointLightingColor'
    ];

    perVertexProgram = GlProgram(
      '''
        precision mediump float;
    
        varying vec2 vTextureCoord;
        varying vec3 vLightWeighting;
    
        uniform bool uUseTextures;
    
        uniform sampler2D uSampler;
    
        void main(void) {
            vec4 fragmentColor;
            if (uUseTextures) {
                fragmentColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
            } else {
                fragmentColor = vec4(1.0, 1.0, 1.0, 1.0);
            }
            gl_FragColor = vec4(fragmentColor.rgb * vLightWeighting, fragmentColor.a);
        }
      ''',
      '''
        attribute vec3 aVertexPosition;
        attribute vec3 aVertexNormal;
        attribute vec2 aTextureCoord;
    
        uniform mat4 uMVMatrix;
        uniform mat4 uPMatrix;
        uniform mat3 uNMatrix;
    
        uniform vec3 uAmbientColor;
    
        uniform vec3 uPointLightingLocation;
        uniform vec3 uPointLightingColor;
    
        uniform bool uUseLighting;
    
        varying vec2 vTextureCoord;
        varying vec3 vLightWeighting;
    
        void main(void) {
            vec4 mvPosition = uMVMatrix * vec4(aVertexPosition, 1.0);
            gl_Position = uPMatrix * mvPosition;
            vTextureCoord = aTextureCoord;
    
            if (!uUseLighting) {
                vLightWeighting = vec3(1.0, 1.0, 1.0);
            } else {
                vec3 lightDirection = normalize(uPointLightingLocation - mvPosition.xyz);
    
                vec3 transformedNormal = uNMatrix * aVertexNormal;
                float directionalLightWeighting = max(dot(transformedNormal, lightDirection), 0.0);
                vLightWeighting = uAmbientColor + uPointLightingColor * directionalLightWeighting;
            }
        }
      ''',
      attributes,
      uniforms,
    );

    currentProgram = perFragmentProgram = GlProgram(
      '''
          precision mediump float;
      
          varying vec2 vTextureCoord;
          varying vec3 vTransformedNormal;
          varying vec4 vPosition;
      
          uniform bool uUseLighting;
          uniform bool uUseTextures;
      
          uniform vec3 uAmbientColor;
      
          uniform vec3 uPointLightingLocation;
          uniform vec3 uPointLightingColor;
      
          uniform sampler2D uSampler;
      
      
          void main(void) {
              vec3 lightWeighting;
              if (!uUseLighting) {
                  lightWeighting = vec3(1.0, 1.0, 1.0);
              } else {
                  vec3 lightDirection = normalize(uPointLightingLocation - vPosition.xyz);
      
                  float directionalLightWeighting = max(dot(normalize(vTransformedNormal), lightDirection), 0.0);
                  lightWeighting = uAmbientColor + uPointLightingColor * directionalLightWeighting;
              }
      
              vec4 fragmentColor;
              if (uUseTextures) {
                  fragmentColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
              } else {
                  fragmentColor = vec4(1.0, 1.0, 1.0, 1.0);
              }
              gl_FragColor = vec4(fragmentColor.rgb * lightWeighting, fragmentColor.a);
          }
        ''',
      '''
          attribute vec3 aVertexPosition;
          attribute vec3 aVertexNormal;
          attribute vec2 aTextureCoord;
      
          uniform mat4 uMVMatrix;
          uniform mat4 uPMatrix;
          uniform mat3 uNMatrix;
      
          varying vec2 vTextureCoord;
          varying vec3 vTransformedNormal;
          varying vec4 vPosition;
      
      
          void main(void) {
              vPosition = uMVMatrix * vec4(aVertexPosition, 1.0);
              gl_Position = uPMatrix * vPosition;
              vTextureCoord = aTextureCoord;
              vTransformedNormal = uNMatrix * aVertexNormal;
          }
        ''',
      attributes,
      uniforms,
    );

    // Handle textures
    loadTexture('moon.bmp', handleMipMapTexture).then((t) => moonTexture = t);
    loadTexture('crate.gif', handleMipMapTexture).then((t) => cubeTexture = t);

    gl.enable(WebGL.DEPTH_TEST);
  }

  int? get aVertexPosition => currentProgram.attributes['aVertexPosition'];
  int? get aVertexNormal => currentProgram.attributes['aVertexNormal'];
  int? get aTextureCoord => currentProgram.attributes['aTextureCoord'];

  UniformLocation? get uPMatrix => currentProgram.uniforms['uPMatrix'];
  UniformLocation? get uMVMatrix => currentProgram.uniforms['uMVMatrix'];
  UniformLocation? get uNMatrix => currentProgram.uniforms['uNMatrix'];
  UniformLocation? get uSampler => currentProgram.uniforms['uSampler'];
  UniformLocation? get uUseTextures => currentProgram.uniforms['uUseTextures'];
  UniformLocation? get uUseLighting => currentProgram.uniforms['uUseLighting'];
  UniformLocation? get uAmbientColor => currentProgram.uniforms['uAmbientColor'];
  UniformLocation? get uPointLightingLocation => currentProgram.uniforms['uPointLightingLocation'];
  UniformLocation? get uPointLightingColor => currentProgram.uniforms['uPointLightingColor'];

  @override
  void drawScene(int viewWidth, int viewHeight, double aspect) {
    if (!isLoaded) return;

    gl.enable(WebGL.DEPTH_TEST);
    gl.disable(WebGL.BLEND);

    gl.viewport(0, 0, viewWidth, viewHeight);
    gl.clear(WebGL.COLOR_BUFFER_BIT | WebGL.DEPTH_BUFFER_BIT);
    pMatrix = Matrix4.perspective(45.0, aspect, 0.1, 100.0);

    final perFragmentLighting = _perFragment.checked!;
    if (perFragmentLighting) {
      currentProgram = perFragmentProgram;
    } else {
      currentProgram = perVertexProgram;
    }
    gl.useProgram(currentProgram.program);

    // One: setup lighting information
    final lighting = _lighting.checked!;
    gl.uniform1i(uUseLighting, lighting ? 1 : 0);
    if (lighting) {
      gl.uniform3f(uAmbientColor, double.parse(_aR.value!), double.parse(_aG.value!), double.parse(_aB.value!));

      gl.uniform3f(
          uPointLightingLocation, double.parse(_lpX.value!), double.parse(_lpY.value!), double.parse(_lpZ.value!));

      gl.uniform3f(uPointLightingColor, double.parse(_pR.value!), double.parse(_pG.value!), double.parse(_pB.value!));
    }
    gl.uniform1i(uUseTextures, _textures.checked! ? 1 : 0);

    mvPushMatrix();

    // Setup the scene -5.0 away and pitch up by 30 degrees
    mvMatrix
      ..translate([0.0, 0.0, -5.0])
      ..rotateX(radians(tilt));

    mvPushMatrix();
    // Rotate and move away from the scene
    mvMatrix
      ..rotateY(radians(moonAngle))
      ..translate([2.0, 0.0, 0.0]);
    gl.activeTexture(WebGL.TEXTURE0);
    gl.bindTexture(WebGL.TEXTURE_2D, moonTexture);
    gl.uniform1i(uSampler, 0);
    moon.draw(vertex: aVertexPosition, normal: aVertexNormal, coord: aTextureCoord, setUniforms: setMatrixUniforms);
    mvPopMatrix();

    mvMatrix
      ..rotateY(radians(cubeAngle))
      ..translate([1.25, 0.0, 0.0]);
    gl.activeTexture(WebGL.TEXTURE0);
    gl.bindTexture(WebGL.TEXTURE_2D, cubeTexture);
    gl.uniform1i(uSampler, 0);
    cube.draw(vertex: aVertexPosition, normal: aVertexNormal, coord: aTextureCoord, setUniforms: setMatrixUniforms);
    mvPopMatrix();
  }

  double moonAngle = 180.0;
  double cubeAngle = 0.0;
  double tilt = 30.0;

  void setMatrixUniforms() {
    gl.uniformMatrix4fv(uPMatrix, false, pMatrix.buf);
    gl.uniformMatrix4fv(uMVMatrix, false, mvMatrix.buf);
    final normalMatrix = mvMatrix.toInverseMat3();
    normalMatrix!.transposeSelf();
    gl.uniformMatrix3fv(uNMatrix, false, normalMatrix.buf);
  }

  @override
  void animate(double now) {
    if (lastTime != 0) {
      final elapsed = now - lastTime;
      moonAngle += 0.05 * elapsed;
      cubeAngle += 0.05 * elapsed;
    }
    lastTime = now;
  }

  @override
  void handleKeys() {
    handleDirection(
        up: () => tilt -= 1.0,
        down: () => tilt += 1.0,
        left: () {
          moonAngle -= 1.0;
          cubeAngle -= 1.0;
        },
        right: () {
          moonAngle += 1.0;
          cubeAngle += 1.0;
        });
  }

  late InputElement _perFragment;
  late InputElement _textures;

  // Lighting enabled / Ambient color
  late InputElement _lighting, _aR, _aG, _aB;

  // Light position
  late InputElement _lpX, _lpY, _lpZ;

  // Point color
  late InputElement _pR, _pG, _pB;

  @override
  void initHtml(DivElement hook) {
    hook.setInnerHtml(
      '''
    <input type="checkbox" id="lighting" checked /> Use lighting<br/>
    <input type="checkbox" id="per-fragment" checked /> Per-fragment lighting<br/>
    <input type="checkbox" id="textures" checked /> Use textures<br/>
    <br/>

    <h2>Point light:</h2>

    <table style="border: 0; padding: 10px;">
        <tr>
            <td><b>Location:</b>
            <td>X: <input type="text" id="lightPositionX" value="0.0" />
            <td>Y: <input type="text" id="lightPositionY" value="0.0" />
            <td>Z: <input type="text" id="lightPositionZ" value="-5.0" />
        </tr>
        <tr>
            <td><b>Colour:</b>
            <td>R: <input type="text" id="pointR" value="0.8" />
            <td>G: <input type="text" id="pointG" value="0.8" />
            <td>B: <input type="text" id="pointB" value="0.8" />
        </tr>
    </table>

    <h2>Ambient light:</h2>

    <table style="border: 0; padding: 10px;">
        <tr>
            <td><b>Colour:</b>
            <td>R: <input type="text" id="ambientR" value="0.2" />
            <td>G: <input type="text" id="ambientG" value="0.2" />
            <td>B: <input type="text" id="ambientB" value="0.2" />
        </tr>
    </table>


    <br/>

    Moon texture courtesy of <a href="http://maps.jpl.nasa.gov/">the Jet Propulsion Laboratory</a>.
    ''',
      treeSanitizer: NullTreeSanitizer(),
    );

    // Re-look up our dom elements
    _lighting = querySelector('#lighting') as InputElement;
    _aR = querySelector('#ambientR') as InputElement;
    _aG = querySelector('#ambientG') as InputElement;
    _aB = querySelector('#ambientB') as InputElement;

    _pR = querySelector('#pointR') as InputElement;
    _pG = querySelector('#pointG') as InputElement;
    _pB = querySelector('#pointB') as InputElement;

    _lpX = querySelector('#lightPositionX') as InputElement;
    _lpY = querySelector('#lightPositionY') as InputElement;
    _lpZ = querySelector('#lightPositionZ') as InputElement;

    _perFragment = querySelector('#per-fragment') as InputElement;
    _textures = querySelector('#textures') as InputElement;
  }
}


<|start_filename|>web/star.dart<|end_filename|>
// Copyright (c) 2013, <NAME>.
/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
part of learn_gl;

/// Note; not happy about this, it just a texturized rectangle that's conflated
/// with particles. Needs clean up.
class Star implements Renderable {
  static Random rand = Random(42);
  static bool loaded = false;
  static int starCount = 0;
  final int id = starCount++;

  double dist = 0;
  double rotationSpeed = 0;
  double angle = 0.0;

  /// Normal color
  double r = 0, g = 0, b = 0;

  /// Twinkle color
  double rT = 0, gT = 0, bT = 0;

  Star(this.dist, this.rotationSpeed) {
    randomizeColors();
    starVertexPositionBuffer = gl.createBuffer();
    gl.bindBuffer(WebGL.ARRAY_BUFFER, starVertexPositionBuffer);
    final vertices = [-1.0, -1.0, 0.0, 1.0, -1.0, 0.0, -1.0, 1.0, 0.0, 1.0, 1.0, 0.0];
    gl.bufferData(
      WebGL.ARRAY_BUFFER,
      Float32List.fromList(vertices),
      WebGL.STATIC_DRAW,
    );

    starVertexTextureCoordBuffer = gl.createBuffer();
    gl.bindBuffer(WebGL.ARRAY_BUFFER, starVertexTextureCoordBuffer);
    final textureCoords = [0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0];
    gl.bufferData(
      WebGL.ARRAY_BUFFER,
      Float32List.fromList(textureCoords),
      WebGL.STATIC_DRAW,
    );
  }

  @override
  void draw(
      {required int vertex,
      int? normal,
      required int coord,
      UniformLocation? color,
      bool twinkle = false,
      double tilt = 0,
      double spin = 0,
      Function()? setUniforms}) {
    mvPushMatrix();

    // Move to the star's position
    mvMatrix
      ..rotateY(radians(angle))
      ..translate([dist, 0.0, 0.0]);

    // Rotate back so that the star is facing the viewer
    mvMatrix
      ..rotateY(radians(-angle))
      ..rotateX(radians(-tilt));

    if (twinkle) {
      // Draw a non-rotating star in the alternate "twinkling" color
      gl.uniform3f(color, rT, gT, bT);
      drawStar(vertex, normal, coord, setUniforms!);
    }

    mvMatrix.rotateZ(radians(spin));

    // Draw the star in its main color
    gl.uniform3f(color, r, g, b);
    drawStar(vertex, normal, coord, setUniforms!);

    mvPopMatrix();
  }

  static const double effectiveFPMS = 60 / 1000;
  void animate(double time) {
    angle += rotationSpeed * effectiveFPMS * time;

    // Decrease the distance, resetting the star to the outside of
    // the spiral if it's at the center.
    dist -= 0.01 * effectiveFPMS * time;
    if (dist < 0.0) {
      dist += 5.0;
      randomizeColors();
    }
  }

  void randomizeColors() {
    r = rand.nextDouble();
    g = rand.nextDouble();
    b = rand.nextDouble();
    rT = rand.nextDouble();
    gT = rand.nextDouble();
    bT = rand.nextDouble();
  }

  late Buffer starVertexPositionBuffer;
  late Buffer starVertexTextureCoordBuffer;

  void drawStar(int vertex, int? normal, int coord, Function() setUniforms) {
    gl.bindBuffer(WebGL.ARRAY_BUFFER, starVertexTextureCoordBuffer);
    gl.vertexAttribPointer(coord, 2, WebGL.FLOAT, false, 0, 0);

    gl.bindBuffer(WebGL.ARRAY_BUFFER, starVertexPositionBuffer);
    gl.vertexAttribPointer(vertex, 3, WebGL.FLOAT, false, 0, 0);

    setUniforms();
    gl.drawArrays(WebGL.TRIANGLE_STRIP, 0, 4);
  }
}


<|start_filename|>web/gl_program.dart<|end_filename|>
// Copyright (c) 2013, <NAME>.
/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
part of learn_gl;

/// Create a WebGL [Program], compiling [Shader]s from passed in sources and
/// cache [UniformLocation]s and AttribLocations.
class GlProgram {
  Map<String, int> attributes = <String, int>{};
  Map<String, UniformLocation> uniforms = <String, UniformLocation>{};
  late Program program;

  late Shader fragShader, vertShader;

  GlProgram(String fragSrc, String vertSrc, List<String> attributeNames, List<String> uniformNames) {
    fragShader = gl.createShader(WebGL.FRAGMENT_SHADER);
    gl.shaderSource(fragShader, fragSrc);
    gl.compileShader(fragShader);
    if (gl.getShaderParameter(fragShader, WebGL.COMPILE_STATUS) == 0) {
      print('Could not compile fragment shaders');
    }
    vertShader = gl.createShader(WebGL.VERTEX_SHADER);
    gl.shaderSource(vertShader, vertSrc);
    gl.compileShader(vertShader);
    if (gl.getShaderParameter(vertShader, WebGL.COMPILE_STATUS) == 0) {
      print('Could not compile vertex shaders');
    }

    program = gl.createProgram();
    gl.attachShader(program, vertShader);
    gl.attachShader(program, fragShader);
    gl.linkProgram(program);

    if (gl.getProgramParameter(program, WebGL.LINK_STATUS) == 0) {
      print('Could not initialise shaders');
    }

    for (var attrib in attributeNames) {
      final attributeLocation = gl.getAttribLocation(program, attrib);
      gl.enableVertexAttribArray(attributeLocation);
      attributes[attrib] = attributeLocation;
    }
    for (var uniform in uniformNames) {
      final uniformLocation = gl.getUniformLocation(program, uniform);
      uniforms[uniform] = uniformLocation;
    }
  }
}


<|start_filename|>web/pyramid.dart<|end_filename|>
// Copyright (c) 2013, <NAME>.
/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
part of learn_gl;

class Pyramid implements Renderable {
  late Buffer positionBuffer, normalBuffer, textureCoordBuffer;
  late Buffer colorBuffer;

  Pyramid() {
    positionBuffer = gl.createBuffer();
    normalBuffer = gl.createBuffer();
    textureCoordBuffer = gl.createBuffer();

    gl.bindBuffer(WebGL.ARRAY_BUFFER, positionBuffer);
    final vertices = [
      // Front face
      0.0, 1.0, 0.0,
      -1.0, -1.0, 1.0,
      1.0, -1.0, 1.0,

      // Right face
      0.0, 1.0, 0.0,
      1.0, -1.0, 1.0,
      1.0, -1.0, -1.0,

      // Back face
      0.0, 1.0, 0.0,
      1.0, -1.0, -1.0,
      -1.0, -1.0, -1.0,

      // Left face
      0.0, 1.0, 0.0,
      -1.0, -1.0, -1.0,
      -1.0, -1.0, 1.0,

      //  NOTE: Missing the bottom triangles :)
      -1.0, -1.0, -1.0,
      1.0, -1.0, -1.0,
      1.0, -1.0, 1.0,
      -1.0, -1.0, -1.0,
      1.0, -1.0, 1.0,
      -1.0, -1.0, 1.0,
    ];
    gl.bufferData(
      WebGL.ARRAY_BUFFER,
      Float32List.fromList(vertices),
      WebGL.STATIC_DRAW,
    );

    normalBuffer = gl.createBuffer();
    gl.bindBuffer(WebGL.ARRAY_BUFFER, normalBuffer);
    final vertexNormals = [
      // Front face
      0.0, 0.4472135901451111, 0.8944271802902222,
      0.0, 0.4472135901451111, 0.8944271802902222,
      0.0, 0.4472135901451111, 0.8944271802902222,

      // Right face
      0.8944271802902222, 0.4472135901451111, 0.0,
      0.8944271802902222, 0.4472135901451111, 0.0,
      0.8944271802902222, 0.4472135901451111, 0.0,

      // Back face
      0.0, 0.4472135901451111, -0.8944271802902222,
      0.0, 0.4472135901451111, -0.8944271802902222,
      0.0, 0.4472135901451111, -0.8944271802902222,

      // Left face
      -0.8944271802902222, 0.4472135901451111, 0.0,
      -0.8944271802902222, 0.4472135901451111, 0.0,
      -0.8944271802902222, 0.4472135901451111, 0.0,

      // Bottom face - non-triangle strip
      0.0, -1.0, 0.0,
      0.0, -1.0, 0.0,
      0.0, -1.0, 0.0,
      0.0, -1.0, 0.0,
      0.0, -1.0, 0.0,
      0.0, -1.0, 0.0
    ];
    gl.bufferData(
      WebGL.ARRAY_BUFFER,
      Float32List.fromList(vertexNormals),
      WebGL.STATIC_DRAW,
    );

    // TODO: Come up with a better way to store color buffer vs texture buffer :)
    colorBuffer = gl.createBuffer();
    gl.bindBuffer(WebGL.ARRAY_BUFFER, colorBuffer);
    final colors = [
      // Front face
      1.0, 0.0, 0.0, 1.0,
      0.0, 1.0, 0.0, 1.0,
      0.0, 0.0, 1.0, 1.0,

      // Right face
      1.0, 0.0, 0.0, 1.0,
      0.0, 0.0, 1.0, 1.0,
      0.0, 1.0, 0.0, 1.0,

      // Back face
      1.0, 0.0, 0.0, 1.0,
      0.0, 1.0, 0.0, 1.0,
      0.0, 0.0, 1.0, 1.0,

      // Left face
      1.0, 0.0, 0.0, 1.0,
      0.0, 0.0, 1.0, 1.0,
      0.0, 1.0, 0.0, 1.0,

      // Bottom face
      0.0, 1.0, 0.0, 1.0,
      0.0, 1.0, 0.0, 1.0,
      0.0, 1.0, 0.0, 1.0,
      0.0, 1.0, 0.0, 1.0,
      0.0, 1.0, 0.0, 1.0,
      0.0, 1.0, 0.0, 1.0
    ];
    gl.bufferData(
      WebGL.ARRAY_BUFFER,
      Float32List.fromList(colors),
      WebGL.STATIC_DRAW,
    );

    // Normal discovery from a list triangles
    //    for (int i = 0; i < vertices.length; i += 9 ) {
    //      Vector3 p0 =  Vector3(vertices[i], vertices[i+1], vertices[i+2]),
    //          p1 =  Vector3(vertices[i+3], vertices[i+4], vertices[i+5]),
    //          p2 =  Vector3(vertices[i+6], vertices[i+7], vertices[i+8]);
    //
    //      Vector3 v0 = p1 - p0, v1 = p2 - p0;
    //      Vector3 normal = v0.cross(v1).normalize();
    //      print("normal = $normal");
    //    }
  }

  @override
  void draw({int? vertex, int? normal, int? coord, int? color, Function()? setUniforms}) {
    if (vertex != null) {
      gl.bindBuffer(WebGL.ARRAY_BUFFER, positionBuffer);
      gl.vertexAttribPointer(vertex, 3, WebGL.FLOAT, false, 0, 0);
    }

    if (normal != null) {
      gl.bindBuffer(WebGL.ARRAY_BUFFER, normalBuffer);
      gl.vertexAttribPointer(normal, 3, WebGL.FLOAT, false, 0, 0);
    }

    if (coord != null) {
      gl.bindBuffer(WebGL.ARRAY_BUFFER, textureCoordBuffer);
      gl.vertexAttribPointer(coord, 2, WebGL.FLOAT, false, 0, 0);
    }

    if (color != null) {
      gl.bindBuffer(WebGL.ARRAY_BUFFER, colorBuffer);
      gl.vertexAttribPointer(color, 4, WebGL.FLOAT, false, 0, 0);
    }

    if (setUniforms != null) setUniforms();
    gl.drawArrays(WebGL.TRIANGLES, 0, 18);
  }
}


<|start_filename|>web/learn_gl.dart<|end_filename|>
// Copyright (c) 2013, <NAME>.
/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
library learn_gl;

import 'dart:async';
import 'dart:convert';
import 'dart:html';
import 'dart:math';
import 'dart:typed_data';
import 'dart:web_gl';

part 'cube.dart';
part 'gl_program.dart';
part 'json_object.dart';
// All the lessons
part 'lesson1.dart';
part 'lesson10.dart';
part 'lesson11.dart';
part 'lesson12.dart';
part 'lesson13.dart';
part 'lesson14.dart';
part 'lesson15.dart';
part 'lesson16.dart';
part 'lesson2.dart';
part 'lesson3.dart';
part 'lesson4.dart';
part 'lesson5.dart';
part 'lesson6.dart';
part 'lesson7.dart';
part 'lesson8.dart';
part 'lesson9.dart';
// Math
part 'matrix4.dart';
part 'pyramid.dart';
part 'rectangle.dart';
// Some of our objects that we're going to support
part 'renderable.dart';
part 'sphere.dart';
part 'star.dart';

final canvas = querySelector('#lesson01-canvas') as CanvasElement;
late RenderingContext2 gl;
late Lesson lesson;

void main() {
  mvMatrix = Matrix4()..identity();
  // Nab the context we'll be drawing to.
  // gl = canvas.getContext3d();
  gl = canvas.getContext('webgl2') as RenderingContext2;

  // Allow some URL customization of the program.
  parseQueryString();

  trackFrameRate = urlParameters.containsKey('fps');
  if (!trackFrameRate) {
    querySelector('#fps')!.remove();
  }
  if (urlParameters.containsKey('width')) {
    final String width = urlParameters['width'];
    canvas.width = int.tryParse(width) ?? 500;
  }

  if (urlParameters.containsKey('height')) {
    final String height = urlParameters['height'];
    canvas.height = int.tryParse(height) ?? 500;
  }

  if (urlParameters.containsKey('overflow')) {
    document.body!.style.overflow = 'hidden';
  }

  var defaultLesson = 1;
  if (urlParameters.containsKey('lsn')) {
    defaultLesson = int.parse(urlParameters['lsn']);
  }

  final lessonSelect = querySelector('#lessonNumber') as SelectElement;
  for (var i = 1; i < 17; i++) {
    lessonSelect.children.add(OptionElement(
      data: 'Lesson $i',
      value: '$i',
      selected: defaultLesson == i,
    ));
  }
  lessonSelect.onChange.listen((event) {
    lesson = selectLesson(lessonSelect.selectedIndex! + 1)!..initHtml(lessonHook);
  });
  lesson = selectLesson(lessonSelect.selectedIndex! + 1)!..initHtml(lessonHook);

  // Set the fill color to black
  gl.clearColor(0, 0, 0, 1.0);

  // Hook into the window's onKeyDown and onKeyUp streams to track key states
  window.onKeyDown.listen((KeyboardEvent event) {
    currentlyPressedKeys.add(event.keyCode);
  });

  window.onKeyUp.listen((event) {
    currentlyPressedKeys.remove(event.keyCode);
  });

  // Start off the infinite animation loop
  tick(0);
}

/// This is the infinite animation loop; we request that the web browser
/// call us back every time its ready for a  frame to be rendered. The [time]
/// parameter is an increasing value based on when the animation loop started.
void tick(time) {
  window.animationFrame.then(tick);
  if (trackFrameRate) frameCount(time);
  lesson.handleKeys();
  lesson.animate(time);
  lesson.drawScene(canvas.width!, canvas.height!, canvas.width! / canvas.height!);
}

/// The global key-state map.
Set<int> currentlyPressedKeys = <int>{};

/// Test if the given [KeyCode] is active.
bool isActive(int code) => currentlyPressedKeys.contains(code);

/// Test if any of the given [KeyCode]s are active, returning true.
bool anyActive(List<int> codes) {
  return codes.firstWhere((code) => currentlyPressedKeys.contains(code), orElse: () => -1) != -1;
}

/// Parse and store the URL parameters for start up.
void parseQueryString() {
  var search = window.location.search!;
  if (search.startsWith('?')) {
    search = search.substring(1);
  }
  final params = search.split('&');
  for (var param in params) {
    final pair = param.split('=');
    if (pair.length == 1) {
      urlParameters[pair[0]] = '';
    } else {
      urlParameters[pair[0]] = pair[1];
    }
  }
}

Map urlParameters = {};

/// Perspective matrix
late Matrix4 pMatrix;

/// Model-View matrix.
late Matrix4 mvMatrix;

List<Matrix4> mvStack = <Matrix4>[];

/// Add a copy of the current Model-View matrix to the the stack for future
/// restoration.
void mvPushMatrix() => mvStack.add(Matrix4.fromMatrix(mvMatrix));

/// Pop the last matrix off the stack and set the Model View matrix.
void mvPopMatrix() => mvMatrix = mvStack.removeLast();

/// Handle common keys through callbacks, making lessons a little easier to code
void handleDirection({Function()? up, Function()? down, Function()? left, Function()? right}) {
  if (left != null && anyActive([KeyCode.A, KeyCode.LEFT])) {
    left();
  }
  if (right != null && anyActive([KeyCode.D, KeyCode.RIGHT])) {
    right();
  }
  if (down != null && anyActive([KeyCode.S, KeyCode.DOWN])) {
    down();
  }
  if (up != null && anyActive([KeyCode.W, KeyCode.UP])) {
    up();
  }
}

/// FPS meter - activated when the url parameter "fps" is included.
const double ALPHA_DECAY = 0.1;
const double INVERSE_ALPHA_DECAY = 1 - ALPHA_DECAY;
const SAMPLE_RATE_MS = 500;
const SAMPLE_FACTOR = 1000 ~/ SAMPLE_RATE_MS;
int frames = 0;
double lastSample = 0;
double averageFps = 1;
DivElement fps = querySelector('#fps') as DivElement;

void frameCount(double now) {
  frames++;
  if ((now - lastSample) < SAMPLE_RATE_MS) return;
  averageFps = averageFps * ALPHA_DECAY + frames * INVERSE_ALPHA_DECAY * SAMPLE_FACTOR;
  fps.text = averageFps.toStringAsFixed(2);
  frames = 0;
  lastSample = now;
}

/// The base for all Learn WebGL lessons.
abstract class Lesson {
  /// Render the scene to the [viewWidth], [viewHeight], and [aspect] ratio.
  void drawScene(int viewWidth, int viewHeight, double aspect);

  /// Animate the scene any way you like. [now] is provided as a clock reference
  /// since the scene rendering started.
  void animate(double now) {}

  /// Handle any keyboard events.
  void handleKeys() {}

  /// Fill in any lesson related instructions and input elements.
  /// This is provided by default.
  void initHtml(DivElement hook) {
    hook.innerHtml = "If you see this, don't worry, the lesson doesn't have "
        'any parameters for you to change! Generally up/down/left/right or '
        'WASD work.';
  }

  /// Added for your convenience to track time between [animate] callbacks.
  double lastTime = 0;
}

/// Load the given image at [url] and call [handle] to execute some GL code.
/// Return a [Future] to asynchronously notify when the texture is complete.
Future<Texture> loadTexture(String url, Function(Texture tex, ImageElement ele) handle) {
  final completer = Completer<Texture>();
  final texture = gl.createTexture();
  final element = ImageElement();
  element.onLoad.listen((e) {
    handle(texture, element);
    completer.complete(texture);
  });
  element.src = url;
  return completer.future;
}

/// This is a common handler for [loadTexture]. It will be explained in future
/// lessons that require textures.
void handleMipMapTexture(Texture texture, ImageElement image) {
  gl.pixelStorei(WebGL.UNPACK_FLIP_Y_WEBGL, 1);
  gl.bindTexture(WebGL.TEXTURE_2D, texture);
  gl.texImage2D(
    WebGL.TEXTURE_2D,
    0,
    WebGL.RGBA,
    WebGL.RGBA,
    WebGL.UNSIGNED_BYTE,
    image,
  );
  gl.texParameteri(
    WebGL.TEXTURE_2D,
    WebGL.TEXTURE_MAG_FILTER,
    WebGL.LINEAR,
  );
  gl.texParameteri(
    WebGL.TEXTURE_2D,
    WebGL.TEXTURE_MIN_FILTER,
    WebGL.LINEAR_MIPMAP_NEAREST,
  );
  gl.generateMipmap(WebGL.TEXTURE_2D);
  gl.bindTexture(WebGL.TEXTURE_2D, null);
}

DivElement lessonHook = querySelector('#lesson_html') as DivElement;
bool trackFrameRate = false;

Lesson? selectLesson(int number) {
  switch (number) {
    case 1:
      return Lesson1();
    case 2:
      return Lesson2();
    case 3:
      return Lesson3();
    case 4:
      return Lesson4();
    case 5:
      return Lesson5();
    case 6:
      return Lesson6();
    case 7:
      return Lesson7();
    case 8:
      return Lesson8();
    case 9:
      return Lesson9();
    case 10:
      return Lesson10();
    case 11:
      return Lesson11();
    case 12:
      return Lesson12();
    case 13:
      return Lesson13();
    case 14:
      return Lesson14();
    case 15:
      return Lesson15();
    case 16:
      return Lesson16();
  }
  return null;
}

/// Work around for setInnerHtml()
class NullTreeSanitizer implements NodeTreeSanitizer {
  static NullTreeSanitizer? instance;
  factory NullTreeSanitizer() {
    instance ??= NullTreeSanitizer._();
    return instance!;
  }

  NullTreeSanitizer._();
  @override
  void sanitizeTree(Node node) {}
}


<|start_filename|>web/sphere.dart<|end_filename|>
// Copyright (c) 2013, <NAME>.
/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
part of learn_gl;

class Sphere implements Renderable {
  final int lats;
  final int lons;
  final double radius;

  late Buffer _positionBuffer;
  late Buffer _normalBuffer;
  late Buffer _textureCoordBuffer;
  late Buffer _indexBuffer;
  int _indexBufferSize = 0;

  Sphere({this.lats = 30, this.lons = 30, this.radius = 2}) {
    final vertexPositions = <double>[];
    final normals = <double>[];
    final textureCoords = <double>[];
    final indexData = <int>[];

    // Step 1: Generate normals, texture coordinates and vertex positions
    for (var lat = 0; lat <= lats; lat++) {
      final theta = lat * pi / lats;
      final sinTheta = sin(theta);
      final cosTheta = cos(theta);

      for (var lon = 0; lon <= lons; lon++) {
        final phi = lon * 2 * pi / lons;
        final sinPhi = sin(phi);
        final cosPhi = cos(phi);

        final x = cosPhi * sinTheta;
        final y = cosTheta;
        final z = sinPhi * sinTheta;
        final u = 1 - (lon / lons);
        final v = 1 - (lat / lats);

        normals.addAll([x, y, z]);
        textureCoords.addAll([u, v]);
        vertexPositions.addAll([radius * x, radius * y, radius * z]);
      }
    }

    // Step 2: Stich vertex positions together as a series of triangles.
    for (var lat = 0; lat < lats; lat++) {
      for (var lon = 0; lon < lons; lon++) {
        final first = (lat * (lons + 1)) + lon;
        final second = first + lons + 1;
        indexData.addAll([first, second, first + 1, second, second + 1, first + 1]);
      }
    }
    _indexBufferSize = indexData.length;

    _normalBuffer = gl.createBuffer();
    gl.bindBuffer(WebGL.ARRAY_BUFFER, _normalBuffer);
    gl.bufferData(
      WebGL.ARRAY_BUFFER,
      Float32List.fromList(normals),
      WebGL.STATIC_DRAW,
    );

    _textureCoordBuffer = gl.createBuffer();
    gl.bindBuffer(WebGL.ARRAY_BUFFER, _textureCoordBuffer);
    gl.bufferData(
      WebGL.ARRAY_BUFFER,
      Float32List.fromList(textureCoords),
      WebGL.STATIC_DRAW,
    );

    _positionBuffer = gl.createBuffer();
    gl.bindBuffer(WebGL.ARRAY_BUFFER, _positionBuffer);
    gl.bufferData(
      WebGL.ARRAY_BUFFER,
      Float32List.fromList(vertexPositions),
      WebGL.STATIC_DRAW,
    );

    _indexBuffer = gl.createBuffer();
    gl.bindBuffer(WebGL.ELEMENT_ARRAY_BUFFER, _indexBuffer);
    gl.bufferData(
      WebGL.ELEMENT_ARRAY_BUFFER,
      Uint16List.fromList(indexData),
      WebGL.STATIC_DRAW,
    );
  }

  @override
  void draw({int? vertex, int? normal, int? coord, Function()? setUniforms}) {
    if (vertex != null) {
      gl.bindBuffer(WebGL.ARRAY_BUFFER, _positionBuffer);
      gl.vertexAttribPointer(vertex, 3, WebGL.FLOAT, false, 0, 0);
    }

    if (normal != null) {
      gl.bindBuffer(WebGL.ARRAY_BUFFER, _normalBuffer);
      gl.vertexAttribPointer(normal, 3, WebGL.FLOAT, false, 0, 0);
    }

    if (coord != null) {
      gl.bindBuffer(WebGL.ARRAY_BUFFER, _textureCoordBuffer);
      gl.vertexAttribPointer(coord, 2, WebGL.FLOAT, false, 0, 0);
    }

    if (setUniforms != null) setUniforms();

    gl.bindBuffer(WebGL.ELEMENT_ARRAY_BUFFER, _indexBuffer);
    gl.drawElements(WebGL.TRIANGLES, _indexBufferSize, WebGL.UNSIGNED_SHORT, 0);
  }
}


<|start_filename|>web/lesson9.dart<|end_filename|>
// Copyright (c) 2013, <NAME>.
/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
part of learn_gl;

/// Twinkle, twinkle little star...
class Lesson9 extends Lesson {
  late GlProgram program;
  Texture? texture;
  List<Star> stars = [];

  bool get isLoaded => texture != null;

  Lesson9() {
    for (var i = 0; i < 50; i++) {
      stars.add(Star((i / 50) * 5.0, i / 50));
    }
    loadTexture('star.gif', (Texture texture, ImageElement ele) {
      gl.pixelStorei(WebGL.UNPACK_FLIP_Y_WEBGL, 1);
      gl.bindTexture(WebGL.TEXTURE_2D, texture);
      gl.texImage2D(
        WebGL.TEXTURE_2D,
        0,
        WebGL.RGBA,
        WebGL.RGBA,
        WebGL.UNSIGNED_BYTE,
        ele,
      );
      gl.texParameteri(
        WebGL.TEXTURE_2D,
        WebGL.TEXTURE_MAG_FILTER,
        WebGL.LINEAR,
      );
      gl.texParameteri(
        WebGL.TEXTURE_2D,
        WebGL.TEXTURE_MIN_FILTER,
        WebGL.LINEAR,
      );

      gl.bindTexture(WebGL.TEXTURE_2D, null);
      this.texture = texture;
    });

    final attributes = ['aVertexPosition', 'aTextureCoord'];
    final uniforms = ['uMVMatrix', 'uPMatrix', 'uColor', 'uSampler'];
    program = GlProgram(
      '''
          precision mediump float;

          varying vec2 vTextureCoord;

          uniform sampler2D uSampler;

          uniform vec3 uColor;

          void main(void) {
              vec4 textureColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
              gl_FragColor = textureColor * vec4(uColor, 1.0);
          }
        ''',
      '''
          attribute vec3 aVertexPosition;
          attribute vec2 aTextureCoord;

          uniform mat4 uMVMatrix;
          uniform mat4 uPMatrix;

          varying vec2 vTextureCoord;

          void main(void) {
              gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
              vTextureCoord = aTextureCoord;
          }
        ''',
      attributes,
      uniforms,
    );
    gl.useProgram(program.program);
  }

  @override
  void drawScene(int viewWidth, int viewHeight, double aspect) {
    if (!isLoaded) return;
    // Basic viewport setup and clearing of the screen
    gl.viewport(0, 0, viewWidth, viewHeight);
    gl.clear(WebGL.COLOR_BUFFER_BIT | WebGL.DEPTH_BUFFER_BIT);

    // Setup the perspective - you might be wondering why we do this every
    // time, and that will become clear in much later lessons. Just know, you
    // are not crazy for thinking of caching this.
    pMatrix = Matrix4.perspective(45.0, aspect, 0.1, 100.0);

    // First stash the current model view matrix before we start moving around.
    mvPushMatrix();
    gl.blendFunc(WebGL.SRC_ALPHA, WebGL.ONE);
    gl.disable(WebGL.DEPTH_TEST);
    gl.enable(WebGL.BLEND);

    mvMatrix
      ..translate([0.0, 0.0, zoom])
      ..rotateX(radians(tilt));

    gl.activeTexture(WebGL.TEXTURE0);
    gl.bindTexture(WebGL.TEXTURE_2D, texture);
    gl.uniform1i(program.uniforms['uSampler'], 0);

    for (var star in stars) {
      star.draw(
          vertex: program.attributes['aVertexPosition']!,
          coord: program.attributes['aTextureCoord']!,
          color: program.uniforms['uColor'],
          twinkle: _twinkle.checked!,
          tilt: tilt,
          spin: spin,
          setUniforms: setMatrixUniforms);
    }
    mvPopMatrix();
  }

  UniformLocation? get uPMatrix => program.uniforms['uPMatrix'];
  UniformLocation? get uMVMatrix => program.uniforms['uMVMatrix'];

  void setMatrixUniforms() {
    gl.uniformMatrix4fv(uPMatrix, false, pMatrix.buf);
    gl.uniformMatrix4fv(uMVMatrix, false, mvMatrix.buf);
  }

  double tilt = 90.0;
  double spin = 0.0;
  double zoom = -15.0;

  @override
  void animate(double now) {
    if (lastTime != 0) {
      final elapsed = now - lastTime;
      for (var star in stars) {
        star.animate(elapsed);
      }
    }
    lastTime = now;
  }

  @override
  void handleKeys() {
    handleDirection(up: () => tilt += 2.0, down: () => tilt -= 2.0);
    if (isActive(KeyCode.PAGE_UP)) {
      zoom -= 0.1;
    }
    if (isActive(KeyCode.PAGE_DOWN)) {
      zoom += 0.1;
    }
  }

  late InputElement _twinkle;
  @override
  void initHtml(DivElement hook) {
    hook.setInnerHtml(
      '''
    <input type="checkbox" id="twinkle" /> Twinkle<br/>
    (Use up/down cursor keys to rotate, and <code>Page Up</code>/<code>Page Down</code> to zoom out/in)
    ''',
      treeSanitizer: NullTreeSanitizer(),
    );

    _twinkle = querySelector('#twinkle') as InputElement;
  }
}


<|start_filename|>web/rectangle.dart<|end_filename|>
// Copyright (c) 2013, <NAME>.
/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
part of learn_gl;

class Rectangle implements Renderable {
  late Buffer? positionBuffer, normalBuffer, textureCoordBuffer, colorBuffer, indexBuffer;

  static const List<double> WHITE_COLOR = [
    1.0, 1.0, 1.0, 1.0, // bottom left
    1.0, 1.0, 1.0, 1.0, // bottom right
    1.0, 1.0, 1.0, 1.0, // top right
    1.0, 1.0, 1.0, 1.0, // top left
  ];

  Rectangle(double width, double height, {double left = 0.0, double bottom = 0.0, required Float32List vertexColors}) {
    positionBuffer = gl.createBuffer();
    normalBuffer = gl.createBuffer();
    textureCoordBuffer = gl.createBuffer();
    colorBuffer = gl.createBuffer();

    gl.bindBuffer(WebGL.ARRAY_BUFFER, positionBuffer);
    final vertices = [
      left, bottom, 0.0, // bottom left
      left + width, bottom, 0.0, // bottom right
      left + width, bottom + height, 0.0, // top right
      left, bottom + height, 0.0, // top left
    ];
    gl.bufferData(
      WebGL.ARRAY_BUFFER,
      Float32List.fromList(vertices),
      WebGL.STATIC_DRAW,
    );

    gl.bindBuffer(WebGL.ARRAY_BUFFER, normalBuffer);
    final vertexNormals = [
      // Front face
      0.0, 0.0, 1.0,
      0.0, 0.0, 1.0,
      0.0, 0.0, 1.0,
      0.0, 0.0, 1.0,
    ];
    gl.bufferData(
      WebGL.ARRAY_BUFFER,
      Float32List.fromList(vertexNormals),
      WebGL.STATIC_DRAW,
    );

    gl.bindBuffer(WebGL.ARRAY_BUFFER, textureCoordBuffer);
    final coords = [
      // Front face
      0.0, 0.0,
      1.0, 0.0,
      1.0, 1.0,
      0.0, 1.0,
    ];
    gl.bufferData(
      WebGL.ARRAY_BUFFER,
      Float32List.fromList(coords),
      WebGL.STATIC_DRAW,
    );

    // TODO: Come up with a better way to store color buffer vs texture buffer :)
    gl.bindBuffer(WebGL.ARRAY_BUFFER, colorBuffer);
    var colors = WHITE_COLOR;
    colors = <double>[];
    if (vertexColors.length == 4) {
      colors.addAll(vertexColors);
      colors.addAll(vertexColors);
      colors.addAll(vertexColors);
      colors.addAll(vertexColors);
    } else if (vertexColors.length == 8) {
      colors.addAll(vertexColors.sublist(0, 4));
      colors.addAll(vertexColors.sublist(0, 4));
      colors.addAll(vertexColors.sublist(4, 8));
      colors.addAll(vertexColors.sublist(4, 8));
    }
//    var colors = [
//      // Front face
//      1.0, 0.0, 0.0, 1.0, // bottom left
//      0.0, 1.0, 0.0, 1.0, // bottom right
//      0.0, 0.0, 1.0, 1.0, // top right
//      1.0, 1.0, 1.0, 1.0, // top left
//    ];
    gl.bufferData(
      WebGL.ARRAY_BUFFER,
      Float32List.fromList(colors),
      WebGL.STATIC_DRAW,
    );

    indexBuffer = gl.createBuffer();
    gl.bindBuffer(WebGL.ELEMENT_ARRAY_BUFFER, indexBuffer);
    gl.bufferData(
        WebGL.ELEMENT_ARRAY_BUFFER,
        Uint16List.fromList([
          0, 1, 2, 0, 2, 3, // Front face
        ]),
        WebGL.STATIC_DRAW);
  }

  @override
  void draw({int? vertex, int? normal, int? coord, int? color, Function()? setUniforms}) {
    if (vertex != null) {
      gl.bindBuffer(WebGL.ARRAY_BUFFER, positionBuffer);
      gl.vertexAttribPointer(vertex, 3, WebGL.FLOAT, false, 0, 0);
    }

    if (normal != null) {
      gl.bindBuffer(WebGL.ARRAY_BUFFER, normalBuffer);
      gl.vertexAttribPointer(normal, 3, WebGL.FLOAT, false, 0, 0);
    }

    if (coord != null) {
      gl.bindBuffer(WebGL.ARRAY_BUFFER, textureCoordBuffer);
      gl.vertexAttribPointer(coord, 2, WebGL.FLOAT, false, 0, 0);
    }

    if (color != null) {
      gl.bindBuffer(WebGL.ARRAY_BUFFER, colorBuffer);
      gl.vertexAttribPointer(color, 4, WebGL.FLOAT, false, 0, 0);
    }

    if (setUniforms != null) setUniforms();
    gl.bindBuffer(WebGL.ELEMENT_ARRAY_BUFFER, indexBuffer);
    gl.drawElements(WebGL.TRIANGLES, 6, WebGL.UNSIGNED_SHORT, 0);
  }
}
