Voxel
 All Classes Namespaces Files Functions Typedefs Enumerations Enumerator Macros Pages
winmain.cpp
1 #include "octree.h"
2 #include "synchronized.h"
3 #include "renderer.h"
4 #include "vector.h"
5 #include "exception.h"
6 #include "windows_exception.h"
7 #include "helpers.h"
8 #include "settings.h"
9 
10 #ifdef _OPENMP
11 #include <omp.h>
12 #endif // _OPENMP
13 
14 #define NOMINMAX
15 #include <windows.h>
16 #include <windowsx.h>
17 #include <wrl.h> // ComPtr
18 
19 #include <d2d1.h>
20 #include <dwrite.h>
21 
22 #include <sstream>
23 #include <array>
24 #include <cmath>
25 
26 #include <stdio.h>
27 #include <wchar.h>
28 #include <assert.h>
29 
30 #pragma comment( lib, "d2d1.lib" )
31 #pragma comment( lib, "dwrite.lib" )
32 
33 
34 
35 
36 /*==============================================================================
37  Global variables
38 ==============================================================================*/
39 
40 Microsoft::WRL::ComPtr< ID2D1Factory > g_pD2DFactory;
41 Microsoft::WRL::ComPtr< ID2D1HwndRenderTarget > g_pRenderTarget;
42 Microsoft::WRL::ComPtr< ID2D1SolidColorBrush > g_pTextBrush;
43 
44 Microsoft::WRL::ComPtr< IDWriteFactory > g_pDWriteFactory;
45 Microsoft::WRL::ComPtr< IDWriteTextFormat > g_pTextFormat;
46 
47 D2D1_SIZE_U g_bitmapSize = { 0, 0 };
48 Microsoft::WRL::ComPtr< ID2D1Bitmap > g_pBitmap;
49 
50 double g_fps = 0;
51 double g_rps = 0;
52 
53 enum MovementKeys {
54 
55  MOVEMENT_KEY_MOVE_FORWARD = 0,
56  MOVEMENT_KEY_MOVE_BACK,
57  MOVEMENT_KEY_MOVE_DOWN,
58  MOVEMENT_KEY_MOVE_UP,
59  MOVEMENT_KEY_MOVE_RIGHT,
60  MOVEMENT_KEY_MOVE_LEFT,
61  MOVEMENT_KEY_TURN_COUNTERCLOCKWISE,
62  MOVEMENT_KEY_TURN_CLOCKWISE,
63  MOVEMENT_KEY_TURN_RIGHT,
64  MOVEMENT_KEY_TURN_LEFT,
65  MOVEMENT_KEY_TURN_UP,
66  MOVEMENT_KEY_TURN_DOWN,
67  MOVEMENT_KEY_ZOOM_IN,
68  MOVEMENT_KEY_ZOOM_OUT,
69 
70  MOVEMENT_KEYS_SIZE
71 };
72 bool g_movementKeys[ MOVEMENT_KEYS_SIZE ];
73 
74 
75 
76 
77 /*==============================================================================
78  Redraw function
79 ==============================================================================*/
80 
81 
82 template< int t_ColorFormat, int t_Platform, int t_RendererPlatform >
83 void Redraw(
84  unsigned int const width,
85  unsigned int const height,
86  Synchronized::Heap< t_Platform >& heap,
87  Synchronized::TextureHeap< t_ColorFormat, t_Platform >& textureHeap,
88  typename Synchronized::Heap< t_Platform >::template Pointer< Octree::Node< t_ColorFormat, t_Platform > > const& pRoot,
89  Renderer::State< t_RendererPlatform > const& renderer,
90  Renderer::Camera const& camera
91 )
92 {
93  if ( ( g_bitmapSize.height <= 0 ) || ( g_bitmapSize.width <= 0 ) )
94  THROW_EXCEPTION( "bitmap is not initialized!" );
95 
96  BYTE* buffer = new BYTE[ g_bitmapSize.height * g_bitmapSize.width * 4 ];
97  { Vector< unsigned int, 2 > const dimensions = { { g_bitmapSize.width, g_bitmapSize.height } };
98 
99 #if 0
100  typename Synchronized::Heap< t_Platform >::template Pointer< Octree::Node< t_ColorFormat, t_Platform > > const pSubtractedRoot = Octree::Subtract(
101  heap,
102  textureHeap,
103  pRoot,
104  g_coordinates,
105  0.1,
106  2
107  );
108  heap.Synchronize();
109  textureHeap.Synchronize();
110 #else // 0/1
111  typename Synchronized::Heap< t_Platform >::template Pointer< Octree::Node< t_ColorFormat, t_Platform > > const& pSubtractedRoot = pRoot;
112 #endif // 0/1
113 
114  renderer.TraceScreen(
115  buffer,
116  dimensions,
117  dimensions[ 0 ] * 4,
118  heap,
119  textureHeap,
120  pSubtractedRoot,
121  camera
122  );
123  }
124 
125  CHECK_HRESULT( g_pBitmap->CopyFromMemory( NULL, buffer, g_bitmapSize.width * 4 ) );
126 
127  delete[] buffer;
128 
129  wchar_t string[ 128 ];
130 #ifdef _OPENMP
131  unsigned int const threads = omp_get_max_threads();
132 #else // _OPENMP
133  unsigned int const threads = 1;
134 #endif // _OPENMP
135  _snwprintf_s( string, ARRAYLENGTH( string ), L"threads = %u, resolution = %dx%d\n%.2f Mrays / frame\n%.2f frames / sec\n%.2f Mrays / sec", threads, g_bitmapSize.width, g_bitmapSize.height, g_bitmapSize.width * g_bitmapSize.height / 1000000.0, g_fps, g_rps / 1000000 );
136 
137  D2D1_SIZE_F const targetSize = g_pRenderTarget->GetSize();
138  D2D1_RECT_F const bitmapTargetRect = { 0, 0, targetSize.width, targetSize.height };
139  D2D1_RECT_F const textTargetRect = { 8, 8, targetSize.width, targetSize.height };
140 
141  g_pRenderTarget->BeginDraw();
142  g_pRenderTarget->DrawBitmap( g_pBitmap.Get(), bitmapTargetRect );
143  g_pRenderTarget->DrawText(
144  string,
145  wcslen( string ),
146  g_pTextFormat.Get(),
147  &textTargetRect,
148  g_pTextBrush.Get(),
149  D2D1_DRAW_TEXT_OPTIONS_NONE,
150  DWRITE_MEASURING_MODE_NATURAL
151  );
152  g_pRenderTarget->EndDraw();
153 }
154 
155 
156 
157 
158 /*==============================================================================
159  WindowProc function
160 ==============================================================================*/
161 
162 
163 LRESULT CALLBACK WindowProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) {
164 
165  LRESULT result = 0;
166 
167  try {
168 
169  switch( message ) {
170 
171  case WM_KEYUP: {
172 
173  switch( wParam ) {
174  case 'W': { g_movementKeys[ MOVEMENT_KEY_MOVE_FORWARD ] = false; break; }
175  case 'S': { g_movementKeys[ MOVEMENT_KEY_MOVE_BACK ] = false; break; }
176  case VK_NEXT: { g_movementKeys[ MOVEMENT_KEY_MOVE_DOWN ] = false; break; }
177  case VK_PRIOR: { g_movementKeys[ MOVEMENT_KEY_MOVE_UP ] = false; break; }
178  case 'D': { g_movementKeys[ MOVEMENT_KEY_MOVE_RIGHT ] = false; break; }
179  case 'A': { g_movementKeys[ MOVEMENT_KEY_MOVE_LEFT ] = false; break; }
180  case 'Q': { g_movementKeys[ MOVEMENT_KEY_TURN_COUNTERCLOCKWISE ] = false; break; }
181  case 'E': { g_movementKeys[ MOVEMENT_KEY_TURN_CLOCKWISE ] = false; break; }
182  case VK_RIGHT: { g_movementKeys[ MOVEMENT_KEY_TURN_RIGHT ] = false; break; }
183  case VK_LEFT: { g_movementKeys[ MOVEMENT_KEY_TURN_LEFT ] = false; break; }
184  case VK_UP: { g_movementKeys[ MOVEMENT_KEY_TURN_UP ] = false; break; }
185  case VK_DOWN: { g_movementKeys[ MOVEMENT_KEY_TURN_DOWN ] = false; break; }
186  case VK_HOME: { g_movementKeys[ MOVEMENT_KEY_ZOOM_OUT ] = false; break; }
187  case VK_END: { g_movementKeys[ MOVEMENT_KEY_ZOOM_IN ] = false; break; }
188  case VK_ESCAPE: { DestroyWindow( hWnd ); break; }
189  };
190  break;
191  }
192 
193  case WM_KEYDOWN: {
194 
195  switch( wParam ) {
196  case 'W': { g_movementKeys[ MOVEMENT_KEY_MOVE_FORWARD ] = true; break; }
197  case 'S': { g_movementKeys[ MOVEMENT_KEY_MOVE_BACK ] = true; break; }
198  case VK_NEXT: { g_movementKeys[ MOVEMENT_KEY_MOVE_DOWN ] = true; break; }
199  case VK_PRIOR: { g_movementKeys[ MOVEMENT_KEY_MOVE_UP ] = true; break; }
200  case 'D': { g_movementKeys[ MOVEMENT_KEY_MOVE_RIGHT ] = true; break; }
201  case 'A': { g_movementKeys[ MOVEMENT_KEY_MOVE_LEFT ] = true; break; }
202  case 'Q': { g_movementKeys[ MOVEMENT_KEY_TURN_COUNTERCLOCKWISE ] = true; break; }
203  case 'E': { g_movementKeys[ MOVEMENT_KEY_TURN_CLOCKWISE ] = true; break; }
204  case VK_RIGHT: { g_movementKeys[ MOVEMENT_KEY_TURN_RIGHT ] = true; break; }
205  case VK_LEFT: { g_movementKeys[ MOVEMENT_KEY_TURN_LEFT ] = true; break; }
206  case VK_UP: { g_movementKeys[ MOVEMENT_KEY_TURN_UP ] = true; break; }
207  case VK_DOWN: { g_movementKeys[ MOVEMENT_KEY_TURN_DOWN ] = true; break; }
208  case VK_HOME: { g_movementKeys[ MOVEMENT_KEY_ZOOM_OUT ] = true; break; }
209  case VK_END: { g_movementKeys[ MOVEMENT_KEY_ZOOM_IN ] = true; break; }
210  };
211  break;
212  }
213 
214  case WM_DESTROY: {
215 
216  PostQuitMessage( ERROR_SUCCESS );
217  break;
218  }
219 
220  case WM_SIZE: {
221 
222  RECT clientRect = { 0, 0, 0, 0 };
223  GetClientRect( hWnd, &clientRect );
224  { D2D1_SIZE_U size = D2D1::SizeU( clientRect.right - clientRect.left, clientRect.bottom - clientRect.top );
225  CHECK_HRESULT( g_pRenderTarget->Resize( size ) );
226  }
227 
228  D2D1_SIZE_U bitmapSize = {
229  clientRect.right - clientRect.left,
230  clientRect.bottom - clientRect.top
231  };
232 
233  if ( ( bitmapSize.width != g_bitmapSize.width ) || ( bitmapSize.height != g_bitmapSize.height ) ) {
234 
235  g_bitmapSize = bitmapSize;
236 
237  D2D1_BITMAP_PROPERTIES properties;
238  ZeroMemory( &properties, sizeof( properties ) );
239  properties.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM; // **NOTE: BGRA
240  properties.pixelFormat.alphaMode = D2D1_ALPHA_MODE_IGNORE;
241  properties.dpiX = 96.0;
242  properties.dpiY = 96.0;
243 
244  CHECK_HRESULT( g_pRenderTarget->CreateBitmap( g_bitmapSize, properties, &g_pBitmap ) );
245  }
246 
247  result = DefWindowProc( hWnd, message, wParam, lParam );
248  break;
249  }
250 
251  default: {
252 
253  result = DefWindowProc( hWnd, message, wParam, lParam );
254  break;
255  }
256  }
257  }
258  catch( std::exception& exception ) {
259 
260 #ifdef _UNICODE
261  char const* const what8 = exception.what();
262  wchar_t what[ 256 ];
263  MultiByteToWideChar( CP_UTF8, 0, what8, -1, what, ARRAYLENGTH( what ) );
264 #else // _UNICODE
265  char const* const what = exception.what();
266 #endif // _UNICODE
267 
268  MessageBox( NULL, what, TEXT( "WindowProc - caught exception" ), MB_ICONEXCLAMATION | MB_OK );
269  PostQuitMessage( ERROR_UNHANDLED_EXCEPTION );
270  }
271  catch( ... ) {
272 
273  MessageBox( NULL, TEXT( "<unknown error>" ), TEXT( "WindowProc - caught exception" ), MB_ICONEXCLAMATION | MB_OK );
274  PostQuitMessage( ERROR_UNHANDLED_EXCEPTION );
275  }
276 
277  return result;
278 }
279 
280 
281 
282 
283 /*==============================================================================
284  WinMain function
285 ==============================================================================*/
286 
287 
288 int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) {
289 
290  LPCTSTR szClassName = TEXT( "WindowClass" );
291  LPCTSTR szTitle = TEXT( "Voxel" );
292 
293  int result = ERROR_SUCCESS;
294 
295  try {
296 
297  // ==== initialize voxel and keyboard data ====
298 
299  //enum { COLOR_FORMAT = COLOR_FORMAT_RGB_565 };
300  enum { COLOR_FORMAT = COLOR_FORMAT_RGBA_8888 };
301 #if ( SETTING_OPENCL != 0 )
302  std::shared_ptr< OpenCL::State > pState( new OpenCL::State() );
303  Synchronized::Heap< PLATFORM_OPENCL > heap( pState, ( 1u << 25 ) );
304  Synchronized::TextureHeap< COLOR_FORMAT, PLATFORM_OPENCL > textureHeap( pState, 27 );
305  Synchronized::Heap< PLATFORM_OPENCL >::Pointer< Octree::Node< COLOR_FORMAT, PLATFORM_OPENCL > > pRoot = Octree::Create( heap, textureHeap );
306  Renderer::State< PLATFORM_OPENCL > renderer( pState );
307 #else // SETTING_OPENCL
309  Synchronized::TextureHeap< COLOR_FORMAT, PLATFORM_HOST > textureHeap;
310  Synchronized::Heap< PLATFORM_HOST >::Pointer< Octree::Node< COLOR_FORMAT, PLATFORM_HOST > > pRoot = Octree::Create( heap, textureHeap );
312 #endif // SETTING_OPENCL
313  heap.Synchronize();
314  textureHeap.Synchronize();
315 
316  Vector< double, 3 > const initialX = { { 1, 0, 0 } };
317  Vector< double, 3 > const initialY = { { 0, 1, 0 } };
318  Vector< double, 3 > const initialZ = { { 0, 0, 1 } };
319  Vector< double, 3 > const initialPosition = { { 0.5, 0.5, 1.5 } };
320  Renderer::Camera camera( initialX, initialY, initialZ, initialPosition );
321 
322  std::fill( g_movementKeys, g_movementKeys + ARRAYLENGTH( g_movementKeys ), false );
323 
324  double const fpsRate = 0.95;
325  double const rateTurn = 0.8;
326  double const rateZoom = 0.4;
327  double const rateMove = 0.2;
328 
329  // ==== create main window ====
330 
331  WNDCLASSEX windowClass;
332  ZeroMemory( &windowClass, sizeof( windowClass ) );
333  windowClass.cbSize = sizeof( windowClass );
334  windowClass.style = CS_HREDRAW | CS_VREDRAW;
335  windowClass.lpfnWndProc = WindowProc;
336  windowClass.cbClsExtra = 0;
337  windowClass.cbWndExtra = 0;
338  windowClass.hInstance = hInstance;
339  windowClass.hIcon = LoadIcon( NULL, IDI_APPLICATION );
340  windowClass.hCursor = LoadCursor( NULL, IDC_ARROW );
341  windowClass.hbrBackground = ( HBRUSH )COLOR_WINDOW;
342  windowClass.lpszMenuName = NULL;
343  windowClass.lpszClassName = szClassName;
344  windowClass.hIconSm = LoadIcon( NULL, IDI_APPLICATION );
345  if ( ! RegisterClassEx( &windowClass ) )
346  THROW_WINDOWS_EXCEPTION( "RegisterClassEx failed!", HRESULT_FROM_WIN32( GetLastError() ) );
347 
348  RECT windowRect = { 0, 0, 800, 600 };
349  AdjustWindowRect(
350  &windowRect,
351  WS_OVERLAPPEDWINDOW,
352  //WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
353  FALSE
354  );
355 
356  HWND hWnd = CreateWindowEx(
357  WS_EX_CLIENTEDGE,
358  szClassName,
359  szTitle,
360  WS_OVERLAPPEDWINDOW,
361  //WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
362  CW_USEDEFAULT,
363  CW_USEDEFAULT,
364  windowRect.right - windowRect.left,
365  windowRect.bottom - windowRect.top,
366  NULL,
367  NULL,
368  hInstance,
369  NULL
370  );
371  if ( hWnd == NULL )
372  THROW_EXCEPTION( "CreateWindowEx failed!" );
373 
374  // ==== initialize DirectX ====
375 
376  D2D1_FACTORY_OPTIONS options;
377  ZeroMemory( &options, sizeof( options ) );
378 
379  CHECK_HRESULT( D2D1CreateFactory( D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof( ID2D1Factory ), &options, &g_pD2DFactory ) );
380 
381  GetClientRect( hWnd, &windowRect );
382  { D2D1_SIZE_U size = D2D1::SizeU( windowRect.right - windowRect.left, windowRect.bottom - windowRect.top );
383  CHECK_HRESULT( g_pD2DFactory->CreateHwndRenderTarget( D2D1::RenderTargetProperties(), D2D1::HwndRenderTargetProperties( hWnd, size ), &g_pRenderTarget ) );
384  }
385 
386  CHECK_HRESULT( g_pRenderTarget->CreateSolidColorBrush(
387  D2D1::ColorF( D2D1::ColorF::White, 1 ),
388  &g_pTextBrush
389  ) );
390 
391  CHECK_HRESULT( DWriteCreateFactory( DWRITE_FACTORY_TYPE_SHARED, __uuidof( IDWriteFactory ), &g_pDWriteFactory ) );
392 
393  CHECK_HRESULT( g_pDWriteFactory->CreateTextFormat(
394  L"Verdana",
395  NULL,
396  DWRITE_FONT_WEIGHT_REGULAR,
397  DWRITE_FONT_STYLE_NORMAL,
398  DWRITE_FONT_STRETCH_NORMAL,
399  12,
400  L"en-us",
401  &g_pTextFormat
402  ) );
403 
404  // ==== message loop ====
405 
406  ShowWindow( hWnd, nCmdShow );
407  UpdateWindow( hWnd );
408 
409  double frames = 0;
410  double rays = 0;
411  double seconds = 0;
412  DWORD ticks = GetTickCount();
413 
414  MSG msg;
415  for ( bool done = false; ! done; ) {
416 
417  // ==== draw frame ====
418 
419  { RECT clientRect = windowRect;
420  GetClientRect( hWnd, &clientRect );
421 
422  Redraw( clientRect.right - clientRect.left, clientRect.bottom - clientRect.top, heap, textureHeap, pRoot, renderer, camera );
423  ValidateRect( hWnd, NULL );
424  }
425 
426  // ==== update counters ====
427 
428  DWORD const ticksOld = ticks;
429  ticks = GetTickCount();
430  double const deltaSeconds = static_cast< double >( ticks - ticksOld ) / 1000;
431 
432  frames = fpsRate * frames + ( 1 - fpsRate );
433  rays = fpsRate * rays + ( 1 - fpsRate ) * g_bitmapSize.width * g_bitmapSize.height;
434  seconds = fpsRate * seconds + ( 1 - fpsRate ) * deltaSeconds;
435 
436  g_fps = frames / seconds;
437  g_rps = rays / seconds;
438 
439  // ==== message loop ====
440 
441  for ( ; ; ) {
442 
443  BOOL peekResult = PeekMessage( &msg, NULL, 0, 0, PM_REMOVE );
444  if ( peekResult < 0 )
445  THROW_EXCEPTION( "PeekMessage failed!" );
446  if ( peekResult == 0 )
447  break;
448 
449  TranslateMessage( &msg );
450  DispatchMessage( &msg );
451 
452  if ( msg.message == WM_QUIT ) {
453 
454  result = msg.wParam;
455  done = true;
456  break;
457  }
458  }
459 
460  // ==== update position ====
461 
462  { double const move = ( g_movementKeys[ MOVEMENT_KEY_MOVE_FORWARD ] ? 1.0 : 0.0 ) - ( g_movementKeys[ MOVEMENT_KEY_MOVE_BACK ] ? 1.0 : 0.0 );
463  if ( move != 0 ) {
464 
465  double const rate = move * rateMove * deltaSeconds;
466  camera.MoveForward( rate );
467  }
468  }
469  { double const move = ( g_movementKeys[ MOVEMENT_KEY_MOVE_DOWN ] ? 1.0 : 0.0 ) - ( g_movementKeys[ MOVEMENT_KEY_MOVE_UP ] ? 1.0 : 0.0 );
470  if ( move != 0 ) {
471 
472  double const rate = move * rateMove * deltaSeconds;
473  camera.MoveDown( rate );
474  }
475  }
476  { double const move = ( g_movementKeys[ MOVEMENT_KEY_MOVE_RIGHT ] ? 1.0 : 0.0 ) - ( g_movementKeys[ MOVEMENT_KEY_MOVE_LEFT ] ? 1.0 : 0.0 );
477  if ( move != 0 ) {
478 
479  double const rate = move * rateMove * deltaSeconds;
480  camera.MoveRight( rate );
481  }
482  }
483  { double const turn = ( g_movementKeys[ MOVEMENT_KEY_TURN_CLOCKWISE ] ? 1.0 : 0.0 ) - ( g_movementKeys[ MOVEMENT_KEY_TURN_COUNTERCLOCKWISE ] ? 1.0 : 0.0 );
484  if ( turn != 0 ) {
485 
486  double const rate = turn * rateTurn * deltaSeconds;
487  camera.TurnClockwise( rate );
488  }
489  }
490  { double const turn = ( g_movementKeys[ MOVEMENT_KEY_TURN_RIGHT ] ? 1.0 : 0.0 ) - ( g_movementKeys[ MOVEMENT_KEY_TURN_LEFT ] ? 1.0 : 0.0 );
491  if ( turn != 0 ) {
492 
493  double const rate = turn * rateTurn * deltaSeconds;
494  camera.TurnRight( rate );
495  }
496  }
497  { double const turn = ( g_movementKeys[ MOVEMENT_KEY_TURN_DOWN ] ? 1.0 : 0.0 ) - ( g_movementKeys[ MOVEMENT_KEY_TURN_UP ] ? 1.0 : 0.0 );
498  if ( turn != 0 ) {
499 
500  double const rate = turn * rateTurn * deltaSeconds;
501  camera.TurnDown( rate );
502  }
503  }
504  { double const zoom = ( g_movementKeys[ MOVEMENT_KEY_ZOOM_OUT ] ? 1.0 : 0.0 ) - ( g_movementKeys[ MOVEMENT_KEY_ZOOM_IN ] ? 1.0 : 0.0 );
505  if ( zoom != 0 ) {
506 
507  double const rate = zoom * rateZoom * deltaSeconds;
508  camera.ZoomOut( rate );
509  }
510  }
511  }
512  }
513  catch( std::exception& exception ) {
514 
515 #ifdef _UNICODE
516  char const* const what8 = exception.what();
517  wchar_t what[ 256 ];
518  MultiByteToWideChar( CP_UTF8, 0, what8, -1, what, ARRAYLENGTH( what ) );
519 #else // _UNICODE
520  char const* const what = exception.what();
521 #endif // _UNICODE
522 
523  MessageBox( NULL, what, TEXT( "WinMain - caught exception" ), MB_ICONEXCLAMATION | MB_OK );
524  result = ERROR_UNHANDLED_EXCEPTION;
525  }
526  catch( ... ) {
527 
528  MessageBox( NULL, TEXT( "<unknown error>" ), TEXT( "WinMain - caught exception" ), MB_ICONEXCLAMATION | MB_OK );
529  result = ERROR_UNHANDLED_EXCEPTION;
530  }
531 
532  return result;
533 }