xref: /haiku/src/apps/mandelbrot/FractalEngine.cpp (revision b08627f310bb2e80bca50176e7a758182384735a)
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