Compare commits

...

3 Commits

Author SHA1 Message Date
4b38abcdc8 ui hints 2025-11-20 14:23:29 +03:00
d2c512641a add interreflections 2025-11-20 14:19:58 +03:00
816d6682b4 add custom display options 2025-11-20 14:12:31 +03:00
4 changed files with 473 additions and 124 deletions

View File

@ -36,6 +36,14 @@ pub struct RedrawArgs {
pub light_spread: f32, pub light_spread: f32,
pub accum_sigma: f32, pub accum_sigma: f32,
pub accum_scale: f32, pub accum_scale: f32,
pub reflections: u32,
pub show_axes: bool,
pub show_shapes: bool,
pub show_hit_emission: bool,
pub show_miss_emission: bool,
pub show_direct_hit: bool,
pub show_indirect_hit: bool,
pub show_light: bool,
} }
pub struct Gpu { pub struct Gpu {
@ -163,7 +171,9 @@ impl Core {
depth_stencil_attachment: None, depth_stencil_attachment: None,
..Default::default() ..Default::default()
}); });
self.pipeline.render(&mut pass, [&self.tripod]); if args.show_axes {
self.pipeline.render(&mut pass, [&self.tripod]);
}
let source = Source { let source = Source {
position_yaw: args.light_position.yaw, position_yaw: args.light_position.yaw,
@ -173,14 +183,16 @@ impl Core {
spread: args.light_spread, spread: args.light_spread,
}; };
let contour: Vec<Vertex> = loop_list(source.contour(17)) if args.show_shapes {
.map(|pos| Vertex { let contour: Vec<Vertex> = loop_list(source.contour(17))
pos, .map(|pos| Vertex {
color: vec3(1., 1., 1.), pos,
}) color: vec3(1., 1., 1.),
.collect(); })
self.pipeline .collect();
.render(&mut pass, [&Mesh::new(&self.device, &contour)]); self.pipeline
.render(&mut pass, [&Mesh::new(&self.device, &contour)]);
}
const BASE_R: f32 = 2.; const BASE_R: f32 = 2.;
const BASE_POS: Vec3 = vec3(0., 0., -BASE_R); const BASE_POS: Vec3 = vec3(0., 0., -BASE_R);
@ -211,102 +223,124 @@ impl Core {
for ray in source_rays { for ray in source_rays {
if let Some(hit) = scene.trace_ray(ray) { if let Some(hit) = scene.trace_ray(ray) {
hits.push(hit); hits.push(hit);
source_ray_display.extend([ if args.show_hit_emission {
Vertex { source_ray_display.extend([
pos: ray.base, Vertex {
color: vec3(1., 1., 1.), pos: ray.base,
}, color: vec3(1., 1., 1.),
Vertex { },
pos: ray.base + 0.1 * ray.dir, Vertex {
color: vec3(0., 1., 0.), pos: ray.base + 0.1 * ray.dir,
}, color: vec3(0., 1., 0.),
Vertex { },
pos: hit.incident.base - 0.02 * hit.incident.dir, ]);
color: vec3(0., 0., 1.), }
}, if args.show_direct_hit {
Vertex { source_ray_display.extend([
pos: hit.incident.base, Vertex {
color: vec3(1., 1., 1.), pos: hit.incident.base - 0.02 * hit.incident.dir,
}, color: vec3(0., 0., 1.),
]); },
} else { Vertex {
source_ray_display.extend([ pos: hit.incident.base,
Vertex { color: vec3(1., 1., 1.),
pos: ray.base, },
color: vec3(1., 1., 1.), ]);
}, }
Vertex { } else {
pos: ray.base + 0.1 * ray.dir, if args.show_miss_emission {
color: vec3(1., 0., 0.), source_ray_display.extend([
}, Vertex {
]); pos: ray.base,
} color: vec3(1., 1., 1.),
} },
let mut hits2: Vec<Hit> = Vec::with_capacity(hits.len()); Vertex {
for hit in &hits { pos: ray.base + 0.1 * ray.dir,
let reflector = Lambertian; color: vec3(1., 0., 0.),
let reflected = reflector.reflect(&mut prng, hit.normal, hit.incident.dir); },
let ray = Ray::new(hit.incident.base, reflected); ]);
let Some(hit2) = scene.trace_ray(ray) else {
continue;
};
hits2.push(hit2);
source_ray_display.extend([
Vertex {
pos: hit2.incident.base - 0.02 * hit2.incident.dir,
color: vec3(1., 0., 1.),
},
Vertex {
pos: hit2.incident.base,
color: vec3(1., 1., 1.),
},
]);
}
hits.extend(hits2);
let mut camera_ray_display: Vec<Vertex> = Vec::with_capacity(camera_rays.len());
let sigma2 = args.accum_sigma.powi(2);
let accum_normalizator = (2. * PI * sigma2).sqrt().recip();
for ray in camera_rays {
let Some(hit) = scene.trace_ray(ray) else {
continue;
};
let mut total_cd = 0.0f32;
for light_hit in &hits {
let d2 = hit.incident.base.distance_squared(light_hit.incident.base);
if d2 > 9. * sigma2 {
continue;
} }
assert!(hit.normal.is_normalized());
assert!(hit.incident.dir.is_normalized());
let reflector = Lambertian;
let in_lm = 1.0;
let out_cd = in_lm
* hit.normal.dot(-hit.incident.dir)
* reflector.brdf(hit.normal, hit.incident.dir, -ray.dir);
let weight = accum_normalizator * (-0.5 * d2 / sigma2).exp();
total_cd += weight * out_cd;
} }
let brightness = 3. * (1. - (1. + total_cd * args.accum_scale).recip());
let r = args.accum_sigma;
let color = vec3(brightness, brightness - 1., brightness - 2.)
.clamp(Vec3::splat(0.), Vec3::splat(1.));
let vertex = |off: Vec3| Vertex {
pos: hit.incident.base + r * off,
color,
};
camera_ray_display.extend([
vertex(-Vec3::X),
vertex(Vec3::X),
vertex(-Vec3::Y),
vertex(Vec3::Y),
vertex(-Vec3::Z),
vertex(Vec3::Z),
]);
} }
self.pipeline if args.reflections > 0 {
.render(&mut pass, [&Mesh::new(&self.device, &source_ray_display)]); let mut hits1 = hits.clone();
self.pipeline for _ in 0..args.reflections {
.render(&mut pass, [&Mesh::new(&self.device, &camera_ray_display)]); let mut hits2: Vec<Hit> = Vec::with_capacity(hits1.len());
for hit in &hits1 {
let reflector = Lambertian;
let reflected = reflector.reflect(&mut prng, hit.normal, hit.incident.dir);
let ray = Ray::new(hit.incident.base, reflected);
let Some(hit2) = scene.trace_ray(ray) else {
continue;
};
hits2.push(hit2);
if args.show_indirect_hit {
source_ray_display.extend([
Vertex {
pos: hit2.incident.base - 0.02 * hit2.incident.dir,
color: vec3(1., 0., 1.),
},
Vertex {
pos: hit2.incident.base,
color: vec3(1., 1., 1.),
},
]);
}
}
hits.extend(&hits2);
hits1 = hits2;
}
}
let mut camera_ray_display: Vec<Vertex> = Vec::with_capacity(camera_rays.len());
if args.show_light {
let sigma2 = args.accum_sigma.powi(2);
let accum_normalizator = (2. * PI * sigma2).sqrt().recip();
for ray in camera_rays {
let Some(hit) = scene.trace_ray(ray) else {
continue;
};
let mut total_cd = 0.0f32;
for light_hit in &hits {
let d2 = hit.incident.base.distance_squared(light_hit.incident.base);
if d2 > 9. * sigma2 {
continue;
}
assert!(hit.normal.is_normalized());
assert!(hit.incident.dir.is_normalized());
let reflector = Lambertian;
let in_lm = 1.0;
let out_cd = in_lm
* hit.normal.dot(-hit.incident.dir)
* reflector.brdf(hit.normal, hit.incident.dir, -ray.dir);
let weight = accum_normalizator * (-0.5 * d2 / sigma2).exp();
total_cd += weight * out_cd;
}
let brightness = 3. * (1. - (1. + total_cd * args.accum_scale).recip());
let r = args.accum_sigma;
let color = vec3(brightness, brightness - 1., brightness - 2.)
.clamp(Vec3::splat(0.), Vec3::splat(1.));
let vertex = |off: Vec3| Vertex {
pos: hit.incident.base + r * off,
color,
};
camera_ray_display.extend([
vertex(-Vec3::X),
vertex(Vec3::X),
vertex(-Vec3::Y),
vertex(Vec3::Y),
vertex(-Vec3::Z),
vertex(Vec3::Z),
]);
}
}
if !source_ray_display.is_empty() {
self.pipeline
.render(&mut pass, [&Mesh::new(&self.device, &source_ray_display)]);
}
if !camera_ray_display.is_empty() {
self.pipeline
.render(&mut pass, [&Mesh::new(&self.device, &camera_ray_display)]);
}
drop(pass); drop(pass);
self.queue.submit(std::iter::once(encoder.finish())); self.queue.submit(std::iter::once(encoder.finish()));

View File

@ -20,6 +20,14 @@ struct RedrawArgs {
float light_spread = 0.; float light_spread = 0.;
float accum_sigma = 1.; float accum_sigma = 1.;
float accum_scale = 1.; float accum_scale = 1.;
std::uint32_t reflections = 0;
bool show_axes = true;
bool show_shapes = true;
bool show_hit_emission = true;
bool show_miss_emission = true;
bool show_direct_hit = true;
bool show_indirect_hit = true;
bool show_light = true;
}; };
} // namespace ffi } // namespace ffi

