1 /* 2 * Copyright 2016, Haiku, Inc. All rights reserved. 3 * Distributed under the terms of the MIT license. 4 * 5 * Authors: 6 * Augustin Cavalier <waddlesplash> 7 * kerwizzy 8 */ 9 #include "FractalEngine.h" 10 11 #include <algorithm> 12 #include <math.h> 13 14 #include <Bitmap.h> 15 #include <String.h> 16 17 #include "Colorsets.h" 18 19 20 FractalEngine::FractalEngine(BHandler* parent, BLooper* looper) 21 : 22 BLooper("FractalEngine"), 23 fMessenger(parent, looper), 24 fBitmapStandby(NULL), 25 fBitmapDisplay(NULL), 26 fIterations(1024), 27 fWidth(0), fHeight(0), 28 fRenderBuffer(NULL), 29 fRenderBufferLen(0), 30 fColorset(Colorset_Royal) 31 { 32 fDoSet = &FractalEngine::DoSet_Mandelbrot; 33 34 fRenderSem = create_sem(0, "RenderSem"); 35 fRenderFinishedSem = create_sem(0, "RenderFinishedSem"); 36 system_info info; 37 get_system_info(&info); 38 fThreadCount = info.cpu_count; 39 if (fThreadCount >= 4) 40 fThreadCount = 4; 41 for (uint8 i = 0; i < fThreadCount; i++) { 42 fRenderThreads[i] = spawn_thread(&FractalEngine::RenderThread, 43 BString().SetToFormat("RenderThread%d", i).String(), 44 B_NORMAL_PRIORITY, this); 45 resume_thread(fRenderThreads[i]); 46 } 47 } 48 49 50 FractalEngine::~FractalEngine() 51 { 52 } 53 54 55 void FractalEngine::MessageReceived(BMessage* msg) 56 { 57 switch (msg->what) { 58 case MSG_CHANGE_SET: 59 switch (msg->GetUInt8("set", 0)) { 60 case 0: fDoSet = &FractalEngine::DoSet_Mandelbrot; break; 61 case 1: fDoSet = &FractalEngine::DoSet_BurningShip; break; 62 case 2: fDoSet = &FractalEngine::DoSet_Tricorn; break; 63 case 3: fDoSet = &FractalEngine::DoSet_Julia; break; 64 case 4: fDoSet = &FractalEngine::DoSet_OrbitTrap; break; 65 case 5: fDoSet = &FractalEngine::DoSet_Multibrot; break; 66 } 67 break; 68 case MSG_SET_PALETTE: 69 switch (msg->GetUInt8("palette", 0)) { 70 case 0: fColorset = Colorset_Royal; break; 71 case 1: fColorset = Colorset_DeepFrost; break; 72 case 2: fColorset = Colorset_Frost; break; 73 case 3: fColorset = Colorset_Fire; break; 74 case 4: fColorset = Colorset_Midnight; break; 75 case 5: fColorset = Colorset_Grassland; break; 76 case 6: fColorset = Colorset_Lightning; break; 77 case 7: fColorset = Colorset_Spring; break; 78 case 8: fColorset = Colorset_HighContrast; break; 79 } 80 break; 81 case MSG_SET_ITERATIONS: 82 fIterations = msg->GetUInt16("iterations", 0); 83 break; 84 85 case MSG_RESIZE: { 86 delete fBitmapStandby; 87 // We don't delete the "display" bitmap; the viewer now owns it 88 delete fRenderBuffer; 89 90 fWidth = msg->GetUInt16("width", 320); 91 fHeight = msg->GetUInt16("height", 240); 92 BRect rect(0, 0, fWidth - 1, fHeight - 1); 93 fBitmapStandby = new BBitmap(rect, B_RGB24); 94 fBitmapDisplay = new BBitmap(rect, B_RGB24); 95 fRenderBufferLen = fWidth * fHeight * 3; 96 fRenderBuffer = new uint8[fRenderBufferLen]; 97 break; 98 } 99 case MSG_RENDER: { 100 // Render to "standby" bitmap 101 Render(msg->GetDouble("locationX", 0), msg->GetDouble("locationY", 0), 102 msg->GetDouble("size", 0.005)); 103 BMessage message(MSG_RENDER_COMPLETE); 104 message.AddPointer("bitmap", (const void*)fBitmapStandby); 105 fMessenger.SendMessage(&message); 106 std::swap(fBitmapStandby, fBitmapDisplay); 107 break; 108 } 109 110 default: 111 BLooper::MessageReceived(msg); 112 break; 113 } 114 } 115 116 117 void FractalEngine::Render(double locationX, double locationY, double size) 118 { 119 fLocationX = locationX; 120 fLocationY = locationY; 121 fSize = size; 122 for (uint8 i = 0; i < fThreadCount; i++) 123 release_sem(fRenderSem); 124 for (uint8 i = 0; i < fThreadCount; i++) 125 acquire_sem(fRenderFinishedSem); 126 127 fBitmapStandby->ImportBits(fRenderBuffer, fRenderBufferLen, fWidth * 3, 128 0, B_RGB24_BIG); 129 } 130 131 132 status_t FractalEngine::RenderThread(void* data) 133 { 134 FractalEngine* engine = static_cast<FractalEngine*>(data); 135 thread_id self = find_thread(NULL); 136 uint8 threadNum = 0; 137 for (uint8 i = 0; i < engine->fThreadCount; i++) { 138 if (engine->fRenderThreads[i] == self) { 139 threadNum = i; 140 break; 141 } 142 } 143 144 while (true) { 145 acquire_sem(engine->fRenderSem); 146 147 uint16 halfWidth = engine->fWidth / 2; 148 uint16 halfHeight = engine->fHeight / 2; 149 const uint32 startY = (engine->fHeight / engine->fThreadCount) * threadNum, 150 endY = (engine->fHeight / engine->fThreadCount) * (threadNum + 1); 151 for (uint32 x = 0; x < engine->fWidth; x++) { 152 for (uint32 y = startY; y < endY; y++) { 153 engine->RenderPixel(x, y, 154 (x * engine->fSize + engine->fLocationX) - (halfWidth * engine->fSize), 155 (y * -(engine->fSize) + engine->fLocationY) - (halfHeight * -(engine->fSize))); 156 } 157 } 158 159 release_sem(engine->fRenderFinishedSem); 160 } 161 return B_OK; 162 } 163 164 165 void FractalEngine::RenderPixel(uint32 x, uint32 y, double real, 166 double imaginary) 167 { 168 int32 iterToEscape = (this->*fDoSet)(real, imaginary); 169 uint16 loc = 0; 170 if (iterToEscape == -1) { 171 // Didn't escape. 172 loc = 999; 173 } else { 174 loc = 998 - (iterToEscape % 999); 175 } 176 177 uint32 offsetBase = fWidth * y * 3 + x * 3; 178 loc *= 3; 179 fRenderBuffer[offsetBase + 0] = fColorset[loc + 0]; 180 fRenderBuffer[offsetBase + 1] = fColorset[loc + 1]; 181 fRenderBuffer[offsetBase + 2] = fColorset[loc + 2]; 182 } 183 184 185 // Magic numbers & other general constants 186 const double gJuliaA = 0; 187 const double gJuliaB = 1; 188 189 const uint8 gEscapeHorizon = 4; 190 191 192 int32 FractalEngine::DoSet_Mandelbrot(double real, double imaginary) 193 { 194 double zReal = 0; 195 double zImaginary = 0; 196 197 for (int32 i = 0; i < fIterations; i++) { 198 double zRealSq = zReal * zReal; 199 double zImaginarySq = zImaginary * zImaginary; 200 double nzReal = (zRealSq + (-1 * zImaginarySq)); 201 202 zImaginary = 2 * (zReal * zImaginary); 203 zReal = nzReal; 204 205 zReal += real; 206 zImaginary += imaginary; 207 208 // If it is outside the 2 unit circle... 209 if ((zRealSq + zImaginarySq) > gEscapeHorizon) { 210 return i; // stop it from running longer 211 } 212 } 213 return -1; 214 } 215 216 217 int32 FractalEngine::DoSet_BurningShip(double real, double imaginary) 218 { 219 double zReal = 0; 220 double zImaginary = 0; 221 222 // It looks "upside down" otherwise. 223 imaginary = -imaginary; 224 225 for (int32 i = 0; i < fIterations; i++) { 226 zReal = fabs(zReal); 227 zImaginary = fabs(zImaginary); 228 229 double zRealSq = zReal * zReal; 230 double zImaginarySq = zImaginary * zImaginary; 231 double nzReal = (zRealSq + (-1 * zImaginarySq)); 232 233 zImaginary = 2 * (zReal * zImaginary); 234 zReal = nzReal; 235 236 zReal += real; 237 zImaginary += imaginary; 238 239 // If it is outside the 2 unit circle... 240 if ((zRealSq + zImaginarySq) > gEscapeHorizon) { 241 return i; // stop it from running longer 242 } 243 } 244 return -1; 245 } 246 247 248 int32 FractalEngine::DoSet_Tricorn(double real, double imaginary) 249 { 250 double zReal = 0; 251 double zImaginary = 0; 252 253 real = -real; 254 255 for (int32 i = 0; i < fIterations; i++) { 256 double znRe = zImaginary * -1; 257 zImaginary = zReal * -1; 258 zReal = znRe; // Swap the real and complex parts each time. 259 260 double zRealSq = zReal * zReal; 261 double zImaginarySq = zImaginary * zImaginary; 262 double nzReal = (zRealSq + (-1 * zImaginarySq)); 263 264 zImaginary = 2 * (zReal * zImaginary); 265 zReal = nzReal; 266 267 zReal += real; 268 zImaginary += imaginary; 269 270 // If it is outside the 2 unit circle... 271 if ((zRealSq + zImaginarySq) > gEscapeHorizon) { 272 return i; // stop it from running longer 273 } 274 } 275 return -1; 276 } 277 278 279 int32 FractalEngine::DoSet_Julia(double real, double imaginary) 280 { 281 double zReal = real; 282 double zImaginary = imaginary; 283 284 double muRe = gJuliaA; 285 double muIm = gJuliaB; 286 287 for (int32 i = 0; i < fIterations; i++) { 288 double zRealSq = zReal * zReal; 289 double zImaginarySq = zImaginary * zImaginary; 290 double nzReal = (zRealSq + (-1 * (zImaginarySq))); 291 292 zImaginary = 2 * (zReal * zImaginary); 293 zReal = nzReal; 294 295 zReal += muRe; 296 zImaginary += muIm; 297 298 // If it is outside the 2 unit circle... 299 if ((zRealSq + zImaginarySq) > gEscapeHorizon) { 300 return i; // stop it from running longer 301 } 302 } 303 return -1; 304 } 305 306 307 int32 FractalEngine::DoSet_OrbitTrap(double real, double imaginary) 308 { 309 double zReal = 0; 310 double zImaginary = 0; 311 312 double closest = 10000000; 313 double distance = 0; 314 double lineDist = 0; 315 316 for (int32 i = 0; i < fIterations; i++) { 317 double zRealSq = zReal * zReal; 318 double zImaginarySq = zImaginary * zImaginary; 319 double nzReal = (zRealSq + (-1 * zImaginarySq)); 320 321 zImaginary = 2 * (zReal * zImaginary); 322 zReal = nzReal; 323 324 zReal += real; 325 zImaginary += imaginary; 326 327 distance = sqrt(zRealSq + zImaginarySq); 328 lineDist = fabs(zReal + zImaginary); 329 330 // If it is closer than ever before... 331 if (lineDist < closest) 332 closest = lineDist; 333 334 if (distance > gEscapeHorizon) { 335 return static_cast<int32>(floor(4 * log(4 / closest))); 336 } 337 } 338 return static_cast<int32>(floor(4 * log(4 / closest))); 339 } 340 341 342 int32 FractalEngine::DoSet_Multibrot(double real, double imaginary) 343 { 344 double zReal = 0; 345 double zImaginary = 0; 346 347 for (int32 i = 0; i < fIterations; i++) { 348 double zRealSq = zReal * zReal; 349 double zImaginarySq = zImaginary * zImaginary; 350 double nzReal = (zRealSq * zReal - 3 * zReal * zImaginarySq); 351 352 zImaginary = 3 * (zRealSq * zImaginary) - (zImaginarySq * zImaginary); 353 354 zReal = nzReal; 355 zReal += real; 356 zImaginary += imaginary; 357 358 // If it is outside the 2 unit circle... 359 if ((zRealSq + zImaginarySq) > gEscapeHorizon) { 360 return i; // stop it from running longer 361 } 362 } 363 return -1; 364 } 365