mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-29 04:07:12 -06:00
@@ -40,6 +40,7 @@ mod clip;
|
||||
mod distort;
|
||||
mod group;
|
||||
mod lighting;
|
||||
mod lighting_presets;
|
||||
mod live;
|
||||
mod marks;
|
||||
pub mod pixel;
|
||||
@@ -72,6 +73,7 @@ pub use group::{
|
||||
OutlineHook, PATHFINDER_EFFECTS, evaluate_container, has_container_appearance, has_pathfinder, is_pathfinder, member_shapes, paints,
|
||||
pathfinder_children,
|
||||
};
|
||||
pub use lighting_presets::{LIGHTING_PRESETS, LightingPreset, lighting_preset_params, matching_lighting_preset};
|
||||
pub use live::{expand_live, expand_live_deep, expanded_live_group, text_outliner};
|
||||
pub use marks::{CROP_MARKS, crop_marks_art, has_crop_marks};
|
||||
pub use pixel::{PIXEL_EFFECTS, PixelFx, PixelSpace};
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
//! Original lighting rigs for the publicly documented 3D preset names.
|
||||
//! Adobe publishes the names, not numeric settings. These values are our own; Standard
|
||||
//! retains the existing default. A preset replaces the light rig and ambient only.
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_three_d::{DEFAULT_LIGHT, Light};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct LightingPreset {
|
||||
pub id: &'static str,
|
||||
pub label: &'static str,
|
||||
rotation: f64,
|
||||
height: f64,
|
||||
softness: f64,
|
||||
ambient: f64,
|
||||
}
|
||||
|
||||
pub const LIGHTING_PRESETS: [LightingPreset; 4] = [
|
||||
LightingPreset { id: "standard", label: "Standard", rotation: DEFAULT_LIGHT.0, height: DEFAULT_LIGHT.1, softness: 0.0, ambient: 25.0 },
|
||||
LightingPreset { id: "diffuse", label: "Diffuse", rotation: DEFAULT_LIGHT.0, height: DEFAULT_LIGHT.1, softness: 100.0, ambient: 35.0 },
|
||||
LightingPreset { id: "topLeft", label: "Top Left", rotation: 135.0, height: 45.0, softness: 0.0, ambient: 25.0 },
|
||||
LightingPreset { id: "right", label: "Right", rotation: 0.0, height: 30.0, softness: 0.0, ambient: 25.0 },
|
||||
];
|
||||
|
||||
impl LightingPreset {
|
||||
/// The same patch is used by commands and the dialog's preview transaction. Null
|
||||
/// `lights` replaces an explicit multi-light rig with this single white light.
|
||||
pub fn params(self) -> Value {
|
||||
json!({
|
||||
"lights": null, "lightColor": "#ffffff", "lightIntensity": 80.0,
|
||||
"lightRotation": self.rotation, "lightHeight": self.height,
|
||||
"lightSoftness": self.softness, "lightBehind": false, "ambient": self.ambient,
|
||||
})
|
||||
}
|
||||
|
||||
fn matches(self, light: Light, ambient: f64) -> bool {
|
||||
let close = |a: f64, b: f64| (a - b).abs() < 1e-6;
|
||||
!light.behind
|
||||
&& light.color.iter().all(|c| close(*c, 1.0))
|
||||
&& close(light.intensity, 0.8)
|
||||
&& close(light.rotation, self.rotation)
|
||||
&& close(light.height, self.height)
|
||||
&& close(light.softness, self.softness / 100.0)
|
||||
&& close(ambient, self.ambient / 100.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lighting_preset_params(id: &str) -> Option<Value> {
|
||||
LIGHTING_PRESETS.iter().find(|p| p.id == id).map(|p| p.params())
|
||||
}
|
||||
|
||||
/// Identify the effective rig, including an equivalent explicit single light. Names
|
||||
/// are UI state derived from settings, so edits and older files cannot leave stale names.
|
||||
pub fn matching_lighting_preset(params: &Value) -> Option<usize> {
|
||||
let upgraded = crate::upgrade_params(crate::REVOLVE, params);
|
||||
let o = crate::revolve_options(&upgraded);
|
||||
let light = if let Some(lights) = &o.lights {
|
||||
let mut entries = lights.iter();
|
||||
let light = *entries.next()?;
|
||||
if entries.next().is_some() {
|
||||
return None;
|
||||
}
|
||||
light
|
||||
} else {
|
||||
Light {
|
||||
rotation: o.light_rotation,
|
||||
height: o.light_height,
|
||||
behind: o.light_behind,
|
||||
color: o.light_color,
|
||||
intensity: o.light_intensity,
|
||||
softness: o.light_softness,
|
||||
}
|
||||
};
|
||||
LIGHTING_PRESETS.iter().position(|preset| preset.matches(light, o.ambient))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn lighting_presets_are_valid_unique_and_standard_preserves_the_default() {
|
||||
for (i, preset) in LIGHTING_PRESETS.iter().enumerate() {
|
||||
let patch = preset.params();
|
||||
crate::lighting::validate(&patch).unwrap();
|
||||
assert_eq!(lighting_preset_params(preset.id), Some(patch.clone()));
|
||||
assert_eq!(matching_lighting_preset(&patch), Some(i));
|
||||
assert!(!patch.as_object().unwrap().contains_key("shade"));
|
||||
assert!(!patch.as_object().unwrap().contains_key("rotationX"));
|
||||
}
|
||||
assert_eq!(matching_lighting_preset(&json!({})), Some(0));
|
||||
assert!(lighting_preset_params("unknown").is_none());
|
||||
assert_eq!(crate::revolve_options(&LIGHTING_PRESETS[0].params()).light_direction(), crate::revolve_options(&json!({})).light_direction());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lighting_preset_matching_uses_effective_lights_and_detects_custom_edits() {
|
||||
let mut p = LIGHTING_PRESETS[3].params();
|
||||
p["lightColor"] = json!("#ff0000");
|
||||
p["lights"] = json!([{"rotation":0,"height":30}]);
|
||||
assert_eq!(matching_lighting_preset(&p), Some(3), "explicit light overrides stale legacy color");
|
||||
p["lights"][0]["color"] = json!("#4080ff");
|
||||
assert_eq!(matching_lighting_preset(&p), None);
|
||||
p["lights"] = json!([{}, {}]);
|
||||
assert_eq!(matching_lighting_preset(&p), None);
|
||||
for (key, value) in [("ambient", json!(26)), ("lightIntensity", json!(79)), ("lightBehind", json!(true))] {
|
||||
let mut p = LIGHTING_PRESETS[0].params();
|
||||
p[key] = value;
|
||||
assert_eq!(matching_lighting_preset(&p), None, "{key}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user