View File

@ -20,24 +20,34 @@ void PhotonLight::updateView() {
.camera_position = SphericalPosition{ .camera_position = SphericalPosition{
.yaw = deg_to_rad(m_ui->cameraYaw->value()), .yaw = deg_to_rad(m_ui->cameraYaw->value()),
.pitch = deg_to_rad(m_ui->cameraPitch->value()), .pitch = deg_to_rad(m_ui->cameraPitch->value()),
.distance = 3.0, .distance = m_ui->cameraDistance->value() / 10.0f,
}, },
.light_position = SphericalPosition{ .light_position = SphericalPosition{
.yaw = deg_to_rad(m_ui->lightYaw->value()), .yaw = deg_to_rad(m_ui->lightYaw->value()),
.pitch = deg_to_rad(m_ui->lightPitch->value()), .pitch = deg_to_rad(m_ui->lightPitch->value()),
.distance = 1.0, .distance = m_ui->lightDistance->value() / 10.0f,
}, },
.light_radius = 0.125, .light_radius = 0.125,
.light_spread = 0.125, .light_spread = 0.125,
.accum_sigma = exp10f(m_ui->accumSigma->value() / 25.0), .accum_sigma = exp10f(m_ui->accumSigma->value() / 25.0),
.accum_scale = exp10f(m_ui->accumScale->value() / 25.0), .accum_scale = exp10f(m_ui->accumScale->value() / 25.0),
.reflections = std::uint32_t(m_ui->reflections->value()),
.show_axes = m_ui->displayAxes->isChecked(),
.show_shapes = m_ui->displayShapes->isChecked(),
.show_hit_emission = m_ui->displayEmitted->isChecked(),
.show_miss_emission = m_ui->displayEmitted->isChecked(),
.show_direct_hit = m_ui->displayDirectHits->isChecked(),
.show_indirect_hit = m_ui->displayIndirectHits->isChecked(),
.show_light = m_ui->displayResult->isChecked(),
}; };
m_ui->cameraYawLabel->setText(tr("Yaw: %1 deg").arg(QString::number(qRadiansToDegrees(args.camera_position.yaw)))); m_ui->cameraYawLabel->setText(tr("Yaw: %1 deg").arg(QString::number(qRadiansToDegrees(args.camera_position.yaw))));
m_ui->cameraPitchLabel->setText(tr("Pitch: %1 deg").arg(QString::number(qRadiansToDegrees(args.camera_position.pitch)))); m_ui->cameraPitchLabel->setText(tr("Pitch: %1 deg").arg(QString::number(qRadiansToDegrees(args.camera_position.pitch))));
m_ui->cameraDistanceLabel->setText(tr("Distance: %1").arg(QString::number(args.camera_position.distance)));
m_ui->lightYawLabel->setText(tr("Yaw: %1 deg").arg(QString::number(qRadiansToDegrees(args.light_position.yaw)))); m_ui->lightYawLabel->setText(tr("Yaw: %1 deg").arg(QString::number(qRadiansToDegrees(args.light_position.yaw))));
m_ui->lightPitchLabel->setText(tr("Pitch: %1 deg").arg(QString::number(qRadiansToDegrees(args.light_position.pitch)))); m_ui->lightPitchLabel->setText(tr("Pitch: %1 deg").arg(QString::number(qRadiansToDegrees(args.light_position.pitch))));
m_ui->accumSigmaLabel->setText(tr("Sigma: %1").arg(QString::number(args.accum_sigma, 'f', 3))); m_ui->lightDistanceLabel->setText(tr("Distance: %1").arg(QString::number(args.light_position.distance)));
m_ui->accumScaleLabel->setText(tr("Scale: %1").arg(QString::number(args.accum_scale, 'f', 3))); m_ui->accumSigmaLabel->setText(tr("Averaging radius: %1").arg(QString::number(args.accum_sigma, 'f', 3)));
m_ui->accumScaleLabel->setText(tr("Brightness: %1").arg(QString::number(args.accum_scale, 'f', 3)));
m_ui->viewport->setView(args); m_ui->viewport->setView(args);
} }

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>800</width> <width>1600</width>
<height>600</height> <height>1200</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -25,7 +25,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>800</width> <width>1600</width>
<height>38</height> <height>38</height>
</rect> </rect>
</property> </property>
@ -51,6 +51,9 @@
<property name="text"> <property name="text">
<string>Yaw</string> <string>Yaw</string>
</property> </property>
<property name="buddy">
<cstring>cameraYaw</cstring>
</property>
</widget> </widget>
</item> </item>
<item> <item>
@ -77,6 +80,9 @@
<property name="text"> <property name="text">
<string>Pitch</string> <string>Pitch</string>
</property> </property>
<property name="buddy">
<cstring>cameraPitch</cstring>
</property>
</widget> </widget>
</item> </item>
<item> <item>
@ -98,6 +104,29 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QLabel" name="cameraDistanceLabel">
<property name="text">
<string>Distance</string>
</property>
<property name="buddy">
<cstring>cameraDistance</cstring>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="cameraDistance">
<property name="maximum">
<number>50</number>
</property>
<property name="value">
<number>30</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -112,6 +141,9 @@
<property name="text"> <property name="text">
<string>Yaw</string> <string>Yaw</string>
</property> </property>
<property name="buddy">
<cstring>lightYaw</cstring>
</property>
</widget> </widget>
</item> </item>
<item> <item>
@ -135,6 +167,9 @@
<property name="text"> <property name="text">
<string>Pitch</string> <string>Pitch</string>
</property> </property>
<property name="buddy">
<cstring>lightPitch</cstring>
</property>
</widget> </widget>
</item> </item>
<item> <item>
@ -159,19 +194,45 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QLabel" name="lightDistanceLabel">
<property name="text">
<string>Distance</string>
</property>
<property name="buddy">
<cstring>lightDistance</cstring>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="lightDistance">
<property name="maximum">
<number>50</number>
</property>
<property name="value">
<number>10</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QGroupBox" name="groupBox_3"> <widget class="QGroupBox" name="groupBox_3">
<property name="title"> <property name="title">
<string>Accumulating</string> <string>Lighting</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_4"> <layout class="QVBoxLayout" name="verticalLayout_4">
<item> <item>
<widget class="QLabel" name="accumSigmaLabel"> <widget class="QLabel" name="accumSigmaLabel">
<property name="text"> <property name="text">
<string>Sigma</string> <string>Averaging radius</string>
</property>
<property name="buddy">
<cstring>accumSigma</cstring>
</property> </property>
</widget> </widget>
</item> </item>
@ -194,7 +255,10 @@
<item> <item>
<widget class="QLabel" name="accumScaleLabel"> <widget class="QLabel" name="accumScaleLabel">
<property name="text"> <property name="text">
<string>Scale</string> <string>Brightness</string>
</property>
<property name="buddy">
<cstring>accumScale</cstring>
</property> </property>
</widget> </widget>
</item> </item>
@ -214,6 +278,95 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Reflections</string>
</property>
<property name="buddy">
<cstring>reflections</cstring>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="reflections">
<property name="maximum">
<number>20</number>
</property>
<property name="value">
<number>2</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>Show</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QCheckBox" name="displayAxes">
<property name="text">
<string>Axes</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="displayShapes">
<property name="text">
<string>Shapes</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="displayEmitted">
<property name="text">
<string>Emitted rays</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="displayDirectHits">
<property name="text">
<string>Direct incident rays</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="displayIndirectHits">
<property name="text">
<string>Indirect incident rays</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="displayResult">
<property name="text">
<string>Average light</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -251,8 +404,8 @@
<slot>updateView()</slot> <slot>updateView()</slot>
<hints> <hints>
<hint type="sourcelabel"> <hint type="sourcelabel">
<x>688</x> <x>1585</x>
<y>159</y> <y>169</y>
</hint> </hint>
<hint type="destinationlabel"> <hint type="destinationlabel">
<x>403</x> <x>403</x>
@ -267,8 +420,8 @@
<slot>updateView()</slot> <slot>updateView()</slot>
<hints> <hints>
<hint type="sourcelabel"> <hint type="sourcelabel">
<x>688</x> <x>1585</x>
<y>215</y> <y>225</y>
</hint> </hint>
<hint type="destinationlabel"> <hint type="destinationlabel">
<x>403</x> <x>403</x>
@ -283,8 +436,8 @@
<slot>updateView()</slot> <slot>updateView()</slot>
<hints> <hints>
<hint type="sourcelabel"> <hint type="sourcelabel">
<x>688</x> <x>1585</x>
<y>319</y> <y>385</y>
</hint> </hint>
<hint type="destinationlabel"> <hint type="destinationlabel">
<x>403</x> <x>403</x>
@ -299,8 +452,8 @@
<slot>updateView()</slot> <slot>updateView()</slot>
<hints> <hints>
<hint type="sourcelabel"> <hint type="sourcelabel">
<x>688</x> <x>1585</x>
<y>375</y> <y>441</y>
</hint> </hint>
<hint type="destinationlabel"> <hint type="destinationlabel">
<x>403</x> <x>403</x>
@ -315,8 +468,8 @@
<slot>updateView()</slot> <slot>updateView()</slot>
<hints> <hints>
<hint type="sourcelabel"> <hint type="sourcelabel">
<x>729</x> <x>1585</x>
<y>479</y> <y>601</y>
</hint> </hint>
<hint type="destinationlabel"> <hint type="destinationlabel">
<x>399</x> <x>399</x>
@ -331,8 +484,8 @@
<slot>updateView()</slot> <slot>updateView()</slot>
<hints> <hints>
<hint type="sourcelabel"> <hint type="sourcelabel">
<x>729</x> <x>1585</x>
<y>535</y> <y>657</y>
</hint> </hint>
<hint type="destinationlabel"> <hint type="destinationlabel">
<x>399</x> <x>399</x>
@ -340,6 +493,150 @@
</hint> </hint>
</hints> </hints>
</connection> </connection>
<connection>
<sender>cameraDistance</sender>
<signal>valueChanged(int)</signal>
<receiver>MainWindow</receiver>
<slot>updateView()</slot>
<hints>
<hint type="sourcelabel">
<x>1489</x>
<y>271</y>
</hint>
<hint type="destinationlabel">
<x>799</x>
<y>599</y>
</hint>
</hints>
</connection>
<connection>
<sender>lightDistance</sender>
<signal>valueChanged(int)</signal>
<receiver>MainWindow</receiver>
<slot>updateView()</slot>
<hints>
<hint type="sourcelabel">
<x>1489</x>
<y>487</y>
</hint>
<hint type="destinationlabel">
<x>799</x>
<y>599</y>
</hint>
</hints>
</connection>
<connection>
<sender>displayAxes</sender>
<signal>stateChanged(int)</signal>
<receiver>MainWindow</receiver>
<slot>updateView()</slot>
<hints>
<hint type="sourcelabel">
<x>1489</x>
<y>799</y>
</hint>
<hint type="destinationlabel">
<x>799</x>
<y>599</y>
</hint>
</hints>
</connection>
<connection>
<sender>displayDirectHits</sender>
<signal>stateChanged(int)</signal>
<receiver>MainWindow</receiver>
<slot>updateView()</slot>
<hints>
<hint type="sourcelabel">
<x>1489</x>
<y>901</y>
</hint>
<hint type="destinationlabel">
<x>799</x>
<y>599</y>
</hint>
</hints>
</connection>
<connection>
<sender>displayEmitted</sender>
<signal>stateChanged(int)</signal>
<receiver>MainWindow</receiver>
<slot>updateView()</slot>
<hints>
<hint type="sourcelabel">
<x>1489</x>
<y>867</y>
</hint>
<hint type="destinationlabel">
<x>799</x>
<y>599</y>
</hint>
</hints>
</connection>
<connection>
<sender>displayIndirectHits</sender>
<signal>stateChanged(int)</signal>
<receiver>MainWindow</receiver>
<slot>updateView()</slot>
<hints>
<hint type="sourcelabel">
<x>1489</x>
<y>935</y>
</hint>
<hint type="destinationlabel">
<x>799</x>
<y>599</y>
</hint>
</hints>
</connection>
<connection>
<sender>displayResult</sender>
<signal>stateChanged(int)</signal>
<receiver>MainWindow</receiver>
<slot>updateView()</slot>
<hints>
<hint type="sourcelabel">
<x>1489</x>
<y>969</y>
</hint>
<hint type="destinationlabel">
<x>799</x>
<y>599</y>
</hint>
</hints>
</connection>
<connection>
<sender>displayShapes</sender>
<signal>stateChanged(int)</signal>
<receiver>MainWindow</receiver>
<slot>updateView()</slot>
<hints>
<hint type="sourcelabel">
<x>1489</x>
<y>833</y>
</hint>
<hint type="destinationlabel">
<x>799</x>
<y>599</y>
</hint>
</hints>
</connection>
<connection>
<sender>reflections</sender>
<signal>valueChanged(int)</signal>
<receiver>MainWindow</receiver>
<slot>updateView()</slot>
<hints>
<hint type="sourcelabel">
<x>1489</x>
<y>712</y>
</hint>
<hint type="destinationlabel">
<x>799</x>
<y>599</y>
</hint>
</hints>
</connection>
</connections> </connections>
<slots> <slots>
<slot>updateView()</slot> <slot>updateView()</slot>