text: #include <math.h>
  #include <epoxy/gl.h>
  #include <graphene-gobject.h>
  
  #include "gthreelight.h"
  #include "gthreeprivate.h"
  
  gboolean
  gthree_light_setup_hash_equal (GthreeLightSetupHash *a,
                                 GthreeLightSetupHash *b)
  {
    return
      a->num_directional == b->num_directional &&
      a->num_point == b->num_point &&
      a->num_spot == b->num_spot &&
      a->num_hemi == b->num_hemi &&
      a->num_shadow == b->num_shadow &&
      a->obj_receive_shadow == b->obj_receive_shadow;
  }
  
  
  typedef struct {
    graphene_vec3_t color;
    float   intensity;
    GthreeLightShadow *shadow;
  } GthreeLightPrivate;
  
  enum {
    PROP_0,
    PROP_COLOR,
    PROP_INTENSITY,
    LAST_PROP
  };
  
  static GParamSpec *obj_props[LAST_PROP] = { NULL, };
  
  G_DEFINE_TYPE_WITH_PRIVATE (GthreeLight, gthree_light, GTHREE_TYPE_OBJECT);
  
  static void
  gthree_light_real_setup (GthreeLight *light,
                           GthreeCamera  *camera,
                           GthreeLightSetup *setup)
  {
  }
  
  static void
  gthree_light_set_property (GObject *self,
                             guint prop_id,
                             const GValue *value,
                             GParamSpec *pspec)
  {
    GthreeLight *light = GTHREE_LIGHT (self);
  
    switch (prop_id)
      {
      case PROP_COLOR:
        gthree_light_set_color (light, g_value_get_boxed (value));
        break;
  
      case PROP_INTENSITY:
        gthree_light_set_intensity (light, g_value_get_float (value));
        break;
  
      default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (self, prop_id, pspec);
      }
  }
  
  static void
  gthree_light_get_property (GObject *self,
                             guint prop_id,
                             GValue *value,
                             GParamSpec *pspec)
  {
    GthreeLight *light = GTHREE_LIGHT (self);
    GthreeLightPrivate *priv = gthree_light_get_instance_private (light);
  
    switch (prop_id)
      {
      case PROP_COLOR:
        g_value_set_boxed (value, &priv->color);
        break;
  
      case PROP_INTENSITY:
        g_value_set_float (value, priv->intensity);
        break;
  
      default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (self, prop_id, pspec);
      }
  }
  
  static void
  gthree_light_init (GthreeLight *light)
  {
    GthreeLightPrivate *priv = gthree_light_get_instance_private (light);
  
    graphene_vec3_init (&priv->color,
                        1.0, 1.0, 1.0);
    priv->intensity = 1.0;
  }
  
  static void
  gthree_light_finalize (GObject *obj)
  {
    GthreeLight *light = GTHREE_LIGHT (obj);
    GthreeLightPrivate *priv = gthree_light_get_instance_private (light);
  
    g_clear_object (&priv->shadow);
  }
  
  static void
  gthree_light_class_init (GthreeLightClass *klass)
  {
    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  
    gobject_class->set_property = gthree_light_set_property;
    gobject_class->get_property = gthree_light_get_property;
    gobject_class->finalize = gthree_light_finalize;
  
    klass->setup = gthree_light_real_setup;
  
    obj_props[PROP_COLOR] =
      g_param_spec_boxed ("color", "Color", "Light color",
                          GRAPHENE_TYPE_VEC3,
                          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
  
    obj_props[PROP_INTENSITY] =
      g_param_spec_float ("intensity", "Intensity", "Intensity",
                          0.0, G_MAXFLOAT, 1.f,
                          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
  
    g_object_class_install_properties (gobject_class, LAST_PROP, obj_props);
  }
  
  GthreeLight *
  gthree_light_new (void)
  {
    return g_object_new (GTHREE_TYPE_LIGHT, NULL);
  }
  
  float
  gthree_light_get_intensity (GthreeLight *light)
  {
    GthreeLightPrivate *priv = gthree_light_get_instance_private (light);
  
    return priv->intensity;
  }
  
  void
  gthree_light_set_intensity (GthreeLight *light,
                              float intensity)
  {
    GthreeLightPrivate *priv = gthree_light_get_instance_private (light);
  
    if (priv->intensity == intensity)
      return;
  
    priv->intensity = intensity;
  
    g_object_notify_by_pspec (G_OBJECT (light), obj_props[PROP_INTENSITY]);
  }
  
  
  const graphene_vec3_t *
  gthree_light_get_color (GthreeLight *light)
  {
    GthreeLightPrivate *priv = gthree_light_get_instance_private (light);
  
    return &priv->color;
  }
  
  void
  gthree_light_set_color (GthreeLight *light,
                          const graphene_vec3_t *color)
  {
    GthreeLightPrivate *priv = gthree_light_get_instance_private (light);
  
    if (graphene_vec3_equal (color, &priv->color))
      return;
  
    priv->color = *color;
  
    g_object_notify_by_pspec (G_OBJECT (light), obj_props[PROP_COLOR]);
  }
  
  /* This is called once per active light in the scene after projection, but before rendering.
   * It needs to set the uniforms for the light, which are later synced to the individual
   * material light uniforms as necessary.
   */
  void
  gthree_light_setup (GthreeLight *light,
                      GthreeCamera  *camera,
                      GthreeLightSetup *setup)
  {
    GthreeLightClass *class = GTHREE_LIGHT_GET_CLASS(light);
  
    class->setup (light, camera, setup);
  }
  
  GthreeLightShadow  *
  gthree_light_get_shadow (GthreeLight   *light)
  {
    GthreeLightPrivate *priv = gthree_light_get_instance_private (light);
  
    return priv->shadow;
  }
  
  void
  gthree_light_set_shadow (GthreeLight   *light,
                           GthreeLightShadow *shadow)
  {
    GthreeLightPrivate *priv = gthree_light_get_instance_private (light);
  
    g_set_object (&priv->shadow, shadow);
  }
  