Rectangle area lights: the source
Open the live demo · Read the guide · View on GitHub
Regions the guide quotes: window (line 39), pane (line 47), room (line 66), floor (line 83), live (line 107), check (line 146).
1/// `LightType.area`: a rectangle with extent, so a room reads as lit by a
2/// window rather than by a bright dot painted behind one, and a polished
3/// floor shows the window's shape.
4///
5/// Quoted by `area_lights.md` and shown whole in the Source tab.
6library;
8import 'dart:math' as math;
10import 'package:flutter3d/flutter3d.dart';
11import 'package:flutter3d_showcase/src/demo/demo.dart';
12import 'package:vector_math/vector_math.dart';
14final class AreaLightsDemo extends ShowcaseDemo {
15 double width = 2.5;
16 double height = 1.5;
17 double floorRoughness = 0.25;
19 late final LightNode _window;
20 late final MeshNode _pane;
21 late final Material _glass;
22 late final Material _floorMaterial;
24 /// A plane is built lying down, facing +Y; a quarter turn about X stands
25 /// it up facing +Z, into the room.
26 static Quaternion get _standUp =>
27 Quaternion.axisAngle(Vector3(1.0, 0.0, 0.0), math.pi / 2);
30 void configureView(DemoContext context) {
31 context.orbit
32 ..distance = 7.5
33 ..yaw = 0.45
34 ..pitch = 0.4;
35 }
38 Scene build(DemoContext context) {
39 // The panel faces the node's local -Z. Set in the back wall and aimed
40 // at a point in front of it, it shines into the room.
41 _window = LightNode(name: 'window', type: LightType.area, intensity: 8.0)
42 ..width = width
43 ..height = height
44 ..setPosition(0.0, 0.3, -1.45);
45 _window.lookAt(Vector3(0.0, 0.3, 3.0));
47 // The light itself is not drawn. A glowing rectangle of the same size,
48 // just behind it, is what a person sees as the window.
49 _glass = Material(
50 name: 'window glass',
51 baseColor: Vector4(0.0, 0.0, 0.0, 1.0),
52 emissive: Vector3(3.0, 3.0, 2.8),
53 );
54 _pane =
55 MeshNode(
56 DeviceMesh.upload(
57 context.device,
58 const PlaneShape(width: 1, depth: 1).build(),
59 ),
60 _glass,
61 name: 'window pane',
62 )
63 ..setRotation(_standUp)
64 ..setPosition(0.0, 0.3, -1.48);
66 final MeshNode backWall =
67 MeshNode(
68 DeviceMesh.upload(
69 context.device,
70 const PlaneShape(width: 6, depth: 4).build(),
71 ),
72 Material(
73 name: 'wall',
74 baseColor: Vector4(0.6, 0.58, 0.55, 1.0),
75 roughness: 0.9,
76 doubleSided: true,
77 ),
78 name: 'back wall',
79 )
80 ..setRotation(_standUp)
81 ..setPosition(0.0, 0.0, -1.5);
83 // A floor of its own, so its roughness can go down to a polish.
84 _floorMaterial = Material(
85 name: 'floor',
86 baseColor: Vector4(0.45, 0.45, 0.47, 1.0),
87 roughness: floorRoughness,
88 );
89 final MeshNode floor = MeshNode(
90 DeviceMesh.upload(
91 context.device,
92 const PlaneShape(width: 6, depth: 5).build(),
93 ),
94 _floorMaterial,
95 name: 'floor',
96 )..setPosition(0.0, -1.8, 1.0);
98 return Scene()
99 ..add(backWall)
100 ..add(_pane)
101 ..add(floor)
102 ..add(_window);
103 }
106 void update(DemoContext context, double dt) {
107 _window
108 ..width = width
109 ..height = height;
110 _pane.setScale(width, 1.0, height);
111 // The light's radiance is its intensity over its area, so a wider
112 // window is dimmer per square metre. The pane dims by the same ratio,
113 // measured from the 2.5 by 1.5 it starts at.
114 final double dim = (2.5 * 1.5) / (width * height);
115 _glass.emissive.setValues(3.0 * dim, 3.0 * dim, 2.8 * dim);
116 _floorMaterial.roughness = floorRoughness;
117 }
120 List<DemoControl> controls(DemoContext context) => <DemoControl>[
121 SliderControl(
122 'Width',
123 min: 0.5,
124 max: 4,
125 value: () => width,
126 onChanged: (double v) => width = v,
127 ),
128 SliderControl(
129 'Height',
130 min: 0.5,
131 max: 3,
132 value: () => height,
133 onChanged: (double v) => height = v,
134 ),
135 SliderControl(
136 'Floor roughness',
137 min: 0.05,
138 max: 1,
139 value: () => floorRoughness,
140 onChanged: (double v) => floorRoughness = v,
141 ),
142 ];
145 void verify(Scene scene, FrameResult frame) {
146 if (_window.type != LightType.area) {
147 throw StateError('the window is not an area light');
148 }
149 if (_window.width != width || _window.height != height) {
150 throw StateError('the panel size did not track the sliders');
151 }
152 final Vector3 halfWidth = _window.readHalfWidth();
153 final Vector3 halfHeight = _window.readHalfHeight();
154 if (halfWidth.length2 == 0.0 || halfHeight.length2 == 0.0) {
155 throw StateError('the panel has no extent to shade against');
156 }
157 if (_floorMaterial.roughness != floorRoughness) {
158 throw StateError('the floor roughness did not track its slider');
159 }
160 if (frame.drawCalls < 3) {
161 throw StateError('the room was not drawn');
162 }
163 }
164}