static inline float inverse_length(vec3 v)
{
  float f = dot(v, v);
  return (1.0f / (__builtin_sqrtf(f)));
}

static inline float light_intensity(vec3 light_vec, vec3 n)
{
  float n_dot_l = dot(n, light_vec);

  float intensity = 0.5f;
  if (n_dot_l > 0) {
    intensity += 0.5f * n_dot_l * (inverse_length(n) * inverse_length(light_vec));
    if (intensity > 1.0f)
      intensity = 1.0f;
  }
  return intensity;
}

static inline void render_tri(ta_parameter_writer& writer,
                              vec3 ap,
                              vec3 bp,
                              vec3 cp,
                              vec2 at,
                              vec2 bt,
                              vec2 ct,
                              float ai,
                              float bi,
                              float ci)
{
  writer.append<ta_vertex_parameter::polygon_type_7>() =
    ta_vertex_parameter::polygon_type_7(polygon_vertex_parameter_control_word(false),
                                        ap.x, ap.y, ap.z,
                                        at.x, at.y,
                                        ai, 0);

  writer.append<ta_vertex_parameter::polygon_type_7>() =
    ta_vertex_parameter::polygon_type_7(polygon_vertex_parameter_control_word(false),
                                        bp.x, bp.y, bp.z,
                                        bt.x, bt.y,
                                        bi, 0);

  writer.append<ta_vertex_parameter::polygon_type_7>() =
    ta_vertex_parameter::polygon_type_7(polygon_vertex_parameter_control_word(true),
                                        cp.x, cp.y, cp.z,
                                        ct.x, ct.y,
                                        ci, 0);
}

static inline void render_quad(ta_parameter_writer& writer,
                               vec3 ap,
                               vec3 bp,
                               vec3 cp,
                               vec3 dp,
                               vec2 at,
                               vec2 bt,
                               vec2 ct,
                               vec2 dt,
                               float ai,
                               float bi,
                               float ci,
                               float di)
{
  writer.append<ta_vertex_parameter::polygon_type_7>() =
    ta_vertex_parameter::polygon_type_7(polygon_vertex_parameter_control_word(false),
                                        ap.x, ap.y, ap.z,
                                        at.x, at.y,
                                        ai, 0);

  writer.append<ta_vertex_parameter::polygon_type_7>() =
    ta_vertex_parameter::polygon_type_7(polygon_vertex_parameter_control_word(false),
                                        bp.x, bp.y, bp.z,
                                        bt.x, bt.y,
                                        bi, 0);

  writer.append<ta_vertex_parameter::polygon_type_7>() =
    ta_vertex_parameter::polygon_type_7(polygon_vertex_parameter_control_word(false),
                                        dp.x, dp.y, dp.z,
                                        dt.x, dt.y,
                                        di, 0);

  writer.append<ta_vertex_parameter::polygon_type_7>() =
    ta_vertex_parameter::polygon_type_7(polygon_vertex_parameter_control_word(true),
                                        cp.x, cp.y, cp.z,
                                        ct.x, ct.y,
                                        ci, 0);
}

static inline vec3 screen_transform(vec3 v)
{
  float dim = 480 / 2.0;

  return {
    v.x / (1.f * v.z) * dim + 640 / 2.0f,
    v.y / (1.f * v.z) * dim + 480 / 2.0f,
    1 / v.z,
  };
}

static inline void render_clip_tri(ta_parameter_writer& writer,
                                   vec3 light_vec,
                                   vec3 ap,
                                   vec3 bp,
                                   vec3 cp,
                                   vec3 an,
                                   vec3 bn,
                                   vec3 cn,
                                   vec2 at,
                                   vec2 bt,
                                   vec2 ct)
{
  const vec3 plane_point = {0.f, 0.f, 1.f};
  const vec3 plane_normal = {0.f, 0.f, 1.f};

  vec3 preclip_position[] = {ap, bp, cp};
  vec3 preclip_normal[]   = {an, bn, cn};
  vec2 preclip_texture[]  = {at, bt, ct};

  vec3 clip_position[4];
  vec3 clip_normal[4];
  vec2 clip_texture[4];
  int output_length = geometry::clip_polygon_3<3>(clip_position,
                                                  clip_normal,
                                                  clip_texture,
                                                  plane_point,
                                                  plane_normal,
                                                  preclip_position,
                                                  preclip_normal,
                                                  preclip_texture);

  {
    float ai;
    float bi;
    float ci;
    float di;

    vec3 ap;
    vec3 bp;
    vec3 cp;
    vec3 dp;

    if (output_length >= 3) {
      // 012
      ap = screen_transform(clip_position[0]);
      bp = screen_transform(clip_position[1]);
      cp = screen_transform(clip_position[2]);

      ai = light_intensity(light_vec, clip_normal[0]);
      bi = light_intensity(light_vec, clip_normal[1]);
      ci = light_intensity(light_vec, clip_normal[2]);

      render_tri(writer,
                 ap,
                 bp,
                 cp,
                 clip_texture[0],
                 clip_texture[1],
                 clip_texture[2],
                 ai,
                 bi,
                 ci);
    }
    if (output_length >= 4) {
      // 023
      dp = screen_transform(clip_position[3]);

      di = light_intensity(light_vec, clip_normal[3]);

      render_tri(writer,
                 ap,
                 cp,
                 dp,
                 clip_texture[0],
                 clip_texture[2],
                 clip_texture[3],
                 ai,
                 ci,
                 di);
    }
  }
}

static inline void render_polygon(ta_parameter_writer& writer,
                                  const vec3 light_vec,
                                  const int polygon_ix,
                                  const polygon * polygon,
                                  const vec3 * position,
                                  const vec3 * normal,
                                  const vec2 * texture)
{
  vec3 ap = position[polygon->a];
  vec3 bp = position[polygon->b];
  vec3 cp = position[polygon->c];
  vec3 dp = position[polygon->d];

  if (ap.z < 0 && bp.z < 0 && cp.z < 0 && dp.z < 0) {
    return;
  }

  vec3 an = normal[polygon_ix];
  vec3 bn = an;
  vec3 cn = an;
  vec3 dn = an;

  vec2 at = texture[polygon->uv_index + 0];
  vec2 bt = texture[polygon->uv_index + 1];
  vec2 ct = texture[polygon->uv_index + 2];
  vec2 dt = texture[polygon->uv_index + 3];

  /*
  at.y = 1.0 - at.y;
  bt.y = 1.0 - bt.y;
  ct.y = 1.0 - ct.y;
  dt.y = 1.0 - dt.y;
  */

  if (ap.z < 0 || bp.z < 0 || cp.z < 0 || dp.z < 0) {
    // abd
    // dbc
    render_clip_tri(writer,
                    light_vec,
                    ap, bp, dp,
                    an, bn, dn,
                    at, bt, dt);

    render_clip_tri(writer,
                    light_vec,
                    dp, bp, cp,
                    dn, bn, cn,
                    dt, bt, ct);
  } else {
    float ai = light_intensity(light_vec, an);
    float bi = light_intensity(light_vec, bn);
    float ci = light_intensity(light_vec, cn);
    float di = light_intensity(light_vec, dn);

    render_quad(writer,
                screen_transform(ap),
                screen_transform(bp),
                screen_transform(cp),
                screen_transform(dp),
                at, bt, ct, dt,
                ai, bi, ci, di);
  }
}
