From 53754b956c83ee068aa40a43a83fb1798e3c5530 Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Fri, 28 Dec 2018 00:23:02 -0800 Subject: [PATCH] Blackbody colors are now parsed, and PsychoBlend has support as well. --- psychoblend/__init__.py | 30 ++++++++++++++- psychoblend/assembly.py | 67 ++++++++++++++++++++++++--------- psychoblend/ui.py | 19 ++++++++-- psychoblend/world.py | 14 +++++-- src/parse/psy.rs | 39 ++++++++++++++++--- src/parse/psy_light.rs | 33 ++++++---------- src/parse/psy_surface_shader.rs | 33 ++++++---------- 7 files changed, 161 insertions(+), 74 deletions(-) diff --git a/psychoblend/__init__.py b/psychoblend/__init__.py index f76adb3..f182333 100644 --- a/psychoblend/__init__.py +++ b/psychoblend/__init__.py @@ -77,6 +77,19 @@ class PsychopathCamera(bpy.types.PropertyGroup): min=0.0, max=10000.0, soft_min=0.0, soft_max=2.0, default=0.0 ) +# Psychopath material +class PsychopathLight(bpy.types.PropertyGroup): + color_type = EnumProperty( + name="Color Type", description="", + items=[('Rec709', 'Rec709', ""), ('Blackbody', 'Blackbody', "")], + default="Rec709" + ) + + color_blackbody_temp = FloatProperty( + name="Temperature", description="Blackbody temperature in kelvin", + min=0.0, max=32000.0, soft_min=800.0, soft_max=6500.0, default=1200.0 + ) + # Custom Mesh properties class PsychopathMesh(bpy.types.PropertyGroup): is_subdivision_surface = BoolProperty( @@ -88,10 +101,16 @@ class PsychopathMesh(bpy.types.PropertyGroup): class PsychopathMaterial(bpy.types.PropertyGroup): surface_shader_type = EnumProperty( name="Surface Shader Type", description="", - items=[('Emit', 'Emit', ""), ('Lambert', 'Lambert', ""), ('GTR', 'GTR', ""), ('GGX', 'GGX', "")], + items=[('Emit', 'Emit', ""), ('Lambert', 'Lambert', ""), ('GGX', 'GGX', "")], default="Lambert" ) + color_type = EnumProperty( + name="Color Type", description="", + items=[('Rec709', 'Rec709', ""), ('Blackbody', 'Blackbody', "")], + default="Rec709" + ) + color = FloatVectorProperty( name="Color", description="", subtype='COLOR', @@ -99,6 +118,11 @@ class PsychopathMaterial(bpy.types.PropertyGroup): default=[0.8,0.8,0.8] ) + color_blackbody_temp = FloatProperty( + name="Temperature", description="Blackbody temperature in kelvin", + min=0.0, max=32000.0, soft_min=800.0, soft_max=6500.0, default=1200.0 + ) + roughness = FloatProperty( name="Roughness", description="", min=-1.0, max=1.0, soft_min=0.0, soft_max=1.0, default=0.1 @@ -135,10 +159,12 @@ def register(): bpy.utils.register_class(PsychopathPreferences) bpy.utils.register_class(RenderPsychopathSettingsScene) bpy.utils.register_class(PsychopathCamera) + bpy.utils.register_class(PsychopathLight) bpy.utils.register_class(PsychopathMesh) bpy.utils.register_class(PsychopathMaterial) bpy.types.Scene.psychopath = PointerProperty(type=RenderPsychopathSettingsScene) bpy.types.Camera.psychopath = PointerProperty(type=PsychopathCamera) + bpy.types.Lamp.psychopath = PointerProperty(type=PsychopathLight) bpy.types.Mesh.psychopath = PointerProperty(type=PsychopathMesh) bpy.types.Material.psychopath = PointerProperty(type=PsychopathMaterial) render.register() @@ -149,10 +175,12 @@ def unregister(): bpy.utils.unregister_class(PsychopathPreferences) bpy.utils.unregister_class(RenderPsychopathSettingsScene) bpy.utils.unregister_class(PsychopathCamera) + bpy.utils.unregister_class(PsychopathLight) bpy.utils.unregister_class(PsychopathMesh) bpy.utils.unregister_class(PsychopathMaterial) del bpy.types.Scene.psychopath del bpy.types.Camera.psychopath + del bpy.types.Lamp.psychopath del bpy.types.Mesh.psychopath del bpy.types.Material.psychopath render.unregister() diff --git a/psychoblend/assembly.py b/psychoblend/assembly.py index 8816f99..8fbd442 100644 --- a/psychoblend/assembly.py +++ b/psychoblend/assembly.py @@ -213,7 +213,12 @@ class SphereLamp: def take_sample(self, render_engine, scene, time): render_engine.update_stats("", "Psychopath: Collecting '{}' at time {}".format(self.ob.name, time)) - self.time_col += [self.ob.data.color * self.ob.data.energy] + + if self.ob.data.psychopath.color_type == 'Rec709': + self.time_col += [('Rec709', self.ob.data.color * self.ob.data.energy)] + elif self.ob.data.psychopath.color_type == 'Blackbody': + self.time_col += [('Blackbody', self.ob.data.psychopath.color_blackbody_temp, self.ob.data.energy)] + self.time_rad += [self.ob.data.shadow_soft_size] def cleanup(self): @@ -225,7 +230,10 @@ class SphereLamp: w.write("SphereLight $%s {\n" % self.name) w.indent() for col in self.time_col: - w.write("Color [%f %f %f]\n" % (col[0], col[1], col[2])) + if col[0] == 'Rec709': + w.write("Color [rec709, %f %f %f]\n" % (col[1][0], col[1][1], col[1][2])) + elif col[0] == 'Blackbody': + w.write("Color [blackbody, %f %f]\n" % (col[1], col[2])) for rad in self.time_rad: w.write("Radius [%f]\n" % rad) @@ -244,7 +252,12 @@ class RectLamp: def take_sample(self, render_engine, scene, time): render_engine.update_stats("", "Psychopath: Collecting '{}' at time {}".format(self.ob.name, time)) - self.time_col += [self.ob.data.color * self.ob.data.energy] + + if self.ob.data.psychopath.color_type == 'Rec709': + self.time_col += [('Rec709', self.ob.data.color * self.ob.data.energy)] + elif self.ob.data.psychopath.color_type == 'Blackbody': + self.time_col += [('Blackbody', self.ob.data.psychopath.color_blackbody_temp, self.ob.data.energy)] + if self.ob.data.shape == 'RECTANGLE': self.time_dim += [(self.ob.data.size, self.ob.data.size_y)] else: @@ -259,7 +272,10 @@ class RectLamp: w.write("RectangleLight $%s {\n" % self.name) w.indent() for col in self.time_col: - w.write("Color [%f %f %f]\n" % (col[0], col[1], col[2])) + if col[0] == 'Rec709': + w.write("Color [rec709, %f %f %f]\n" % (col[1][0], col[1][1], col[1][2])) + elif col[0] == 'Blackbody': + w.write("Color [blackbody, %f %f]\n" % (col[1], col[2])) for dim in self.time_dim: w.write("Dimensions [%f %f]\n" % dim) @@ -314,23 +330,40 @@ class Material: w.indent() if self.mat.psychopath.surface_shader_type == 'Emit': w.write("Type [Emit]\n") - color = self.mat.psychopath.color - w.write("Color [%f %f %f]\n" % (color[0], color[1], color[2])) + if self.mat.psychopath.color_type == 'Rec709': + col = self.mat.psychopath.color + w.write("Color [rec709, %f %f %f]\n" % ( + col[0], col[1], col[2], + )) + elif self.mat.psychopath.color_type == 'Blackbody': + w.write("Color [blackbody, %f %f]\n" % ( + self.mat.psychopath.color_blackbody_temp, + 1.0, + )) elif self.mat.psychopath.surface_shader_type == 'Lambert': w.write("Type [Lambert]\n") - color = self.mat.psychopath.color - w.write("Color [%f %f %f]\n" % (color[0], color[1], color[2])) - elif self.mat.psychopath.surface_shader_type == 'GTR': - w.write("Type [GTR]\n") - color = self.mat.psychopath.color - w.write("Color [%f %f %f]\n" % (color[0], color[1], color[2])) - w.write("Roughness [%f]\n" % self.mat.psychopath.roughness) - w.write("TailShape [%f]\n" % self.mat.psychopath.tail_shape) - w.write("Fresnel [%f]\n" % self.mat.psychopath.fresnel) + if self.mat.psychopath.color_type == 'Rec709': + col = self.mat.psychopath.color + w.write("Color [rec709, %f %f %f]\n" % ( + col[0], col[1], col[2], + )) + elif self.mat.psychopath.color_type == 'Blackbody': + w.write("Color [blackbody, %f %f]\n" % ( + self.mat.psychopath.color_blackbody_temp, + 1.0, + )) elif self.mat.psychopath.surface_shader_type == 'GGX': w.write("Type [GGX]\n") - color = self.mat.psychopath.color - w.write("Color [%f %f %f]\n" % (color[0], color[1], color[2])) + if self.mat.psychopath.color_type == 'Rec709': + col = self.mat.psychopath.color + w.write("Color [rec709, %f %f %f]\n" % ( + col[0], col[1], col[2], + )) + elif self.mat.psychopath.color_type == 'Blackbody': + w.write("Color [blackbody, %f %f]\n" % ( + self.mat.psychopath.color_blackbody_temp, + 1.0, + )) w.write("Roughness [%f]\n" % self.mat.psychopath.roughness) w.write("Fresnel [%f]\n" % self.mat.psychopath.fresnel) else: diff --git a/psychoblend/ui.py b/psychoblend/ui.py index a1b4b6e..9f54115 100644 --- a/psychoblend/ui.py +++ b/psychoblend/ui.py @@ -125,7 +125,14 @@ class DATA_PT_psychopath_lamp(PsychopathPanel, bpy.types.Panel): if ob.data.type != 'HEMI' and ob.data.type != 'AREA': col.prop(ob.data, "shadow_soft_size") - col.prop(ob.data, "color") + + col.prop(ob.data.psychopath, "color_type") + + if ob.data.psychopath.color_type == 'Rec709': + col.prop(ob.data, "color") + elif ob.data.psychopath.color_type == 'Blackbody': + col.prop(ob.data.psychopath, "color_blackbody_temp") + col.prop(ob.data, "energy") @@ -239,10 +246,16 @@ class MATERIAL_PT_psychopath_surface(PsychopathPanel, bpy.types.Panel): def draw(self, context): layout = self.layout + col = layout.column() mat = context.material - layout.prop(mat.psychopath, "surface_shader_type") - layout.prop(mat.psychopath, "color") + col.prop(mat.psychopath, "surface_shader_type") + + col.prop(mat.psychopath, "color_type") + if mat.psychopath.color_type == 'Rec709': + col.prop(mat.psychopath, "color") + elif mat.psychopath.color_type == 'Blackbody': + col.prop(mat.psychopath, "color_blackbody_temp") if mat.psychopath.surface_shader_type == 'GTR': layout.prop(mat.psychopath, "roughness") diff --git a/psychoblend/world.py b/psychoblend/world.py index f4503aa..6620f48 100644 --- a/psychoblend/world.py +++ b/psychoblend/world.py @@ -116,7 +116,7 @@ class BackgroundShader: w.write("BackgroundShader {\n") w.indent(); w.write("Type [Color]\n") - w.write("Color [%f %f %f]\n" % self.color) + w.write("Color [rec709, %f %f %f]\n" % self.color) w.unindent() w.write("}\n") @@ -132,7 +132,12 @@ class DistantDiskLamp: def take_sample(self, render_engine, scene, time): render_engine.update_stats("", "Psychopath: Collecting '{}' at time {}".format(self.ob.name, time)) self.time_dir += [tuple(self.ob.matrix_world.to_3x3() * Vector((0, 0, -1)))] - self.time_col += [self.ob.data.color * self.ob.data.energy] + + if self.ob.data.psychopath.color_type == 'Rec709': + self.time_col += [('Rec709', self.ob.data.color * self.ob.data.energy)] + elif self.ob.data.psychopath.color_type == 'Blackbody': + self.time_col += [('Blackbody', self.ob.data.psychopath.color_blackbody_temp, self.ob.data.energy)] + self.time_rad += [self.ob.data.shadow_soft_size] def export(self, render_engine, w): @@ -142,7 +147,10 @@ class DistantDiskLamp: for direc in self.time_dir: w.write("Direction [%f %f %f]\n" % (direc[0], direc[1], direc[2])) for col in self.time_col: - w.write("Color [%f %f %f]\n" % (col[0], col[1], col[2])) + if col[0] == 'Rec709': + w.write("Color [rec709, %f %f %f]\n" % (col[1][0], col[1][1], col[1][2])) + elif col[0] == 'Blackbody': + w.write("Color [blackbody, %f %f]\n" % (col[1], col[2])) for rad in self.time_rad: w.write("Radius [%f]\n" % rad) diff --git a/src/parse/psy.rs b/src/parse/psy.rs index 343278c..f55996b 100644 --- a/src/parse/psy.rs +++ b/src/parse/psy.rs @@ -499,12 +499,8 @@ fn parse_world<'a>(arena: &'a MemArena, tree: &'a DataTree) -> Result, .. }) = bgs.iter_children_with_type("Color").nth(0) { - if let IResult::Done(_, color) = - closure!(tuple!(ws_f32, ws_f32, ws_f32))(contents.trim().as_bytes()) - { - // TODO: proper color space management, not just assuming - // rec.709. - background_color = Color::new_xyz(rec709_e_to_xyz(color)); + if let Ok(color) = parse_color(contents) { + background_color = color; } else { return Err(PsyParseError::IncorrectLeafData( byte_offset, @@ -582,3 +578,34 @@ pub fn make_transform_format_error(byte_offset: usize) -> PsyParseError { the form '[# # # # # # # # # # # # # # # #]'.", ) } + +pub fn parse_color(contents: &str) -> Result { + let items: Vec<_> = contents.split(",").map(|s| s.trim()).collect(); + if items.len() != 2 { + return Err(PsyParseError::UnknownError(0)); + } + + match items[0] { + "rec709" => { + if let IResult::Done(_, color) = + closure!(tuple!(ws_f32, ws_f32, ws_f32))(items[1].as_bytes()) + { + return Ok(Color::new_xyz(rec709_e_to_xyz(color))); + } else { + return Err(PsyParseError::UnknownError(0)); + } + } + + "blackbody" => { + if let IResult::Done(_, (temperature, factor)) = + closure!(tuple!(ws_f32, ws_f32))(items[1].as_bytes()) + { + return Ok(Color::new_blackbody(temperature, factor)); + } else { + return Err(PsyParseError::UnknownError(0)); + } + } + + _ => return Err(PsyParseError::UnknownError(0)), + } +} diff --git a/src/parse/psy_light.rs b/src/parse/psy_light.rs index ea968ca..f23ef54 100644 --- a/src/parse/psy_light.rs +++ b/src/parse/psy_light.rs @@ -12,7 +12,11 @@ use crate::{ math::Vector, }; -use super::{basics::ws_f32, psy::PsyParseError, DataTree}; +use super::{ + basics::ws_f32, + psy::{parse_color, PsyParseError}, + DataTree, +}; pub fn parse_distant_disk_light<'a>( arena: &'a MemArena, @@ -62,13 +66,8 @@ pub fn parse_distant_disk_light<'a>( contents, byte_offset, } if type_name == "Color" => { - if let IResult::Done(_, color) = - closure!(tuple!(ws_f32, ws_f32, ws_f32))(contents.as_bytes()) - { - // TODO: handle color space conversions properly. - // Probably will need a special color type with its - // own parser...? - colors.push(Color::new_xyz(rec709_e_to_xyz(color))); + if let Ok(color) = parse_color(contents) { + colors.push(color); } else { // Found color, but its contents is not in the right format return Err(PsyParseError::UnknownError(byte_offset)); @@ -116,13 +115,8 @@ pub fn parse_sphere_light<'a>( contents, byte_offset, } if type_name == "Color" => { - if let IResult::Done(_, color) = - closure!(tuple!(ws_f32, ws_f32, ws_f32))(contents.as_bytes()) - { - // TODO: handle color space conversions properly. - // Probably will need a special color type with its - // own parser...? - colors.push(Color::new_xyz(rec709_e_to_xyz(color))); + if let Ok(color) = parse_color(contents) { + colors.push(color); } else { // Found color, but its contents is not in the right format return Err(PsyParseError::UnknownError(byte_offset)); @@ -172,13 +166,8 @@ pub fn parse_rectangle_light<'a>( contents, byte_offset, } if type_name == "Color" => { - if let IResult::Done(_, color) = - closure!(tuple!(ws_f32, ws_f32, ws_f32))(contents.as_bytes()) - { - // TODO: handle color space conversions properly. - // Probably will need a special color type with its - // own parser...? - colors.push(Color::new_xyz(rec709_e_to_xyz(color))); + if let Ok(color) = parse_color(contents) { + colors.push(color); } else { // Found color, but its contents is not in the right format return Err(PsyParseError::UnknownError(byte_offset)); diff --git a/src/parse/psy_surface_shader.rs b/src/parse/psy_surface_shader.rs index 27996f8..ebd1380 100644 --- a/src/parse/psy_surface_shader.rs +++ b/src/parse/psy_surface_shader.rs @@ -11,7 +11,11 @@ use crate::{ shading::{SimpleSurfaceShader, SurfaceShader}, }; -use super::{basics::ws_f32, psy::PsyParseError, DataTree}; +use super::{ + basics::ws_f32, + psy::{parse_color, PsyParseError}, + DataTree, +}; // pub struct TriangleMesh { // time_samples: usize, @@ -38,13 +42,8 @@ pub fn parse_surface_shader<'a>( let color = if let Some((_, contents, byte_offset)) = tree.iter_leaf_children_with_type("Color").nth(0) { - if let IResult::Done(_, color) = - closure!(tuple!(ws_f32, ws_f32, ws_f32))(contents.as_bytes()) - { - // TODO: handle color space conversions properly. - // Probably will need a special color type with its - // own parser...? - Color::new_xyz(rec709_e_to_xyz(color)) + if let Ok(color) = parse_color(contents) { + color } else { // Found color, but its contents is not in the right format return Err(PsyParseError::UnknownError(byte_offset)); @@ -64,13 +63,8 @@ pub fn parse_surface_shader<'a>( let color = if let Some((_, contents, byte_offset)) = tree.iter_leaf_children_with_type("Color").nth(0) { - if let IResult::Done(_, color) = - closure!(tuple!(ws_f32, ws_f32, ws_f32))(contents.as_bytes()) - { - // TODO: handle color space conversions properly. - // Probably will need a special color type with its - // own parser...? - Color::new_xyz(rec709_e_to_xyz(color)) + if let Ok(color) = parse_color(contents) { + color } else { // Found color, but its contents is not in the right format return Err(PsyParseError::UnknownError(byte_offset)); @@ -125,13 +119,8 @@ pub fn parse_surface_shader<'a>( let color = if let Some((_, contents, byte_offset)) = tree.iter_leaf_children_with_type("Color").nth(0) { - if let IResult::Done(_, color) = - closure!(tuple!(ws_f32, ws_f32, ws_f32))(contents.as_bytes()) - { - // TODO: handle color space conversions properly. - // Probably will need a special color type with its - // own parser...? - Color::new_xyz(rec709_e_to_xyz(color)) + if let Ok(color) = parse_color(contents) { + color } else { // Found color, but its contents is not in the right format return Err(PsyParseError::UnknownError(byte_offset));