use super::{
impl_server_render_resource, CompositionProperty, IAnimatedServerObject, IServerObject, IServerRenderResource,
IServerRenderResourceObserver, ServerCompositionVisual, ServerCompositor, ServerLayerVisual, ServerRenderResource,
};
use crate::media::imaging::BitmapBlendingMode;
use crate::media::{Color, RenderOptions};
use crate::platform::{AlphaFormat, IDrawingContextImpl, MultiplyLayerFactors, PixelFormats, SharedBitmapImpl};
use crate::rendering::composition::generated::{
ServerAmbientLightHooks, ServerAmbientLightProps, ServerCompositionLightHooks, ServerCompositionLightProps,
ServerDistantLightHooks, ServerDistantLightProps, ServerPointLightHooks, ServerPointLightProps,
ServerSpotLightHooks, ServerSpotLightProps, ServerVisualUnorderedCollectionHooks,
ServerVisualUnorderedCollectionProps,
};
use crate::rendering::composition::transport::BatchStreamReader;
use crate::{Matrix, PixelSize, Point, Rect, Vector, Vector3D};
use std::any::{Any, TypeId};
use std::cell::RefCell;
use std::rc::{Rc, Weak};
use std::sync::Arc;
use std::time::Duration;
pub const LIGHT_MAP_CELL: f64 = 4.0;
pub const LIGHT_MAP_MAX_CELLS: i32 = 128;
pub const LIGHT_MAP_SAMPLE_SPACING: f64 = 2.0;
pub const LIGHT_MAP_MAX_SAMPLES: i32 = 1024;
pub const LIGHT_MAP_MAX_SCALE: f64 = 16.0;
const MAX_LIGHT_DEPTH: usize = 4096;
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct LightColor {
pub r: f64,
pub g: f64,
pub b: f64,
}
impl LightColor {
pub const BLACK: LightColor = LightColor { r: 0.0, g: 0.0, b: 0.0 };
pub const WHITE: LightColor = LightColor { r: 1.0, g: 1.0, b: 1.0 };
pub const fn new(r: f64, g: f64, b: f64) -> LightColor {
LightColor { r, g, b }
}
pub fn from_color(color: Color, intensity: f64) -> LightColor {
let scale = intensity.max(0.0) * color.a as f64 / 255.0;
LightColor {
r: color.r as f64 / 255.0 * scale,
g: color.g as f64 / 255.0 * scale,
b: color.b as f64 / 255.0 * scale,
}
}
pub fn scale(self, factor: f64) -> LightColor {
LightColor { r: self.r * factor, g: self.g * factor, b: self.b * factor }
}
pub fn add(self, other: LightColor) -> LightColor {
LightColor { r: self.r + other.r, g: self.g + other.g, b: self.b + other.b }
}
pub fn lerp(self, other: LightColor, t: f64) -> LightColor {
LightColor {
r: self.r + (other.r - self.r) * t,
g: self.g + (other.g - self.g) * t,
b: self.b + (other.b - self.b) * t,
}
}
pub fn clamped(self) -> LightColor {
LightColor { r: self.r.clamp(0.0, 1.0), g: self.g.clamp(0.0, 1.0), b: self.b.clamp(0.0, 1.0) }
}
pub fn atop_pixel(self) -> [u8; 4] {
let light = self.clamped();
let m = light.r.min(light.g).min(light.b);
let byte = |value: f64| (value * 255.0).round().clamp(0.0, 255.0) as u8;
[byte(light.r - m), byte(light.g - m), byte(light.b - m), byte(1.0 - m)]
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct LightAttenuation {
pub constant: f64,
pub linear: f64,
pub quadratic: f64,
pub min_cutoff: f64,
pub max_cutoff: f64,
}
impl LightAttenuation {
pub const NONE: LightAttenuation =
LightAttenuation { constant: 1.0, linear: 0.0, quadratic: 0.0, min_cutoff: 0.0, max_cutoff: f64::MAX };
pub fn factor(&self, distance: f64) -> f64 {
if distance > self.max_cutoff {
return 0.0;
}
let d = distance.max(self.min_cutoff).max(0.0);
let denominator = self.constant + self.linear * d + self.quadratic * d * d;
if denominator <= 0.0 {
1.0
} else {
1.0 / denominator
}
}
}
fn length(v: Vector3D) -> f64 {
(v.x * v.x + v.y * v.y + v.z * v.z).sqrt()
}
pub fn point_light_incidence(position: Vector3D, point: Point) -> (f64, f64) {
let to_light = Vector3D::new(position.x - point.x, position.y - point.y, position.z);
let distance = length(to_light);
if distance <= 0.0 || position.z <= 0.0 {
return (0.0, distance);
}
(position.z / distance, distance)
}
pub fn distant_light_incidence(direction: Vector3D) -> f64 {
let len = length(direction);
if len <= 0.0 {
return 0.0;
}
(-direction.z / len).max(0.0)
}
fn smoothstep(t: f64) -> f64 {
let t = t.clamp(0.0, 1.0);
t * t * (3.0 - 2.0 * t)
}
pub fn spot_light_cone(
position: Vector3D,
direction: Vector3D,
inner_cone_angle: f64,
outer_cone_angle: f64,
point: Point,
) -> f64 {
let to_point = Vector3D::new(point.x - position.x, point.y - position.y, -position.z);
let (to_point_length, direction_length) = (length(to_point), length(direction));
if to_point_length <= 0.0 || direction_length <= 0.0 {
return 0.0;
}
let cos_angle = (to_point.x * direction.x + to_point.y * direction.y + to_point.z * direction.z)
/ (to_point_length * direction_length);
let outer = outer_cone_angle.clamp(0.0, std::f64::consts::PI);
let inner = inner_cone_angle.clamp(0.0, outer);
let (cos_inner, cos_outer) = (inner.cos(), outer.cos());
if cos_angle >= cos_inner {
return 1.0;
}
if cos_angle <= cos_outer || cos_inner <= cos_outer {
return 0.0;
}
smoothstep((cos_angle - cos_outer) / (cos_inner - cos_outer))
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum LightSource {
Ambient { color: LightColor },
Distant { color: LightColor, direction: Vector3D },
Point { color: LightColor, position: Vector3D, attenuation: LightAttenuation },
Spot {
inner_color: LightColor,
outer_color: LightColor,
position: Vector3D,
direction: Vector3D,
inner_cone_angle: f64,
outer_cone_angle: f64,
attenuation: LightAttenuation,
},
}
impl LightSource {
pub fn light_at(&self, point: Point) -> LightColor {
match *self {
LightSource::Ambient { color } => color,
LightSource::Distant { color, direction } => color.scale(distant_light_incidence(direction)),
LightSource::Point { color, position, attenuation } => {
let (incidence, distance) = point_light_incidence(position, point);
color.scale(incidence * attenuation.factor(distance))
}
LightSource::Spot {
inner_color,
outer_color,
position,
direction,
inner_cone_angle,
outer_cone_angle,
attenuation,
} => {
let cone = spot_light_cone(position, direction, inner_cone_angle, outer_cone_angle, point);
if cone <= 0.0 {
return LightColor::BLACK;
}
let (incidence, distance) = point_light_incidence(position, point);
outer_color.lerp(inner_color, cone).scale(cone * incidence * attenuation.factor(distance))
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct LightingMaterial {
pub ambient: f64,
pub diffuse: f64,
pub specular: f64,
pub shine: f64,
}
impl LightingMaterial {
pub const VISUAL: LightingMaterial = LightingMaterial { ambient: 1.0, diffuse: 1.0, specular: 0.0, shine: 16.0 };
}
fn specular_factor(to_light: Vector3D, shine: f64) -> f64 {
let len = length(to_light);
if len <= 0.0 || to_light.z <= 0.0 {
return 0.0;
}
let half = Vector3D::new(to_light.x / len, to_light.y / len, to_light.z / len + 1.0);
let half_length = length(half);
if half_length <= 0.0 {
return 0.0;
}
(half.z / half_length).max(0.0).powf(shine.max(1.0))
}
impl LightSource {
pub fn lit_at(&self, point: Point, material: &LightingMaterial) -> LightColor {
if let LightSource::Ambient { color } = *self {
return color.scale(material.ambient);
}
let diffuse = if material.diffuse == 0.0 { LightColor::BLACK } else { self.light_at(point).scale(material.diffuse) };
if material.specular == 0.0 {
return diffuse;
}
let specular = match *self {
LightSource::Ambient { .. } => LightColor::BLACK,
LightSource::Distant { color, direction } => {
color.scale(specular_factor(Vector3D::new(-direction.x, -direction.y, -direction.z), material.shine))
}
LightSource::Point { color, position, attenuation } => {
let to_light = Vector3D::new(position.x - point.x, position.y - point.y, position.z);
color.scale(specular_factor(to_light, material.shine) * attenuation.factor(length(to_light)))
}
LightSource::Spot {
inner_color,
outer_color,
position,
direction,
inner_cone_angle,
outer_cone_angle,
attenuation,
} => {
let cone = spot_light_cone(position, direction, inner_cone_angle, outer_cone_angle, point);
if cone <= 0.0 {
LightColor::BLACK
} else {
let to_light = Vector3D::new(position.x - point.x, position.y - point.y, position.z);
outer_color
.lerp(inner_color, cone)
.scale(cone * specular_factor(to_light, material.shine) * attenuation.factor(length(to_light)))
}
}
};
diffuse.add(specular.scale(material.specular))
}
}
#[derive(Clone, Debug, Default)]
pub struct LightMap {
lights: Vec<(LightSource, Matrix)>,
}
impl LightMap {
pub fn new() -> LightMap {
LightMap::default()
}
pub fn add(&mut self, source: LightSource, to_light_space: Matrix) {
self.lights.push((source, to_light_space));
}
pub fn is_empty(&self) -> bool {
self.lights.is_empty()
}
pub fn lit_at(&self, point: Point, material: &LightingMaterial) -> LightColor {
let mut light = LightColor::BLACK;
for (source, to_light_space) in &self.lights {
light = light.add(source.lit_at(to_light_space.transform(point), material));
}
light
}
pub fn light_at(&self, point: Point) -> LightColor {
self.lit_at(point, &LightingMaterial::VISUAL).clamped()
}
pub fn size_for(rect: Rect) -> PixelSize {
let cells = |length: f64| ((length / LIGHT_MAP_CELL).ceil() as i32).clamp(2, LIGHT_MAP_MAX_CELLS);
PixelSize::new(cells(rect.width), cells(rect.height))
}
pub fn size_for_scale(rect: Rect, device_scale: f64) -> PixelSize {
let scale = if device_scale.is_finite() && device_scale > 0.0 { device_scale } else { 1.0 };
let samples = |length: f64| {
((length * scale / LIGHT_MAP_SAMPLE_SPACING).ceil().min(f64::from(LIGHT_MAP_MAX_SAMPLES)) as i32)
.clamp(2, LIGHT_MAP_MAX_SAMPLES)
};
PixelSize::new(samples(rect.width), samples(rect.height))
}
fn samples(&self, rect: Rect, size: PixelSize, material: &LightingMaterial) -> Vec<LightColor> {
let (columns, rows) = (size.width.max(1), size.height.max(1));
let mut samples = Vec::with_capacity((columns * rows) as usize);
for row in 0..rows {
let y = rect.y + (row as f64 + 0.5) * rect.height / rows as f64;
for column in 0..columns {
let x = rect.x + (column as f64 + 0.5) * rect.width / columns as f64;
samples.push(self.lit_at(Point::new(x, y), material));
}
}
samples
}
pub fn factors(&self, rect: Rect, size: PixelSize, material: &LightingMaterial) -> Option<MultiplyLayerFactors> {
let samples = self.samples(rect, size, material);
if samples.iter().all(|light| light.r == 1.0 && light.g == 1.0 && light.b == 1.0) {
return None;
}
let brightest = samples.iter().fold(0.0f64, |max, light| max.max(light.r).max(light.g).max(light.b));
let scale = brightest.clamp(1.0, LIGHT_MAP_MAX_SCALE);
let byte = |value: f64| (value / scale * 255.0).round().clamp(0.0, 255.0) as u8;
let mut rgba = Vec::with_capacity(samples.len() * 4);
for light in samples {
rgba.extend_from_slice(&[byte(light.r), byte(light.g), byte(light.b), 255]);
}
Some(MultiplyLayerFactors { size, rgba: Arc::new(rgba), scale: scale as f32 })
}
pub fn pixels(&self, rect: Rect, size: PixelSize) -> Option<Vec<u8>> {
let samples = self.samples(rect, size, &LightingMaterial::VISUAL);
let mut pixels = Vec::with_capacity(samples.len() * 4);
let mut changes = false;
for light in samples {
let pixel = light.atop_pixel();
changes |= pixel[3] != 0;
pixels.extend_from_slice(&pixel);
}
changes.then_some(pixels)
}
}
fn no_composition_property(_name: &str) -> Option<&'static CompositionProperty> {
None
}
pub struct ServerVisualUnorderedCollection {
base: ServerRenderResource,
props: ServerVisualUnorderedCollectionProps,
}
impl ServerVisualUnorderedCollection {
pub fn new(compositor: &Rc<ServerCompositor>) -> Rc<ServerVisualUnorderedCollection> {
let collection = Rc::new_cyclic(|this: &Weak<ServerVisualUnorderedCollection>| {
ServerVisualUnorderedCollection {
base: ServerRenderResource::new(
compositor,
this.clone() as Weak<dyn IAnimatedServerObject>,
this.clone() as Weak<dyn IServerRenderResource>,
),
props: ServerVisualUnorderedCollectionProps::new(),
}
});
collection.base.object().activate();
collection
}
pub fn visuals(&self) -> Vec<Rc<ServerCompositionVisual>> {
self.props
.list()
.list()
.into_iter()
.filter_map(|visual| visual.into_any_rc().downcast::<ServerCompositionVisual>().ok())
.collect()
}
pub fn count(&self) -> usize {
self.props.list().count()
}
pub fn contains(&self, visual: &ServerCompositionVisual) -> bool {
let target = visual as *const ServerCompositionVisual;
self.props.list().list().iter().any(|item| std::ptr::addr_eq(Rc::as_ptr(item), target))
}
pub fn is_disposed(&self) -> bool {
self.base.is_disposed()
}
pub fn of(object: &Option<Rc<dyn IServerObject>>) -> Option<&ServerVisualUnorderedCollection> {
object.as_ref().and_then(|object| object.as_any().downcast_ref::<ServerVisualUnorderedCollection>())
}
}
impl_server_render_resource!(ServerVisualUnorderedCollection, base, composition_property: no_composition_property);
impl ServerVisualUnorderedCollectionHooks for ServerVisualUnorderedCollection {}
impl IServerObject for ServerVisualUnorderedCollection {
fn deserialize_changes_core(&self, reader: &mut BatchStreamReader<'_>, committed_at: Duration) {
self.props.deserialize_changes_core(self, reader, committed_at);
}
fn values_invalidated(&self) {
self.base.core().invalidated(self);
}
fn dispose(&self) {
self.props.list().clear();
self.base.core().dispose();
}
fn as_render_resource(self: Rc<Self>) -> Option<Rc<dyn IServerRenderResource>> {
Some(self)
}
fn as_animated(self: Rc<Self>) -> Option<Rc<dyn IAnimatedServerObject>> {
Some(self)
}
fn get_props(&self, type_id: TypeId) -> Option<&dyn Any> {
self.props.find_props(type_id)
}
fn as_any(&self) -> &dyn Any {
self
}
fn into_any_rc(self: Rc<Self>) -> Rc<dyn Any> {
self
}
}
pub trait IServerCompositionLight {
fn light_props(&self) -> &ServerCompositionLightProps;
fn light_state(&self) -> &ServerLightState;
fn is_disposed(&self) -> bool;
fn coordinate_space(&self) -> Option<Rc<ServerCompositionVisual>>;
fn source(&self) -> LightSource;
fn target_of(&self, visual: &ServerCompositionVisual) -> Option<Rc<ServerCompositionVisual>> {
let props = self.light_props();
let (targets, exclusions) = (props.targets(), props.exclusions_from_targets());
let targets = ServerVisualUnorderedCollection::of(&targets)?;
if targets.count() == 0 {
return None;
}
let exclusions = ServerVisualUnorderedCollection::of(&exclusions);
let mut current = visual.weak().upgrade();
let mut depth = 0;
while let Some(candidate) = current {
if exclusions.is_some_and(|exclusions| exclusions.contains(&candidate)) {
return None;
}
if targets.contains(&candidate) {
return Some(candidate);
}
depth += 1;
if depth > MAX_LIGHT_DEPTH {
return None;
}
current = candidate.parent();
}
None
}
}
fn to_root(visual: &ServerCompositionVisual) -> Matrix {
let mut matrix = visual.own_transform().unwrap_or(Matrix::IDENTITY);
let mut parent = visual.parent();
let mut depth = 0;
while let Some(current) = parent {
if let Some(transform) = current.own_transform() {
matrix = matrix * transform;
}
depth += 1;
if depth > MAX_LIGHT_DEPTH {
break;
}
parent = current.parent();
}
matrix
}
pub fn light_map_of(visual: &ServerCompositionVisual) -> Option<LightMap> {
let lights: Vec<_> = visual.compositor()?.live_lights().into_iter().filter(|light| light.light_props().is_enabled()).collect();
if lights.is_empty() {
return None;
}
let mut map = LightMap::new();
let mut visual_to_root: Option<Matrix> = None;
for light in lights {
let Some(target) = light.target_of(visual) else { continue };
let space = light.coordinate_space().unwrap_or(target);
let to_root_of_visual = *visual_to_root.get_or_insert_with(|| to_root(visual));
let Some(from_root_to_space) = to_root(&space).try_invert() else { continue };
map.add(light.source(), to_root_of_visual * from_root_to_space);
}
(!map.is_empty()).then_some(map)
}
pub fn invalidate_lights_with_moved_spaces(compositor: &ServerCompositor) -> bool {
let mut invalidated = false;
for light in compositor.live_lights() {
let current = light.coordinate_space().map(|space| to_root(&space));
let state = light.light_state();
let previous = state.space_transform.replace(current);
if let (Some(previous), Some(current)) = (previous, current) {
if previous != current && light.light_props().is_enabled() {
state.invalidate_lit_visuals(&*light);
invalidated = true;
}
}
}
invalidated
}
fn invalidate_subtree(visual: &Rc<ServerCompositionVisual>) {
let mut stack = vec![visual.clone()];
while let Some(current) = stack.pop() {
current.invalidate_content();
if let Some(children) = current.children() {
stack.extend(children.list().iter().cloned());
}
}
}
struct LightObserver {
light: Weak<dyn IServerCompositionLight>,
}
impl IServerRenderResourceObserver for LightObserver {
fn dependency_queued_invalidate(&self, _sender: &dyn IServerRenderResource) {
if let Some(light) = self.light.upgrade() {
light.light_state().invalidate_lit_visuals(&*light);
}
}
}
#[derive(Default)]
pub struct ServerLightState {
observer: RefCell<Option<Rc<dyn IServerRenderResourceObserver>>>,
lit_targets: RefCell<Vec<Weak<ServerCompositionVisual>>>,
xaml_binding: RefCell<Option<(String, Option<Weak<ServerCompositionVisual>>)>>,
brush_lit: RefCell<Vec<Weak<ServerCompositionVisual>>>,
space_transform: std::cell::Cell<Option<Matrix>>,
}
pub(crate) struct XamlLightChanges {
pub(crate) id: Option<String>,
pub(crate) scope: Option<super::ServerObjectId>,
}
impl ServerLightState {
fn attach(base: &ServerRenderResource, state: &ServerLightState, light: Weak<dyn IServerCompositionLight>) {
let observer: Rc<dyn IServerRenderResourceObserver> = Rc::new(LightObserver { light: light.clone() });
base.core().add_observer(&observer);
*state.observer.borrow_mut() = Some(observer);
if let Some(compositor) = base.object().compositor() {
compositor.register_light(light);
}
}
pub fn xaml_binding(&self) -> Option<(String, Option<Rc<ServerCompositionVisual>>)> {
self.xaml_binding
.borrow()
.as_ref()
.map(|(id, scope)| (id.clone(), scope.as_ref().and_then(Weak::upgrade)))
}
fn set_xaml_binding(&self, id: Option<String>, scope: Option<Rc<ServerCompositionVisual>>) {
*self.xaml_binding.borrow_mut() = id.map(|id| (id, scope.as_ref().map(Rc::downgrade)));
}
fn note_brush_lit(&self, visual: &Rc<ServerCompositionVisual>) {
let mut brush_lit = self.brush_lit.borrow_mut();
brush_lit.retain(|existing| existing.strong_count() > 0);
if !brush_lit.iter().any(|existing| std::ptr::eq(existing.as_ptr(), Rc::as_ptr(visual))) {
brush_lit.push(Rc::downgrade(visual));
}
}
pub fn lit_targets(&self) -> Vec<Rc<ServerCompositionVisual>> {
self.lit_targets.borrow().iter().filter_map(Weak::upgrade).collect()
}
fn invalidate_lit_visuals(&self, light: &dyn IServerCompositionLight) {
let current = if light.is_disposed() {
Vec::new()
} else {
let targets = light.light_props().targets();
ServerVisualUnorderedCollection::of(&targets).map(|targets| targets.visuals()).unwrap_or_default()
};
let previous = std::mem::take(&mut *self.lit_targets.borrow_mut());
for visual in previous.iter().filter_map(Weak::upgrade).chain(current.iter().cloned()) {
invalidate_subtree(&visual);
}
*self.lit_targets.borrow_mut() = current.iter().map(Rc::downgrade).collect();
let brush_lit: Vec<_> = self.brush_lit.borrow().iter().filter_map(Weak::upgrade).collect();
for visual in brush_lit {
visual.invalidate_content();
}
}
fn detach(&self) {
let previous = std::mem::take(&mut *self.lit_targets.borrow_mut());
for visual in previous.iter().filter_map(Weak::upgrade) {
invalidate_subtree(&visual);
}
for visual in std::mem::take(&mut *self.brush_lit.borrow_mut()).iter().filter_map(Weak::upgrade) {
visual.invalidate_content();
}
self.observer.borrow_mut().take();
}
}
fn visual_of(object: Option<Rc<dyn IServerObject>>) -> Option<Rc<ServerCompositionVisual>> {
object.and_then(|visual| visual.into_any_rc().downcast::<ServerCompositionVisual>().ok())
}
fn attenuation_of(constant: f32, linear: f32, quadratic: f32, min_cutoff: f32, max_cutoff: f32) -> LightAttenuation {
LightAttenuation {
constant: constant as f64,
linear: linear as f64,
quadratic: quadratic as f64,
min_cutoff: min_cutoff as f64,
max_cutoff: max_cutoff as f64,
}
}
macro_rules! server_light {
(
$(#[$meta:meta])* $name:ident, $props:ident, $hooks:ident,
coordinate_space: $coordinate_space:expr,
source: $source:expr $(,)?
) => {
$(#[$meta])*
pub struct $name {
base: ServerRenderResource,
props: $props,
state: ServerLightState,
}
impl $name {
pub fn new(compositor: &Rc<ServerCompositor>) -> Rc<$name> {
let light = Rc::new_cyclic(|this: &Weak<$name>| $name {
base: ServerRenderResource::new(
compositor,
this.clone() as Weak<dyn IAnimatedServerObject>,
this.clone() as Weak<dyn IServerRenderResource>,
),
props: $props::new(),
state: ServerLightState::default(),
});
light.base.object().activate();
let weak = Rc::downgrade(&light) as Weak<dyn IServerCompositionLight>;
ServerLightState::attach(&light.base, &light.state, weak);
light
}
pub fn props(&self) -> &$props {
&self.props
}
pub fn is_disposed(&self) -> bool {
self.base.is_disposed()
}
}
impl_server_render_resource!($name, base, composition_property: $props::get_composition_property);
impl ServerCompositionLightHooks for $name {}
impl $hooks for $name {}
impl IServerCompositionLight for $name {
fn light_props(&self) -> &ServerCompositionLightProps {
self.props.base()
}
fn light_state(&self) -> &ServerLightState {
&self.state
}
fn is_disposed(&self) -> bool {
self.base.is_disposed()
}
fn coordinate_space(&self) -> Option<Rc<ServerCompositionVisual>> {
let coordinate_space: fn(&$props) -> Option<Rc<dyn IServerObject>> = $coordinate_space;
visual_of(coordinate_space(&self.props))
}
fn source(&self) -> LightSource {
let source: fn(&$props) -> LightSource = $source;
source(&self.props)
}
}
impl IServerObject for $name {
fn deserialize_changes_core(&self, reader: &mut BatchStreamReader<'_>, committed_at: Duration) {
self.props.deserialize_changes_core(self, reader, committed_at);
if let Some(changes) = reader.read_value::<XamlLightChanges>() {
let compositor = self.base.compositor();
let scope = changes.scope.and_then(|id| compositor.as_ref().and_then(|compositor| compositor.get_object(id)));
self.state.set_xaml_binding(changes.id, visual_of(scope));
self.base.core().invalidated(self);
}
}
fn values_invalidated(&self) {
self.base.core().invalidated(self);
}
fn dispose(&self) {
self.state.detach();
self.base.core().dispose();
}
fn as_render_resource(self: Rc<Self>) -> Option<Rc<dyn IServerRenderResource>> {
Some(self)
}
fn as_animated(self: Rc<Self>) -> Option<Rc<dyn IAnimatedServerObject>> {
Some(self)
}
fn get_props(&self, type_id: TypeId) -> Option<&dyn Any> {
self.props.find_props(type_id)
}
fn as_any(&self) -> &dyn Any {
self
}
fn into_any_rc(self: Rc<Self>) -> Rc<dyn Any> {
self
}
}
};
}
server_light!(
ServerAmbientLight,
ServerAmbientLightProps,
ServerAmbientLightHooks,
coordinate_space: |_props| None,
source: |props| LightSource::Ambient { color: LightColor::from_color(props.color(), props.intensity() as f64) },
);
server_light!(
ServerDistantLight,
ServerDistantLightProps,
ServerDistantLightHooks,
coordinate_space: |props| props.coordinate_space(),
source: |props| LightSource::Distant {
color: LightColor::from_color(props.color(), props.intensity() as f64),
direction: props.direction(),
},
);
server_light!(
ServerPointLight,
ServerPointLightProps,
ServerPointLightHooks,
coordinate_space: |props| props.coordinate_space(),
source: |props| LightSource::Point {
color: LightColor::from_color(props.color(), props.intensity() as f64),
position: props.offset(),
attenuation: attenuation_of(
props.constant_attenuation(),
props.linear_attenuation(),
props.quadratic_attenuation(),
props.min_attenuation_cutoff(),
props.max_attenuation_cutoff(),
),
},
);
server_light!(
ServerSpotLight,
ServerSpotLightProps,
ServerSpotLightHooks,
coordinate_space: |props| props.coordinate_space(),
source: |props| LightSource::Spot {
inner_color: LightColor::from_color(props.inner_cone_color(), props.inner_cone_intensity() as f64),
outer_color: LightColor::from_color(props.outer_cone_color(), props.outer_cone_intensity() as f64),
position: props.offset(),
direction: props.direction(),
inner_cone_angle: props.inner_cone_angle() as f64,
outer_cone_angle: props.outer_cone_angle() as f64,
attenuation: attenuation_of(
props.constant_attenuation(),
props.linear_attenuation(),
props.quadratic_attenuation(),
props.min_attenuation_cutoff(),
props.max_attenuation_cutoff(),
),
},
);
fn device_scale(matrix: &Matrix) -> f64 {
let x = (matrix.m11 * matrix.m11 + matrix.m12 * matrix.m12).sqrt();
let y = (matrix.m21 * matrix.m21 + matrix.m22 * matrix.m22).sqrt();
x.max(y)
}
pub struct LitContent {
kind: LitContentKind,
}
enum LitContentKind {
Multiplied,
Atop { bitmap: Arc<SharedBitmapImpl>, rect: Rect, size: PixelSize },
}
impl LitContent {
pub fn begin(visual: &ServerCompositionVisual, canvas: &mut dyn IDrawingContextImpl) -> Option<LitContent> {
if visual.content_as::<ServerLayerVisual>().is_some() {
return None;
}
let map = light_map_of(visual)?;
let rect = visual.compute_own_content_bounds()?.to_rect();
if rect.width <= 0.0 || rect.height <= 0.0 {
return None;
}
let size = LightMap::size_for_scale(rect, device_scale(&canvas.transform()));
if let Some(layers) = canvas.as_drawing_context_impl_with_layer_effects() {
let factors = map.factors(rect, size, &LightingMaterial::VISUAL)?;
layers.push_multiply_layer(rect, &factors);
return Some(LitContent { kind: LitContentKind::Multiplied });
}
let pixels = map.pixels(rect, size)?;
let compositor = visual.compositor()?;
let bitmap = compositor.render_interface().with_platform_render_interface(|render_interface| {
render_interface.load_bitmap_from_pixels(
PixelFormats::RGBA8888,
AlphaFormat::Premul,
&pixels,
size,
Vector::new(96.0, 96.0),
size.width * 4,
)
})?;
canvas.push_layer(rect);
Some(LitContent { kind: LitContentKind::Atop { bitmap, rect, size } })
}
pub fn end(self, canvas: &mut dyn IDrawingContextImpl) {
match self.kind {
LitContentKind::Multiplied => {
if let Some(layers) = canvas.as_drawing_context_impl_with_layer_effects() {
layers.pop_multiply_layer();
}
}
LitContentKind::Atop { bitmap, rect, size } => {
canvas.push_render_options(RenderOptions {
bitmap_blending_mode: BitmapBlendingMode::SourceAtop,
..RenderOptions::default()
});
let source = Rect::new(0.0, 0.0, size.width as f64, size.height as f64);
canvas.draw_bitmap(&*bitmap, 1.0, source, rect);
canvas.pop_render_options();
canvas.pop_layer();
}
}
}
}
pub const SCENE_LIGHTING_DISTANCE_UNIT: f64 = 100.0;
impl LightAttenuation {
fn in_units_of(self, unit: f64) -> LightAttenuation {
LightAttenuation { linear: self.linear / unit, quadratic: self.quadratic / (unit * unit), ..self }
}
}
impl LightSource {
fn with_distance_unit(self, unit: f64) -> LightSource {
match self {
LightSource::Point { color, position, attenuation } => {
LightSource::Point { color, position, attenuation: attenuation.in_units_of(unit) }
}
LightSource::Spot { inner_color, outer_color, position, direction, inner_cone_angle, outer_cone_angle, attenuation } => {
LightSource::Spot {
inner_color,
outer_color,
position,
direction,
inner_cone_angle,
outer_cone_angle,
attenuation: attenuation.in_units_of(unit),
}
}
other => other,
}
}
}
impl LightMap {
pub fn scene_pixels(&self, rect: Rect, size: PixelSize, material: &LightingMaterial) -> Vec<u8> {
let channel = |value: f64| (value * 255.0).round().clamp(0.0, 255.0) as u8;
let mut pixels = Vec::with_capacity((size.width.max(1) * size.height.max(1) * 4) as usize);
for light in self.samples(rect, size, material) {
let light = light.clamped();
pixels.extend_from_slice(&[channel(light.r), channel(light.g), channel(light.b), 255]);
}
pixels
}
}
pub fn scene_light_map(visual: &Rc<ServerCompositionVisual>, brush_light_ids: &[String]) -> LightMap {
let mut map = LightMap::new();
let mut visual_to_root: Option<Matrix> = None;
let Some(compositor) = visual.compositor() else { return map };
for light in compositor.live_lights() {
let space = if let Some(target) = light.target_of(visual) {
light.coordinate_space().unwrap_or(target)
} else if let Some((id, Some(scope))) = light.light_state().xaml_binding() {
if !brush_light_ids.iter().any(|existing| *existing == id) || !super::is_self_or_ancestor(&scope, visual) {
continue;
}
light.light_state().note_brush_lit(visual);
light.coordinate_space().unwrap_or(scope)
} else {
continue;
};
if !light.light_props().is_enabled() {
continue;
}
let to_root_of_visual = *visual_to_root.get_or_insert_with(|| to_root(visual));
let Some(from_root_to_space) = to_root(&space).try_invert() else { continue };
map.add(light.source().with_distance_unit(SCENE_LIGHTING_DISTANCE_UNIT), to_root_of_visual * from_root_to_space);
}
map
}
pub fn paint_scene_lighting(canvas: &mut dyn IDrawingContextImpl, rect: Rect, material: &LightingMaterial) {
if rect.width <= 0.0 || rect.height <= 0.0 {
return;
}
let Some(visual) = super::painting_visual() else { return };
let ids = super::brush_light_ids();
let map = scene_light_map(&visual, &ids);
let size = LightMap::size_for_scale(rect, device_scale(&canvas.transform()));
let pixels = map.scene_pixels(rect, size, material);
let Some(compositor) = visual.compositor() else { return };
let bitmap = compositor.render_interface().with_platform_render_interface(|render_interface| {
render_interface.load_bitmap_from_pixels(
PixelFormats::RGBA8888,
AlphaFormat::Premul,
&pixels,
size,
Vector::new(96.0, 96.0),
size.width * 4,
)
});
if let Some(bitmap) = bitmap {
let source = Rect::new(0.0, 0.0, size.width as f64, size.height as f64);
canvas.draw_bitmap(&*bitmap, 1.0, source, rect);
}
}