use graphics::{node, Node, NodeRef, NodeMut};
use color::{Rgb, ToRgb, ToRgba};
use glin;
use graphics::{self, Mvp, CoordinateOrigin};
use na::*;
use angle::*;
use std::borrow::Borrow;
use std::marker::PhantomData;
use std::mem;
use super::{Light, Renderer, Render3d,
ShadowMap, shadow_mapping::SpotShadowMap};
use glin::RenderSurface;
pub struct SpotLight{
node: Node,
attenuation_constant: f32,
attenuation_linear: f32,
attenuation_quadratic: f32,
color: Rgb<f32>,
radius: f32,
cutoff: f32,
strength: f32,
spot_angle: Deg<f32>,
direction: Vec3,
spot_blend: f32,
}
#[repr(C)]
struct NonPaddedData{
world_position: Vec3,
radius: f32,
color: Rgb<f32>,
strength: f32,
spot_angle: Rad<f32>,
spot_cos_cutoff: f32,
spot_cos_inner_cutoff: f32,
spot_blend: f32,
spot_direction: Vec3,
pad: f32,
shadow_map_idx: [i32;4],
}
#[allow(dead_code)]
pub struct Data{
data: NonPaddedData,
pad: [f32;16],
}
#[repr(C)]
pub struct DataWithShadow{
data: NonPaddedData,
view_matrix: Mat4,
}
impl SpotLight{
pub fn new() -> SpotLight{
SpotLight{
node: Node::identity(),
attenuation_constant: 1.0,
attenuation_linear: 0.1,
attenuation_quadratic: 0.01,
color: rgb!(1.0f32,1.0f32,1.0f32),
radius: 0.1,
cutoff: 45.0,
strength: 1.0,
spot_angle: Deg(45.),
direction: Vec3::z(),
spot_blend: 1.,
}
}
pub fn set_color<C: ToRgb + ToRgba>(&mut self, color: &C){
self.color = color.to_rgb();
}
pub fn set_att_constant(&mut self, att: f32){
self.attenuation_constant = att;
}
pub fn set_att_linear(&mut self, att: f32){
self.attenuation_linear = att;
}
pub fn set_att_quadratic(&mut self, att: f32){
self.attenuation_quadratic = att;
}
pub fn set_strength(&mut self, strength: f32){
self.strength = strength;
}
pub fn set_spot_angle(&mut self, angle: Deg<f32>){
self.spot_angle = angle;
}
pub fn set_spot_blend(&mut self, blend: f32){
self.spot_blend = blend;
}
pub fn spot_angle(&self) -> Deg<f32>{
self.spot_angle
}
pub fn strength(&self) -> f32{
self.strength
}
fn non_padded_data(&self, shadow_map_idx: [i32;4]) -> NonPaddedData{
NonPaddedData{
world_position: self.global_position().xyz().to_vec(),
color: self.color,
radius: self.radius,
strength: self.strength,
spot_angle: self.spot_angle.to_rad(),
spot_cos_cutoff: self.spot_angle.cos(),
spot_cos_inner_cutoff: (self.spot_angle * (1. - self.spot_blend)).cos(),
spot_blend: self.spot_blend,
spot_direction: self.direction,
pad: unsafe{mem::uninitialized()},
shadow_map_idx,
}
}
pub fn data(&self) -> Data{
Data{
data: self.non_padded_data([-1;4]),
pad: [unsafe{mem::uninitialized()}; 16]
}
}
pub fn with_shadow_maps<'l, S: SpotShadowMap<G>, G, B: Borrow<S>>(&'l self, shadow_maps: Vec<B>) -> SpotLightWithShadowMap<'l, S, G, B>{
let ratio = 1.;
let view = self.inv_global_transformation();
let mut projections = vec![];
let mut projection_views = vec![];
for shadow_map in shadow_maps.iter() {
let projection = *Perspective3::new(
ratio,
self.spot_angle.to_rad().value() * 2.,
shadow_map.borrow().near_clip(),
shadow_map.borrow().far_clip())
.as_matrix();
let projection_view = projection.fast_mul(&view);
projections.push(projection);
projection_views.push(projection_view);
}
SpotLightWithShadowMap{
shadow_maps,
light: self,
view,
projections,
projection_views,
marker: PhantomData,
marker2: PhantomData,
}
}
}
impl NodeRef for SpotLight{
fn node(&self) -> &Node{
&self.node
}
}
impl NodeMut for SpotLight{
fn node_mut(&mut self) -> &mut Node{
&mut self.node
}
fn update_with_parent_flags(&mut self, parent: Option<&Node>, flags: node::Flags) -> bool{
let changed = self.node.update_with_parent_flags(parent, flags);
let global_rot: Mat3<f32> = Mat3::from_iterator(self.global_transformation().columns(0,3).rows(0,3).iter().map(|v| *v));
self.direction = normalize(&(global_rot * Vec3::z()));
changed
}
}
impl Light for SpotLight{
fn ty(&self) -> &str{
"SPOT_LIGHT"
}
fn uniforms(&self, _shadow_map_idx_offset: &mut usize) -> Vec<glin::program::Uniform>{
uniforms!{
type: 2f32,
world_position: self.global_position().xyz().to_vec(),
color: self.color,
constantAttenuation: self.attenuation_constant,
linearAttenuation: self.attenuation_linear,
quadraticAttenuation: self.attenuation_quadratic,
radius: self.radius,
strength: self.strength,
cutoff: self.cutoff,
spot_angle: self.spot_angle.to_rad().value(),
spot_cos_cutoff: self.spot_angle.cos(),
spot_cos_inner_cutoff: (self.spot_angle * (1. - self.spot_blend)).cos(),
spot_direction: self.direction,
spot_blend: self.spot_blend,
}
}
fn shadow_maps(&self) -> Vec<&dyn ShadowMap>{
vec![]
}
}
use graphics::Vertex3DNormal;
use super::Renderer3d;
geometry_cache!(Vertex3DNormal, {
let rot = UnitQuat::from_axis_angle(&Unit::new_unchecked(Vec3::x()), Deg(90.).to_rad().value());
let mut cone = graphics::cone(1., 1., 20);
for v in cone.vertices_mut().iter_mut() {
v.position = rot * (v.position - vec3(0., 1., 0.));
}
cone
});
program_cache!(glin::program::Settings{
version: ::gl::default_glsl_version(),
extensions: vec![],
precission: glin::program::ShaderPrecision::High,
defines: vec![],
shaders: vec![
(glin::gl::VERTEX_SHADER, include_str!("shaders/spot_light_cone.vs.glsl")),
(glin::gl::FRAGMENT_SHADER, include_str!("shaders/spot_light_cone.fs.glsl")),
],
bindings: ::gl::default_attribute_bindings(),
.. Default::default()
}, get_program, SHADER);
impl Render3d for SpotLight{
fn render<R: RenderSurface>(&self, renderer: &Renderer<R>) where Self: Sized{
let cone_height = self.spot_angle.cos();
let cone_radius = self.spot_angle.sin();
let cone_ratio = cone_radius / cone_height;
let cone_height = self.strength.sqrt();
let cone_radius = cone_height * cone_ratio;
let scale = vec3( cone_radius, cone_radius, cone_height);
let model = self.node().clone() * graphics::Node::new(origin(), one(), scale);
let light_color = rgba!(self.color * self.strength / 5., 0.3);
let uniforms = uniforms!{
color: light_color,
light_position: self.global_position().to_vec(),
};
let renderer = renderer.with_model(&model);
let renderer = renderer.with_properties(&[
glin::Property::Blend(true),
glin::Property::BlendFunc(glin::gl::SRC_ALPHA, glin::gl::ONE_MINUS_SRC_ALPHA),
]);
renderer.with_properties(&[glin::Property::CullFace(Some(glin::gl::FRONT))])
.draw_vao(get_geometry(&renderer), get_program(&renderer), &uniforms);
renderer.with_properties(&[glin::Property::CullFace(Some(glin::gl::BACK))])
.draw_vao(get_geometry(&renderer), get_program(&renderer), &uniforms);
}
}
pub struct SpotLightWithShadowMap<'l, S, G, B>{
light: &'l SpotLight,
view: Mat4,
shadow_maps: Vec<B>,
projections: Vec<Mat4>,
projection_views: Vec<Mat4>,
marker: PhantomData<G>,
marker2: PhantomData<S>,
}
impl<'l, S: SpotShadowMap<G>, G, B: Borrow<S>> SpotLightWithShadowMap<'l, S, G, B>{
pub fn view(&self) -> &Mat4{
&self.view
}
pub fn projection_view(&self, index: usize) -> &Mat4 {
&self.projection_views[index]
}
pub fn projection(&self, index: usize) -> &Mat4 {
&self.projections[index]
}
pub fn projection_views(&self) -> &[Mat4] {
&self.projection_views
}
pub fn mvp(&self, index: usize) -> Mvp{
Mvp::from_parts(
*self.projection(index),
self.view,
self.global_position(),
self.shadow_maps[index].borrow().near_clip(),
self.shadow_maps[index].borrow().far_clip(),
vec4!(0.),
CoordinateOrigin::CenterUp,
self.shadow_maps[index].borrow().viewport())
}
fn shadow_map_idx(&self, offset: i32) -> [i32;4]{
let mut shadow_maps = self.shadow_maps.iter();
let idx0 = shadow_maps.next()
.map(|s| s.borrow().shadow_sampler().shadow_sampler_idx() + offset + 0)
.unwrap_or(-1);
let idx1 = shadow_maps.next()
.map(|s| s.borrow().shadow_sampler().shadow_sampler_idx() + offset + 1)
.unwrap_or(-1);
let idx2 = shadow_maps.next()
.map(|s| s.borrow().shadow_sampler().shadow_sampler_idx() + offset + 2)
.unwrap_or(-1);
let idx3 = shadow_maps.next()
.map(|s| s.borrow().shadow_sampler().shadow_sampler_idx() + offset + 3)
.unwrap_or(-1);
[idx0, idx1, idx2, idx3]
}
pub fn data(&self, shadow_map_idx_offset: usize) -> DataWithShadow{
DataWithShadow{
data: self.light.non_padded_data(self.shadow_map_idx(shadow_map_idx_offset as i32)),
view_matrix: self.view,
}
}
}
impl<'l, S: SpotShadowMap<G>, G, B: Borrow<S>> NodeRef for SpotLightWithShadowMap<'l, S, G, B>{
fn node(&self) -> &Node{
&self.light.node
}
}
impl<'l, S: SpotShadowMap<G>, G, B: Borrow<S>> Light for SpotLightWithShadowMap<'l, S, G, B>{
fn ty(&self) -> &str{
"SPOT_LIGHT_SHADOW"
}
fn uniforms(&self, shadow_map_idx_offset: &mut usize) -> Vec<glin::program::Uniform>{
let mut uniforms = self.light.uniforms(shadow_map_idx_offset);
uniforms.extend(uniforms!{
view_matrix: self.view,
shadow_map_idx: self.shadow_map_idx(*shadow_map_idx_offset as i32)
});
uniforms.extend(self.projection_views.iter().enumerate().map(|(i, projection_view)|{
let i = *shadow_map_idx_offset + i;
glin::program::uniform(format!("shadow_matrices[{}]", i), projection_view)
}));
uniforms.extend(self.shadow_maps.iter().enumerate().flat_map(|(i,s)| {
let i = *shadow_map_idx_offset + i;
s.borrow().uniforms(i)
}));
*shadow_map_idx_offset += self.shadow_maps.len();
uniforms
}
fn shadow_maps(&self) -> Vec<&dyn ShadowMap>{
self.shadow_maps.iter().map(|s| s.borrow() as &dyn ShadowMap).collect()
}
}
impl<'l, S: SpotShadowMap<G>, G, B: Borrow<S>> Render3d for SpotLightWithShadowMap<'l, S, G, B>{
fn render<R: RenderSurface>(&self, renderer: &Renderer<R>){
self.light.render(renderer);
}
}