1 module scene; 2 3 import gfx.math; 4 5 interface LightAnim 6 { 7 void anim(in float dt); 8 @property FVec3 color(); 9 @property bool on(); 10 } 11 12 /// constant light without animation 13 class ConstantLight : LightAnim 14 { 15 FVec3 _color; 16 17 this(FVec3 color) 18 { 19 _color = color; 20 } 21 22 override void anim(in float) {} 23 override @property FVec3 color() 24 { 25 return _color; 26 } 27 override @property bool on() 28 { 29 return true; 30 } 31 } 32 33 private float smoothstep(float edge0, float edge1, float x) { 34 // Scale, bias and saturate x to 0..1 range 35 x = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0); 36 // Evaluate polynomial 37 return x * x * (3 - 2 * x); 38 } 39 40 private float clamp(float x, float lowerlimit, float upperlimit) { 41 if (x < lowerlimit) 42 x = lowerlimit; 43 if (x > upperlimit) 44 x = upperlimit; 45 return x; 46 } 47 48 /// blinking light 49 class BlinkLight : LightAnim 50 { 51 LightAnim _anim; 52 float _on; 53 float _period; 54 float _step; 55 float _phase; 56 float _level; 57 58 this(LightAnim anim, in float on, in float off, in float step, in float phase) 59 in (on > step && off > step, "Blink light on or off is smaller than step") 60 in (phase >= 0f && phase < (on + off), "Blink light initial phase out of period") 61 { 62 _anim = anim; 63 _on = on; 64 _period = on + off; 65 _step = step; 66 _phase = phase; 67 computeLevel(); 68 } 69 70 final void computeLevel() 71 { 72 if (_phase < (_on-_step)) { 73 _level = 1f; 74 } 75 else if (_phase < _on) { 76 // step down 77 _level = 1f - smoothstep(_on - _step, _on, _phase); 78 } 79 else if (_phase < (_period-_step)) { 80 _level = 0f; 81 } 82 else { 83 // step up 84 _level = smoothstep(_period - _step, _period, _phase); 85 } 86 } 87 88 override void anim(in float dt) 89 { 90 _anim.anim(dt); 91 _phase += dt; 92 while (_phase >= _period) _phase -= _period; 93 computeLevel(); 94 } 95 96 override @property FVec3 color() 97 { 98 return _anim.color * _level; 99 } 100 101 override @property bool on() 102 { 103 return _level > 0f && _anim.on; 104 } 105 } 106 107 struct MovingObj 108 { 109 /// position of the object relative to its parent 110 FVec3 position = fvec(0, 0, 0); 111 /// rotation speed of the object around itself 112 /// using Euler angles, in rad/s 113 FVec3 spin = fvec(0, 0, 0); 114 /// current rotation of the object 115 FMat3 rotation = FMat3.identity; 116 117 /// rotate the rotation matrix with the spin euler vector 118 /// over dt secs 119 void rotate(in float dt) 120 { 121 const euler = spin * dt; 122 rotation *= eulerAngles(euler); 123 } 124 125 /// Compute the full transform 126 FMat4 transform() 127 { 128 return translate( 129 mat( 130 vec(rotation[0], 0), 131 vec(rotation[1], 0), 132 vec(rotation[2], 0), 133 vec(0, 0, 0, 1), 134 ), 135 position, 136 ); 137 } 138 } 139 140 struct SaucerBody 141 { 142 FMat4 transform = FMat4.identity; 143 FVec3 color = fvec(0, 0, 0); 144 float shininess = 1f; 145 } 146 147 struct Saucer 148 { 149 MovingObj mov; 150 size_t saucerIdx; 151 SaucerBody[] bodies; 152 153 FVec3 lightPos; 154 float lightLuminosity; 155 LightAnim lightAnim; 156 157 void anim(in float dt) 158 { 159 mov.rotate(dt); 160 lightAnim.anim(dt); 161 } 162 } 163 164 struct SaucerSubStruct 165 { 166 MovingObj mov; 167 Saucer[] saucers; 168 } 169 170 struct DeferredScene 171 { 172 MovingObj mov; 173 SaucerSubStruct[] subStructs; 174 175 /// prepare scene and return approximative bounding radius 176 float prepare() 177 { 178 /// Build a structure of 9 sub structures, each containing 9 saucers. 179 /// Saucers have a fixed position relative to their sub-structure, 180 /// which have fixed position relative to the super structure. 181 /// Each object (structure, sub-structure and saucer spin around their center) 182 /// is setup such as to not have collisions. 183 184 import std.algorithm : map; 185 import std.array : array; 186 import std.math : PI, sqrt; 187 import std.random : choice, Random, uniform; 188 189 const margin = 1f; 190 const saucerSz = 3f; 191 const saucerBound = saucerSz + 2 * margin; // 5 192 const subStructSz = 3 * saucerBound; // 15 193 const subStructBound = subStructSz + 2 * margin; // 17 194 const sceneSz = subStructBound * 3; // 51 195 const numSubStructs = 9; 196 const numSaucers = 9; 197 198 const subDist = (sceneSz / 2f) - (subStructSz / 2f); 199 const saucerDist = (subStructBound / 2f) - (saucerSz / 2f); 200 201 auto rnd = Random(43); 202 size_t saucerIdx = 0; 203 204 FVec3 spin(in float minRpm, in float maxRpm) 205 { 206 const float[2] sign = [-1f, 1f]; 207 const signs = fvec( 208 choice(sign[], rnd), 209 choice(sign[], rnd), 210 choice(sign[], rnd), 211 ); 212 213 enum RPM = 2*PI / 60; 214 return signs * fvec( 215 uniform(minRpm * RPM, maxRpm * RPM, rnd), 216 uniform(minRpm * RPM, maxRpm * RPM, rnd), 217 uniform(minRpm * RPM, maxRpm * RPM, rnd), 218 ); 219 } 220 221 MovingObj[] buildMovingObjs(in uint num, in float dist, in float minRpm, in float maxRpm) 222 { 223 MovingObj[] objs = new MovingObj[num]; 224 225 const coord = dist * sqrt(2f) / 2; 226 227 foreach(i; 0 .. num) { 228 FVec3 pos = fvec(0, 0, 0); 229 if (i > 0) { 230 const mask = i - 1; 231 pos.x = (mask & 0b001) ? coord : -coord; 232 pos.y = (mask & 0b010) ? coord : -coord; 233 pos.z = (mask & 0b100) ? coord : -coord; 234 } 235 objs[i] = MovingObj(pos, spin(minRpm, maxRpm), FMat3.identity); 236 } 237 return objs; 238 } 239 240 FVec3 color() { 241 return normalize( 242 fvec( 243 uniform(0.1, 1.0, rnd), 244 uniform(0.1, 1.0, rnd), 245 uniform(0.1, 1.0, rnd), 246 ) 247 ); 248 } 249 250 LightAnim makeAnim() 251 { 252 import std.algorithm : max; 253 254 auto base = new ConstantLight(color()); 255 const float period = uniform(1.0, 10.0, rnd); 256 const float onOff = uniform(0.1, 0.6, rnd); // on 10% to 60% 257 const float on = max(0.5f, period * onOff); 258 const float off = period - on; 259 const float step = 0.3f; 260 const float phase = uniform(0f, period, rnd); 261 return new BlinkLight(base, on, off, step, phase); 262 } 263 264 265 Saucer makeSaucer(MovingObj mov) { 266 267 SaucerBody body; 268 SaucerBody cockpit; 269 SaucerBody bulb; 270 const bodyCol = color(); 271 const bulbCol = color(); 272 FVec3 bulbPos; 273 { 274 const xy = uniform(2.5, 3, rnd); 275 const z = uniform(0.8, 1, rnd); 276 body.transform = scale(fvec(xy, xy, z)); 277 body.color = bodyCol * 0.8; 278 body.shininess = uniform(6.0, 16.0, rnd); 279 } 280 { 281 const s = uniform(0.5, 1, rnd); 282 const x = uniform(-0.5, 0, rnd); 283 const z = uniform(0.8, 1, rnd); 284 cockpit.transform = translation(fvec(x, 0, z)) 285 * scale(FVec3(s)); 286 cockpit.color = bodyCol; 287 cockpit.shininess = uniform(20.0, 32.0, rnd); 288 } 289 { 290 const s = uniform(0.2, 0.3, rnd); 291 const x = uniform(1.0, 2.0, rnd); 292 const z = uniform(1.5, 2.5, rnd); 293 bulbPos = fvec(x, 0, z); 294 bulb.transform = translation(bulbPos) 295 * scale(FVec3(s)); 296 bulb.color = bulbCol; 297 bulb.shininess = uniform(20.0, 32.0, rnd); 298 } 299 300 return Saucer( 301 mov, saucerIdx++, [body, cockpit, bulb], 302 bulbPos, 303 uniform(2f, 12f, rnd), // light luminosity 304 makeAnim(), 305 ); 306 } 307 308 SaucerSubStruct makeSubStruct(MovingObj mov) { 309 return SaucerSubStruct( 310 mov, 311 buildMovingObjs(numSaucers, saucerDist, 3f, 7f) 312 .map!(mov => makeSaucer(mov)) 313 .array 314 ); 315 } 316 317 mov.spin = spin(2f, 3f); 318 subStructs = buildMovingObjs(numSubStructs, subDist, 1f, 5f) 319 .map!(mov => makeSubStruct(mov)) 320 .array; 321 322 return sceneSz / 2f; 323 } 324 325 @property size_t saucerCount() 326 { 327 import std.algorithm : map, sum; 328 329 return subStructs 330 .map!(ss => ss.saucers.length) 331 .sum(); 332 } 333 }