1/*
2 * Copyright 2008 Haiku Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Alexandre Deckner
7 *
8 */
9
10/*
11 * Original Be Sample source modified to use a quaternion for the object's orientation
12 */
13
14/*
15	Copyright 1999, Be Incorporated.   All Rights Reserved.
16	This file may be used under the terms of the Be Sample Code License.
17*/
18
19#include "ObjectView.h"
20
21#include <Application.h>
22#include <Catalog.h>
23#include <Cursor.h>
24#include <InterfaceKit.h>
25#include <FindDirectory.h>
26
27#include "FPS.h"
28#include "GLObject.h"
29#include "ResScroll.h"
30
31#undef B_TRANSLATION_CONTEXT
32#define B_TRANSLATION_CONTEXT "ObjectView"
33
34float displayScale = 1.0;
35float depthOfView = 30.0;
36float zRatio = 10.0;
37
38float white[3] = {1.0, 1.0, 1.0};
39float dimWhite[3] = {0.25, 0.25, 0.25};
40float black[3] = {0.0, 0.0, 0.0};
41float foggy[3] = {0.4, 0.4, 0.4};
42float blue[3] = {0.0, 0.0, 1.0};
43float dimBlue[3] = {0.0, 0.0, 0.5};
44float yellow[3] = {1.0, 1.0, 0.0};
45float dimYellow[3] = {0.5, 0.5, 0.0};
46float green[3] = {0.0, 1.0, 0.0};
47float dimGreen[3] = {0.0, 0.5, 0.0};
48float red[3] = {1.0, 0.0, 0.0};
49
50float* bgColor = black;
51
52const char *kNoResourceError = B_TRANSLATE("The Teapot 3D model was "
53									"not found in application resources. "
54									"Please repair the program installation.");
55
56struct light {
57	float *ambient;
58	float *diffuse;
59	float *specular;
60};
61
62
63light lights[] = {
64	{NULL, NULL, NULL},
65	{dimWhite, white, white},
66	{dimWhite, yellow, yellow},
67	{dimWhite, red, red},
68	{dimWhite, blue, blue},
69	{dimWhite, green, green}
70};
71
72
73
74long
75signalEvent(sem_id event)
76{
77	int32 c;
78	get_sem_count(event,&c);
79	if (c < 0)
80		release_sem_etc(event,-c,0);
81
82	return 0;
83}
84
85
86long
87setEvent(sem_id event)
88{
89	int32 c;
90	get_sem_count(event,&c);
91	if (c < 0)
92	  release_sem_etc(event,-c,0);
93
94	return 0;
95}
96
97
98long
99waitEvent(sem_id event)
100{
101	acquire_sem(event);
102
103	int32 c;
104	get_sem_count(event,&c);
105	if (c > 0)
106		acquire_sem_etc(event,c,0,0);
107
108	return 0;
109}
110
111
112static int32
113simonThread(void* cookie)
114{
115	ObjectView* objectView = reinterpret_cast<ObjectView*>(cookie);
116	BScreen screen(objectView->Window());
117
118	int noPause = 0;
119	while (acquire_sem_etc(objectView->quittingSem, 1, B_TIMEOUT, 0) == B_NO_ERROR) {
120		if (objectView->SpinIt()) {
121			objectView->DrawFrame(noPause);
122			release_sem(objectView->quittingSem);
123			noPause = 1;
124		} else {
125			release_sem(objectView->quittingSem);
126			noPause = 0;
127			waitEvent(objectView->drawEvent);
128		}
129		if (objectView->LimitFps())
130			screen.WaitForRetrace();
131	}
132	return 0;
133}
134
135
136ObjectView::ObjectView(BRect rect, const char *name, ulong resizingMode,
137	ulong options)
138	: BGLView(rect, name, resizingMode, 0, options),
139	fHistEntries(0),
140	fOldestEntry(0),
141	fFps(true),
142	fLimitFps(true),
143	fLastGouraud(true),
144	fGouraud(true),
145	fLastZbuf(true),
146	fZbuf(true),
147	fLastCulling(true),
148	fCulling(true),
149	fLastLighting(true),
150	fLighting(true),
151	fLastFilled(true),
152	fFilled(true),
153	fLastPersp(false),
154	fPersp(false),
155	fLastTextured(false),
156	fTextured(false),
157	fLastFog(false),
158	fFog(false),
159	fForceRedraw(false),
160	fLastYXRatio(1),
161	fYxRatio(1)
162{
163	fTrackingInfo.isTracking = false;
164	fTrackingInfo.pickedObject = NULL;
165	fTrackingInfo.buttons = 0;
166	fTrackingInfo.lastX = 0.0f;
167	fTrackingInfo.lastY = 0.0f;
168	fTrackingInfo.lastDx = 0.0f;
169	fTrackingInfo.lastDy = 0.0f;
170
171	fLastObjectDistance = fObjectDistance = depthOfView / 8;
172	quittingSem = create_sem(1, "quitting sem");
173	drawEvent = create_sem(0, "draw event");
174
175	TriangleObject *Tri = new TriangleObject(this);
176	if (Tri->InitCheck() == B_OK) {
177		fObjListLock.Lock();
178		fObjects.AddItem(Tri);
179		fObjListLock.Unlock();
180	} else {
181		BAlert *NoResourceAlert	= new BAlert(B_TRANSLATE("Error"),
182						kNoResourceError, B_TRANSLATE("OK"), NULL, NULL,
183						B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_STOP_ALERT);
184		NoResourceAlert->SetFlags(NoResourceAlert->Flags() | B_CLOSE_ON_ESCAPE);
185		NoResourceAlert->Go();
186		delete Tri;
187	}
188}
189
190
191ObjectView::~ObjectView()
192{
193	delete_sem(quittingSem);
194	delete_sem(drawEvent);
195}
196
197
198void
199ObjectView::AttachedToWindow()
200{
201	float position[] = {0.0, 3.0, 3.0, 0.0};
202	float position1[] = {-3.0, -3.0, 3.0, 0.0};
203	float position2[] = {3.0, 0.0, 0.0, 0.0};
204	float local_view[] = {0.0, 0.0};
205//	float ambient[] = {0.1745, 0.03175, 0.03175};
206//	float diffuse[] = {0.61424, 0.10136, 0.10136};
207//	float specular[] = {0.727811, 0.626959, 0.626959};
208//	rgb_color black = {0, 0, 0, 255};
209	BRect bounds = Bounds();
210
211	BGLView::AttachedToWindow();
212	Window()->SetPulseRate(100000);
213
214	LockGL();
215
216	glEnable(GL_DITHER);
217	glEnable(GL_CULL_FACE);
218	glCullFace(GL_BACK);
219	glDepthFunc(GL_LESS);
220
221	glShadeModel(GL_SMOOTH);
222
223	glLightfv(GL_LIGHT0, GL_POSITION, position);
224	glLightfv(GL_LIGHT0 + 1, GL_POSITION, position1);
225	glLightfv(GL_LIGHT0 + 2, GL_POSITION, position2);
226	glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER, local_view);
227
228	glEnable(GL_LIGHT0);
229	glLightfv(GL_LIGHT0, GL_SPECULAR, lights[lightWhite].specular);
230	glLightfv(GL_LIGHT0, GL_DIFFUSE,lights[lightWhite].diffuse);
231	glLightfv(GL_LIGHT0, GL_AMBIENT,lights[lightWhite].ambient);
232	glEnable(GL_LIGHT1);
233	glLightfv(GL_LIGHT1, GL_SPECULAR, lights[lightBlue].specular);
234	glLightfv(GL_LIGHT1, GL_DIFFUSE,lights[lightBlue].diffuse);
235	glLightfv(GL_LIGHT1, GL_AMBIENT,lights[lightBlue].ambient);
236
237	glFrontFace(GL_CW);
238	glEnable(GL_LIGHTING);
239	glEnable(GL_AUTO_NORMAL);
240	glEnable(GL_NORMALIZE);
241
242	glMaterialf(GL_FRONT, GL_SHININESS, 0.6 * 128.0);
243
244	glClearColor(bgColor[0], bgColor[1], bgColor[2], 1.0);
245	glColor3f(1.0, 1.0, 1.0);
246
247	glViewport(0, 0, (GLint)bounds.IntegerWidth() + 1,
248				(GLint)bounds.IntegerHeight() + 1);
249	glMatrixMode(GL_PROJECTION);
250	glLoadIdentity();
251
252	float scale = displayScale;
253	glOrtho(-scale, scale, -scale, scale, -scale * depthOfView,
254			scale * depthOfView);
255	glMatrixMode(GL_MODELVIEW);
256	glLoadIdentity();
257
258	UnlockGL();
259
260	fDrawThread = spawn_thread(simonThread, "Simon", B_NORMAL_PRIORITY, this);
261	resume_thread(fDrawThread);
262	fForceRedraw = true;
263	setEvent(drawEvent);
264}
265
266
267void
268ObjectView::DetachedFromWindow()
269{
270	BGLView::DetachedFromWindow();
271
272	status_t dummy;
273	long locks = 0;
274
275	while (Window()->IsLocked()) {
276		locks++;
277		Window()->Unlock();
278	}
279
280	acquire_sem(quittingSem);
281	release_sem(drawEvent);
282	wait_for_thread(fDrawThread, &dummy);
283	release_sem(quittingSem);
284
285	while (locks--)
286		Window()->Lock();
287}
288
289
290void
291ObjectView::Pulse()
292{
293	Window()->Lock();
294	BRect parentBounds = Parent()->Bounds();
295	BRect bounds = Bounds();
296	parentBounds.OffsetTo(0, 0);
297	bounds.OffsetTo(0, 0);
298	if (bounds != parentBounds) {
299		ResizeTo(parentBounds.right - parentBounds.left,
300				 parentBounds.bottom - parentBounds.top);
301	}
302	Window()->Unlock();
303}
304
305
306void
307ObjectView::MessageReceived(BMessage* msg)
308{
309	BMenuItem* item = NULL;
310	bool toggleItem = false;
311
312	switch (msg->what) {
313		case kMsgFPS:
314			fFps = (fFps) ? false : true;
315			msg->FindPointer("source", reinterpret_cast<void**>(&item));
316			item->SetMarked(fFps);
317			fForceRedraw = true;
318			setEvent(drawEvent);
319			break;
320		case kMsgAddModel:
321		{
322			TriangleObject *Tri = new TriangleObject(this);
323			if (Tri->InitCheck() == B_OK) {
324				fObjListLock.Lock();
325				fObjects.AddItem(Tri);
326				fObjListLock.Unlock();
327			} else {
328				BAlert *NoResourceAlert	= new BAlert(B_TRANSLATE("Error"),
329						kNoResourceError, B_TRANSLATE("OK"), NULL, NULL,
330						B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_STOP_ALERT);
331				NoResourceAlert->SetFlags(NoResourceAlert->Flags() | B_CLOSE_ON_ESCAPE);
332				NoResourceAlert->Go();
333				delete Tri;
334			}
335			setEvent(drawEvent);
336			break;
337		}
338		case kMsgLights:
339		{
340			msg->FindPointer("source", reinterpret_cast<void**>(&item));
341			long lightNum = msg->FindInt32("num");
342			long color = msg->FindInt32("color");
343			BMenu *menu = item->Menu();
344			long index = menu->IndexOf(item);
345			menu->ItemAt(index)->SetMarked(true);
346			for (int i = 0; i < menu->CountItems(); i++) {
347				if (i != index)
348					menu->ItemAt(i)->SetMarked(false);
349			}
350
351			LockGL();
352			if (color != lightNone) {
353				glEnable(GL_LIGHT0 + lightNum - 1);
354				glLightfv(GL_LIGHT0 + lightNum - 1, GL_SPECULAR,
355					lights[color].specular);
356				glLightfv(GL_LIGHT0 + lightNum - 1, GL_DIFFUSE,
357					lights[color].diffuse);
358				glLightfv(GL_LIGHT0 + lightNum - 1, GL_AMBIENT,
359					lights[color].ambient);
360			} else {
361				glDisable(GL_LIGHT0 + lightNum - 1);
362			}
363			UnlockGL();
364			fForceRedraw = true;
365			setEvent(drawEvent);
366			break;
367		}
368		case kMsgGouraud:
369			fGouraud = !fGouraud;
370			toggleItem = true;
371			break;
372		case kMsgZBuffer:
373			fZbuf = !fZbuf;
374			toggleItem = true;
375			break;
376		case kMsgCulling:
377			fCulling = !fCulling;
378			toggleItem = true;
379			break;
380		case kMsgLighting:
381			fLighting = !fLighting;
382			toggleItem = true;
383			break;
384		case kMsgFilled:
385			fFilled = !fFilled;
386			toggleItem = true;
387			break;
388		case kMsgPerspective:
389			fPersp = !fPersp;
390			toggleItem = true;
391			break;
392		case kMsgFog:
393			fFog = !fFog;
394			toggleItem = true;
395			break;
396		case kMsgLimitFps:
397			fLimitFps = !fLimitFps;
398			toggleItem = true;
399			break;
400	}
401
402	if (toggleItem && msg->FindPointer("source", reinterpret_cast<void**>(&item)) == B_OK){
403		item->SetMarked(!item->IsMarked());
404		setEvent(drawEvent);
405	}
406
407	BGLView::MessageReceived(msg);
408}
409
410
411int
412ObjectView::ObjectAtPoint(const BPoint &point)
413{
414	LockGL();
415	glShadeModel(GL_FLAT);
416	glDisable(GL_LIGHTING);
417	glDisable(GL_FOG);
418	glClearColor(black[0], black[1], black[2], 1.0);
419	glClear(GL_COLOR_BUFFER_BIT | (fZbuf ? GL_DEPTH_BUFFER_BIT : 0));
420
421	float idColor[3];
422	idColor[1] = idColor[2] = 0;
423	for (int i = 0; i < fObjects.CountItems(); i++) {
424		// to take into account 16 bits colorspaces,
425		// only use the 5 highest bits of the red channel
426		idColor[0] = (255 - (i << 3)) / 255.0;
427		reinterpret_cast<GLObject*>(fObjects.ItemAt(i))->Draw(true, idColor);
428	}
429	glReadBuffer(GL_BACK);
430	uchar pixel[256];
431	glReadPixels((GLint)point.x, (GLint)(Bounds().bottom - point.y), 1, 1,
432		GL_RGB, GL_UNSIGNED_BYTE, pixel);
433	int objNum = pixel[0];
434	objNum = (255 - objNum) >> 3;
435
436	EnforceState();
437	UnlockGL();
438
439	return objNum;
440}
441
442
443void
444ObjectView::MouseDown(BPoint point)
445{
446	GLObject* object = NULL;
447
448	BMessage *msg = Window()->CurrentMessage();
449	uint32 buttons = msg->FindInt32("buttons");
450	object = reinterpret_cast<GLObject*>(fObjects.ItemAt(ObjectAtPoint(point)));
451
452	if (object != NULL){
453		if (buttons == B_PRIMARY_MOUSE_BUTTON || buttons == B_SECONDARY_MOUSE_BUTTON) {
454			fTrackingInfo.pickedObject = object;
455			fTrackingInfo.buttons = buttons;
456			fTrackingInfo.isTracking = true;
457			fTrackingInfo.lastX = point.x;
458			fTrackingInfo.lastY = point.y;
459			fTrackingInfo.lastDx = 0.0f;
460			fTrackingInfo.lastDy = 0.0f;
461			fTrackingInfo.pickedObject->Spin(0.0f, 0.0f);
462
463
464			SetMouseEventMask(B_POINTER_EVENTS,
465						B_LOCK_WINDOW_FOCUS | B_NO_POINTER_HISTORY);
466
467			BCursor grabbingCursor(B_CURSOR_ID_GRABBING);
468			SetViewCursor(&grabbingCursor);
469		} else {
470			ConvertToScreen(&point);
471			object->MenuInvoked(point);
472		}
473	}
474}
475
476
477void
478ObjectView::MouseUp(BPoint point)
479{
480	if (fTrackingInfo.isTracking) {
481
482		//spin the teapot on release, TODO: use a marching sum and divide by time
483		if (fTrackingInfo.buttons == B_PRIMARY_MOUSE_BUTTON
484			&& fTrackingInfo.pickedObject != NULL
485			&& (fabs(fTrackingInfo.lastDx) > 1.0f
486				|| fabs(fTrackingInfo.lastDy) > 1.0f) ) {
487
488			fTrackingInfo.pickedObject->Spin(0.5f * fTrackingInfo.lastDy, 0.5f * fTrackingInfo.lastDx);
489
490			setEvent(drawEvent);
491		}
492
493		//stop tracking
494		fTrackingInfo.isTracking = false;
495		fTrackingInfo.buttons = 0;
496		fTrackingInfo.pickedObject = NULL;
497		fTrackingInfo.lastX = 0.0f;
498		fTrackingInfo.lastY = 0.0f;
499		fTrackingInfo.lastDx = 0.0f;
500		fTrackingInfo.lastDy = 0.0f;
501
502		BCursor grabCursor(B_CURSOR_ID_GRAB);
503		SetViewCursor(&grabCursor);
504	}
505}
506
507
508void
509ObjectView::MouseMoved(BPoint point, uint32 transit, const BMessage *msg)
510{
511	if (fTrackingInfo.isTracking && fTrackingInfo.pickedObject != NULL) {
512
513		float dx = point.x - fTrackingInfo.lastX;
514		float dy = point.y - fTrackingInfo.lastY;
515		fTrackingInfo.lastX = point.x;
516		fTrackingInfo.lastY = point.y;
517
518		if (fTrackingInfo.buttons == B_PRIMARY_MOUSE_BUTTON) {
519
520			fTrackingInfo.pickedObject->Spin(0.0f, 0.0f);
521			fTrackingInfo.pickedObject->RotateWorldSpace(dx,dy);
522			fTrackingInfo.lastDx = dx;
523			fTrackingInfo.lastDy = dy;
524
525			setEvent(drawEvent);
526
527		} else if (fTrackingInfo.buttons == B_SECONDARY_MOUSE_BUTTON) {
528
529			float xinc = (dx * 2 * displayScale / Bounds().Width());
530			float yinc = (-dy * 2 * displayScale / Bounds().Height());
531			float zinc = 0;
532
533			if (fPersp) {
534				zinc = yinc * (fTrackingInfo.pickedObject->z / displayScale);
535				xinc *= -(fTrackingInfo.pickedObject->z * 4 / zRatio);
536				yinc *= -(fTrackingInfo.pickedObject->z * 4 / zRatio);
537			}
538
539			fTrackingInfo.pickedObject->x += xinc;
540			if (modifiers() & B_SHIFT_KEY)
541				fTrackingInfo.pickedObject->z += zinc;
542			else
543	  			fTrackingInfo.pickedObject->y += yinc;
544
545			fForceRedraw = true;
546			setEvent(drawEvent);
547		}
548	} else {
549		void* object = fObjects.ItemAt(ObjectAtPoint(point));
550		BCursor cursor(object != NULL
551			? B_CURSOR_ID_GRAB : B_CURSOR_ID_SYSTEM_DEFAULT);
552		SetViewCursor(&cursor);
553	}
554}
555
556
557void
558ObjectView::FrameResized(float width, float height)
559{
560	BGLView::FrameResized(width, height);
561
562	LockGL();
563
564	width = Bounds().Width();
565	height = Bounds().Height();
566	fYxRatio = height / width;
567	glViewport(0, 0, (GLint)width + 1, (GLint)height + 1);
568
569	// To prevent weird buffer contents
570	glClear(GL_COLOR_BUFFER_BIT);
571
572	glMatrixMode(GL_PROJECTION);
573	glLoadIdentity();
574	float scale = displayScale;
575
576	if (fPersp) {
577		gluPerspective(60, 1.0 / fYxRatio, 0.15, 120);
578	} else {
579		if (fYxRatio < 1) {
580			glOrtho(-scale / fYxRatio, scale / fYxRatio, -scale, scale, -1.0,
581				depthOfView * 4);
582		} else {
583			glOrtho(-scale, scale, -scale * fYxRatio, scale * fYxRatio, -1.0,
584				depthOfView * 4);
585		}
586	}
587
588	fLastYXRatio = fYxRatio;
589
590	glMatrixMode(GL_MODELVIEW);
591
592	UnlockGL();
593
594	fForceRedraw = true;
595	setEvent(drawEvent);
596}
597
598
599bool
600ObjectView::RepositionView()
601{
602	if (!(fPersp != fLastPersp) &&
603		!(fLastObjectDistance != fObjectDistance) &&
604		!(fLastYXRatio != fYxRatio)) {
605		return false;
606	}
607
608	LockGL();
609
610	glMatrixMode(GL_PROJECTION);
611	glLoadIdentity();
612	float scale = displayScale;
613
614	if (fPersp) {
615		gluPerspective(60, 1.0 / fYxRatio, 0.15, 120);
616	} else {
617		if (fYxRatio < 1) {
618			glOrtho(-scale / fYxRatio, scale / fYxRatio, -scale, scale, -1.0,
619				depthOfView * 4);
620		} else {
621			glOrtho(-scale, scale, -scale * fYxRatio, scale * fYxRatio, -1.0,
622				depthOfView * 4);
623		}
624	}
625
626	glMatrixMode(GL_MODELVIEW);
627
628	UnlockGL();
629
630	fLastObjectDistance = fObjectDistance;
631	fLastPersp = fPersp;
632	fLastYXRatio = fYxRatio;
633	return true;
634}
635
636
637void
638ObjectView::EnforceState()
639{
640	glShadeModel(fGouraud ? GL_SMOOTH : GL_FLAT);
641
642	if (fZbuf)
643		glEnable(GL_DEPTH_TEST);
644	else
645		glDisable(GL_DEPTH_TEST);
646
647	if (fCulling)
648		glEnable(GL_CULL_FACE);
649	else
650		glDisable(GL_CULL_FACE);
651
652	if (fLighting)
653		glEnable(GL_LIGHTING);
654	else
655		glDisable(GL_LIGHTING);
656
657	if (fFilled)
658		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
659	else
660		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
661
662	if (fFog) {
663		glFogf(GL_FOG_START, 10.0);
664		glFogf(GL_FOG_DENSITY, 0.2);
665		glFogf(GL_FOG_END, depthOfView);
666		glFogfv(GL_FOG_COLOR, foggy);
667		glEnable(GL_FOG);
668		bgColor = foggy;
669		glClearColor(bgColor[0], bgColor[1], bgColor[2], 1.0);
670	} else {
671		glDisable(GL_FOG);
672		bgColor = black;
673		glClearColor(bgColor[0], bgColor[1], bgColor[2], 1.0);
674	}
675}
676
677
678bool
679ObjectView::SpinIt()
680{
681	bool changed = false;
682
683	if (fGouraud != fLastGouraud) {
684		LockGL();
685		glShadeModel(fGouraud ? GL_SMOOTH : GL_FLAT);
686		UnlockGL();
687		fLastGouraud = fGouraud;
688		changed = true;
689	}
690
691	if (fZbuf != fLastZbuf) {
692		LockGL();
693		if (fZbuf)
694			glEnable(GL_DEPTH_TEST);
695		else
696			glDisable(GL_DEPTH_TEST);
697		UnlockGL();
698		fLastZbuf = fZbuf;
699		changed = true;
700	}
701
702	if (fCulling != fLastCulling) {
703		LockGL();
704		if (fCulling)
705			glEnable(GL_CULL_FACE);
706		else
707			glDisable(GL_CULL_FACE);
708		UnlockGL();
709		fLastCulling = fCulling;
710		changed = true;
711	}
712
713	if (fLighting != fLastLighting) {
714		LockGL();
715		if (fLighting)
716			glEnable(GL_LIGHTING);
717		else
718			glDisable(GL_LIGHTING);
719		UnlockGL();
720		fLastLighting = fLighting;
721		changed = true;
722	}
723
724	if (fFilled != fLastFilled) {
725		LockGL();
726		if (fFilled) {
727			glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
728		} else {
729			glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
730		}
731		UnlockGL();
732		fLastFilled = fFilled;
733		changed = true;
734	}
735
736	if (fFog != fLastFog) {
737		if (fFog) {
738			glFogf(GL_FOG_START, 1.0);
739			glFogf(GL_FOG_DENSITY, 0.2);
740			glFogf(GL_FOG_END, depthOfView);
741			glFogfv(GL_FOG_COLOR, foggy);
742			glEnable(GL_FOG);
743			bgColor = foggy;
744			glClearColor(bgColor[0], bgColor[1], bgColor[2], 1.0);
745		} else {
746			glDisable(GL_FOG);
747			bgColor = black;
748			glClearColor(bgColor[0], bgColor[1], bgColor[2], 1.0);
749		}
750		fLastFog = fFog;
751		changed = true;
752	}
753
754	changed = changed || RepositionView();
755	changed = changed || fForceRedraw;
756	fForceRedraw = false;
757
758	for (int i = 0; i < fObjects.CountItems(); i++) {
759		bool hack = reinterpret_cast<GLObject*>(fObjects.ItemAt(i))->SpinIt();
760		changed = changed || hack;
761	}
762
763	return changed;
764}
765
766
767void
768ObjectView::DrawFrame(bool noPause)
769{
770	LockGL();
771	glClear(GL_COLOR_BUFFER_BIT | (fZbuf ? GL_DEPTH_BUFFER_BIT : 0));
772
773	fObjListLock.Lock();
774	for (int i = 0; i < fObjects.CountItems(); i++) {
775	  GLObject *object = reinterpret_cast<GLObject*>(fObjects.ItemAt(i));
776		if (object->Solidity() == 0)
777			object->Draw(false, NULL);
778	}
779	EnforceState();
780	for (int i = 0; i < fObjects.CountItems(); i++) {
781		GLObject *object = reinterpret_cast<GLObject*>(fObjects.ItemAt(i));
782		if (object->Solidity() != 0)
783			object->Draw(false, NULL);
784	}
785	fObjListLock.Unlock();
786
787	glDisable(GL_BLEND);
788	glDepthMask(GL_TRUE);
789
790	if (noPause) {
791		uint64 now = system_time();
792		float fps = 1.0 / ((now - fLastFrame) / 1000000.0);
793		fLastFrame = now;
794		int entry;
795		if (fHistEntries < HISTSIZE) {
796			entry = (fOldestEntry + fHistEntries) % HISTSIZE;
797			fHistEntries++;
798		} else {
799			entry = fOldestEntry;
800			fOldestEntry = (fOldestEntry + 1) % HISTSIZE;
801		}
802
803		fFpsHistory[entry] = fps;
804
805		if (fHistEntries > 5) {
806			fps = 0;
807			for (int i = 0; i < fHistEntries; i++)
808				fps += fFpsHistory[(fOldestEntry + i) % HISTSIZE];
809
810			fps /= fHistEntries;
811
812			if (fFps) {
813				glPushAttrib(GL_ENABLE_BIT | GL_LIGHTING_BIT);
814				glPushMatrix();
815				glLoadIdentity();
816				glTranslatef(-0.9, -0.9, 0);
817				glScalef(0.10, 0.10, 0.10);
818				glDisable(GL_LIGHTING);
819				glDisable(GL_DEPTH_TEST);
820				glDisable(GL_BLEND);
821				glColor3f(1.0, 1.0, 0);
822				glMatrixMode(GL_PROJECTION);
823				glPushMatrix();
824				glLoadIdentity();
825				glMatrixMode(GL_MODELVIEW);
826
827				FPS::drawCounter(fps);
828
829				glMatrixMode(GL_PROJECTION);
830				glPopMatrix();
831				glMatrixMode(GL_MODELVIEW);
832				glPopMatrix();
833				glPopAttrib();
834			}
835		}
836	} else {
837		fHistEntries = 0;
838		fOldestEntry = 0;
839	}
840	SwapBuffers();
841	UnlockGL();
842}
843