From ef56c16b2b717f8bf99acd6a89f9d8349d2f6b6a Mon Sep 17 00:00:00 2001 From: mfeemster Date: Tue, 8 Jul 2014 00:11:14 -0700 Subject: [PATCH] Initial source commit Initial source commit --- Builds/MSVC/VS2010/Ember.vcxproj | 315 + Builds/MSVC/VS2010/Ember.vcxproj.filters | 131 + Builds/MSVC/VS2010/EmberAnimate.vcxproj | 335 + .../MSVC/VS2010/EmberAnimate.vcxproj.filters | 62 + Builds/MSVC/VS2010/EmberCL.vcxproj | 304 + Builds/MSVC/VS2010/EmberCL.vcxproj.filters | 69 + Builds/MSVC/VS2010/EmberGenome.vcxproj | 329 + .../MSVC/VS2010/EmberGenome.vcxproj.filters | 62 + Builds/MSVC/VS2010/EmberRender.vcxproj | 337 + .../MSVC/VS2010/EmberRender.vcxproj.filters | 62 + Builds/MSVC/VS2010/EmberTester.vcxproj | 324 + .../MSVC/VS2010/EmberTester.vcxproj.filters | 51 + Builds/MSVC/VS2010/Fractorium.sln | 981 + Builds/MSVC/VS2010/Fractorium.vcxproj | 1109 + Builds/MSVC/VS2010/Fractorium.vcxproj.filters | 338 + .../FractoriumInstaller.wixproj | 74 + .../VS2010/FractoriumInstaller/Product.wxs | 186 + .../VS2010/FractoriumInstaller/msvcp100.dll | Bin 0 -> 608080 bytes .../VS2010/FractoriumInstaller/msvcr100.dll | Bin 0 -> 829264 bytes Builds/MSVC/VS2010/ReadMe.txt | 277 + Builds/MSVC/VS2010/zlib.props | 37 + Data/Variations.xlsx | Bin 0 -> 28988 bytes Data/Version History.txt | 686 + Data/Wiki/AboutDialog.htm | 26 + Data/Wiki/AlgorithmExplanation.htm | 318 + Data/Wiki/Building.htm | 232 + Data/Wiki/CodingPhilosophy.htm | 15 + Data/Wiki/CommandLinePrograms.htm | 29 + Data/Wiki/Developers.htm | 9 + Data/Wiki/EmberCLImplementationDetails.htm | 266 + Data/Wiki/EmberImplementationDetails.htm | 171 + Data/Wiki/FinalRenderDialog.htm | 229 + Data/Wiki/FlameTab.htm | 319 + Data/Wiki/FractoriumUserGuide.htm | 159 + Data/Wiki/InfoTab.htm | 45 + Data/Wiki/LibraryTab.htm | 34 + Data/Wiki/MainPage.htm | 101 + Data/Wiki/Menus.htm | 214 + Data/Wiki/OptionsDialog.htm | 178 + Data/Wiki/PaletteTab.htm | 108 + Data/Wiki/ProjectOverview.htm | 88 + Data/Wiki/SideBar.htm | 21 + Data/Wiki/Toolbar.htm | 9 + Data/Wiki/Users.htm | 18 + Data/Wiki/XformsTab.htm | 343 + Data/flam3-palettes.xml | 23135 ++++++++++++++++ Data/gplv3.rtf | 474 + Source/Ember/Affine2D.cpp | 352 + Source/Ember/Affine2D.h | 142 + Source/Ember/CarToRas.h | 252 + Source/Ember/DensityFilter.h | 340 + Source/Ember/DllMain.cpp | 20 + Source/Ember/Ember.cpp | 410 + Source/Ember/Ember.h | 1607 ++ Source/Ember/EmberDefines.h | 83 + Source/Ember/EmberPch.cpp | 1 + Source/Ember/EmberPch.h | 62 + Source/Ember/EmberToXml.h | 686 + Source/Ember/Interpolate.h | 1018 + Source/Ember/Isaac.h | 386 + Source/Ember/Iterator.h | 541 + Source/Ember/Palette.h | 571 + Source/Ember/PaletteList.h | 226 + Source/Ember/Point.h | 217 + Source/Ember/Renderer.cpp | 2222 ++ Source/Ember/Renderer.h | 412 + Source/Ember/SheepTools.h | 1394 + Source/Ember/SpatialFilter.h | 909 + Source/Ember/TemporalFilter.h | 347 + Source/Ember/Timing.cpp | 7 + Source/Ember/Timing.h | 231 + Source/Ember/Utils.h | 887 + Source/Ember/Variation.h | 2168 ++ Source/Ember/VariationList.h | 537 + Source/Ember/Variations01.h | 6296 +++++ Source/Ember/Variations02.h | 5756 ++++ Source/Ember/Variations03.h | 4596 +++ Source/Ember/Variations04.h | 5233 ++++ Source/Ember/Variations05.h | 3253 +++ Source/Ember/VariationsDC.h | 1039 + Source/Ember/Xform.h | 1173 + Source/Ember/XmlToEmber.h | 1350 + Source/EmberAnimate/EmberAnimate.cpp | 372 + Source/EmberAnimate/EmberAnimate.h | 16 + Source/EmberAnimate/EmberAnimate.rc | 98 + Source/EmberAnimate/resource.h | 15 + Source/EmberCL/DEOpenCLKernelCreator.cpp | 698 + Source/EmberCL/DEOpenCLKernelCreator.h | 89 + Source/EmberCL/DllMain.cpp | 20 + Source/EmberCL/EmberCLFunctions.h | 413 + Source/EmberCL/EmberCLPch.h | 39 + Source/EmberCL/EmberCLStructs.h | 383 + .../EmberCL/FinalAccumOpenCLKernelCreator.cpp | 517 + .../EmberCL/FinalAccumOpenCLKernelCreator.h | 87 + Source/EmberCL/IterOpenCLKernelCreator.cpp | 785 + Source/EmberCL/IterOpenCLKernelCreator.h | 89 + Source/EmberCL/OpenCLWrapper.cpp | 1366 + Source/EmberCL/OpenCLWrapper.h | 219 + Source/EmberCL/RendererCL.cpp | 1340 + Source/EmberCL/RendererCL.h | 156 + Source/EmberCommon/EmberCommon.h | 261 + Source/EmberCommon/EmberCommonPch.cpp | 1 + Source/EmberCommon/EmberCommonPch.h | 54 + Source/EmberCommon/EmberOptions.h | 705 + Source/EmberCommon/JpegUtils.h | 362 + Source/EmberCommon/SimpleGlob.h | 959 + Source/EmberCommon/SimpleOpt.h | 1063 + Source/EmberGenome/EmberGenome.cpp | 776 + Source/EmberGenome/EmberGenome.h | 23 + Source/EmberGenome/EmberGenome.rc | 98 + Source/EmberGenome/resource.h | 15 + Source/EmberRender/EmberRender.cpp | 386 + Source/EmberRender/EmberRender.h | 16 + Source/EmberRender/EmberRender.rc | 98 + Source/EmberRender/resource.h | 15 + Source/EmberTester/EmberTester.cpp | 1671 ++ Source/EmberTester/EmberTester.h | 10 + Source/Fractorium/AboutDialog.cpp | 14 + Source/Fractorium/AboutDialog.h | 22 + Source/Fractorium/AboutDialog.ui | 199 + Source/Fractorium/DoubleSpinBox.cpp | 215 + Source/Fractorium/DoubleSpinBox.h | 80 + Source/Fractorium/EmberFile.h | 145 + Source/Fractorium/EmberTreeWidgetItem.h | 120 + Source/Fractorium/FinalRenderDialog.cpp | 550 + Source/Fractorium/FinalRenderDialog.h | 113 + Source/Fractorium/FinalRenderDialog.ui | 904 + .../Fractorium/FinalRenderEmberController.cpp | 553 + .../Fractorium/FinalRenderEmberController.h | 143 + Source/Fractorium/Fractorium.aps | Bin 0 -> 158048 bytes Source/Fractorium/Fractorium.cpp | 544 + Source/Fractorium/Fractorium.h | 445 + Source/Fractorium/Fractorium.qrc | 44 + Source/Fractorium/Fractorium.rc | Bin 0 -> 4574 bytes Source/Fractorium/Fractorium.ui | 5304 ++++ .../Fractorium/FractoriumEmberController.cpp | 244 + Source/Fractorium/FractoriumEmberController.h | 446 + Source/Fractorium/FractoriumInfo.cpp | 58 + Source/Fractorium/FractoriumLibrary.cpp | 277 + Source/Fractorium/FractoriumMenus.cpp | 752 + Source/Fractorium/FractoriumPalette.cpp | 235 + Source/Fractorium/FractoriumParams.cpp | 627 + Source/Fractorium/FractoriumPch.cpp | 1 + Source/Fractorium/FractoriumPch.h | 44 + Source/Fractorium/FractoriumRender.cpp | 630 + Source/Fractorium/FractoriumSettings.cpp | 239 + Source/Fractorium/FractoriumSettings.h | 198 + Source/Fractorium/FractoriumToolbar.cpp | 21 + Source/Fractorium/FractoriumXforms.cpp | 345 + Source/Fractorium/FractoriumXformsAffine.cpp | 492 + Source/Fractorium/FractoriumXformsColor.cpp | 204 + .../Fractorium/FractoriumXformsVariations.cpp | 316 + Source/Fractorium/FractoriumXformsXaos.cpp | 175 + Source/Fractorium/GLEmberController.cpp | 254 + Source/Fractorium/GLEmberController.h | 146 + Source/Fractorium/GLWidget.cpp | 1435 + Source/Fractorium/GLWidget.h | 76 + ...nsparent-glass-icon-animals-spiderweb2.png | Bin 0 -> 133174 bytes ...ent-glass-icon-animals-spiderweb2_crop.png | Bin 0 -> 150713 bytes ...n-symbols-shapes-shape-square-clear-16.png | Bin 0 -> 2952 bytes ...glass-icon-alphanumeric-question-mark3.png | Bin 0 -> 3493 bytes Source/Fractorium/Icons/Fractorium.ico | Bin 0 -> 60516 bytes Source/Fractorium/Icons/add.png | Bin 0 -> 911 bytes .../Icons/application_side_boxes.png | Bin 0 -> 482 bytes Source/Fractorium/Icons/arrow-redo.png | Bin 0 -> 1429 bytes Source/Fractorium/Icons/arrow-undo.png | Bin 0 -> 1460 bytes Source/Fractorium/Icons/arrow_down.png | Bin 0 -> 460 bytes Source/Fractorium/Icons/arrow_in.png | Bin 0 -> 538 bytes Source/Fractorium/Icons/arrow_left.png | Bin 0 -> 398 bytes Source/Fractorium/Icons/arrow_out.png | Bin 0 -> 531 bytes Source/Fractorium/Icons/arrow_right.png | Bin 0 -> 404 bytes .../Icons/arrow_rotate_anticlockwise.png | Bin 0 -> 584 bytes .../Icons/arrow_rotate_clockwise.png | Bin 0 -> 563 bytes Source/Fractorium/Icons/arrow_turn_left.png | Bin 0 -> 532 bytes Source/Fractorium/Icons/arrow_turn_right.png | Bin 0 -> 514 bytes Source/Fractorium/Icons/arrow_up.png | Bin 0 -> 459 bytes Source/Fractorium/Icons/banner.bmp | Bin 0 -> 85894 bytes Source/Fractorium/Icons/cog.png | Bin 0 -> 512 bytes Source/Fractorium/Icons/configure.png | Bin 0 -> 1016 bytes Source/Fractorium/Icons/database-import.png | Bin 0 -> 722 bytes Source/Fractorium/Icons/database-medium.png | Bin 0 -> 1406 bytes Source/Fractorium/Icons/databases.png | Bin 0 -> 661 bytes Source/Fractorium/Icons/del.png | Bin 0 -> 643 bytes Source/Fractorium/Icons/dialog.bmp | Bin 0 -> 461814 bytes .../Icons/display-brightness-off.png | Bin 0 -> 1797 bytes .../Fractorium/Icons/document-hf-insert.png | Bin 0 -> 498 bytes Source/Fractorium/Icons/drive-harddisk-5.png | Bin 0 -> 1251 bytes Source/Fractorium/Icons/editraise.png | Bin 0 -> 932 bytes Source/Fractorium/Icons/eraser.png | Bin 0 -> 1326 bytes Source/Fractorium/Icons/folder-visiting-4.png | Bin 0 -> 1214 bytes Source/Fractorium/Icons/infomation.png | Bin 0 -> 2056 bytes Source/Fractorium/Icons/layer--plus.png | Bin 0 -> 614 bytes Source/Fractorium/Icons/layers-stack.png | Bin 0 -> 739 bytes Source/Fractorium/Icons/layers.png | Bin 0 -> 634 bytes Source/Fractorium/Icons/monitor.png | Bin 0 -> 647 bytes Source/Fractorium/Icons/page_copy.png | Bin 0 -> 634 bytes Source/Fractorium/Icons/page_paste.png | Bin 0 -> 675 bytes Source/Fractorium/Icons/proxy.png | Bin 0 -> 2491 bytes .../Icons/shape_flip_horizontal.png | Bin 0 -> 459 bytes .../Fractorium/Icons/shape_flip_vertical.png | Bin 0 -> 438 bytes Source/Fractorium/Icons/stop.png | Bin 0 -> 677 bytes Source/Fractorium/Icons/window-close.png | Bin 0 -> 582 bytes Source/Fractorium/Main.cpp | 25 + Source/Fractorium/OptionsDialog.cpp | 210 + Source/Fractorium/OptionsDialog.h | 56 + Source/Fractorium/OptionsDialog.ui | 746 + Source/Fractorium/SpinBox.cpp | 205 + Source/Fractorium/SpinBox.h | 45 + Source/Fractorium/StealthComboBox.h | 30 + Source/Fractorium/TableWidget.h | 57 + Source/Fractorium/TwoButtonWidget.cpp | 2 + Source/Fractorium/TwoButtonWidget.h | 113 + Source/Fractorium/VariationTreeWidgetItem.h | 89 + Source/Fractorium/resource.h | 17 + 214 files changed, 108754 insertions(+) create mode 100644 Builds/MSVC/VS2010/Ember.vcxproj create mode 100644 Builds/MSVC/VS2010/Ember.vcxproj.filters create mode 100644 Builds/MSVC/VS2010/EmberAnimate.vcxproj create mode 100644 Builds/MSVC/VS2010/EmberAnimate.vcxproj.filters create mode 100644 Builds/MSVC/VS2010/EmberCL.vcxproj create mode 100644 Builds/MSVC/VS2010/EmberCL.vcxproj.filters create mode 100644 Builds/MSVC/VS2010/EmberGenome.vcxproj create mode 100644 Builds/MSVC/VS2010/EmberGenome.vcxproj.filters create mode 100644 Builds/MSVC/VS2010/EmberRender.vcxproj create mode 100644 Builds/MSVC/VS2010/EmberRender.vcxproj.filters create mode 100644 Builds/MSVC/VS2010/EmberTester.vcxproj create mode 100644 Builds/MSVC/VS2010/EmberTester.vcxproj.filters create mode 100644 Builds/MSVC/VS2010/Fractorium.sln create mode 100644 Builds/MSVC/VS2010/Fractorium.vcxproj create mode 100644 Builds/MSVC/VS2010/Fractorium.vcxproj.filters create mode 100644 Builds/MSVC/VS2010/FractoriumInstaller/FractoriumInstaller.wixproj create mode 100644 Builds/MSVC/VS2010/FractoriumInstaller/Product.wxs create mode 100644 Builds/MSVC/VS2010/FractoriumInstaller/msvcp100.dll create mode 100644 Builds/MSVC/VS2010/FractoriumInstaller/msvcr100.dll create mode 100644 Builds/MSVC/VS2010/ReadMe.txt create mode 100644 Builds/MSVC/VS2010/zlib.props create mode 100644 Data/Variations.xlsx create mode 100644 Data/Version History.txt create mode 100644 Data/Wiki/AboutDialog.htm create mode 100644 Data/Wiki/AlgorithmExplanation.htm create mode 100644 Data/Wiki/Building.htm create mode 100644 Data/Wiki/CodingPhilosophy.htm create mode 100644 Data/Wiki/CommandLinePrograms.htm create mode 100644 Data/Wiki/Developers.htm create mode 100644 Data/Wiki/EmberCLImplementationDetails.htm create mode 100644 Data/Wiki/EmberImplementationDetails.htm create mode 100644 Data/Wiki/FinalRenderDialog.htm create mode 100644 Data/Wiki/FlameTab.htm create mode 100644 Data/Wiki/FractoriumUserGuide.htm create mode 100644 Data/Wiki/InfoTab.htm create mode 100644 Data/Wiki/LibraryTab.htm create mode 100644 Data/Wiki/MainPage.htm create mode 100644 Data/Wiki/Menus.htm create mode 100644 Data/Wiki/OptionsDialog.htm create mode 100644 Data/Wiki/PaletteTab.htm create mode 100644 Data/Wiki/ProjectOverview.htm create mode 100644 Data/Wiki/SideBar.htm create mode 100644 Data/Wiki/Toolbar.htm create mode 100644 Data/Wiki/Users.htm create mode 100644 Data/Wiki/XformsTab.htm create mode 100644 Data/flam3-palettes.xml create mode 100644 Data/gplv3.rtf create mode 100644 Source/Ember/Affine2D.cpp create mode 100644 Source/Ember/Affine2D.h create mode 100644 Source/Ember/CarToRas.h create mode 100644 Source/Ember/DensityFilter.h create mode 100644 Source/Ember/DllMain.cpp create mode 100644 Source/Ember/Ember.cpp create mode 100644 Source/Ember/Ember.h create mode 100644 Source/Ember/EmberDefines.h create mode 100644 Source/Ember/EmberPch.cpp create mode 100644 Source/Ember/EmberPch.h create mode 100644 Source/Ember/EmberToXml.h create mode 100644 Source/Ember/Interpolate.h create mode 100644 Source/Ember/Isaac.h create mode 100644 Source/Ember/Iterator.h create mode 100644 Source/Ember/Palette.h create mode 100644 Source/Ember/PaletteList.h create mode 100644 Source/Ember/Point.h create mode 100644 Source/Ember/Renderer.cpp create mode 100644 Source/Ember/Renderer.h create mode 100644 Source/Ember/SheepTools.h create mode 100644 Source/Ember/SpatialFilter.h create mode 100644 Source/Ember/TemporalFilter.h create mode 100644 Source/Ember/Timing.cpp create mode 100644 Source/Ember/Timing.h create mode 100644 Source/Ember/Utils.h create mode 100644 Source/Ember/Variation.h create mode 100644 Source/Ember/VariationList.h create mode 100644 Source/Ember/Variations01.h create mode 100644 Source/Ember/Variations02.h create mode 100644 Source/Ember/Variations03.h create mode 100644 Source/Ember/Variations04.h create mode 100644 Source/Ember/Variations05.h create mode 100644 Source/Ember/VariationsDC.h create mode 100644 Source/Ember/Xform.h create mode 100644 Source/Ember/XmlToEmber.h create mode 100644 Source/EmberAnimate/EmberAnimate.cpp create mode 100644 Source/EmberAnimate/EmberAnimate.h create mode 100644 Source/EmberAnimate/EmberAnimate.rc create mode 100644 Source/EmberAnimate/resource.h create mode 100644 Source/EmberCL/DEOpenCLKernelCreator.cpp create mode 100644 Source/EmberCL/DEOpenCLKernelCreator.h create mode 100644 Source/EmberCL/DllMain.cpp create mode 100644 Source/EmberCL/EmberCLFunctions.h create mode 100644 Source/EmberCL/EmberCLPch.h create mode 100644 Source/EmberCL/EmberCLStructs.h create mode 100644 Source/EmberCL/FinalAccumOpenCLKernelCreator.cpp create mode 100644 Source/EmberCL/FinalAccumOpenCLKernelCreator.h create mode 100644 Source/EmberCL/IterOpenCLKernelCreator.cpp create mode 100644 Source/EmberCL/IterOpenCLKernelCreator.h create mode 100644 Source/EmberCL/OpenCLWrapper.cpp create mode 100644 Source/EmberCL/OpenCLWrapper.h create mode 100644 Source/EmberCL/RendererCL.cpp create mode 100644 Source/EmberCL/RendererCL.h create mode 100644 Source/EmberCommon/EmberCommon.h create mode 100644 Source/EmberCommon/EmberCommonPch.cpp create mode 100644 Source/EmberCommon/EmberCommonPch.h create mode 100644 Source/EmberCommon/EmberOptions.h create mode 100644 Source/EmberCommon/JpegUtils.h create mode 100644 Source/EmberCommon/SimpleGlob.h create mode 100644 Source/EmberCommon/SimpleOpt.h create mode 100644 Source/EmberGenome/EmberGenome.cpp create mode 100644 Source/EmberGenome/EmberGenome.h create mode 100644 Source/EmberGenome/EmberGenome.rc create mode 100644 Source/EmberGenome/resource.h create mode 100644 Source/EmberRender/EmberRender.cpp create mode 100644 Source/EmberRender/EmberRender.h create mode 100644 Source/EmberRender/EmberRender.rc create mode 100644 Source/EmberRender/resource.h create mode 100644 Source/EmberTester/EmberTester.cpp create mode 100644 Source/EmberTester/EmberTester.h create mode 100644 Source/Fractorium/AboutDialog.cpp create mode 100644 Source/Fractorium/AboutDialog.h create mode 100644 Source/Fractorium/AboutDialog.ui create mode 100644 Source/Fractorium/DoubleSpinBox.cpp create mode 100644 Source/Fractorium/DoubleSpinBox.h create mode 100644 Source/Fractorium/EmberFile.h create mode 100644 Source/Fractorium/EmberTreeWidgetItem.h create mode 100644 Source/Fractorium/FinalRenderDialog.cpp create mode 100644 Source/Fractorium/FinalRenderDialog.h create mode 100644 Source/Fractorium/FinalRenderDialog.ui create mode 100644 Source/Fractorium/FinalRenderEmberController.cpp create mode 100644 Source/Fractorium/FinalRenderEmberController.h create mode 100644 Source/Fractorium/Fractorium.aps create mode 100644 Source/Fractorium/Fractorium.cpp create mode 100644 Source/Fractorium/Fractorium.h create mode 100644 Source/Fractorium/Fractorium.qrc create mode 100644 Source/Fractorium/Fractorium.rc create mode 100644 Source/Fractorium/Fractorium.ui create mode 100644 Source/Fractorium/FractoriumEmberController.cpp create mode 100644 Source/Fractorium/FractoriumEmberController.h create mode 100644 Source/Fractorium/FractoriumInfo.cpp create mode 100644 Source/Fractorium/FractoriumLibrary.cpp create mode 100644 Source/Fractorium/FractoriumMenus.cpp create mode 100644 Source/Fractorium/FractoriumPalette.cpp create mode 100644 Source/Fractorium/FractoriumParams.cpp create mode 100644 Source/Fractorium/FractoriumPch.cpp create mode 100644 Source/Fractorium/FractoriumPch.h create mode 100644 Source/Fractorium/FractoriumRender.cpp create mode 100644 Source/Fractorium/FractoriumSettings.cpp create mode 100644 Source/Fractorium/FractoriumSettings.h create mode 100644 Source/Fractorium/FractoriumToolbar.cpp create mode 100644 Source/Fractorium/FractoriumXforms.cpp create mode 100644 Source/Fractorium/FractoriumXformsAffine.cpp create mode 100644 Source/Fractorium/FractoriumXformsColor.cpp create mode 100644 Source/Fractorium/FractoriumXformsVariations.cpp create mode 100644 Source/Fractorium/FractoriumXformsXaos.cpp create mode 100644 Source/Fractorium/GLEmberController.cpp create mode 100644 Source/Fractorium/GLEmberController.h create mode 100644 Source/Fractorium/GLWidget.cpp create mode 100644 Source/Fractorium/GLWidget.h create mode 100644 Source/Fractorium/Icons/010425-3d-transparent-glass-icon-animals-spiderweb2.png create mode 100644 Source/Fractorium/Icons/010425-3d-transparent-glass-icon-animals-spiderweb2_crop.png create mode 100644 Source/Fractorium/Icons/016938-3d-transparent-glass-icon-symbols-shapes-shape-square-clear-16.png create mode 100644 Source/Fractorium/Icons/068123-3d-transparent-glass-icon-alphanumeric-question-mark3.png create mode 100644 Source/Fractorium/Icons/Fractorium.ico create mode 100644 Source/Fractorium/Icons/add.png create mode 100644 Source/Fractorium/Icons/application_side_boxes.png create mode 100644 Source/Fractorium/Icons/arrow-redo.png create mode 100644 Source/Fractorium/Icons/arrow-undo.png create mode 100644 Source/Fractorium/Icons/arrow_down.png create mode 100644 Source/Fractorium/Icons/arrow_in.png create mode 100644 Source/Fractorium/Icons/arrow_left.png create mode 100644 Source/Fractorium/Icons/arrow_out.png create mode 100644 Source/Fractorium/Icons/arrow_right.png create mode 100644 Source/Fractorium/Icons/arrow_rotate_anticlockwise.png create mode 100644 Source/Fractorium/Icons/arrow_rotate_clockwise.png create mode 100644 Source/Fractorium/Icons/arrow_turn_left.png create mode 100644 Source/Fractorium/Icons/arrow_turn_right.png create mode 100644 Source/Fractorium/Icons/arrow_up.png create mode 100644 Source/Fractorium/Icons/banner.bmp create mode 100644 Source/Fractorium/Icons/cog.png create mode 100644 Source/Fractorium/Icons/configure.png create mode 100644 Source/Fractorium/Icons/database-import.png create mode 100644 Source/Fractorium/Icons/database-medium.png create mode 100644 Source/Fractorium/Icons/databases.png create mode 100644 Source/Fractorium/Icons/del.png create mode 100644 Source/Fractorium/Icons/dialog.bmp create mode 100644 Source/Fractorium/Icons/display-brightness-off.png create mode 100644 Source/Fractorium/Icons/document-hf-insert.png create mode 100644 Source/Fractorium/Icons/drive-harddisk-5.png create mode 100644 Source/Fractorium/Icons/editraise.png create mode 100644 Source/Fractorium/Icons/eraser.png create mode 100644 Source/Fractorium/Icons/folder-visiting-4.png create mode 100644 Source/Fractorium/Icons/infomation.png create mode 100644 Source/Fractorium/Icons/layer--plus.png create mode 100644 Source/Fractorium/Icons/layers-stack.png create mode 100644 Source/Fractorium/Icons/layers.png create mode 100644 Source/Fractorium/Icons/monitor.png create mode 100644 Source/Fractorium/Icons/page_copy.png create mode 100644 Source/Fractorium/Icons/page_paste.png create mode 100644 Source/Fractorium/Icons/proxy.png create mode 100644 Source/Fractorium/Icons/shape_flip_horizontal.png create mode 100644 Source/Fractorium/Icons/shape_flip_vertical.png create mode 100644 Source/Fractorium/Icons/stop.png create mode 100644 Source/Fractorium/Icons/window-close.png create mode 100644 Source/Fractorium/Main.cpp create mode 100644 Source/Fractorium/OptionsDialog.cpp create mode 100644 Source/Fractorium/OptionsDialog.h create mode 100644 Source/Fractorium/OptionsDialog.ui create mode 100644 Source/Fractorium/SpinBox.cpp create mode 100644 Source/Fractorium/SpinBox.h create mode 100644 Source/Fractorium/StealthComboBox.h create mode 100644 Source/Fractorium/TableWidget.h create mode 100644 Source/Fractorium/TwoButtonWidget.cpp create mode 100644 Source/Fractorium/TwoButtonWidget.h create mode 100644 Source/Fractorium/VariationTreeWidgetItem.h create mode 100644 Source/Fractorium/resource.h diff --git a/Builds/MSVC/VS2010/Ember.vcxproj b/Builds/MSVC/VS2010/Ember.vcxproj new file mode 100644 index 0000000..b74f349 --- /dev/null +++ b/Builds/MSVC/VS2010/Ember.vcxproj @@ -0,0 +1,315 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + ReleaseNvidia + Win32 + + + ReleaseNvidia + x64 + + + Release + Win32 + + + Release + x64 + + + + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67} + Win32Proj + EmberNs + Ember + + + + DynamicLibrary + true + MultiByte + false + + + DynamicLibrary + true + MultiByte + false + + + DynamicLibrary + false + false + MultiByte + + + DynamicLibrary + false + false + MultiByte + + + DynamicLibrary + false + false + MultiByte + + + DynamicLibrary + false + false + MultiByte + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;BUILDING_EMBER;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + 4251;4661 + $(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\libxml2\include;$(ProjectDir)..\..\..\..\tbb\include + Default + EmberPch.h + true + + + Windows + true + + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;BUILDING_EMBER;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + 4251;4661 + $(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\libxml2\include;$(ProjectDir)..\..\..\..\tbb\include + Default + EmberPch.h + Precise + /bigobj %(AdditionalOptions) + true + + + Windows + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;BUILDING_EMBER;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + 4251;4661 + $(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\libxml2\include;$(ProjectDir)..\..\..\..\tbb\include + Speed + NotSet + EmberPch.h + true + + + Windows + true + true + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;BUILDING_EMBER;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + 4251;4661 + $(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\libxml2\include;$(ProjectDir)..\..\..\..\tbb\include + Speed + NotSet + EmberPch.h + true + + + Windows + true + true + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;BUILDING_EMBER;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + 4251;4661 + $(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\libxml2\include;$(ProjectDir)..\..\..\..\tbb\include + Speed + EmberPch.h + true + /bigobj %(AdditionalOptions) + Precise + + + Windows + true + true + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;BUILDING_EMBER;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + 4251;4661 + $(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\libxml2\include;$(ProjectDir)..\..\..\..\tbb\include + Speed + EmberPch.h + true + /bigobj %(AdditionalOptions) + Precise + + + Windows + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + Create + Create + Create + Create + Create + + + + + + {1d6039f6-5078-416f-a3af-a36efc7e6a1c} + + + {f62787dd-1327-448b-9818-030062bcfaa5} + + + + + + \ No newline at end of file diff --git a/Builds/MSVC/VS2010/Ember.vcxproj.filters b/Builds/MSVC/VS2010/Ember.vcxproj.filters new file mode 100644 index 0000000..16ebec4 --- /dev/null +++ b/Builds/MSVC/VS2010/Ember.vcxproj.filters @@ -0,0 +1,131 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {bc119dca-b280-4071-b72d-f8c377b2e192} + + + {39f9b624-d25e-4af7-9f76-3b1a36a8a0f5} + + + {1ae77918-b5ee-4186-9fec-802fed55144e} + + + + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files\Filters + + + Header Files\Filters + + + Header Files\Filters + + + Header Files\Xml + + + Header Files\Xml + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files\Variations + + + Header Files\Variations + + + Header Files\Variations + + + Header Files\Variations + + + Header Files\Variations + + + Header Files\Variations + + + Header Files\Variations + + + Header Files\Variations + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/Builds/MSVC/VS2010/EmberAnimate.vcxproj b/Builds/MSVC/VS2010/EmberAnimate.vcxproj new file mode 100644 index 0000000..a8e8863 --- /dev/null +++ b/Builds/MSVC/VS2010/EmberAnimate.vcxproj @@ -0,0 +1,335 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + ReleaseNvidia + Win32 + + + ReleaseNvidia + x64 + + + Release + Win32 + + + Release + x64 + + + + {35285FCF-6FA8-410E-841B-70AE744D38B8} + Win32Proj + EmberAnimate + EmberAnimate + + + + Application + true + MultiByte + false + + + Application + true + MultiByte + false + + + Application + false + false + MultiByte + + + Application + false + false + MultiByte + + + Application + false + false + MultiByte + + + Application + false + false + MultiByte + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + $(ProjectDir)..\..\..\Source\Ember;$(ProjectDir)..\..\..\Source\EmberCommon;$(ProjectDir)..\..\..\Source\EmberCL;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libjpeg;$(ProjectDir)..\..\..\..\libpng;$(ProjectDir)..\..\..\..\libxml2\include;$(AMDAPPSDKROOT)\include;$(CUDA_PATH)include + 4251 + EmberCommonPch.h + + + Console + true + opencl.lib;Ws2_32.lib;%(AdditionalDependencies) + $(AMDAPPSDKROOT)\lib\x86;$(CUDA_PATH)lib\$(PlatformName) + + + xcopy /F /Y /R /D "$(SolutionDir)$(Platform)\$(Configuration)\*.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(ProjectDir)..\..\..\..\tbb\build\vsproject\ia32\$(Configuration)\tbb_debug.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(ProjectDir)..\..\..\..\tbb\build\vsproject\ia32\$(Configuration)\tbb_debug.pdb" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)" + + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + $(ProjectDir)..\..\..\Source\Ember;$(ProjectDir)..\..\..\Source\EmberCommon;$(ProjectDir)..\..\..\Source\EmberCL;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libjpeg;$(ProjectDir)..\..\..\..\libpng;$(ProjectDir)..\..\..\..\libxml2\include;$(AMDAPPSDKROOT)\include;$(CUDA_PATH)include + 4251 + EmberCommonPch.h + + + Console + true + opencl.lib;Ws2_32.lib;%(AdditionalDependencies) + $(AMDAPPSDKROOT)\lib\x86_64;$(CUDA_PATH)lib\$(PlatformName) + + + xcopy /F /Y /R /D "$(SolutionDir)$(Platform)\$(Configuration)\*.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)intel64\$(Configuration)\tbb_debug.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)intel64\$(Configuration)\tbb_debug.pdb" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)" + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + $(ProjectDir)..\..\..\Source\Ember;$(ProjectDir)..\..\..\Source\EmberCommon;$(ProjectDir)..\..\..\Source\EmberCL;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libjpeg;$(ProjectDir)..\..\..\..\libpng;$(ProjectDir)..\..\..\..\libxml2\include;$(AMDAPPSDKROOT)\include;$(CUDA_PATH)include + 4251 + EmberCommonPch.h + + + Console + true + true + true + opencl.lib;Ws2_32.lib;%(AdditionalDependencies) + $(AMDAPPSDKROOT)\lib\x86;$(CUDA_PATH)lib\$(PlatformName) + + + xcopy /F /Y /R /D "$(SolutionDir)$(Platform)\$(Configuration)\*.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(ProjectDir)..\..\..\..\tbb\build\vsproject\ia32\$(Configuration)\tbb.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(ProjectDir)..\..\..\..\tbb\build\vsproject\ia32\$(Configuration)\tbb.pdb" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)" + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + $(ProjectDir)..\..\..\Source\Ember;$(ProjectDir)..\..\..\Source\EmberCommon;$(ProjectDir)..\..\..\Source\EmberCL;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libjpeg;$(ProjectDir)..\..\..\..\libpng;$(ProjectDir)..\..\..\..\libxml2\include;$(AMDAPPSDKROOT)\include;$(CUDA_PATH)include + 4251 + EmberCommonPch.h + + + Console + true + true + true + opencl.lib;Ws2_32.lib;%(AdditionalDependencies) + $(AMDAPPSDKROOT)\lib\x86;$(CUDA_PATH)lib\$(PlatformName) + + + xcopy /F /Y /R /D "$(SolutionDir)$(Platform)\$(Configuration)\*.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(ProjectDir)..\..\..\..\tbb\build\vsproject\ia32\$(Configuration)\tbb.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(ProjectDir)..\..\..\..\tbb\build\vsproject\ia32\$(Configuration)\tbb.pdb" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)" + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + $(ProjectDir)..\..\..\Source\Ember;$(ProjectDir)..\..\..\Source\EmberCommon;$(ProjectDir)..\..\..\Source\EmberCL;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libjpeg;$(ProjectDir)..\..\..\..\libpng;$(ProjectDir)..\..\..\..\libxml2\include;$(AMDAPPSDKROOT)\include;$(CUDA_PATH)include + 4251 + EmberCommonPch.h + + + Console + true + true + true + opencl.lib;Ws2_32.lib;%(AdditionalDependencies) + $(AMDAPPSDKROOT)\lib\x86_64;$(CUDA_PATH)lib\$(PlatformName) + + + xcopy /F /Y /R /D "$(SolutionDir)$(Platform)\$(Configuration)\*.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)intel64\$(Configuration)\tbb.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)intel64\$(Configuration)\tbb.pdb" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)" + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + $(ProjectDir)..\..\..\Source\Ember;$(ProjectDir)..\..\..\Source\EmberCommon;$(ProjectDir)..\..\..\Source\EmberCL;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libjpeg;$(ProjectDir)..\..\..\..\libpng;$(ProjectDir)..\..\..\..\libxml2\include;$(CUDA_PATH)include + 4251 + EmberCommonPch.h + + + Console + true + true + true + opencl.lib;Ws2_32.lib;%(AdditionalDependencies) + $(CUDA_PATH)lib\$(PlatformName) + + + xcopy /F /Y /R /D "$(SolutionDir)$(Platform)\$(Configuration)\*.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)intel64\$(Configuration)\tbb.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)intel64\$(Configuration)\tbb.pdb" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)" + + + + + + + + + {019dbd2a-273d-4ba4-bf86-b5efe2ed76b1} + + + {d6973076-9317-4ef2-a0b8-b7a18ac0713e} + + + {1d6039f6-5078-416f-a3af-a36efc7e6a1c} + + + {f62787dd-1327-448b-9818-030062bcfaa5} + + + {2bdb7a54-bb1a-476b-a6e5-f81e90ad4e67} + + + {f6a9102c-69a9-48fb-bc4b-49e49af43236} + + + + + + + + + + + + + + + + Create + Create + Create + Create + Create + Create + + + + + + + + + \ No newline at end of file diff --git a/Builds/MSVC/VS2010/EmberAnimate.vcxproj.filters b/Builds/MSVC/VS2010/EmberAnimate.vcxproj.filters new file mode 100644 index 0000000..dc3b4f5 --- /dev/null +++ b/Builds/MSVC/VS2010/EmberAnimate.vcxproj.filters @@ -0,0 +1,62 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + + Resource Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/Builds/MSVC/VS2010/EmberCL.vcxproj b/Builds/MSVC/VS2010/EmberCL.vcxproj new file mode 100644 index 0000000..b1e05f7 --- /dev/null +++ b/Builds/MSVC/VS2010/EmberCL.vcxproj @@ -0,0 +1,304 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + ReleaseNvidia + Win32 + + + ReleaseNvidia + x64 + + + Release + Win32 + + + Release + x64 + + + + {F6A9102C-69A9-48FB-BC4B-49E49AF43236} + Win32Proj + EmberCLns + EmberCL + + + + DynamicLibrary + true + MultiByte + false + + + DynamicLibrary + true + MultiByte + false + + + DynamicLibrary + false + false + MultiByte + + + DynamicLibrary + false + false + MultiByte + + + DynamicLibrary + false + false + MultiByte + + + DynamicLibrary + false + false + MultiByte + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;BUILDING_EMBERCL;AMD_OS_WIN;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + 4251;4661 + $(ProjectDir)..\..\..\Source\Ember\;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libxml2\include;$(AMDAPPSDKROOT)\include;$(CUDA_PATH)include + Default + EmberCLPch.h + + + Windows + true + opencl.lib;Opengl32.lib;%(AdditionalDependencies) + $(AMDAPPSDKROOT)\lib\x86;$(CUDA_PATH)lib\$(PlatformName) + + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;BUILDING_EMBERCL;AMD_OS_WIN;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + 4251;4661 + $(ProjectDir)..\..\..\Source\Ember\;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libxml2\include;$(AMDAPPSDKROOT)\include;$(CUDA_PATH)include + Default + EmberCLPch.h + + + Windows + true + opencl.lib;Opengl32.lib;%(AdditionalDependencies) + $(AMDAPPSDKROOT)\lib\x86_64;$(CUDA_PATH)lib\$(PlatformName) + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;BUILDING_EMBERCL;AMD_OS_WIN;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + 4251;4661 + $(ProjectDir)..\..\..\Source\Ember\;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libxml2\include;$(AMDAPPSDKROOT)\include;$(CUDA_PATH)include + Speed + NotSet + EmberCLPch.h + + + Windows + true + true + true + opencl.lib;Opengl32.lib;%(AdditionalDependencies) + $(AMDAPPSDKROOT)\lib\x86;$(CUDA_PATH)lib\$(PlatformName) + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;BUILDING_EMBERCL;AMD_OS_WIN;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + 4251;4661 + $(ProjectDir)..\..\..\Source\Ember\;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libxml2\include;$(AMDAPPSDKROOT)\include;$(CUDA_PATH)include + Speed + NotSet + EmberCLPch.h + + + Windows + true + true + true + opencl.lib;Opengl32.lib;%(AdditionalDependencies) + $(AMDAPPSDKROOT)\lib\x86;$(CUDA_PATH)lib\$(PlatformName) + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;BUILDING_EMBERCL;AMD_OS_WIN;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + 4251;4661 + $(ProjectDir)..\..\..\Source\Ember\;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libxml2\include;$(AMDAPPSDKROOT)\include;$(CUDA_PATH)include + Speed + EmberCLPch.h + /bigobj %(AdditionalOptions) + Precise + + + Windows + true + true + true + opencl.lib;Opengl32.lib;%(AdditionalDependencies) + $(AMDAPPSDKROOT)\lib\x86_64;$(CUDA_PATH)lib\$(PlatformName) + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;BUILDING_EMBERCL;AMD_OS_WIN;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + 4251;4661 + $(ProjectDir)..\..\..\Source\Ember\;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libxml2\include;$(CUDA_PATH)include + Speed + EmberCLPch.h + /bigobj %(AdditionalOptions) + Precise + + + Windows + true + true + true + opencl.lib;Opengl32.lib;%(AdditionalDependencies) + $(CUDA_PATH)lib\$(PlatformName) + + + + + + + + {f62787dd-1327-448b-9818-030062bcfaa5} + + + {2bdb7a54-bb1a-476b-a6e5-f81e90ad4e67} + true + true + false + true + false + + + + + Create + Create + Create + Create + Create + Create + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Builds/MSVC/VS2010/EmberCL.vcxproj.filters b/Builds/MSVC/VS2010/EmberCL.vcxproj.filters new file mode 100644 index 0000000..bb0f844 --- /dev/null +++ b/Builds/MSVC/VS2010/EmberCL.vcxproj.filters @@ -0,0 +1,69 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {d66f35ca-a4cd-470a-9c56-653b0665b598} + + + + + + + + Source Files + + + Source Files + + + Source Files + + + Kernel Creators + + + Kernel Creators + + + Kernel Creators + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Kernel Creators + + + Kernel Creators + + + Kernel Creators + + + \ No newline at end of file diff --git a/Builds/MSVC/VS2010/EmberGenome.vcxproj b/Builds/MSVC/VS2010/EmberGenome.vcxproj new file mode 100644 index 0000000..9e95f4f --- /dev/null +++ b/Builds/MSVC/VS2010/EmberGenome.vcxproj @@ -0,0 +1,329 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + ReleaseNvidia + Win32 + + + ReleaseNvidia + x64 + + + Release + Win32 + + + Release + x64 + + + + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7} + Win32Proj + EmberGenome + EmberGenome + + + + Application + true + MultiByte + false + + + Application + true + MultiByte + false + + + Application + false + false + MultiByte + + + Application + false + false + MultiByte + + + Application + false + false + MultiByte + + + Application + false + false + MultiByte + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + $(ProjectDir)..\..\..\Source\Ember;$(ProjectDir)..\..\..\Source\EmberCommon;$(ProjectDir)..\..\..\Source\EmberCL;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libjpeg;$(ProjectDir)..\..\..\..\libpng;$(ProjectDir)..\..\..\..\libxml2\include;$(AMDAPPSDKROOT)\include;$(CUDA_PATH)include + 4251 + EmberCommonPch.h + + + Console + true + opencl.lib;Ws2_32.lib;%(AdditionalDependencies) + $(AMDAPPSDKROOT)\lib\x86;$(CUDA_PATH)lib\$(PlatformName) + + + xcopy /F /Y /R /D "$(SolutionDir)$(Platform)\$(Configuration)\*.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(ProjectDir)..\..\..\..\tbb\build\vsproject\ia32\$(Configuration)\tbb_debug.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(ProjectDir)..\..\..\..\tbb\build\vsproject\ia32\$(Configuration)\tbb_debug.pdb" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)" + + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + $(ProjectDir)..\..\..\Source\Ember;$(ProjectDir)..\..\..\Source\EmberCommon;$(ProjectDir)..\..\..\Source\EmberCL;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libjpeg;$(ProjectDir)..\..\..\..\libpng;$(ProjectDir)..\..\..\..\libxml2\include;$(AMDAPPSDKROOT)\include;$(CUDA_PATH)include + 4251 + EmberCommonPch.h + + + Console + true + opencl.lib;Ws2_32.lib;%(AdditionalDependencies) + $(AMDAPPSDKROOT)\lib\x86_64;$(CUDA_PATH)lib\$(PlatformName) + + + xcopy /F /Y /R /D "$(SolutionDir)$(Platform)\$(Configuration)\*.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)intel64\$(Configuration)\tbb_debug.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)intel64\$(Configuration)\tbb_debug.pdb" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)" + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + $(ProjectDir)..\..\..\Source\Ember;$(ProjectDir)..\..\..\Source\EmberCommon;$(ProjectDir)..\..\..\Source\EmberCL;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libjpeg;$(ProjectDir)..\..\..\..\libpng;$(ProjectDir)..\..\..\..\libxml2\include;$(AMDAPPSDKROOT)\include;$(CUDA_PATH)include + 4251 + EmberCommonPch.h + + + Console + true + true + true + opencl.lib;Ws2_32.lib;%(AdditionalDependencies) + $(AMDAPPSDKROOT)\lib\x86;$(CUDA_PATH)lib\$(PlatformName) + + + xcopy /F /Y /R /D "$(SolutionDir)$(Platform)\$(Configuration)\*.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(ProjectDir)..\..\..\..\tbb\build\vsproject\ia32\$(Configuration)\tbb.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(ProjectDir)..\..\..\..\tbb\build\vsproject\ia32\$(Configuration)\tbb.pdb" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)" + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + $(ProjectDir)..\..\..\Source\Ember;$(ProjectDir)..\..\..\Source\EmberCommon;$(ProjectDir)..\..\..\Source\EmberCL;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libjpeg;$(ProjectDir)..\..\..\..\libpng;$(ProjectDir)..\..\..\..\libxml2\include;$(AMDAPPSDKROOT)\include;$(CUDA_PATH)include + 4251 + EmberCommonPch.h + + + Console + true + true + true + opencl.lib;Ws2_32.lib;%(AdditionalDependencies) + $(AMDAPPSDKROOT)\lib\x86;$(CUDA_PATH)lib\$(PlatformName) + + + xcopy /F /Y /R /D "$(SolutionDir)$(Platform)\$(Configuration)\*.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(ProjectDir)..\..\..\..\tbb\build\vsproject\ia32\$(Configuration)\tbb.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(ProjectDir)..\..\..\..\tbb\build\vsproject\ia32\$(Configuration)\tbb.pdb" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)" + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + $(ProjectDir)..\..\..\Source\Ember;$(ProjectDir)..\..\..\Source\EmberCommon;$(ProjectDir)..\..\..\Source\EmberCL;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libjpeg;$(ProjectDir)..\..\..\..\libpng;$(ProjectDir)..\..\..\..\libxml2\include;$(AMDAPPSDKROOT)\include;$(CUDA_PATH)include + 4251 + EmberCommonPch.h + + + Console + true + true + true + opencl.lib;Ws2_32.lib;%(AdditionalDependencies) + $(AMDAPPSDKROOT)\lib\x86_64;$(CUDA_PATH)lib\$(PlatformName) + + + xcopy /F /Y /R /D "$(SolutionDir)$(Platform)\$(Configuration)\*.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)intel64\$(Configuration)\tbb.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)intel64\$(Configuration)\tbb.pdb" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)" + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + $(ProjectDir)..\..\..\Source\Ember;$(ProjectDir)..\..\..\Source\EmberCommon;$(ProjectDir)..\..\..\Source\EmberCL;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libjpeg;$(ProjectDir)..\..\..\..\libpng;$(ProjectDir)..\..\..\..\libxml2\include;$(CUDA_PATH)include + 4251 + EmberCommonPch.h + + + Console + true + true + true + opencl.lib;Ws2_32.lib;%(AdditionalDependencies) + $(CUDA_PATH)lib\$(PlatformName) + + + xcopy /F /Y /R /D "$(SolutionDir)$(Platform)\$(Configuration)\*.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)intel64\$(Configuration)\tbb.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)intel64\$(Configuration)\tbb.pdb" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)" + + + + + + + + + {1d6039f6-5078-416f-a3af-a36efc7e6a1c} + + + {f62787dd-1327-448b-9818-030062bcfaa5} + + + {2bdb7a54-bb1a-476b-a6e5-f81e90ad4e67} + + + {f6a9102c-69a9-48fb-bc4b-49e49af43236} + + + + + + + + + + + + + + + Create + Create + Create + Create + Create + Create + + + + + + + + + + \ No newline at end of file diff --git a/Builds/MSVC/VS2010/EmberGenome.vcxproj.filters b/Builds/MSVC/VS2010/EmberGenome.vcxproj.filters new file mode 100644 index 0000000..5ae7193 --- /dev/null +++ b/Builds/MSVC/VS2010/EmberGenome.vcxproj.filters @@ -0,0 +1,62 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + + Resource Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/Builds/MSVC/VS2010/EmberRender.vcxproj b/Builds/MSVC/VS2010/EmberRender.vcxproj new file mode 100644 index 0000000..b9e1c6f --- /dev/null +++ b/Builds/MSVC/VS2010/EmberRender.vcxproj @@ -0,0 +1,337 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + ReleaseNvidia + Win32 + + + ReleaseNvidia + x64 + + + Release + Win32 + + + Release + x64 + + + + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29} + Win32Proj + EmberRender + EmberRender + + + + Application + true + MultiByte + false + + + Application + true + MultiByte + false + + + Application + false + false + MultiByte + + + Application + false + false + MultiByte + + + Application + false + false + MultiByte + + + Application + false + false + MultiByte + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + $(ProjectDir)..\..\..\Source\Ember;$(ProjectDir)..\..\..\Source\EmberCommon;$(ProjectDir)..\..\..\Source\EmberCL;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libjpeg;$(ProjectDir)..\..\..\..\libpng;$(ProjectDir)..\..\..\..\libxml2\include;$(AMDAPPSDKROOT)\include;$(CUDA_PATH)include + 4251 + EmberCommonPch.h + + + Console + true + opencl.lib;Ws2_32.lib;%(AdditionalDependencies) + $(AMDAPPSDKROOT)\lib\x86;$(CUDA_PATH)lib\$(PlatformName) + + + xcopy /F /Y /R /D "$(SolutionDir)$(Platform)\$(Configuration)\*.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(ProjectDir)..\..\..\..\tbb\build\vsproject\ia32\$(Configuration)\tbb_debug.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(ProjectDir)..\..\..\..\tbb\build\vsproject\ia32\$(Configuration)\tbb_debug.pdb" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)" + + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + $(ProjectDir)..\..\..\Source\Ember;$(ProjectDir)..\..\..\Source\EmberCommon;$(ProjectDir)..\..\..\Source\EmberCL;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libjpeg;$(ProjectDir)..\..\..\..\libpng;$(ProjectDir)..\..\..\..\libxml2\include;$(AMDAPPSDKROOT)\include;$(CUDA_PATH)include + 4251 + EmberCommonPch.h + + + Console + true + opencl.lib;Ws2_32.lib;%(AdditionalDependencies) + $(AMDAPPSDKROOT)\lib\x86_64;$(CUDA_PATH)lib\$(PlatformName) + + + xcopy /F /Y /R /D "$(SolutionDir)$(Platform)\$(Configuration)\*.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)intel64\$(Configuration)\tbb_debug.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)intel64\$(Configuration)\tbb_debug.pdb" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)" + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + $(ProjectDir)..\..\..\Source\Ember;$(ProjectDir)..\..\..\Source\EmberCommon;$(ProjectDir)..\..\..\Source\EmberCL;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libjpeg;$(ProjectDir)..\..\..\..\libpng;$(ProjectDir)..\..\..\..\libxml2\include;$(AMDAPPSDKROOT)\include;$(CUDA_PATH)include + 4251 + EmberCommonPch.h + + + Console + true + true + true + opencl.lib;Ws2_32.lib;%(AdditionalDependencies) + $(AMDAPPSDKROOT)\lib\x86;$(CUDA_PATH)lib\$(PlatformName) + + + xcopy /F /Y /R /D "$(SolutionDir)$(Platform)\$(Configuration)\*.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(ProjectDir)..\..\..\..\tbb\build\vsproject\ia32\$(Configuration)\tbb.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(ProjectDir)..\..\..\..\tbb\build\vsproject\ia32\$(Configuration)\tbb.pdb" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)" + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + $(ProjectDir)..\..\..\Source\Ember;$(ProjectDir)..\..\..\Source\EmberCommon;$(ProjectDir)..\..\..\Source\EmberCL;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libjpeg;$(ProjectDir)..\..\..\..\libpng;$(ProjectDir)..\..\..\..\libxml2\include;$(AMDAPPSDKROOT)\include;$(CUDA_PATH)include + 4251 + EmberCommonPch.h + + + Console + true + true + true + opencl.lib;Ws2_32.lib;%(AdditionalDependencies) + $(AMDAPPSDKROOT)\lib\x86;$(CUDA_PATH)lib\$(PlatformName) + + + xcopy /F /Y /R /D "$(SolutionDir)$(Platform)\$(Configuration)\*.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(ProjectDir)..\..\..\..\tbb\build\vsproject\ia32\$(Configuration)\tbb.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(ProjectDir)..\..\..\..\tbb\build\vsproject\ia32\$(Configuration)\tbb.pdb" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)" + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + $(ProjectDir)..\..\..\Source\Ember;$(ProjectDir)..\..\..\Source\EmberCommon;$(ProjectDir)..\..\..\Source\EmberCL;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libjpeg;$(ProjectDir)..\..\..\..\libpng;$(ProjectDir)..\..\..\..\libxml2\include;$(AMDAPPSDKROOT)\include;$(CUDA_PATH)include + 4251 + EmberCommonPch.h + Precise + + + Console + true + true + true + opencl.lib;Ws2_32.lib;%(AdditionalDependencies) + $(AMDAPPSDKROOT)\lib\x86_64;$(CUDA_PATH)lib\$(PlatformName) + + + xcopy /F /Y /R /D "$(SolutionDir)$(Platform)\$(Configuration)\*.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)intel64\$(Configuration)\tbb.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)intel64\$(Configuration)\tbb.pdb" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)" + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + $(ProjectDir)..\..\..\Source\Ember;$(ProjectDir)..\..\..\Source\EmberCommon;$(ProjectDir)..\..\..\Source\EmberCL;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libjpeg;$(ProjectDir)..\..\..\..\libpng;$(ProjectDir)..\..\..\..\libxml2\include;$(CUDA_PATH)include + 4251 + EmberCommonPch.h + Precise + + + Console + true + true + true + opencl.lib;Ws2_32.lib;%(AdditionalDependencies) + $(CUDA_PATH)lib\$(PlatformName) + + + xcopy /F /Y /R /D "$(SolutionDir)$(Platform)\$(Configuration)\*.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)intel64\$(Configuration)\tbb.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)intel64\$(Configuration)\tbb.pdb" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)" + + + + + + + + + {019dbd2a-273d-4ba4-bf86-b5efe2ed76b1} + + + {d6973076-9317-4ef2-a0b8-b7a18ac0713e} + + + {1d6039f6-5078-416f-a3af-a36efc7e6a1c} + + + {f62787dd-1327-448b-9818-030062bcfaa5} + + + {2bdb7a54-bb1a-476b-a6e5-f81e90ad4e67} + + + {f6a9102c-69a9-48fb-bc4b-49e49af43236} + + + + + + + + + + + + + + + Create + Create + Create + Create + Create + Create + + + + + + + + + + \ No newline at end of file diff --git a/Builds/MSVC/VS2010/EmberRender.vcxproj.filters b/Builds/MSVC/VS2010/EmberRender.vcxproj.filters new file mode 100644 index 0000000..38a8460 --- /dev/null +++ b/Builds/MSVC/VS2010/EmberRender.vcxproj.filters @@ -0,0 +1,62 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + + Resource Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/Builds/MSVC/VS2010/EmberTester.vcxproj b/Builds/MSVC/VS2010/EmberTester.vcxproj new file mode 100644 index 0000000..9b6d1c5 --- /dev/null +++ b/Builds/MSVC/VS2010/EmberTester.vcxproj @@ -0,0 +1,324 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + ReleaseNvidia + Win32 + + + ReleaseNvidia + x64 + + + Release + Win32 + + + Release + x64 + + + + {320F611A-F9CE-4196-A8DC-FA24B2E8A320} + Win32Proj + EmberTester + EmberTester + + + + Application + true + MultiByte + false + + + Application + true + MultiByte + false + + + Application + false + false + MultiByte + + + Application + false + false + MultiByte + + + Application + false + false + MultiByte + + + Application + false + false + MultiByte + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + $(ProjectDir)..\..\..\Source\Ember;$(ProjectDir)..\..\..\Source\EmberCommon;$(ProjectDir)..\..\..\Source\EmberCL;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libjpeg;$(ProjectDir)..\..\..\..\libpng;$(ProjectDir)..\..\..\..\libxml2\include;$(AMDAPPSDKROOT)\include;$(CUDA_PATH)include + 4251 + EmberCommonPch.h + + + Console + true + opencl.lib;Ws2_32.lib;%(AdditionalDependencies) + $(AMDAPPSDKROOT)\lib\x86;$(CUDA_PATH)lib\$(PlatformName) + + + xcopy /F /Y /R /D "$(SolutionDir)$(Platform)\$(Configuration)\*.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(ProjectDir)..\..\..\..\tbb\build\vsproject\ia32\$(Configuration)\tbb_debug.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(ProjectDir)..\..\..\..\tbb\build\vsproject\ia32\$(Configuration)\tbb_debug.pdb" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)" + + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + $(ProjectDir)..\..\..\Source\Ember;$(ProjectDir)..\..\..\Source\EmberCommon;$(ProjectDir)..\..\..\Source\EmberCL;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libjpeg;$(ProjectDir)..\..\..\..\libpng;$(ProjectDir)..\..\..\..\libxml2\include;$(AMDAPPSDKROOT)\include;$(CUDA_PATH)include + 4251 + EmberCommonPch.h + + + Console + true + opencl.lib;Ws2_32.lib;%(AdditionalDependencies) + $(AMDAPPSDKROOT)\lib\x86_64;$(CUDA_PATH)lib\$(PlatformName) + + + xcopy /F /Y /R /D "$(SolutionDir)$(Platform)\$(Configuration)\*.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)intel64\$(Configuration)\tbb_debug.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)intel64\$(Configuration)\tbb_debug.pdb" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)" + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + $(ProjectDir)..\..\..\Source\Ember;$(ProjectDir)..\..\..\Source\EmberCommon;$(ProjectDir)..\..\..\Source\EmberCL;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libjpeg;$(ProjectDir)..\..\..\..\libpng;$(ProjectDir)..\..\..\..\libxml2\include;$(AMDAPPSDKROOT)\include;$(CUDA_PATH)include + 4251 + EmberCommonPch.h + + + Console + true + true + true + opencl.lib;Ws2_32.lib;%(AdditionalDependencies) + $(AMDAPPSDKROOT)\lib\x86;$(CUDA_PATH)lib\$(PlatformName) + + + xcopy /F /Y /R /D "$(SolutionDir)$(Platform)\$(Configuration)\*.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(ProjectDir)..\..\..\..\tbb\build\vsproject\ia32\$(Configuration)\tbb.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(ProjectDir)..\..\..\..\tbb\build\vsproject\ia32\$(Configuration)\tbb.pdb" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)" + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + $(ProjectDir)..\..\..\Source\Ember;$(ProjectDir)..\..\..\Source\EmberCommon;$(ProjectDir)..\..\..\Source\EmberCL;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libjpeg;$(ProjectDir)..\..\..\..\libpng;$(ProjectDir)..\..\..\..\libxml2\include;$(AMDAPPSDKROOT)\include;$(CUDA_PATH)include + 4251 + EmberCommonPch.h + + + Console + true + true + true + opencl.lib;Ws2_32.lib;%(AdditionalDependencies) + $(AMDAPPSDKROOT)\lib\x86;$(CUDA_PATH)lib\$(PlatformName) + + + xcopy /F /Y /R /D "$(SolutionDir)$(Platform)\$(Configuration)\*.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(ProjectDir)..\..\..\..\tbb\build\vsproject\ia32\$(Configuration)\tbb.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(ProjectDir)..\..\..\..\tbb\build\vsproject\ia32\$(Configuration)\tbb.pdb" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)" + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + $(ProjectDir)..\..\..\Source\Ember;$(ProjectDir)..\..\..\Source\EmberCommon;$(ProjectDir)..\..\..\Source\EmberCL;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libjpeg;$(ProjectDir)..\..\..\..\libpng;$(ProjectDir)..\..\..\..\libxml2\include;$(AMDAPPSDKROOT)\include;$(CUDA_PATH)include + 4251 + EmberCommonPch.h + + + Console + true + true + true + opencl.lib;Ws2_32.lib;%(AdditionalDependencies) + $(AMDAPPSDKROOT)\lib\x86_64;$(CUDA_PATH)lib\$(PlatformName) + + + xcopy /F /Y /R /D "$(SolutionDir)$(Platform)\$(Configuration)\*.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)intel64\$(Configuration)\tbb.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)intel64\$(Configuration)\tbb.pdb" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)" + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(TargetDir)$(TargetName).pdb + $(ProjectDir)..\..\..\Source\Ember;$(ProjectDir)..\..\..\Source\EmberCommon;$(ProjectDir)..\..\..\Source\EmberCL;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libjpeg;$(ProjectDir)..\..\..\..\libpng;$(ProjectDir)..\..\..\..\libxml2\include;$(CUDA_PATH)include + 4251 + EmberCommonPch.h + + + Console + true + true + true + opencl.lib;Ws2_32.lib;%(AdditionalDependencies) + $(CUDA_PATH)lib\$(PlatformName) + + + xcopy /F /Y /R /D "$(SolutionDir)$(Platform)\$(Configuration)\*.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)intel64\$(Configuration)\tbb.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)intel64\$(Configuration)\tbb.pdb" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)" + + + + + + + + {1d6039f6-5078-416f-a3af-a36efc7e6a1c} + + + {f62787dd-1327-448b-9818-030062bcfaa5} + + + {2bdb7a54-bb1a-476b-a6e5-f81e90ad4e67} + + + {f6a9102c-69a9-48fb-bc4b-49e49af43236} + + + + + + + + + + + + + + Create + Create + Create + Create + Create + Create + + + + + + + \ No newline at end of file diff --git a/Builds/MSVC/VS2010/EmberTester.vcxproj.filters b/Builds/MSVC/VS2010/EmberTester.vcxproj.filters new file mode 100644 index 0000000..f5ad239 --- /dev/null +++ b/Builds/MSVC/VS2010/EmberTester.vcxproj.filters @@ -0,0 +1,51 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/Builds/MSVC/VS2010/Fractorium.sln b/Builds/MSVC/VS2010/Fractorium.sln new file mode 100644 index 0000000..9ca557c --- /dev/null +++ b/Builds/MSVC/VS2010/Fractorium.sln @@ -0,0 +1,981 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Ember", "Ember.vcxproj", "{2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EmberTester", "EmberTester.vcxproj", "{320F611A-F9CE-4196-A8DC-FA24B2E8A320}" + ProjectSection(ProjectDependencies) = postProject + {60F89955-91C6-3A36-8000-13C592FEC2DF} = {60F89955-91C6-3A36-8000-13C592FEC2DF} + {EB33566E-DA7F-4D28-9077-88C0B7C77E35} = {EB33566E-DA7F-4D28-9077-88C0B7C77E35} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Fractorium", "Fractorium.vcxproj", "{6547D5FA-64CE-44BA-9D3C-B6E217456445}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EmberRender", "EmberRender.vcxproj", "{4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}" + ProjectSection(ProjectDependencies) = postProject + {EB33566E-DA7F-4D28-9077-88C0B7C77E35} = {EB33566E-DA7F-4D28-9077-88C0B7C77E35} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EmberAnimate", "EmberAnimate.vcxproj", "{35285FCF-6FA8-410E-841B-70AE744D38B8}" + ProjectSection(ProjectDependencies) = postProject + {EB33566E-DA7F-4D28-9077-88C0B7C77E35} = {EB33566E-DA7F-4D28-9077-88C0B7C77E35} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EmberGenome", "EmberGenome.vcxproj", "{7930CAAC-9FC4-4202-B6A3-E760F73F88B7}" + ProjectSection(ProjectDependencies) = postProject + {EB33566E-DA7F-4D28-9077-88C0B7C77E35} = {EB33566E-DA7F-4D28-9077-88C0B7C77E35} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EmberCL", "EmberCL.vcxproj", "{F6A9102C-69A9-48FB-BC4B-49E49AF43236}" +EndProject +Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "FractoriumInstaller", "FractoriumInstaller\FractoriumInstaller.wixproj", "{C8096C47-E358-438C-A520-146D46B0637D}" + ProjectSection(ProjectDependencies) = postProject + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1} = {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1} + {F6A9102C-69A9-48FB-BC4B-49E49AF43236} = {F6A9102C-69A9-48FB-BC4B-49E49AF43236} + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29} = {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29} + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67} = {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67} + {60F89955-91C6-3A36-8000-13C592FEC2DF} = {60F89955-91C6-3A36-8000-13C592FEC2DF} + {EB33566E-DA7F-4D28-9077-88C0B7C77E35} = {EB33566E-DA7F-4D28-9077-88C0B7C77E35} + {D6973076-9317-4EF2-A0B8-B7A18AC0713E} = {D6973076-9317-4EF2-A0B8-B7A18AC0713E} + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7} = {7930CAAC-9FC4-4202-B6A3-E760F73F88B7} + {35285FCF-6FA8-410E-841B-70AE744D38B8} = {35285FCF-6FA8-410E-841B-70AE744D38B8} + {F62787DD-1327-448B-9818-030062BCFAA5} = {F62787DD-1327-448B-9818-030062BCFAA5} + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C} = {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C} + {6547D5FA-64CE-44BA-9D3C-B6E217456445} = {6547D5FA-64CE-44BA-9D3C-B6E217456445} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tbb", "..\..\..\..\tbb\build\vs2010\tbb.vcxproj", "{F62787DD-1327-448B-9818-030062BCFAA5}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "..\..\..\..\libpng\projects\vstudio\zlib\zlib.vcxproj", "{60F89955-91C6-3A36-8000-13C592FEC2DF}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pnglibconf", "..\..\..\..\libpng\projects\vstudio\pnglibconf\pnglibconf.vcxproj", "{EB33566E-DA7F-4D28-9077-88C0B7C77E35}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libpng", "..\..\..\..\libpng\projects\vstudio\libpng\libpng.vcxproj", "{D6973076-9317-4EF2-A0B8-B7A18AC0713E}" + ProjectSection(ProjectDependencies) = postProject + {EB33566E-DA7F-4D28-9077-88C0B7C77E35} = {EB33566E-DA7F-4D28-9077-88C0B7C77E35} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jpeg", "..\..\..\..\libjpeg\jpeg.vcxproj", "{019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libxml2", "..\..\..\..\libxml2\win32\VC10\libxml2.vcxproj", "{1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}" + ProjectSection(ProjectDependencies) = postProject + {60F89955-91C6-3A36-8000-13C592FEC2DF} = {60F89955-91C6-3A36-8000-13C592FEC2DF} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug Library|Mixed Platforms = Debug Library|Mixed Platforms + Debug Library|Win32 = Debug Library|Win32 + Debug Library|x64 = Debug Library|x64 + Debug Library|x86 = Debug Library|x86 + Debug MX|Mixed Platforms = Debug MX|Mixed Platforms + Debug MX|Win32 = Debug MX|Win32 + Debug MX|x64 = Debug MX|x64 + Debug MX|x86 = Debug MX|x86 + Debug|Mixed Platforms = Debug|Mixed Platforms + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Debug-MT|Mixed Platforms = Debug-MT|Mixed Platforms + Debug-MT|Win32 = Debug-MT|Win32 + Debug-MT|x64 = Debug-MT|x64 + Debug-MT|x86 = Debug-MT|x86 + Release Library|Mixed Platforms = Release Library|Mixed Platforms + Release Library|Win32 = Release Library|Win32 + Release Library|x64 = Release Library|x64 + Release Library|x86 = Release Library|x86 + Release MX|Mixed Platforms = Release MX|Mixed Platforms + Release MX|Win32 = Release MX|Win32 + Release MX|x64 = Release MX|x64 + Release MX|x86 = Release MX|x86 + Release|Mixed Platforms = Release|Mixed Platforms + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + Release-MT|Mixed Platforms = Release-MT|Mixed Platforms + Release-MT|Win32 = Release-MT|Win32 + Release-MT|x64 = Release-MT|x64 + Release-MT|x86 = Release-MT|x86 + ReleaseNvidia|Mixed Platforms = ReleaseNvidia|Mixed Platforms + ReleaseNvidia|Win32 = ReleaseNvidia|Win32 + ReleaseNvidia|x64 = ReleaseNvidia|x64 + ReleaseNvidia|x86 = ReleaseNvidia|x86 + ReleaseWithoutAsm|Mixed Platforms = ReleaseWithoutAsm|Mixed Platforms + ReleaseWithoutAsm|Win32 = ReleaseWithoutAsm|Win32 + ReleaseWithoutAsm|x64 = ReleaseWithoutAsm|x64 + ReleaseWithoutAsm|x86 = ReleaseWithoutAsm|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Debug Library|Mixed Platforms.ActiveCfg = Debug|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Debug Library|Mixed Platforms.Build.0 = Debug|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Debug Library|Win32.ActiveCfg = Debug|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Debug Library|x64.ActiveCfg = Debug|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Debug Library|x64.Build.0 = Debug|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Debug Library|x86.ActiveCfg = Debug|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Debug MX|Mixed Platforms.ActiveCfg = Debug|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Debug MX|Mixed Platforms.Build.0 = Debug|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Debug MX|Win32.ActiveCfg = Debug|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Debug MX|x64.ActiveCfg = Debug|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Debug MX|x64.Build.0 = Debug|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Debug MX|x86.ActiveCfg = Debug|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Debug|Mixed Platforms.Build.0 = Debug|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Debug|Win32.ActiveCfg = Debug|Win32 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Debug|Win32.Build.0 = Debug|Win32 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Debug|x64.ActiveCfg = Debug|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Debug|x64.Build.0 = Debug|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Debug|x86.ActiveCfg = Debug|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Debug-MT|Mixed Platforms.ActiveCfg = Debug|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Debug-MT|Mixed Platforms.Build.0 = Debug|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Debug-MT|Win32.ActiveCfg = Debug|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Debug-MT|x64.ActiveCfg = Debug|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Debug-MT|x64.Build.0 = Debug|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Debug-MT|x86.ActiveCfg = Debug|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Release Library|Mixed Platforms.ActiveCfg = Release|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Release Library|Mixed Platforms.Build.0 = Release|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Release Library|Win32.ActiveCfg = Release|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Release Library|x64.ActiveCfg = Release|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Release Library|x64.Build.0 = Release|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Release Library|x86.ActiveCfg = Release|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Release MX|Mixed Platforms.ActiveCfg = Release|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Release MX|Mixed Platforms.Build.0 = Release|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Release MX|Win32.ActiveCfg = Release|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Release MX|x64.ActiveCfg = Release|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Release MX|x64.Build.0 = Release|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Release MX|x86.ActiveCfg = Release|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Release|Mixed Platforms.ActiveCfg = Release|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Release|Mixed Platforms.Build.0 = Release|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Release|Win32.ActiveCfg = Release|Win32 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Release|Win32.Build.0 = Release|Win32 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Release|x64.ActiveCfg = Release|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Release|x64.Build.0 = Release|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Release|x86.ActiveCfg = Release|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Release-MT|Mixed Platforms.ActiveCfg = Release|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Release-MT|Mixed Platforms.Build.0 = Release|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Release-MT|Win32.ActiveCfg = Release|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Release-MT|x64.ActiveCfg = Release|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Release-MT|x64.Build.0 = Release|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.Release-MT|x86.ActiveCfg = Release|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.ReleaseNvidia|Mixed Platforms.ActiveCfg = Release|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.ReleaseNvidia|Mixed Platforms.Build.0 = Release|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.ReleaseNvidia|Win32.ActiveCfg = Release|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.ReleaseNvidia|x64.ActiveCfg = ReleaseNvidia|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.ReleaseNvidia|x64.Build.0 = ReleaseNvidia|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.ReleaseNvidia|x86.ActiveCfg = Release|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.ReleaseWithoutAsm|Mixed Platforms.ActiveCfg = Release|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.ReleaseWithoutAsm|Mixed Platforms.Build.0 = Release|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.ReleaseWithoutAsm|x64.ActiveCfg = Release|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.ReleaseWithoutAsm|x64.Build.0 = Release|x64 + {2BDB7A54-BB1A-476B-A6E5-F81E90AD4E67}.ReleaseWithoutAsm|x86.ActiveCfg = Release|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Debug Library|Mixed Platforms.ActiveCfg = Debug|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Debug Library|Mixed Platforms.Build.0 = Debug|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Debug Library|Win32.ActiveCfg = Debug|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Debug Library|x64.ActiveCfg = Debug|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Debug Library|x64.Build.0 = Debug|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Debug Library|x86.ActiveCfg = Debug|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Debug MX|Mixed Platforms.ActiveCfg = Debug|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Debug MX|Mixed Platforms.Build.0 = Debug|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Debug MX|Win32.ActiveCfg = Debug|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Debug MX|x64.ActiveCfg = Debug|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Debug MX|x64.Build.0 = Debug|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Debug MX|x86.ActiveCfg = Debug|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Debug|Mixed Platforms.Build.0 = Debug|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Debug|Win32.ActiveCfg = Debug|Win32 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Debug|Win32.Build.0 = Debug|Win32 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Debug|x64.ActiveCfg = Debug|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Debug|x64.Build.0 = Debug|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Debug|x86.ActiveCfg = Debug|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Debug-MT|Mixed Platforms.ActiveCfg = Debug|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Debug-MT|Mixed Platforms.Build.0 = Debug|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Debug-MT|Win32.ActiveCfg = Debug|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Debug-MT|x64.ActiveCfg = Debug|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Debug-MT|x64.Build.0 = Debug|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Debug-MT|x86.ActiveCfg = Debug|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Release Library|Mixed Platforms.ActiveCfg = Release|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Release Library|Mixed Platforms.Build.0 = Release|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Release Library|Win32.ActiveCfg = Release|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Release Library|x64.ActiveCfg = Release|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Release Library|x64.Build.0 = Release|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Release Library|x86.ActiveCfg = Release|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Release MX|Mixed Platforms.ActiveCfg = Release|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Release MX|Mixed Platforms.Build.0 = Release|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Release MX|Win32.ActiveCfg = Release|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Release MX|x64.ActiveCfg = Release|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Release MX|x64.Build.0 = Release|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Release MX|x86.ActiveCfg = Release|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Release|Mixed Platforms.ActiveCfg = Release|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Release|Mixed Platforms.Build.0 = Release|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Release|Win32.ActiveCfg = Release|Win32 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Release|Win32.Build.0 = Release|Win32 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Release|x64.ActiveCfg = Release|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Release|x86.ActiveCfg = Release|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Release-MT|Mixed Platforms.ActiveCfg = Release|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Release-MT|Mixed Platforms.Build.0 = Release|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Release-MT|Win32.ActiveCfg = Release|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Release-MT|x64.ActiveCfg = Release|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Release-MT|x64.Build.0 = Release|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.Release-MT|x86.ActiveCfg = Release|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.ReleaseNvidia|Mixed Platforms.ActiveCfg = Release|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.ReleaseNvidia|Mixed Platforms.Build.0 = Release|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.ReleaseNvidia|Win32.ActiveCfg = Release|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.ReleaseNvidia|x64.ActiveCfg = ReleaseNvidia|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.ReleaseNvidia|x86.ActiveCfg = Release|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.ReleaseWithoutAsm|Mixed Platforms.ActiveCfg = Release|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.ReleaseWithoutAsm|Mixed Platforms.Build.0 = Release|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.ReleaseWithoutAsm|x64.ActiveCfg = Release|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.ReleaseWithoutAsm|x64.Build.0 = Release|x64 + {320F611A-F9CE-4196-A8DC-FA24B2E8A320}.ReleaseWithoutAsm|x86.ActiveCfg = Release|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Debug Library|Mixed Platforms.ActiveCfg = Debug|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Debug Library|Mixed Platforms.Build.0 = Debug|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Debug Library|Win32.ActiveCfg = Debug|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Debug Library|x64.ActiveCfg = Debug|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Debug Library|x64.Build.0 = Debug|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Debug Library|x86.ActiveCfg = Debug|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Debug MX|Mixed Platforms.ActiveCfg = Debug|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Debug MX|Mixed Platforms.Build.0 = Debug|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Debug MX|Win32.ActiveCfg = Debug|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Debug MX|x64.ActiveCfg = Debug|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Debug MX|x64.Build.0 = Debug|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Debug MX|x86.ActiveCfg = Debug|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Debug|Mixed Platforms.Build.0 = Debug|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Debug|Win32.ActiveCfg = Debug|Win32 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Debug|Win32.Build.0 = Debug|Win32 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Debug|x64.ActiveCfg = Debug|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Debug|x64.Build.0 = Debug|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Debug|x86.ActiveCfg = Debug|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Debug-MT|Mixed Platforms.ActiveCfg = Debug|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Debug-MT|Mixed Platforms.Build.0 = Debug|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Debug-MT|Win32.ActiveCfg = Debug|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Debug-MT|x64.ActiveCfg = Debug|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Debug-MT|x64.Build.0 = Debug|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Debug-MT|x86.ActiveCfg = Debug|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Release Library|Mixed Platforms.ActiveCfg = Release|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Release Library|Mixed Platforms.Build.0 = Release|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Release Library|Win32.ActiveCfg = Release|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Release Library|x64.ActiveCfg = Release|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Release Library|x64.Build.0 = Release|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Release Library|x86.ActiveCfg = Release|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Release MX|Mixed Platforms.ActiveCfg = Release|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Release MX|Mixed Platforms.Build.0 = Release|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Release MX|Win32.ActiveCfg = Release|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Release MX|x64.ActiveCfg = Release|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Release MX|x64.Build.0 = Release|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Release MX|x86.ActiveCfg = Release|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Release|Mixed Platforms.ActiveCfg = Release|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Release|Mixed Platforms.Build.0 = Release|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Release|Win32.ActiveCfg = Release|Win32 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Release|Win32.Build.0 = Release|Win32 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Release|x64.ActiveCfg = Release|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Release|x64.Build.0 = Release|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Release|x86.ActiveCfg = Release|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Release-MT|Mixed Platforms.ActiveCfg = Release|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Release-MT|Mixed Platforms.Build.0 = Release|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Release-MT|Win32.ActiveCfg = Release|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Release-MT|x64.ActiveCfg = Release|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Release-MT|x64.Build.0 = Release|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.Release-MT|x86.ActiveCfg = Release|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.ReleaseNvidia|Mixed Platforms.ActiveCfg = Release|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.ReleaseNvidia|Mixed Platforms.Build.0 = Release|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.ReleaseNvidia|Win32.ActiveCfg = Release|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.ReleaseNvidia|x64.ActiveCfg = ReleaseNvidia|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.ReleaseNvidia|x64.Build.0 = ReleaseNvidia|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.ReleaseNvidia|x86.ActiveCfg = Release|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.ReleaseWithoutAsm|Mixed Platforms.ActiveCfg = Release|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.ReleaseWithoutAsm|Mixed Platforms.Build.0 = Release|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.ReleaseWithoutAsm|x64.ActiveCfg = Release|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.ReleaseWithoutAsm|x64.Build.0 = Release|x64 + {6547D5FA-64CE-44BA-9D3C-B6E217456445}.ReleaseWithoutAsm|x86.ActiveCfg = Release|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Debug Library|Mixed Platforms.ActiveCfg = Debug|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Debug Library|Mixed Platforms.Build.0 = Debug|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Debug Library|Win32.ActiveCfg = Debug|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Debug Library|x64.ActiveCfg = Debug|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Debug Library|x64.Build.0 = Debug|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Debug Library|x86.ActiveCfg = Debug|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Debug MX|Mixed Platforms.ActiveCfg = Debug|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Debug MX|Mixed Platforms.Build.0 = Debug|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Debug MX|Win32.ActiveCfg = Debug|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Debug MX|x64.ActiveCfg = Debug|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Debug MX|x64.Build.0 = Debug|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Debug MX|x86.ActiveCfg = Debug|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Debug|Mixed Platforms.Build.0 = Debug|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Debug|Win32.ActiveCfg = Debug|Win32 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Debug|Win32.Build.0 = Debug|Win32 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Debug|x64.ActiveCfg = Debug|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Debug|x64.Build.0 = Debug|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Debug|x86.ActiveCfg = Debug|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Debug-MT|Mixed Platforms.ActiveCfg = Debug|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Debug-MT|Mixed Platforms.Build.0 = Debug|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Debug-MT|Win32.ActiveCfg = Debug|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Debug-MT|x64.ActiveCfg = Debug|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Debug-MT|x64.Build.0 = Debug|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Debug-MT|x86.ActiveCfg = Debug|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Release Library|Mixed Platforms.ActiveCfg = Release|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Release Library|Mixed Platforms.Build.0 = Release|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Release Library|Win32.ActiveCfg = Release|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Release Library|x64.ActiveCfg = Release|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Release Library|x64.Build.0 = Release|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Release Library|x86.ActiveCfg = Release|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Release MX|Mixed Platforms.ActiveCfg = Release|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Release MX|Mixed Platforms.Build.0 = Release|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Release MX|Win32.ActiveCfg = Release|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Release MX|x64.ActiveCfg = Release|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Release MX|x64.Build.0 = Release|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Release MX|x86.ActiveCfg = Release|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Release|Mixed Platforms.ActiveCfg = Release|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Release|Mixed Platforms.Build.0 = Release|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Release|Win32.ActiveCfg = Release|Win32 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Release|Win32.Build.0 = Release|Win32 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Release|x64.ActiveCfg = Release|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Release|x64.Build.0 = Release|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Release|x86.ActiveCfg = Release|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Release-MT|Mixed Platforms.ActiveCfg = Release|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Release-MT|Mixed Platforms.Build.0 = Release|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Release-MT|Win32.ActiveCfg = Release|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Release-MT|x64.ActiveCfg = Release|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Release-MT|x64.Build.0 = Release|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.Release-MT|x86.ActiveCfg = Release|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.ReleaseNvidia|Mixed Platforms.ActiveCfg = Release|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.ReleaseNvidia|Mixed Platforms.Build.0 = Release|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.ReleaseNvidia|Win32.ActiveCfg = Release|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.ReleaseNvidia|x64.ActiveCfg = ReleaseNvidia|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.ReleaseNvidia|x64.Build.0 = ReleaseNvidia|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.ReleaseNvidia|x86.ActiveCfg = Release|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.ReleaseWithoutAsm|Mixed Platforms.ActiveCfg = Release|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.ReleaseWithoutAsm|Mixed Platforms.Build.0 = Release|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.ReleaseWithoutAsm|x64.ActiveCfg = Release|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.ReleaseWithoutAsm|x64.Build.0 = Release|x64 + {4A191F4C-03AC-4F1B-AFFD-F5483ECEBD29}.ReleaseWithoutAsm|x86.ActiveCfg = Release|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Debug Library|Mixed Platforms.ActiveCfg = Debug|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Debug Library|Mixed Platforms.Build.0 = Debug|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Debug Library|Win32.ActiveCfg = Debug|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Debug Library|x64.ActiveCfg = Debug|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Debug Library|x64.Build.0 = Debug|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Debug Library|x86.ActiveCfg = Debug|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Debug MX|Mixed Platforms.ActiveCfg = Debug|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Debug MX|Mixed Platforms.Build.0 = Debug|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Debug MX|Win32.ActiveCfg = Debug|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Debug MX|x64.ActiveCfg = Debug|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Debug MX|x64.Build.0 = Debug|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Debug MX|x86.ActiveCfg = Debug|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Debug|Mixed Platforms.Build.0 = Debug|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Debug|Win32.ActiveCfg = Debug|Win32 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Debug|Win32.Build.0 = Debug|Win32 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Debug|x64.ActiveCfg = Debug|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Debug|x64.Build.0 = Debug|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Debug|x86.ActiveCfg = Debug|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Debug-MT|Mixed Platforms.ActiveCfg = Debug|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Debug-MT|Mixed Platforms.Build.0 = Debug|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Debug-MT|Win32.ActiveCfg = Debug|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Debug-MT|x64.ActiveCfg = Debug|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Debug-MT|x64.Build.0 = Debug|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Debug-MT|x86.ActiveCfg = Debug|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Release Library|Mixed Platforms.ActiveCfg = Release|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Release Library|Mixed Platforms.Build.0 = Release|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Release Library|Win32.ActiveCfg = Release|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Release Library|x64.ActiveCfg = Release|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Release Library|x64.Build.0 = Release|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Release Library|x86.ActiveCfg = Release|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Release MX|Mixed Platforms.ActiveCfg = Release|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Release MX|Mixed Platforms.Build.0 = Release|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Release MX|Win32.ActiveCfg = Release|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Release MX|x64.ActiveCfg = Release|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Release MX|x64.Build.0 = Release|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Release MX|x86.ActiveCfg = Release|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Release|Mixed Platforms.ActiveCfg = Release|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Release|Mixed Platforms.Build.0 = Release|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Release|Win32.ActiveCfg = Release|Win32 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Release|Win32.Build.0 = Release|Win32 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Release|x64.ActiveCfg = Release|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Release|x64.Build.0 = Release|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Release|x86.ActiveCfg = Release|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Release-MT|Mixed Platforms.ActiveCfg = Release|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Release-MT|Mixed Platforms.Build.0 = Release|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Release-MT|Win32.ActiveCfg = Release|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Release-MT|x64.ActiveCfg = Release|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Release-MT|x64.Build.0 = Release|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.Release-MT|x86.ActiveCfg = Release|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.ReleaseNvidia|Mixed Platforms.ActiveCfg = Release|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.ReleaseNvidia|Mixed Platforms.Build.0 = Release|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.ReleaseNvidia|Win32.ActiveCfg = Release|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.ReleaseNvidia|x64.ActiveCfg = ReleaseNvidia|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.ReleaseNvidia|x64.Build.0 = ReleaseNvidia|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.ReleaseNvidia|x86.ActiveCfg = Release|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.ReleaseWithoutAsm|Mixed Platforms.ActiveCfg = Release|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.ReleaseWithoutAsm|Mixed Platforms.Build.0 = Release|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.ReleaseWithoutAsm|x64.ActiveCfg = Release|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.ReleaseWithoutAsm|x64.Build.0 = Release|x64 + {35285FCF-6FA8-410E-841B-70AE744D38B8}.ReleaseWithoutAsm|x86.ActiveCfg = Release|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Debug Library|Mixed Platforms.ActiveCfg = Debug|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Debug Library|Mixed Platforms.Build.0 = Debug|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Debug Library|Win32.ActiveCfg = Debug|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Debug Library|x64.ActiveCfg = Debug|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Debug Library|x64.Build.0 = Debug|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Debug Library|x86.ActiveCfg = Debug|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Debug MX|Mixed Platforms.ActiveCfg = Debug|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Debug MX|Mixed Platforms.Build.0 = Debug|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Debug MX|Win32.ActiveCfg = Debug|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Debug MX|x64.ActiveCfg = Debug|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Debug MX|x64.Build.0 = Debug|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Debug MX|x86.ActiveCfg = Debug|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Debug|Mixed Platforms.Build.0 = Debug|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Debug|Win32.ActiveCfg = Debug|Win32 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Debug|Win32.Build.0 = Debug|Win32 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Debug|x64.ActiveCfg = Debug|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Debug|x64.Build.0 = Debug|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Debug|x86.ActiveCfg = Debug|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Debug-MT|Mixed Platforms.ActiveCfg = Debug|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Debug-MT|Mixed Platforms.Build.0 = Debug|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Debug-MT|Win32.ActiveCfg = Debug|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Debug-MT|x64.ActiveCfg = Debug|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Debug-MT|x64.Build.0 = Debug|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Debug-MT|x86.ActiveCfg = Debug|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Release Library|Mixed Platforms.ActiveCfg = Release|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Release Library|Mixed Platforms.Build.0 = Release|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Release Library|Win32.ActiveCfg = Release|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Release Library|x64.ActiveCfg = Release|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Release Library|x64.Build.0 = Release|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Release Library|x86.ActiveCfg = Release|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Release MX|Mixed Platforms.ActiveCfg = Release|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Release MX|Mixed Platforms.Build.0 = Release|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Release MX|Win32.ActiveCfg = Release|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Release MX|x64.ActiveCfg = Release|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Release MX|x64.Build.0 = Release|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Release MX|x86.ActiveCfg = Release|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Release|Mixed Platforms.ActiveCfg = Release|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Release|Mixed Platforms.Build.0 = Release|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Release|Win32.ActiveCfg = Release|Win32 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Release|Win32.Build.0 = Release|Win32 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Release|x64.ActiveCfg = Release|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Release|x64.Build.0 = Release|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Release|x86.ActiveCfg = Release|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Release-MT|Mixed Platforms.ActiveCfg = Release|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Release-MT|Mixed Platforms.Build.0 = Release|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Release-MT|Win32.ActiveCfg = Release|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Release-MT|x64.ActiveCfg = Release|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Release-MT|x64.Build.0 = Release|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.Release-MT|x86.ActiveCfg = Release|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.ReleaseNvidia|Mixed Platforms.ActiveCfg = Release|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.ReleaseNvidia|Mixed Platforms.Build.0 = Release|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.ReleaseNvidia|Win32.ActiveCfg = Release|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.ReleaseNvidia|x64.ActiveCfg = ReleaseNvidia|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.ReleaseNvidia|x64.Build.0 = ReleaseNvidia|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.ReleaseNvidia|x86.ActiveCfg = Release|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.ReleaseWithoutAsm|Mixed Platforms.ActiveCfg = Release|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.ReleaseWithoutAsm|Mixed Platforms.Build.0 = Release|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.ReleaseWithoutAsm|x64.ActiveCfg = Release|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.ReleaseWithoutAsm|x64.Build.0 = Release|x64 + {7930CAAC-9FC4-4202-B6A3-E760F73F88B7}.ReleaseWithoutAsm|x86.ActiveCfg = Release|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Debug Library|Mixed Platforms.ActiveCfg = Debug|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Debug Library|Mixed Platforms.Build.0 = Debug|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Debug Library|Win32.ActiveCfg = Debug|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Debug Library|x64.ActiveCfg = Debug|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Debug Library|x64.Build.0 = Debug|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Debug Library|x86.ActiveCfg = Debug|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Debug MX|Mixed Platforms.ActiveCfg = Debug|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Debug MX|Mixed Platforms.Build.0 = Debug|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Debug MX|Win32.ActiveCfg = Debug|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Debug MX|x64.ActiveCfg = Debug|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Debug MX|x64.Build.0 = Debug|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Debug MX|x86.ActiveCfg = Debug|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Debug|Mixed Platforms.Build.0 = Debug|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Debug|Win32.ActiveCfg = Debug|Win32 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Debug|Win32.Build.0 = Debug|Win32 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Debug|x64.ActiveCfg = Debug|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Debug|x64.Build.0 = Debug|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Debug|x86.ActiveCfg = Debug|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Debug-MT|Mixed Platforms.ActiveCfg = Debug|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Debug-MT|Mixed Platforms.Build.0 = Debug|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Debug-MT|Win32.ActiveCfg = Debug|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Debug-MT|x64.ActiveCfg = Debug|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Debug-MT|x64.Build.0 = Debug|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Debug-MT|x86.ActiveCfg = Debug|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Release Library|Mixed Platforms.ActiveCfg = Release|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Release Library|Mixed Platforms.Build.0 = Release|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Release Library|Win32.ActiveCfg = Release|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Release Library|x64.ActiveCfg = Release|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Release Library|x64.Build.0 = Release|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Release Library|x86.ActiveCfg = Release|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Release MX|Mixed Platforms.ActiveCfg = Release|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Release MX|Mixed Platforms.Build.0 = Release|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Release MX|Win32.ActiveCfg = Release|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Release MX|x64.ActiveCfg = Release|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Release MX|x64.Build.0 = Release|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Release MX|x86.ActiveCfg = Release|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Release|Mixed Platforms.ActiveCfg = Release|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Release|Mixed Platforms.Build.0 = Release|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Release|Win32.ActiveCfg = Release|Win32 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Release|Win32.Build.0 = Release|Win32 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Release|x64.ActiveCfg = Release|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Release|x64.Build.0 = Release|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Release|x86.ActiveCfg = Release|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Release-MT|Mixed Platforms.ActiveCfg = Release|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Release-MT|Mixed Platforms.Build.0 = Release|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Release-MT|Win32.ActiveCfg = Release|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Release-MT|x64.ActiveCfg = Release|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Release-MT|x64.Build.0 = Release|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.Release-MT|x86.ActiveCfg = Release|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.ReleaseNvidia|Mixed Platforms.ActiveCfg = ReleaseNvidia|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.ReleaseNvidia|Mixed Platforms.Build.0 = ReleaseNvidia|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.ReleaseNvidia|Win32.ActiveCfg = ReleaseNvidia|Win32 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.ReleaseNvidia|Win32.Build.0 = ReleaseNvidia|Win32 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.ReleaseNvidia|x64.ActiveCfg = ReleaseNvidia|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.ReleaseNvidia|x64.Build.0 = ReleaseNvidia|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.ReleaseNvidia|x86.ActiveCfg = ReleaseNvidia|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.ReleaseWithoutAsm|Mixed Platforms.ActiveCfg = Release|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.ReleaseWithoutAsm|Mixed Platforms.Build.0 = Release|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.ReleaseWithoutAsm|x64.ActiveCfg = Release|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.ReleaseWithoutAsm|x64.Build.0 = Release|x64 + {F6A9102C-69A9-48FB-BC4B-49E49AF43236}.ReleaseWithoutAsm|x86.ActiveCfg = Release|x64 + {C8096C47-E358-438C-A520-146D46B0637D}.Debug Library|Mixed Platforms.ActiveCfg = Debug|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Debug Library|Mixed Platforms.Build.0 = Debug|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Debug Library|Win32.ActiveCfg = Debug|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Debug Library|x64.ActiveCfg = Debug|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Debug Library|x86.ActiveCfg = Debug|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Debug Library|x86.Build.0 = Debug|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Debug MX|Mixed Platforms.ActiveCfg = Debug|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Debug MX|Mixed Platforms.Build.0 = Debug|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Debug MX|Win32.ActiveCfg = Debug|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Debug MX|x64.ActiveCfg = Debug|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Debug MX|x86.ActiveCfg = Debug|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Debug MX|x86.Build.0 = Debug|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Debug|Mixed Platforms.Build.0 = Debug|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Debug|Win32.ActiveCfg = Debug|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Debug|x64.ActiveCfg = Debug|x64 + {C8096C47-E358-438C-A520-146D46B0637D}.Debug|x86.ActiveCfg = Debug|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Debug|x86.Build.0 = Debug|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Debug-MT|Mixed Platforms.ActiveCfg = Debug|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Debug-MT|Mixed Platforms.Build.0 = Debug|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Debug-MT|Win32.ActiveCfg = Debug|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Debug-MT|x64.ActiveCfg = Debug|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Debug-MT|x86.ActiveCfg = Debug|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Debug-MT|x86.Build.0 = Debug|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Release Library|Mixed Platforms.ActiveCfg = Release|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Release Library|Mixed Platforms.Build.0 = Release|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Release Library|Win32.ActiveCfg = Release|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Release Library|x64.ActiveCfg = Release|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Release Library|x86.ActiveCfg = Release|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Release Library|x86.Build.0 = Release|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Release MX|Mixed Platforms.ActiveCfg = Release|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Release MX|Mixed Platforms.Build.0 = Release|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Release MX|Win32.ActiveCfg = Release|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Release MX|x64.ActiveCfg = Release|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Release MX|x86.ActiveCfg = Release|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Release MX|x86.Build.0 = Release|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Release|Mixed Platforms.Build.0 = Release|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Release|Win32.ActiveCfg = Release|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Release|x64.ActiveCfg = Release|x64 + {C8096C47-E358-438C-A520-146D46B0637D}.Release|x64.Build.0 = Release|x64 + {C8096C47-E358-438C-A520-146D46B0637D}.Release|x86.ActiveCfg = Release|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Release|x86.Build.0 = Release|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Release-MT|Mixed Platforms.ActiveCfg = Release|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Release-MT|Mixed Platforms.Build.0 = Release|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Release-MT|Win32.ActiveCfg = Release|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Release-MT|x64.ActiveCfg = Release|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Release-MT|x86.ActiveCfg = Release|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.Release-MT|x86.Build.0 = Release|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.ReleaseNvidia|Mixed Platforms.ActiveCfg = Release|x64 + {C8096C47-E358-438C-A520-146D46B0637D}.ReleaseNvidia|Mixed Platforms.Build.0 = Release|x64 + {C8096C47-E358-438C-A520-146D46B0637D}.ReleaseNvidia|Win32.ActiveCfg = Release|x64 + {C8096C47-E358-438C-A520-146D46B0637D}.ReleaseNvidia|x64.ActiveCfg = ReleaseNvidia|x64 + {C8096C47-E358-438C-A520-146D46B0637D}.ReleaseNvidia|x64.Build.0 = ReleaseNvidia|x64 + {C8096C47-E358-438C-A520-146D46B0637D}.ReleaseNvidia|x86.ActiveCfg = Release|x64 + {C8096C47-E358-438C-A520-146D46B0637D}.ReleaseWithoutAsm|Mixed Platforms.ActiveCfg = Release|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.ReleaseWithoutAsm|Mixed Platforms.Build.0 = Release|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.ReleaseWithoutAsm|x64.ActiveCfg = Release|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.ReleaseWithoutAsm|x86.ActiveCfg = Release|x86 + {C8096C47-E358-438C-A520-146D46B0637D}.ReleaseWithoutAsm|x86.Build.0 = Release|x86 + {F62787DD-1327-448B-9818-030062BCFAA5}.Debug Library|Mixed Platforms.ActiveCfg = Debug|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Debug Library|Mixed Platforms.Build.0 = Debug|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Debug Library|Win32.ActiveCfg = Debug|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Debug Library|x64.ActiveCfg = Debug|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Debug Library|x64.Build.0 = Debug|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Debug Library|x86.ActiveCfg = Debug|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Debug MX|Mixed Platforms.ActiveCfg = Debug|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Debug MX|Mixed Platforms.Build.0 = Debug|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Debug MX|Win32.ActiveCfg = Debug|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Debug MX|x64.ActiveCfg = Debug|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Debug MX|x64.Build.0 = Debug|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Debug MX|x86.ActiveCfg = Debug|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Debug|Mixed Platforms.Build.0 = Debug|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Debug|Win32.ActiveCfg = Debug|Win32 + {F62787DD-1327-448B-9818-030062BCFAA5}.Debug|Win32.Build.0 = Debug|Win32 + {F62787DD-1327-448B-9818-030062BCFAA5}.Debug|x64.ActiveCfg = Debug|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Debug|x64.Build.0 = Debug|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Debug|x86.ActiveCfg = Debug|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Debug-MT|Mixed Platforms.ActiveCfg = Debug-MT|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Debug-MT|Mixed Platforms.Build.0 = Debug-MT|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Debug-MT|Win32.ActiveCfg = Debug-MT|Win32 + {F62787DD-1327-448B-9818-030062BCFAA5}.Debug-MT|Win32.Build.0 = Debug-MT|Win32 + {F62787DD-1327-448B-9818-030062BCFAA5}.Debug-MT|x64.ActiveCfg = Debug-MT|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Debug-MT|x64.Build.0 = Debug-MT|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Debug-MT|x86.ActiveCfg = Debug-MT|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Release Library|Mixed Platforms.ActiveCfg = Release|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Release Library|Mixed Platforms.Build.0 = Release|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Release Library|Win32.ActiveCfg = Release|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Release Library|x64.ActiveCfg = Release|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Release Library|x64.Build.0 = Release|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Release Library|x86.ActiveCfg = Release|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Release MX|Mixed Platforms.ActiveCfg = Release|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Release MX|Mixed Platforms.Build.0 = Release|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Release MX|Win32.ActiveCfg = Release|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Release MX|x64.ActiveCfg = Release|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Release MX|x64.Build.0 = Release|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Release MX|x86.ActiveCfg = Release|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Release|Mixed Platforms.ActiveCfg = Release|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Release|Mixed Platforms.Build.0 = Release|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Release|Win32.ActiveCfg = Release|Win32 + {F62787DD-1327-448B-9818-030062BCFAA5}.Release|Win32.Build.0 = Release|Win32 + {F62787DD-1327-448B-9818-030062BCFAA5}.Release|x64.ActiveCfg = Release|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Release|x64.Build.0 = Release|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Release|x86.ActiveCfg = Release|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Release-MT|Mixed Platforms.ActiveCfg = Release-MT|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Release-MT|Mixed Platforms.Build.0 = Release-MT|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Release-MT|Win32.ActiveCfg = Release-MT|Win32 + {F62787DD-1327-448B-9818-030062BCFAA5}.Release-MT|Win32.Build.0 = Release-MT|Win32 + {F62787DD-1327-448B-9818-030062BCFAA5}.Release-MT|x64.ActiveCfg = Release-MT|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Release-MT|x64.Build.0 = Release-MT|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.Release-MT|x86.ActiveCfg = Release-MT|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.ReleaseNvidia|Mixed Platforms.ActiveCfg = Release|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.ReleaseNvidia|Mixed Platforms.Build.0 = Release|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.ReleaseNvidia|Win32.ActiveCfg = Release|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.ReleaseNvidia|x64.ActiveCfg = Release|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.ReleaseNvidia|x64.Build.0 = Release|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.ReleaseNvidia|x86.ActiveCfg = Release|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.ReleaseWithoutAsm|Mixed Platforms.ActiveCfg = Release|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.ReleaseWithoutAsm|Mixed Platforms.Build.0 = Release|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.ReleaseWithoutAsm|x64.ActiveCfg = Release|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.ReleaseWithoutAsm|x64.Build.0 = Release|x64 + {F62787DD-1327-448B-9818-030062BCFAA5}.ReleaseWithoutAsm|x86.ActiveCfg = Release|x64 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Debug Library|Mixed Platforms.ActiveCfg = Debug Library|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Debug Library|Mixed Platforms.Build.0 = Debug Library|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Debug Library|Win32.ActiveCfg = Debug Library|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Debug Library|Win32.Build.0 = Debug Library|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Debug Library|x64.ActiveCfg = Debug Library|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Debug Library|x86.ActiveCfg = Debug Library|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Debug MX|Mixed Platforms.ActiveCfg = Debug Library|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Debug MX|Mixed Platforms.Build.0 = Debug Library|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Debug MX|Win32.ActiveCfg = Debug Library|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Debug MX|Win32.Build.0 = Debug Library|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Debug MX|x64.ActiveCfg = Debug Library|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Debug MX|x86.ActiveCfg = Debug Library|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Debug|Mixed Platforms.Build.0 = Debug|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Debug|Win32.ActiveCfg = Debug|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Debug|Win32.Build.0 = Debug|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Debug|x64.ActiveCfg = Debug|x64 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Debug|x64.Build.0 = Debug|x64 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Debug|x86.ActiveCfg = Debug|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Debug-MT|Mixed Platforms.ActiveCfg = Debug|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Debug-MT|Mixed Platforms.Build.0 = Debug|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Debug-MT|Win32.ActiveCfg = Debug|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Debug-MT|Win32.Build.0 = Debug|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Debug-MT|x64.ActiveCfg = Debug|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Debug-MT|x86.ActiveCfg = Debug|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Release Library|Mixed Platforms.ActiveCfg = Release Library|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Release Library|Mixed Platforms.Build.0 = Release Library|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Release Library|Win32.ActiveCfg = Release Library|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Release Library|Win32.Build.0 = Release Library|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Release Library|x64.ActiveCfg = Release Library|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Release Library|x86.ActiveCfg = Release Library|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Release MX|Mixed Platforms.ActiveCfg = Release Library|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Release MX|Mixed Platforms.Build.0 = Release Library|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Release MX|Win32.ActiveCfg = Release Library|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Release MX|Win32.Build.0 = Release Library|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Release MX|x64.ActiveCfg = Release Library|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Release MX|x86.ActiveCfg = Release Library|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Release|Mixed Platforms.Build.0 = Release|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Release|Win32.ActiveCfg = Release|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Release|Win32.Build.0 = Release|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Release|x64.ActiveCfg = Release|x64 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Release|x64.Build.0 = Release|x64 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Release|x86.ActiveCfg = Release|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Release-MT|Mixed Platforms.ActiveCfg = Release|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Release-MT|Mixed Platforms.Build.0 = Release|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Release-MT|Win32.ActiveCfg = Release|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Release-MT|Win32.Build.0 = Release|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Release-MT|x64.ActiveCfg = Release|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.Release-MT|x86.ActiveCfg = Release|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.ReleaseNvidia|Mixed Platforms.ActiveCfg = Release|x64 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.ReleaseNvidia|Mixed Platforms.Build.0 = Release|x64 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.ReleaseNvidia|Win32.ActiveCfg = Release|x64 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.ReleaseNvidia|x64.ActiveCfg = Release|x64 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.ReleaseNvidia|x64.Build.0 = Release|x64 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.ReleaseNvidia|x86.ActiveCfg = Release|x64 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.ReleaseWithoutAsm|Mixed Platforms.ActiveCfg = Release|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.ReleaseWithoutAsm|Mixed Platforms.Build.0 = Release|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.ReleaseWithoutAsm|Win32.Build.0 = Release|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.ReleaseWithoutAsm|x64.ActiveCfg = Release|Win32 + {60F89955-91C6-3A36-8000-13C592FEC2DF}.ReleaseWithoutAsm|x86.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Debug Library|Mixed Platforms.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Debug Library|Mixed Platforms.Build.0 = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Debug Library|Win32.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Debug Library|Win32.Build.0 = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Debug Library|x64.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Debug Library|x86.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Debug MX|Mixed Platforms.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Debug MX|Mixed Platforms.Build.0 = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Debug MX|Win32.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Debug MX|Win32.Build.0 = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Debug MX|x64.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Debug MX|x86.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Debug|Mixed Platforms.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Debug|Mixed Platforms.Build.0 = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Debug|Win32.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Debug|Win32.Build.0 = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Debug|x64.ActiveCfg = Release|x64 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Debug|x64.Build.0 = Release|x64 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Debug|x86.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Debug-MT|Mixed Platforms.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Debug-MT|Mixed Platforms.Build.0 = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Debug-MT|Win32.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Debug-MT|Win32.Build.0 = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Debug-MT|x64.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Debug-MT|x86.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Release Library|Mixed Platforms.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Release Library|Mixed Platforms.Build.0 = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Release Library|Win32.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Release Library|Win32.Build.0 = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Release Library|x64.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Release Library|x86.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Release MX|Mixed Platforms.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Release MX|Mixed Platforms.Build.0 = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Release MX|Win32.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Release MX|Win32.Build.0 = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Release MX|x64.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Release MX|x86.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Release|Mixed Platforms.Build.0 = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Release|Win32.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Release|Win32.Build.0 = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Release|x64.ActiveCfg = Release|x64 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Release|x64.Build.0 = Release|x64 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Release|x86.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Release-MT|Mixed Platforms.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Release-MT|Mixed Platforms.Build.0 = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Release-MT|Win32.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Release-MT|Win32.Build.0 = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Release-MT|x64.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.Release-MT|x86.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.ReleaseNvidia|Mixed Platforms.ActiveCfg = Release|x64 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.ReleaseNvidia|Mixed Platforms.Build.0 = Release|x64 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.ReleaseNvidia|Win32.ActiveCfg = Release|x64 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.ReleaseNvidia|x64.ActiveCfg = Release|x64 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.ReleaseNvidia|x64.Build.0 = Release|x64 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.ReleaseNvidia|x86.ActiveCfg = Release|x64 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.ReleaseWithoutAsm|Mixed Platforms.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.ReleaseWithoutAsm|Mixed Platforms.Build.0 = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.ReleaseWithoutAsm|Win32.Build.0 = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.ReleaseWithoutAsm|x64.ActiveCfg = Release|Win32 + {EB33566E-DA7F-4D28-9077-88C0B7C77E35}.ReleaseWithoutAsm|x86.ActiveCfg = Release|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug Library|Mixed Platforms.ActiveCfg = Debug Library|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug Library|Mixed Platforms.Build.0 = Debug Library|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug Library|Win32.ActiveCfg = Debug Library|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug Library|Win32.Build.0 = Debug Library|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug Library|x64.ActiveCfg = Debug Library|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug Library|x86.ActiveCfg = Debug Library|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug MX|Mixed Platforms.ActiveCfg = Debug Library|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug MX|Mixed Platforms.Build.0 = Debug Library|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug MX|Win32.ActiveCfg = Debug Library|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug MX|Win32.Build.0 = Debug Library|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug MX|x64.ActiveCfg = Debug Library|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug MX|x86.ActiveCfg = Debug Library|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug|Mixed Platforms.Build.0 = Debug|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug|Win32.ActiveCfg = Debug|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug|Win32.Build.0 = Debug|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug|x64.ActiveCfg = Debug|x64 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug|x64.Build.0 = Debug|x64 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug|x86.ActiveCfg = Debug|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug-MT|Mixed Platforms.ActiveCfg = Debug|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug-MT|Mixed Platforms.Build.0 = Debug|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug-MT|Win32.ActiveCfg = Debug|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug-MT|Win32.Build.0 = Debug|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug-MT|x64.ActiveCfg = Debug|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug-MT|x86.ActiveCfg = Debug|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release Library|Mixed Platforms.ActiveCfg = Release Library|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release Library|Mixed Platforms.Build.0 = Release Library|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release Library|Win32.ActiveCfg = Release Library|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release Library|Win32.Build.0 = Release Library|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release Library|x64.ActiveCfg = Release Library|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release Library|x86.ActiveCfg = Release Library|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release MX|Mixed Platforms.ActiveCfg = Release Library|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release MX|Mixed Platforms.Build.0 = Release Library|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release MX|Win32.ActiveCfg = Release Library|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release MX|Win32.Build.0 = Release Library|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release MX|x64.ActiveCfg = Release Library|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release MX|x86.ActiveCfg = Release Library|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release|Mixed Platforms.Build.0 = Release|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release|Win32.ActiveCfg = Release|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release|Win32.Build.0 = Release|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release|x64.ActiveCfg = Release|x64 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release|x64.Build.0 = Release|x64 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release|x86.ActiveCfg = Release|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release-MT|Mixed Platforms.ActiveCfg = Release|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release-MT|Mixed Platforms.Build.0 = Release|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release-MT|Win32.ActiveCfg = Release|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release-MT|Win32.Build.0 = Release|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release-MT|x64.ActiveCfg = Release|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release-MT|x86.ActiveCfg = Release|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.ReleaseNvidia|Mixed Platforms.ActiveCfg = Release|x64 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.ReleaseNvidia|Mixed Platforms.Build.0 = Release|x64 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.ReleaseNvidia|Win32.ActiveCfg = Release|x64 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.ReleaseNvidia|x64.ActiveCfg = Release|x64 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.ReleaseNvidia|x64.Build.0 = Release|x64 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.ReleaseNvidia|x86.ActiveCfg = Release|x64 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.ReleaseWithoutAsm|Mixed Platforms.ActiveCfg = Release|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.ReleaseWithoutAsm|Mixed Platforms.Build.0 = Release|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.ReleaseWithoutAsm|Win32.Build.0 = Release|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.ReleaseWithoutAsm|x64.ActiveCfg = Release|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.ReleaseWithoutAsm|x86.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Debug Library|Mixed Platforms.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Debug Library|Mixed Platforms.Build.0 = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Debug Library|Win32.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Debug Library|Win32.Build.0 = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Debug Library|x64.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Debug Library|x86.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Debug MX|Mixed Platforms.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Debug MX|Mixed Platforms.Build.0 = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Debug MX|Win32.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Debug MX|Win32.Build.0 = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Debug MX|x64.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Debug MX|x86.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Debug|Mixed Platforms.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Debug|Mixed Platforms.Build.0 = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Debug|Win32.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Debug|Win32.Build.0 = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Debug|x64.ActiveCfg = Release|x64 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Debug|x64.Build.0 = Release|x64 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Debug|x86.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Debug-MT|Mixed Platforms.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Debug-MT|Mixed Platforms.Build.0 = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Debug-MT|Win32.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Debug-MT|Win32.Build.0 = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Debug-MT|x64.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Debug-MT|x86.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Release Library|Mixed Platforms.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Release Library|Mixed Platforms.Build.0 = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Release Library|Win32.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Release Library|Win32.Build.0 = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Release Library|x64.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Release Library|x86.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Release MX|Mixed Platforms.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Release MX|Mixed Platforms.Build.0 = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Release MX|Win32.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Release MX|Win32.Build.0 = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Release MX|x64.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Release MX|x86.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Release|Mixed Platforms.Build.0 = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Release|Win32.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Release|Win32.Build.0 = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Release|x64.ActiveCfg = Release|x64 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Release|x64.Build.0 = Release|x64 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Release|x86.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Release-MT|Mixed Platforms.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Release-MT|Mixed Platforms.Build.0 = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Release-MT|Win32.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Release-MT|Win32.Build.0 = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Release-MT|x64.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.Release-MT|x86.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.ReleaseNvidia|Mixed Platforms.ActiveCfg = Release|x64 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.ReleaseNvidia|Mixed Platforms.Build.0 = Release|x64 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.ReleaseNvidia|Win32.ActiveCfg = Release|x64 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.ReleaseNvidia|x64.ActiveCfg = Release|x64 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.ReleaseNvidia|x64.Build.0 = Release|x64 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.ReleaseNvidia|x86.ActiveCfg = Release|x64 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.ReleaseWithoutAsm|Mixed Platforms.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.ReleaseWithoutAsm|Mixed Platforms.Build.0 = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.ReleaseWithoutAsm|Win32.Build.0 = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.ReleaseWithoutAsm|x64.ActiveCfg = Release|Win32 + {019DBD2A-273D-4BA4-BF86-B5EFE2ED76B1}.ReleaseWithoutAsm|x86.ActiveCfg = Release|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Debug Library|Mixed Platforms.ActiveCfg = Debug|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Debug Library|Mixed Platforms.Build.0 = Debug|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Debug Library|Win32.ActiveCfg = Debug|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Debug Library|Win32.Build.0 = Debug|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Debug Library|x64.ActiveCfg = Debug|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Debug Library|x86.ActiveCfg = Debug|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Debug MX|Mixed Platforms.ActiveCfg = Debug|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Debug MX|Mixed Platforms.Build.0 = Debug|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Debug MX|Win32.ActiveCfg = Debug|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Debug MX|Win32.Build.0 = Debug|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Debug MX|x64.ActiveCfg = Debug|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Debug MX|x86.ActiveCfg = Debug|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Debug|Mixed Platforms.Build.0 = Debug|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Debug|Win32.ActiveCfg = Debug|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Debug|Win32.Build.0 = Debug|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Debug|x64.ActiveCfg = Debug|x64 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Debug|x64.Build.0 = Debug|x64 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Debug|x86.ActiveCfg = Debug|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Debug-MT|Mixed Platforms.ActiveCfg = Debug|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Debug-MT|Mixed Platforms.Build.0 = Debug|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Debug-MT|Win32.ActiveCfg = Debug|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Debug-MT|Win32.Build.0 = Debug|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Debug-MT|x64.ActiveCfg = Debug|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Debug-MT|x86.ActiveCfg = Debug|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Release Library|Mixed Platforms.ActiveCfg = Release|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Release Library|Mixed Platforms.Build.0 = Release|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Release Library|Win32.ActiveCfg = Release|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Release Library|Win32.Build.0 = Release|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Release Library|x64.ActiveCfg = Release|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Release Library|x86.ActiveCfg = Release|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Release MX|Mixed Platforms.ActiveCfg = Release|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Release MX|Mixed Platforms.Build.0 = Release|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Release MX|Win32.ActiveCfg = Release|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Release MX|Win32.Build.0 = Release|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Release MX|x64.ActiveCfg = Release|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Release MX|x86.ActiveCfg = Release|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Release|Mixed Platforms.Build.0 = Release|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Release|Win32.ActiveCfg = Release|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Release|Win32.Build.0 = Release|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Release|x64.ActiveCfg = Release|x64 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Release|x64.Build.0 = Release|x64 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Release|x86.ActiveCfg = Release|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Release-MT|Mixed Platforms.ActiveCfg = Release|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Release-MT|Mixed Platforms.Build.0 = Release|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Release-MT|Win32.ActiveCfg = Release|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Release-MT|Win32.Build.0 = Release|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Release-MT|x64.ActiveCfg = Release|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.Release-MT|x86.ActiveCfg = Release|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.ReleaseNvidia|Mixed Platforms.ActiveCfg = Release|x64 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.ReleaseNvidia|Mixed Platforms.Build.0 = Release|x64 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.ReleaseNvidia|Win32.ActiveCfg = Release|x64 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.ReleaseNvidia|x64.ActiveCfg = Release|x64 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.ReleaseNvidia|x64.Build.0 = Release|x64 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.ReleaseNvidia|x86.ActiveCfg = Release|x64 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.ReleaseWithoutAsm|Mixed Platforms.ActiveCfg = Release|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.ReleaseWithoutAsm|Mixed Platforms.Build.0 = Release|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.ReleaseWithoutAsm|Win32.Build.0 = Release|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.ReleaseWithoutAsm|x64.ActiveCfg = Release|Win32 + {1D6039F6-5078-416F-A3AF-A36EFC7E6A1C}.ReleaseWithoutAsm|x86.ActiveCfg = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Builds/MSVC/VS2010/Fractorium.vcxproj b/Builds/MSVC/VS2010/Fractorium.vcxproj new file mode 100644 index 0000000..65951e9 --- /dev/null +++ b/Builds/MSVC/VS2010/Fractorium.vcxproj @@ -0,0 +1,1109 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + ReleaseNvidia + Win32 + + + ReleaseNvidia + x64 + + + Release + Win32 + + + Release + x64 + + + + {6547D5FA-64CE-44BA-9D3C-B6E217456445} + Qt4VSv1.0 + + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + AllRules.ruleset + AllRules.ruleset + + + + + AllRules.ruleset + AllRules.ruleset + AllRules.ruleset + AllRules.ruleset + + + + + + + + + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + $(SolutionDir)Obj\$(TargetName)\$(Platform)\$(Configuration)\ + + + + UNICODE;WIN32;QT_DLL;QT_CORE_LIB;QT_GUI_LIB;QT_MULTIMEDIA_LIB;QT_HELP_LIB;QT_OPENGL_LIB;QT_WIDGETS_LIB;QT_XML_LIB;%(PreprocessorDefinitions) + .;$(QTDIR)\include;$(ProjectDir)..\..\..\Fractorium\GeneratedFiles;$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName;$(QTDIR)\..\qtmultimedia\include\QtMultimedia;$(QTDIR)\..\qtmultimedia\include;$(QTDIR)\..\qttools\include;$(QTDIR)\..\qttools\include\QtHelp;$(QTDIR)\include\QtConcurrent;$(QTDIR)\include\QtCore;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtOpenGL;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtXml;.\GeneratedFiles;$(ProjectDir)..\..\..\Source\Ember;$(ProjectDir)..\..\..\Source\EmberCL;$(ProjectDir)..\..\..\Source\EmberCommon;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libjpeg;$(ProjectDir)..\..\..\..\libpng;$(ProjectDir)..\..\..\..\libxml2\include;$(ProjectDir)..\..\..\..\glew\include;$(AMDAPPSDKROOT)\include;$(CUDA_PATH)include;.\GeneratedFiles\$(ConfigurationName);%(AdditionalIncludeDirectories) + Disabled + ProgramDatabase + MultiThreadedDebugDLL + false + Use + FractoriumPch.h + $(IntDir)$(TargetName).pch + $(TargetDir)$(TargetName).pdb + 4661 + + + Windows + $(OutDir)\$(ProjectName).exe + $(QTDIR)\lib;$(AMDAPPSDKROOT)\lib\x86;$(CUDA_PATH)lib\$(PlatformName);%(AdditionalLibraryDirectories) + true + qtmaind.lib;Qt5Cored.lib;Qt5Guid.lib;Qt5OpenGLd.lib;opengl32.lib;glu32.lib;opencl.lib;Qt5Widgetsd.lib;Qt5Xmld.lib;%(AdditionalDependencies) + + + xcopy /F /Y /R /D "$(SolutionDir)$(Platform)\$(Configuration)\*.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(ProjectDir)..\..\..\..\tbb\build\vsproject\ia32\$(Configuration)\tbb_debug.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(ProjectDir)..\..\..\..\tbb\build\vsproject\ia32\$(Configuration)\tbb_debug.pdb" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)" + + + + + UNICODE;WIN32;QT_DLL;QT_CORE_LIB;QT_GUI_LIB;QT_MULTIMEDIA_LIB;QT_HELP_LIB;QT_OPENGL_LIB;QT_WIDGETS_LIB;QT_XML_LIB;%(PreprocessorDefinitions) + .;$(QTDIR)\include;$(ProjectDir)..\..\..\Fractorium\GeneratedFiles;$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName;$(QTDIR)\..\qtmultimedia\include\QtMultimedia;$(QTDIR)\..\qtmultimedia\include;$(QTDIR)\..\qttools\include;$(QTDIR)\..\qttools\include\QtHelp;$(QTDIR)\include\QtConcurrent;$(QTDIR)\include\QtCore;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtOpenGL;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtXml;.\GeneratedFiles;$(ProjectDir)..\..\..\Source\Ember;$(ProjectDir)..\..\..\Source\EmberCL;$(ProjectDir)..\..\..\Source\EmberCommon;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libjpeg;$(ProjectDir)..\..\..\..\libpng;$(ProjectDir)..\..\..\..\libxml2\include;$(ProjectDir)..\..\..\..\glew\include;$(AMDAPPSDKROOT)\include;$(CUDA_PATH)include;.\GeneratedFiles\$(ConfigurationName);%(AdditionalIncludeDirectories) + Disabled + ProgramDatabase + MultiThreadedDebugDLL + false + Use + FractoriumPch.h + $(IntDir)$(TargetName).pch + $(TargetDir)$(TargetName).pdb + 4661 + /bigobj %(AdditionalOptions) + + + Windows + $(OutDir)\$(ProjectName).exe + $(QTDIR)\lib;$(AMDAPPSDKROOT)\lib\x86_64;$(CUDA_PATH)lib\$(PlatformName);%(AdditionalLibraryDirectories) + true + qtmaind.lib;Qt5Cored.lib;Qt5Guid.lib;Qt5OpenGLd.lib;opengl32.lib;glu32.lib;opencl.lib;Qt5Widgetsd.lib;Qt5Xmld.lib;Ws2_32.lib;%(AdditionalDependencies) + + + xcopy /F /Y /R /D "$(SolutionDir)$(Platform)\$(Configuration)\*.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)intel64\$(Configuration)\tbb_debug.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)intel64\$(Configuration)\tbb_debug.pdb" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)" + + + + + UNICODE;WIN32;QT_DLL;QT_NO_DEBUG;NDEBUG;QT_CORE_LIB;QT_GUI_LIB;QT_MULTIMEDIA_LIB;QT_HELP_LIB;QT_OPENGL_LIB;QT_WIDGETS_LIB;QT_XML_LIB;%(PreprocessorDefinitions) + .;$(QTDIR)\include;$(ProjectDir)..\..\..\Fractorium\GeneratedFiles;$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName;$(QTDIR)\..\qtmultimedia\include\QtMultimedia;$(QTDIR)\..\qtmultimedia\include;$(QTDIR)\..\qttools\include;$(QTDIR)\..\qttools\include\QtHelp;$(QTDIR)\include\QtConcurrent;$(QTDIR)\include\QtCore;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtOpenGL;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtXml;.\GeneratedFiles;$(ProjectDir)..\..\..\Source\Ember;$(ProjectDir)..\..\..\Source\EmberCL;$(ProjectDir)..\..\..\Source\EmberCommon;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libjpeg;$(ProjectDir)..\..\..\..\libpng;$(ProjectDir)..\..\..\..\libxml2\include;$(ProjectDir)..\..\..\..\glew\include;$(AMDAPPSDKROOT)\include;$(CUDA_PATH)include;.\GeneratedFiles\$(ConfigurationName);%(AdditionalIncludeDirectories) + ProgramDatabase + MultiThreadedDLL + false + Use + FractoriumPch.h + $(IntDir)$(TargetName).pch + $(TargetDir)$(TargetName).pdb + 4661 + + + Windows + $(OutDir)\$(ProjectName).exe + $(QTDIR)\lib;$(AMDAPPSDKROOT)\lib\x86;$(CUDA_PATH)lib\$(PlatformName);%(AdditionalLibraryDirectories) + true + qtmain.lib;Qt5Core.lib;Qt5Gui.lib;Qt5OpenGL.lib;opengl32.lib;glu32.lib;opencl.lib;Qt5Widgets.lib;Qt5Xml.lib;%(AdditionalDependencies) + + + xcopy /F /Y /R /D "$(SolutionDir)$(Platform)\$(Configuration)\*.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(ProjectDir)..\..\..\..\tbb\build\vsproject\ia32\$(Configuration)\tbb.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(ProjectDir)..\..\..\..\tbb\build\vsproject\ia32\$(Configuration)\tbb.pdb" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)" + + + + + UNICODE;WIN32;QT_DLL;QT_NO_DEBUG;NDEBUG;QT_CORE_LIB;QT_GUI_LIB;QT_MULTIMEDIA_LIB;QT_HELP_LIB;QT_OPENGL_LIB;QT_WIDGETS_LIB;QT_XML_LIB;%(PreprocessorDefinitions) + .;$(QTDIR)\include;$(ProjectDir)..\..\..\Fractorium\GeneratedFiles;$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName;$(QTDIR)\..\qtmultimedia\include\QtMultimedia;$(QTDIR)\..\qtmultimedia\include;$(QTDIR)\..\qttools\include;$(QTDIR)\..\qttools\include\QtHelp;$(QTDIR)\include\QtConcurrent;$(QTDIR)\include\QtCore;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtOpenGL;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtXml;.\GeneratedFiles;$(ProjectDir)..\..\..\Source\Ember;$(ProjectDir)..\..\..\Source\EmberCL;$(ProjectDir)..\..\..\Source\EmberCommon;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libjpeg;$(ProjectDir)..\..\..\..\libpng;$(ProjectDir)..\..\..\..\libxml2\include;$(ProjectDir)..\..\..\..\glew\include;$(AMDAPPSDKROOT)\include;$(CUDA_PATH)include;.\GeneratedFiles\$(ConfigurationName);%(AdditionalIncludeDirectories) + ProgramDatabase + MultiThreadedDLL + false + Use + FractoriumPch.h + $(IntDir)$(TargetName).pch + $(TargetDir)$(TargetName).pdb + 4661 + + + Windows + $(OutDir)\$(ProjectName).exe + $(QTDIR)\lib;$(AMDAPPSDKROOT)\lib\x86;$(CUDA_PATH)lib\$(PlatformName);%(AdditionalLibraryDirectories) + true + qtmain.lib;Qt5Core.lib;Qt5Gui.lib;Qt5OpenGL.lib;opengl32.lib;glu32.lib;opencl.lib;Qt5Widgets.lib;Qt5Xml.lib;%(AdditionalDependencies) + + + xcopy /F /Y /R /D "$(SolutionDir)$(Platform)\$(Configuration)\*.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(ProjectDir)..\..\..\..\tbb\build\vsproject\ia32\$(Configuration)\tbb.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(ProjectDir)..\..\..\..\tbb\build\vsproject\ia32\$(Configuration)\tbb.pdb" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)" + + + + + UNICODE;WIN32;QT_DLL;QT_NO_DEBUG;NDEBUG;QT_CORE_LIB;QT_GUI_LIB;QT_MULTIMEDIA_LIB;QT_HELP_LIB;QT_OPENGL_LIB;QT_WIDGETS_LIB;QT_XML_LIB;%(PreprocessorDefinitions) + .;$(QTDIR)\include;$(ProjectDir)..\..\..\Fractorium\GeneratedFiles;$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName;$(QTDIR)\..\qtmultimedia\include\QtMultimedia;$(QTDIR)\..\qtmultimedia\include;$(QTDIR)\..\qttools\include;$(QTDIR)\..\qttools\include\QtHelp;$(QTDIR)\include\QtConcurrent;$(QTDIR)\include\QtCore;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtOpenGL;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtXml;.\GeneratedFiles;$(ProjectDir)..\..\..\Source\Ember;$(ProjectDir)..\..\..\Source\EmberCL;$(ProjectDir)..\..\..\Source\EmberCommon;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libjpeg;$(ProjectDir)..\..\..\..\libpng;$(ProjectDir)..\..\..\..\libxml2\include;$(ProjectDir)..\..\..\..\glew\include;$(AMDAPPSDKROOT)\include;$(CUDA_PATH)include;.\GeneratedFiles\$(ConfigurationName);%(AdditionalIncludeDirectories) + ProgramDatabase + MultiThreadedDLL + false + Use + FractoriumPch.h + $(IntDir)$(TargetName).pch + $(TargetDir)$(TargetName).pdb + 4661 + + + Windows + $(OutDir)\$(ProjectName).exe + $(QTDIR)\lib;$(AMDAPPSDKROOT)\lib\x86_64;$(CUDA_PATH)lib\$(PlatformName);%(AdditionalLibraryDirectories) + true + qtmain.lib;Qt5Core.lib;Qt5Gui.lib;Qt5OpenGL.lib;opengl32.lib;glu32.lib;opencl.lib;Qt5Widgets.lib;Ws2_32.lib;%(AdditionalDependencies) + 0.1 + + + xcopy /F /Y /R /D "$(SolutionDir)$(Platform)\$(Configuration)\*.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)intel64\$(Configuration)\tbb.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)intel64\$(Configuration)\tbb.pdb" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)" + + + + + UNICODE;WIN32;QT_DLL;QT_NO_DEBUG;NDEBUG;QT_CORE_LIB;QT_GUI_LIB;QT_MULTIMEDIA_LIB;QT_HELP_LIB;QT_OPENGL_LIB;QT_WIDGETS_LIB;QT_XML_LIB;%(PreprocessorDefinitions) + .;$(QTDIR)\include;$(ProjectDir)..\..\..\Fractorium\GeneratedFiles;$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName;$(QTDIR)\..\qtmultimedia\include\QtMultimedia;$(QTDIR)\..\qtmultimedia\include;$(QTDIR)\..\qttools\include;$(QTDIR)\..\qttools\include\QtHelp;$(QTDIR)\include\QtConcurrent;$(QTDIR)\include\QtCore;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtOpenGL;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtXml;.\GeneratedFiles;$(ProjectDir)..\..\..\Source\Ember;$(ProjectDir)..\..\..\Source\EmberCL;$(ProjectDir)..\..\..\Source\EmberCommon;$(ProjectDir)..\..\..\..\glm;$(ProjectDir)..\..\..\..\tbb\include;$(ProjectDir)..\..\..\..\libjpeg;$(ProjectDir)..\..\..\..\libpng;$(ProjectDir)..\..\..\..\libxml2\include;$(ProjectDir)..\..\..\..\glew\include;$(CUDA_PATH)include;.\GeneratedFiles\$(ConfigurationName);%(AdditionalIncludeDirectories) + ProgramDatabase + MultiThreadedDLL + false + Use + FractoriumPch.h + $(IntDir)$(TargetName).pch + $(TargetDir)$(TargetName).pdb + 4661 + + + Windows + $(OutDir)\$(ProjectName).exe + $(QTDIR)\lib;$(CUDA_PATH)lib\$(PlatformName);%(AdditionalLibraryDirectories) + true + qtmain.lib;Qt5Core.lib;Qt5Gui.lib;Qt5OpenGL.lib;opengl32.lib;glu32.lib;opencl.lib;Qt5Widgets.lib;Ws2_32.lib;%(AdditionalDependencies) + 0.1 + + + xcopy /F /Y /R /D "$(SolutionDir)$(Platform)\$(Configuration)\*.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)intel64\$(Configuration)\tbb.dll" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)intel64\$(Configuration)\tbb.pdb" "$(OutDir)" +xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)" + + + + + Create + Create + Create + Create + Create + Create + + + + + Use + Use + Use + Use + Use + Use + + + + + + + + + + + + + + + + + + + + + + + Create + Create + Create + Create + Create + Create + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + + + + + + + + + + + + + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + + + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing OptionsDialog.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/OptionsDialog.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing OptionsDialog.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/OptionsDialog.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing OptionsDialog.h... + Moc%27ing OptionsDialog.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/OptionsDialog.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/OptionsDialog.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing OptionsDialog.h... + Moc%27ing OptionsDialog.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/OptionsDialog.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/OptionsDialog.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + + + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing AboutDialog.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/AboutDialog.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing AboutDialog.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/AboutDialog.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing AboutDialog.h... + Moc%27ing AboutDialog.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/AboutDialog.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/AboutDialog.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing AboutDialog.h... + Moc%27ing AboutDialog.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/AboutDialog.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/AboutDialog.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + + + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing FinalRenderDialog.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/FinalRenderDialog.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing FinalRenderDialog.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/FinalRenderDialog.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing FinalRenderDialog.h... + Moc%27ing FinalRenderDialog.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/FinalRenderDialog.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/FinalRenderDialog.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing FinalRenderDialog.h... + Moc%27ing FinalRenderDialog.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/FinalRenderDialog.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/FinalRenderDialog.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + + + %(AdditionalInputs) + + + + + + + %(AdditionalInputs) + + + + + + + %(AdditionalInputs) + %(AdditionalInputs) + + + + + + + + + + + + + %(AdditionalInputs) + %(AdditionalInputs) + + + + + + + + + + + + + + + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing FractoriumSettings.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/FractoriumSettings.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing FractoriumSettings.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/FractoriumSettings.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing FractoriumSettings.h... + Moc%27ing FractoriumSettings.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/FractoriumSettings.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/FractoriumSettings.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing FractoriumSettings.h... + Moc%27ing FractoriumSettings.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/FractoriumSettings.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/FractoriumSettings.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + + + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing TwoButtonWidget.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/TwoButtonWidget.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing TwoButtonWidget.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/TwoButtonWidget.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing TwoButtonWidget.h... + Moc%27ing TwoButtonWidget.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/TwoButtonWidget.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/TwoButtonWidget.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing TwoButtonWidget.h... + Moc%27ing TwoButtonWidget.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/TwoButtonWidget.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/TwoButtonWidget.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + + + + + + + + + + + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing GLWidget.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/GLWidget.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing GLWidget.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/GLWidget.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing GLWidget.h... + Moc%27ing GLWidget.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/GLWidget.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/GLWidget.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing GLWidget.h... + Moc%27ing GLWidget.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/GLWidget.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/GLWidget.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + + + + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing DoubleSpinBox.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/DoubleSpinBox.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing DoubleSpinBox.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/DoubleSpinBox.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing DoubleSpinBox.h... + Moc%27ing DoubleSpinBox.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/DoubleSpinBox.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/DoubleSpinBox.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing DoubleSpinBox.h... + Moc%27ing DoubleSpinBox.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/DoubleSpinBox.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/DoubleSpinBox.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + + + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing SpinBox.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/SpinBox.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing SpinBox.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/SpinBox.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing SpinBox.h... + Moc%27ing SpinBox.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/SpinBox.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/SpinBox.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing SpinBox.h... + Moc%27ing SpinBox.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/SpinBox.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/SpinBox.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + + + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing StealthComboBox.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/StealthComboBox.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing StealthComboBox.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/StealthComboBox.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing StealthComboBox.h... + Moc%27ing StealthComboBox.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/StealthComboBox.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/StealthComboBox.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing StealthComboBox.h... + Moc%27ing StealthComboBox.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/StealthComboBox.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/StealthComboBox.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + + + + %(AdditionalInputs) + + + + + + + %(AdditionalInputs) + + + + + + + %(AdditionalInputs) + %(AdditionalInputs) + + + + + + + + + + + + + %(AdditionalInputs) + %(AdditionalInputs) + + + + + + + + + + + + + + + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing TableWidget.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/TableWidget.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing TableWidget.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/TableWidget.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing TableWidget.h... + Moc%27ing TableWidget.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/TableWidget.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/TableWidget.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing TableWidget.h... + Moc%27ing TableWidget.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/TableWidget.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/TableWidget.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + + + + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing Fractorium.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/Fractorium.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing Fractorium.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/Fractorium.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing Fractorium.h... + Moc%27ing Fractorium.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/Fractorium.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/Fractorium.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + $(QTDIR)\bin\moc.exe;%(FullPath);%(AdditionalInputs) + Moc%27ing Fractorium.h... + Moc%27ing Fractorium.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/Fractorium.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/Fractorium.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + + + + + + Document + %(FullPath);%(AdditionalInputs) + Rcc%27ing %(Identity)... + .\GeneratedFiles\qrc_%(Filename).cpp;%(Outputs) + "$(QTDIR)\bin\rcc.exe" -name "%(Filename)" -no-compress "%(FullPath)" -o .\GeneratedFiles\qrc_%(Filename).cpp + %(FullPath);%(AdditionalInputs) + Rcc%27ing %(Identity)... + .\GeneratedFiles\qrc_%(Filename).cpp;%(Outputs) + "$(QTDIR)\bin\rcc.exe" -name "%(Filename)" -no-compress "%(FullPath)" -o .\GeneratedFiles\qrc_%(Filename).cpp + %(FullPath);%(AdditionalInputs) + %(FullPath);%(AdditionalInputs) + Rcc%27ing %(Identity)... + Rcc%27ing %(Identity)... + .\GeneratedFiles\qrc_%(Filename).cpp;%(Outputs) + .\GeneratedFiles\qrc_%(Filename).cpp;%(Outputs) + "$(QTDIR)\bin\rcc.exe" -name "%(Filename)" -no-compress "%(FullPath)" -o .\GeneratedFiles\qrc_%(Filename).cpp + "$(QTDIR)\bin\rcc.exe" -name "%(Filename)" -no-compress "%(FullPath)" -o .\GeneratedFiles\qrc_%(Filename).cpp + %(FullPath);%(AdditionalInputs) + %(FullPath);%(AdditionalInputs) + Rcc%27ing %(Identity)... + Rcc%27ing %(Identity)... + .\GeneratedFiles\qrc_%(Filename).cpp;%(Outputs) + .\GeneratedFiles\qrc_%(Filename).cpp;%(Outputs) + "$(QTDIR)\bin\rcc.exe" -name "%(Filename)" -no-compress "%(FullPath)" -o .\GeneratedFiles\qrc_%(Filename).cpp + "$(QTDIR)\bin\rcc.exe" -name "%(Filename)" -no-compress "%(FullPath)" -o .\GeneratedFiles\qrc_%(Filename).cpp + + + + + + + + Document + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + Uic%27ing %(Identity)... + .\GeneratedFiles\ui_%(Filename).h;%(Outputs) + "$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)" + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + Uic%27ing %(Identity)... + .\GeneratedFiles\ui_%(Filename).h;%(Outputs) + "$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)" + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + Uic%27ing %(Identity)... + Uic%27ing %(Identity)... + .\GeneratedFiles\ui_%(Filename).h;%(Outputs) + .\GeneratedFiles\ui_%(Filename).h;%(Outputs) + "$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)" + "$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)" + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + Uic%27ing %(Identity)... + Uic%27ing %(Identity)... + .\GeneratedFiles\ui_%(Filename).h;%(Outputs) + .\GeneratedFiles\ui_%(Filename).h;%(Outputs) + "$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)" + "$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)" + Designer + + + + + {019dbd2a-273d-4ba4-bf86-b5efe2ed76b1} + + + {d6973076-9317-4ef2-a0b8-b7a18ac0713e} + + + {1d6039f6-5078-416f-a3af-a36efc7e6a1c} + + + {f62787dd-1327-448b-9818-030062bcfaa5} + + + {2bdb7a54-bb1a-476b-a6e5-f81e90ad4e67} + + + {f6a9102c-69a9-48fb-bc4b-49e49af43236} + + + + + Document + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + Uic%27ing %(Identity)... + .\GeneratedFiles\ui_%(Filename).h;%(Outputs) + "$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)" + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + Uic%27ing %(Identity)... + .\GeneratedFiles\ui_%(Filename).h;%(Outputs) + "$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)" + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + Uic%27ing %(Identity)... + Uic%27ing %(Identity)... + .\GeneratedFiles\ui_%(Filename).h;%(Outputs) + .\GeneratedFiles\ui_%(Filename).h;%(Outputs) + "$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)" + "$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)" + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + Uic%27ing %(Identity)... + Uic%27ing %(Identity)... + .\GeneratedFiles\ui_%(Filename).h;%(Outputs) + .\GeneratedFiles\ui_%(Filename).h;%(Outputs) + "$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)" + "$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)" + + + + + Document + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + Uic%27ing %(Identity)... + .\GeneratedFiles\ui_%(Filename).h;%(Outputs) + "$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)" + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + Uic%27ing %(Identity)... + .\GeneratedFiles\ui_%(Filename).h;%(Outputs) + "$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)" + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + Uic%27ing %(Identity)... + Uic%27ing %(Identity)... + .\GeneratedFiles\ui_%(Filename).h;%(Outputs) + .\GeneratedFiles\ui_%(Filename).h;%(Outputs) + "$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)" + "$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)" + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + Uic%27ing %(Identity)... + Uic%27ing %(Identity)... + .\GeneratedFiles\ui_%(Filename).h;%(Outputs) + .\GeneratedFiles\ui_%(Filename).h;%(Outputs) + "$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)" + "$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)" + Designer + + + + + Document + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + Uic%27ing %(Identity)... + .\GeneratedFiles\ui_%(Filename).h;%(Outputs) + "$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)" + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + Uic%27ing %(Identity)... + .\GeneratedFiles\ui_%(Filename).h;%(Outputs) + "$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)" + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + Uic%27ing %(Identity)... + Uic%27ing %(Identity)... + .\GeneratedFiles\ui_%(Filename).h;%(Outputs) + .\GeneratedFiles\ui_%(Filename).h;%(Outputs) + "$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)" + "$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)" + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + Uic%27ing %(Identity)... + Uic%27ing %(Identity)... + .\GeneratedFiles\ui_%(Filename).h;%(Outputs) + .\GeneratedFiles\ui_%(Filename).h;%(Outputs) + "$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)" + "$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)" + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Builds/MSVC/VS2010/Fractorium.vcxproj.filters b/Builds/MSVC/VS2010/Fractorium.vcxproj.filters new file mode 100644 index 0000000..cdf1d0e --- /dev/null +++ b/Builds/MSVC/VS2010/Fractorium.vcxproj.filters @@ -0,0 +1,338 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;cxx;c;def + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h + + + {99349809-55BA-4b9d-BF79-8FDBB0286EB3} + ui + + + {D9D6E242-F8AF-46E4-B9FD-80ECBC20BA3E} + qrc;* + false + + + {71ED8ED8-ACB9-4CE9-BBE1-E00B30144E11} + moc;h;cpp + False + + + {44a6e761-1e1f-46ce-820d-b80d1c0265ae} + cpp;moc + False + + + {cc25f297-1a73-4c08-9b5f-8dad7c7c7452} + cpp;moc + False + + + {d61ea4d8-e7a6-4d86-934e-992611e1c181} + + + {84e24710-0e4f-4aa3-9f74-82cd2a3b39a7} + + + {5555e39d-b8d2-4bac-bf6c-6763228b15bc} + + + {26fa32d9-268c-4021-8398-d40d46344dff} + + + {811962f9-51c1-48ba-a9da-f5ce981aea71} + + + {52886ad8-fa57-4c60-b799-7c648147b7f1} + cpp;moc + False + + + + + Source Files + + + Generated Files + + + Source Files + + + Widgets + + + Widgets + + + Dialogs + + + Dialogs + + + Dialogs + + + MainWindows + + + MainWindows + + + MainWindows + + + MainWindows + + + MainWindows + + + MainWindows + + + MainWindows + + + MainWindows + + + MainWindows + + + MainWindows + + + MainWindows + + + MainWindows + + + MainWindows + + + Source Files + + + MainWindows + + + Controllers + + + Controllers + + + Controllers + + + Generated Files\Debug + + + Generated Files\Release + + + Generated Files\ReleaseNvidia + + + Generated Files\Debug + + + Generated Files\Release + + + Generated Files\ReleaseNvidia + + + Generated Files\Debug + + + Generated Files\Release + + + Generated Files\ReleaseNvidia + + + Generated Files\Debug + + + Generated Files\Release + + + Generated Files\ReleaseNvidia + + + Generated Files\Debug + + + Generated Files\Release + + + Generated Files\ReleaseNvidia + + + Generated Files\Debug + + + Generated Files\Release + + + Generated Files\ReleaseNvidia + + + Generated Files\Debug + + + Generated Files\Release + + + Generated Files\ReleaseNvidia + + + Generated Files\Debug + + + Generated Files\Release + + + Generated Files\ReleaseNvidia + + + Generated Files\Debug + + + Generated Files\Release + + + Generated Files\ReleaseNvidia + + + Generated Files\Debug + + + Generated Files\Release + + + Generated Files\ReleaseNvidia + + + Generated Files\Debug + + + Generated Files\Release + + + Generated Files\ReleaseNvidia + + + + + Generated Files + + + Glm + + + Header Files + + + Header Files + + + Generated Files + + + Generated Files + + + Generated Files + + + Header Files + + + Controllers + + + Controllers + + + Controllers + + + Header Files + + + + + Resource Files + + + Form Files + + + Form Files + + + Form Files + + + Form Files + + + Widgets + + + Widgets + + + Widgets + + + Dialogs + + + Dialogs + + + Dialogs + + + Widgets + + + MainWindows + + + MainWindows + + + Widgets + + + Widgets + + + Header Files + + + Widgets + + + + + Resource Files + + + + + + \ No newline at end of file diff --git a/Builds/MSVC/VS2010/FractoriumInstaller/FractoriumInstaller.wixproj b/Builds/MSVC/VS2010/FractoriumInstaller/FractoriumInstaller.wixproj new file mode 100644 index 0000000..42d0b4f --- /dev/null +++ b/Builds/MSVC/VS2010/FractoriumInstaller/FractoriumInstaller.wixproj @@ -0,0 +1,74 @@ + + + + Debug + x86 + 3.7 + {c8096c47-e358-438c-a520-146d46b0637d} + 2.0 + Fractorium_Beta_0.4.0.2 + Package + $(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.targets + $(MSBuildExtensionsPath)\Microsoft\WiX\v3.x\Wix.targets + + + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + obj\$(Configuration)\ + Debug + + + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + obj\$(Configuration)\ + + + Debug + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + obj\$(Platform)\$(Configuration)\ + + + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + obj\$(Platform)\$(Configuration)\ + True + + + GpuType=AMD + + + bin\$(Platform)\$(Configuration)\ + obj\$(Platform)\$(Configuration)\ + + + True + $(SolutionDir)..\..\..\Bin\$(Platform)\$(Configuration)\ + obj\$(Platform)\$(Configuration)\ + + + GpuType=NVIDIA + + + bin\$(Platform)\$(Configuration)\ + obj\$(Platform)\$(Configuration)\ + + + bin\$(Platform)\$(Configuration)\ + obj\$(Platform)\$(Configuration)\ + + + + + + + $(WixExtDir)\WixUIExtension.dll + WixUIExtension + + + + + \ No newline at end of file diff --git a/Builds/MSVC/VS2010/FractoriumInstaller/Product.wxs b/Builds/MSVC/VS2010/FractoriumInstaller/Product.wxs new file mode 100644 index 0000000..a94ec47 --- /dev/null +++ b/Builds/MSVC/VS2010/FractoriumInstaller/Product.wxs @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Builds/MSVC/VS2010/FractoriumInstaller/msvcp100.dll b/Builds/MSVC/VS2010/FractoriumInstaller/msvcp100.dll new file mode 100644 index 0000000000000000000000000000000000000000..68fa0d31adf5e925c83dd14b48028052dda5ada1 GIT binary patch literal 608080 zcmd>nd3+RQ^7rHz7|t1lk$513Mh#}gXgm@FGD8ySfr$hG1yPBj27F&14dTzkNUNAFm%t z_jEr;JyrEo)l*f^(^Xe3b>+BRu3Y@nb(d=muJm7!|F-|fljU;t?Eg|v*Na`=7_cTQ z^2UI%Q>V=g)Xup5rWx1W8kl_DZMWSX4P1X?U`Fh=z_i-}6{E%kZoU168wd61(KX*_ zI{(AXXV=Vo=5F)fw3hSlz8=5l_A9%4GC%*r-A8c!cKgt~ujA*X#G%)9gAm&@H>G%k>_xGlekZ zay3rJ)kk-e=g?oi&vv=ou51@d@|B;@>%vI==FKj669lHXl6A3ejK^kEiPfE4w;suI z9elczYcM^m?&8`*1oCxpd4}Lx)WucZ7uT9DE?+mmb12t!GJbaWzvf)m9x;@RRR%?G zoE^m*-#%lUH1@Re0rWm-#tqj+uXDN1UI*aNrfUPP`%?=B!A65bF;`IpjV3%UR|&39 zS2_E;wk$%o&sMig5zmFl;B{>i926{m!`YmfKPA@?LlW zT)B?J^^{iShB>PJe|;;d?od;%OOmrB{q$uY=eo9u|0Mb9e9x6q{9phj`lE#MuB04B zG2K50r4FsniguBd9lC!Eeo4wENx3?|NQ!R=Oj>WWux;DQdmQ)k?ECW)_WerTzL%7G z>&F`X9+%7bwZ=71H;a*+GMieX>$=%Rv7=4N6lP{YKqOIN`p>LD&*BYcv)jJ)`rL!Z zxXmv4cR||kJy)i_&rN&Ze0l2orS+ba_Ld*R?{5IlQb&9AyV}t|{jN!SzXJ2jL?FFy zIL6-vY3+H^eiu04r}j&Z;kOhY05-AdD=EE-6H?vIXs)CV^O#VBCQ5ch$pW86Bzy4Kxb@j(m5^PL`Bjpm`95Y^Zh}V0xDu`$<2u9zk`+rX5l=Xtqm1urR z3b)5gbE2MjX>K$JoVW)4Sd`edjo$U3cPNmZp@1Yu#9S+qHdW*Yj)F^RIMp=aRQFW| zNa_S&VXHQ=hs(8;JCGOdM?dnykMXy7vm`IkaDz)TJ&oi`Qr*62XGvXBM32?6`I6T8 zD%3S5Yc;?xnq(ofO&EaE@se^xQoaT(+XoBu7_jWVC7#;*F+rd%8?Xxoul; zj5Pn?a**66pE}wTf^?D8NPdyFZ4x!jx2Iq4aw&Vn{jJyGe!Vn*167@9P(o5RT-mBM zdAv`Or{+m=ZGd@Vx@cT^M^dFl+O2l;8>va7`JT8h{(^YmyhM|NE?&;UA7y>B_pi?r zu`shfl^@SviHB0%A%jn1Ig;{*q(01#wcS_ZUFq|Lv}m5BFlS*>Mj-B0|M5_&x-WSi z3J`LmC!hdh*(~rKw76a^C4G{zTT**`N%9 zA6il|rT*9a`k1Qu)z1k*=X{^{Ca{S399a(Aq2Esz`@c3wO*##8mj@OF8RU2)SU*ViYwc~)JPjs ze-M+#K-6(0xot#+fT^@0UpR(jC=DgPp~j+iaGazZ6vVDj>EsIM&17cEY^tHZ`T1_M*%!@>tKeoDBXT9COJyUP)FcS~2=28;usvpxQl+8SXfGZWij*~g`ZOp~0#(gK z!YP+K#&eA+Od$`*Zc_H@{viUm3mLeRNPWaN1kAbaDCZaDUj2v$kzNjSVcZp0hQ@}* zg{}x)DXA+oBG>$bWOhrNDwiA613kKjyk=mmt`Y1QQoar=EfP#OaC@M#_+u#C*u@zB z8O4IQK_VCaMDoY${%P+rA{R3ZS3@4F{&(sKlX$Cb7zI>@l?{ZE5s}jTbx@yrLMp4D z@lBFieCs^XVga>?Kau=e-QQQVSYotTixw9cE!Ju?s6|Ot8^MIy>nB1)M&ujviz4bR z^e37hh^Vj7yR`vuwC-2-0@Za1?}9s^$-%OP<^GcFiG3ocsWXc~cf_HUq)BJ!;@V3F~}v|Z3tg63cVd=>-~L7K$6Na`ai=wWv8 zP90*cj!FRZjl&3%N-A1|EAWVWxe|hE9G;1euAq+4dI^!D56dRJG(}THz7eIVcHMr0 zPC#LnL;r>dL%H!0wW=mU%nZRm6qB@93t3aSI|(>eR>(K5LCFOEHOMi{t*Sk`|5xwi z60L6Z0jiaNiijjBb#&3IJxft&rM6k zga{?%lXD-H^mXB~#u=Z5mG>kCf{3bMGIW2RhT0WFeF;UObR>PfRQAD)Pb6i#?tgGM zgL7-m#yX-0BoN~X5D{JUYJgbV_tTt_@uix_Z*JmgImfF>VY^bxD0T--UUNS?;11Alb+BG*{eLy=+eLW?-XA z`7lhn3I^I(V{)OgSx*|w0M=#Vh}CKDk_PBY} zI_TPLFx@@}!P=13F}1BpM%1nB$0<`IMF7eCD)5Lt07X)3Y*1hxhHlu2w8+7ktic0 zK`ElvK&B$?=a&(F!0uaFM}+ms9uzKNeV$ghL>q#_PE`U_yZ_2W%34FsH+$>dSqD8X z8HRwh>OvV6W#rKwTJBqqu?{{cN%1*gE7koE(MU8O)byMU%79h@-Cws8gH`=!%o7|F z&|VA^ed8M>({80Uu$()G8{+C&cJ)D``f#gyjH?qQb`#sEeov`%v?qqg*Lh$st+!kS zVzJnb-@4P*S9n(%@^~Lfn6N<0xg}a1+Fk(+?Y9JvoMmchtTk14KQUa#Gw51q+AC~} zq|s2X!uphF2PDP)&T7*zfp&#gq$+{7LabD?DvJ+A+XzEdT zQ6aCI2SRVUZvp;-iRb}MpoqL`1un4XNg6``aoN=Yntkds1lKX8xWKlfPZ?Xa8+ za`QS-_wQ?@wpZ6sd3p6zaj6xT*`jts5shi#Jo*)1vIrN9Z1qx6<}qCyHTrkn(yY^!a;#$>gqv^?|JW#wY+Y^-{ zN_ifsm2<+1D^j)|wpv(efVBp+0i6&PP{zRV%)DejC`i<4h~^gWB-3~oGy#+fm66Ny zfJZ_p$ppVJ{iv*A`bFdz0CHv~TLNny^8IRCENrvZ zv_X5J2vIzqM7QexMzAODmNQe%FF+?7=M&DdV44OA$ZDUE5Fvd`UL3{}fN+o40WN{G zFHzzFlm!N0;70NbbbkrnR4Bipr%#&7!DRD(W-o%YV6c>4)KOS$EE7~8 zw;xkt<3f^Hn4p0qW>OU%jnjC-78xdwT-h8^Fp(NUI+Pxj(6QpA_3}84XQHzX%pr)Y zMC|X)`p7`6H#zcpN=gpnl{5p*NcNiDDdt|fj1f2`Z-B5FNxZB-i-??6oG9K2ZJ9T# zTvEb5DPHP=e-#TkeH|@-rLr-3Gd?G-X8nIUst8e1w}nY@#%w`~0`0{R+qWZqEGOaD zAzH;J`vl|QEs(vTGDpt8;<5Ta44lLA6n-Q?Jr(}yYOOD#jS##e zdHLtp{SOgk;*(40PmumdF0TEz2CnT^cyb$1xS!x5L-iWgfhtOk3Tj_k1eqrceMNzOXv7Aq;V+ z`?~;AtU%5Zw&2{DF{^1c63}iKim450$L@&_P9?@03pWwCuY@o?Tla4PYEeB&Mv>|t zY*a6y>LuFgRDJP$lIsg#Sd-FpB}16dWD*gM`-6k>T(M5tuGb+A!27Vs*7j9!J47k^x;Lus~hl&b@`REStX4Lg2%M%pxLLRJ0T-$?7o0xnj%0@fj0hhW(W3!VpS!T z!4RM~<7XnU(x+aEU+jur%ui>4>hRU6B~YnLV7C+OOmGsARAWh9(o2|sB#Rtz4K{W9 zGEZi~J1K0`&Kns|BZJyB*bv~ok$GA%d|bqZbMuJ{bI^2=*|hB&wPGbeB1v&ceSlh| zfd+NIhnt1vLk_Cn1gM<~P!ia|AGVNY5f#*akHSzQJ}LlX9hhN?xHkvW5{E>+K|Q9| zZb7cRhytkqq>*5#2B6&I{jUQF;F&c4rsd0RAKvk(y%dsx;Z63>0PjYO2=IQk-G;Z= zf_Fd~yw$|mpcTv>0vfu>1oN{EnZSha!Cv(w#Hxp7CDez3)~fyRx1ur{g6LxH1rs}v zJQo_RfzdRsQLiq7{@UX(x4>08{HI0{m5P9wQYzGe;VSpU*1*U|7sCzsXx7!(%FP)Q zFfb`%0`Pzc2Z#x$1UFSzhO_PvWAlkqGGKEy@C|JKiflQ9`k&OA)ZcY6Q9nM{ z>aJX$>pCxiIH8hbV07w&L@YbGUy^5fWF) zt&wu;SEBW$4u&1iU|=P4Azn1=3aM2glAYp_{1*l67@)-otPI!`dUXkSr@*nUSKa5b z1aPkZNj|l<4#tc1*a%WWa#piGpO%B>YHMsg5WuQ`KB*Rm>feGlSZ&c$q59RANgYWF z1c;^NcbH24raa(rB-ZlXPz}SJ3xo1bNM3akrbaiaPzYlOH8C-AV!5>bhff5RbYXHq z+F8r9l!;|1` zhHBQfUxapm0UDITc))=Dr2il)5TD;}t`L)z^zd9s9i2yQmFWJbs4Zo<#6KmuNK!9_ z$_-!u!>c8ExP&|slxWr$&SP+PXnzV|aicGxu4pXa&1jk&{qeCJI?)=-uGe@h(Ob>2 zWM>-7Sp+t0Yv)+n03v;W>!^cv2lyCDuq6U-91l}Z#9Ojr%cS@{J{QoajPe27f0zmv zi2}bUd=SaScn66C*Z3$Xc>iMiUA5Hob4mwqgMSh?RJ-ocGuo)vc_lUldLDJ35JzVd+P!q z*y)#4M&}9tFlHH^!!IK8G{LhG7}Mb8n(qyA)KAenma#Vu7*PYHf^e*-SyD*&++ z6`Dnbi`giIdz?HOuwnU*EKbk`(&MtAb~TUL49oXr=Ny%Cc;{|GdHFd4NK%kmVno_s z1AR>$1^?X5@Xs9xT{G!ggKX{l<$g_!1An>dmUu`htHgHvaxc6L?qyAp?w4DRH$UQ+ zYs25BvX4&pzw;8$K&SU@qV0XlsB`68uz&<+Yr~4h(jUnKi{}&WVI{heMv%N6%9to) zsQQnnkHvALMCaG=#+5uPq6#0PQt48z@JPxV?7nLzE27IrE}WHgzs7DgVKF>}W?;O) zaS1z=%nrEU$hSulk8J^hD}9B9I9R6j)iICZl>g2ccC2kZ2GZ?6HU=XPfc=QPG)_b( zm%*2Jt>K6Jehv6i_~GD=5j0obO?^_mh^0F%FXBc&zk+4p3zLJ67S+nOd%oLa5Hqi(_bfxYP<>- zYqKNpqV3PA^nm65-_6l%=CExqPG&|>dW46)qFQDc93{%eZr9FhYq(WyKhB*7u?iSUTiR#VQ ziRy+gZEqo}cgUBv@P!Wg(pErN8sa|9m&X48HhfzNKcSHwQtW8@@B6miXWib)w>9@? z1g`DdItP@+l(70KxnTOX3jfBu1K-x!(4gQ7__kIXzO7|Mn^fP{vH1qh;KmK# zz}gr8+<62VIGCLzJ5W%xH{c4o9^^fOy+A4$>?QcTf z&P9-%vI^Bv)mjU3!@--Q%rII6<)!#d6h^x#NPj4dhIVfG3%BFGp$kC(u}4NBk;;%{ zX>qV%t2kJW$I{(w#&T|sxBDonnaSEcCgtMeVYdv|Aa%5DkFumH(+I1JZ4$JGyz z-K6@@Pp#gMs?S?XixZa_2D>;@7;M|0GOKPS#|(kNKU`$kz)|>YDFE2fF?#{G5&JX7 z+(Q68NC17$gp@H)JI12IN5;J8j~y6uN#-%PXQ$-R zmYg54Q_jvDgAF0DszD&-AMLT=D*03NX%rW79Jmt2a3x0Ck-4{ged_KSaZ6agc68LV`C zM@|-J1E1o(cM7>j$?GGMvbLPcn5i|BZ0!V1TMibZ!d|JgbQWTHNYhceBOH>JX}n1) zE1U5lv>0BSss|qSlQDKvP#Zej$e{d%8-zovauz31tj{5SX7_sVkIcRwUSV**c6$Jg z99oyf@l!%3@?;#^+|+B>NATUBcmh0`OUdOYrD-%rxi>_)YuhWQx*x@B0ix0Q{iv4K zsI6C3_xI`LugzHA#w!P7weEX$-QFmA7S~~~PnrUEz~4i_e;<(6dYtL)`~K-{e+yw( zcw{oSue$FcdCH_Q#{EmKld>rT<4*7A#gu-oZ?m7a>;)qe=FEPil2sm6zCUgFsF;yUK2X_3X(EjU#>|ReiQbcLXJ+n@O34Zzyxv=m@B0Xiya$r{V-*TC0)cm?H7=Stk=Z%SA^&iH-OE0D% zEyBE|=S5HCwKkUNsNn=Ts}m@z7% z-q0m`jR$qY-+vtT2G^0r+BX7=72&PYxxVP>_#2po1wPylN+0ae=+h$UFy`h4W+dsM z?w>$BA>Zta$ddzj0*aRVB>DUXNiWAeJO6Ki^B=Px9C9b3R4~m#R~u^A@YBJ%t}u7( zGhd?x+M|q2w9(_SJA0+>|20OjRN?qP*wkd*JMlC*1L+a9HGrVO~~$#faefjM+J8Oj}2-7a5?d`%#Znb^ykGGw+pLpsYvCWWW#HPy6MBX3CH+S>BkY>e@;g#Mhx+xhcdH8nR()}NL-eR%VW4KTC!aHSRpPe#bpg$RQHR*Hp1P{ zfp7*%Hnw^2UI*kO|Av?YiPWf|`=6#?@g)IT7)zzs=0%6ABLlksZGcZZE#ykb!$fqT z6u%o;$epElO`Z!$b;@PHGo{(cft^EVK)wch>;nM!-_SliA&IFE{MvUaz_Xn>7C zyS!nRAkPyJ2z7euuX&OxL`F_hpfO;vo`n=C+Jf>Fy+G&6N~)6fj-H{NhZ^V|?=emO z3e71m6CvZe?p<&M1tRK@3h-E55#;4^FQXg^L~oZ=C68;5TcK_Fo=v5ymDrbx0|P48 z%s>K6Zk#2{F8YayMxGh#jt`_36ksn&0X90iX##v8t=dOMjo9a<WRrG+b(2TZpbeG11pa zy1xv4C3^#X34tQof4k{1W_dDVp2jVm;xP{`L7l*(Oec8EEuzetNse7_Yy&zpBlb_4 zcE;zdCXJL6X%E~{F9YeW;CY91|BYxK+Ku~;c2_Dr!BtuELPEb`8w^H);DST3i^AYJ zWgHG}SV%|(>yckvUPv_wjmKd>84Y>|9OlkFWn}Y>w{^QP^~uV&RlK#Xp3j^EwnKX< z`5PBK3MAP18(d6v4@6kmxeIL0N-KY()4%wYbZq$>?dE0EIN7W`5x^{!;x7{vbgGB3 zw@;1}{~HM>_x%!R#VE?)qPzDS+{>P|gmc&mJ$62i?ZOhW4tvyK0DR_>!m>k_d@Qdv z1p_FGH*qo0lnn3^uucOB4#klH?qSE?Ljrwb=Tiq0!T&18bP2%=Q>+$*u@k}D)QvhT z0!l*o`+sN%^L>OJMJ5aom#6IEvIPRBnw3k7DM0(LizYUmg|P|`AI#zcB+fEoxeXlJ zuyhfjB5q}>FiaG`aKzLQzNw-0>QrWQ_jL>tLFYYcLAP=KasWpgJPQCt#@$BK9--2- z!E>PpWkEi8SR=H-^Gduyh6Ft|cN_7D)g^QhyNyVPbY!=Y0#rktEn`^*$YH%=x6vQp zPF?$*P6TOX_v!x1hq<=99~d07PvD(%lEH|C0SX^9Z33?elp+TABm0N8en~3~=l-G1 z7%lYO=P=5k>&ikYF@*eX(0FFf!!p!umGiJzRR6Q6{_miuZsa_C(iPP^l=JY=1DS&e zs?yxi{X;)G_8niOkNpkc7i0gD%n8*$)Brvp@;wR&cysJep>C_O-!G~^FRHsz$KKSX zqho&*dK*i-411^2#xB+(8TU>h$l7$^D=TN2eHCrwf85#3%*Uy+$Go?RAo|h0O}8<- zwhFS$#U49nkma0*fKx}1h5d~1PtjV*G2h8BaoxI2r_yEt0kpAJA}A z_XMI4HkxB|Nx;Q1f@Mjf8BghxQ5+P0iDm{~Kq7VU$wHdVH}4~lxHHiOtl z%ODv0iXNiAAWdqCN$`hzz3o9}uO-m=W|qjlAtnTyJA*%9CbFqYyV!nwAJlzI(Fx=> z(nXx-Jdu2K&>B)^w&}QhzyWVHYA-eSK^^!!1H7+dM3@dA!2B`cEwtc0Ar0QP_d$KI zBomnJ?t>Cwk_Gf9R0cUywBUbvAJk9-zEt(c`JCo--_V31fRZ&t$p8_y2KzXGK%gf< zMwIIBH8@359&r;np{bGKX{U7>{tPE|TJ5b8W2Zey&)v@y5__w}`JrhXb+Q0q2$_kX za{=Ix%S?{CgF#5yTQwFfc3^MS*(mnE-dkml<5QK#A@)|y(K2w_SdajyjFF{hjHCWy za~#j-5-NEdKVfgxb?97j?5#S1eyjeEEHqW4`S$i!wL>#&ADKPYBmd(VtNK}nu}%eY zF;*Em-W==OrRG@I=g?T4xnk4MAmBaEf_FI==|Ha7O{m#^t{9m#mNO({F1UH(5$_=r zqhwLmn}=y{dbIYCeWUOmGHRJbQw{jv>FZ@14`<-S$Vkj!;E~K1Mj9qfO3oS?Zjv4l zhK&_t`>P`S4e$<#c&nlm=&#hTkTfJ=%iP!jD7G z0zc!)uC6iTK%JXK=c@Y^zGJ&TgJq4tIkq3zN2hpcsBBrs(=6-%$ed*7u&l+JT~#d0 zTBPfjvEKn(__4f5T}QwmBqQ8^7Mg;dFm`U7EPlDiGN3sy2fBVuKo~9oZtWPBD%t|X z(;RWD*A3!l~I_K-9Fwyynp5;l>*j}*=4a|;4kihIO%80Bbh*?vdrO3Qt-Z~$Mo5lWx>5pthISWiUC z%6&5q;+gJ0vWVzfMiB-{8G}wbQQ#YH2P~DN`9Vp}eG`!eJa0X|4EuvAW@f||a8wE+ zX7QQ?=2J}r{b>ZE5L_b7+-G5~OCMRJlo!Q@LNf(jvr1s2;2#;eIM(MZNkM3V4kNY&Qg6v>~5&@_H&*pMVkL*jA&mR#6I8n&4#mAQC zyPD{=L+`whu{>-C@cPu>(gA~{5jRyXF+Bf*%lMo#+9M1JC4EWMsC*QD;AOyd)icNw zUjU)DsqzDI9^uJn*zb!cFdptiS$L!SqHKTHrb?WlhyqAZ2M41A=M^8cH1}ib zBuD9ltx?c7c`kE%6dhWmj{6MIUZnfKKyz^Q?8dK0;cG924(*4%>Jid#{D}QsBMn zea5bJ!qpSMG4Wm~C_-YcUChr!gU5jR!6=@J`L1}9g82!a4qzVhDea7I%Kz?PLEF$c z-JnQ&n|A|&FN?IhaTpE~5&Ko($RhJ~C;RmzeoZV~q&?$C(ezWi4f#g?0mq7p{R2OI zP8iJ6ZUUE>hYBp5PtW)WH8^$;Sz~;2z`M2uX`AB)=xefA@mh5mrbLG>~@lMtui=L14;w}t?J+6_7s6h zWM;r{BVcHA;m9->H_pz$nI-VE3uqxrjG_cF2!xCjAw~in4Ld)%soWSEQQ`zKG=l0v z+&c6r@->vX2)Q`r3Zy4SWa=)qjETc9mf}tG!b&5QHRKrp8z?ErgNF_el-}Wqog_?h zP##@loUm7acQ;{i6K^AB+k})LB-?jp{OPp)D8NJkWDyQvzI{K=t%KfIpZ1#$p`|AH1b z=TFdpyg(boK--)~F;`Ant8t=@;mx>r)7%SATE1(0KqHZXfqvpn2Kpud9ltBj6&=1d zfI&f@VlW)%u7Ix}Y==NMUONKaRWRQW=zb$(pnFmcC4UhLK6yu=U@D;ie;5UgXdEc` znrsC&QdR$ufu;y4{FdlO#3J&Z7UJ>To8~I zC)Sd*!zsceJ+U6jn1E;?7Y(pb#5RqB3S}a`2=PO& z#%pAYmTiiD0eg$<7OUqf@5_4r@#rXzZM2;nS)9=~fB;gYr7k=b zQ=6h^cI>nOTp{!f;THgICqqj0_fG}TV*=>Vg95-3tXuBjDz_NuEYaTmQcM*nNjfWG zwkOt~EF)7jBZ*}mF?4)G?IfhTM!7o98PWZtMN8|9&a1Vh)ROTZws)rJt@dTneIEfR z`qtL(>869SXfvUj_}rF2_FX%&sD74^Me~8xpCpTBqk*<%QCE7a`foOAqRsh2AQ+P} zRTzQJ63#GSMX;@rY8h%Uz*-UcC%`bMtU-^X#nRJK*ziRCBW9NBj7jRJ0Jlfzsg5ph;7}i*qd274HlyiDt?B*U>&4s-OTB zRFM24c6xxr*M(&$R@DoUD+hN890U;lZ<1@n*ft6+Mm|dDjMjc3vhip(1Vk?VV=1}x zTbK$R@t6Ip#py45u+4gg4}!j95TKjozwx90D)37Jgu6HCx zT$OP=#3#mXtM0rY6DZG;U;SH!^Vw4Qw2b-!hd)H%Azc=lPm+gW8HO`*?Jl7|>m{shtxyGO z44h$q9?l%A1rxd**d9y~0h3?AbkrQWFyxfQ4ihC*55xQ+p)?HhJq<>Rxv>8g zMc{_T8N@Q(;rwx545ut&m-c4LuP)+Q2?>UT#YF}9L4u)3`zyB0IHwq4<*w=Nj1hRR zB+|8o_o;XZyq_yn)`_WOT(5~So|Znwv@t1R(1Om(vBU(yb^4FM^_Lm#z}1MCfa_O6 zzuX&0kz0iZTx>O;*Wvc*E7Z!KvuEB41ZS}=!r5%KNq z1ZC^EpX%=7#0$&3AkBMguJ&qgeCOjOX!9SM^{W5VQ&Psa)qz~rrqwOJ3b9&N<7QQyzni;jl*YrHxSv5k69<}>MVa*d*w-gTz~>V(>o+Z zTJ2wz<~Px-Zd$O>N(1dARpV@wo6r3|o~5tdDt(JxTEQyb-jrH%j+Jk^UxG?wQr zDu-=TUa$zef#mX%Mdd;Am6HMU@LpmWr((czWz%hp#T6u{jD_%9Cku`%&`$lBn2lD! zTebI(qa{HGSAZRNNriDdxsImd@B(%m0o13x9R{^&j{voX#4`TybURsq`X8G92B-rq zQ13VZP;t%xp9iOX@^Q8W?07p{&_c1j26L?2alpymUOY$yx3Eq`cJyy=1st54sEjNy zo~hzl0jP;Y9Z_zMc!n((Mid=&ZxqiE3NzC`_w@vf2EAc#n1TJ? zQrm?2(5M~$nbGEp^fG=A5s{RX>fUFxnU~h4>JEcs7Bit_$oaczOgEd(s{d@fYdRlx zs6l!Q7m#7o(Iu#S7KyUfGE9c6$Ns#nSr6Y=fS`<>L$jg?)2fPSh9gU3y%qNaoGq;< zWcO%XrN;=%R7ks?$w~|M4Dme(U8AKOE(YT--lEU!%qO-}{EJ!$a3^+kqLmTSwCk_O zxG64;BM@+GTn{OJgqztS$%s{#!bMC?N#2Sdv^TjVPT41G+(|It@&eL2Y2Q>Dz|ZiN z3&>FmB`h1s4o;cFXyG@cUExQ$vWJ+>=FHtSjR-s$GZ*{@vS@TZ9IGC- z4^?-yXo@2A&}A?qu?xv?w_;je!H$ruMIn#&H{^Dw%`HoIJKzJ_B*Uj`cakJkM`yJr zBGiQfvliVb!vg4O@v!_wA>{5{jzQo6Bkb%eBDZNo&Z?kp2cug^WtPyh-?xPxEfnpn zh;7@N=DuSI3McJOpcSKJGC+1$A|PlE!x-cfT+o2&Pb6jH5fzDmJ}L=kCeDKIYX!T<=>-sDeQT&8i#Sa{b+2Gy z< z$m{un-ipx!3+emOk@v(k(&ik#5@d=o;n^^Ln)q78mFz++)6-%|&TnXUhg^KgM=OMhtor zXlBMMLQatuKbj3XK%erXnAqG!v0iG(lth-kRNc>0L&{zV1`>W+g;_mSjYIeI`Tw;x zD2`djv+Z#4A^F98>V<5UTcy%-V^h`35KpVSfqK|Oo^pVb8u=Cz(s5o zrxLu%l999Sa1JvMGe`T&f5fQ7Cd2;BW$=enod+>$j_sS&vtx z-A(_f@?!pD1iwjG8K?SSTV|(7X}6G~hn1$F_mNEzyl-Vsrr=6&js(leqz z=T8yz46BzS5cfI`2x6*63Ujo9E!ps+?}@h@jShpfh+YU23Wsucno?0(5<^xJ@~(a{ z8LoD9d=>&C|Ecs=d=Q?z9vwMLrP9LK6UnYT#-v-43!&0@QHZ>(sl=YOB#W)B ziT3=l=0<=l}y$;N@*VgAH!MEQ^`!I1LLNGZ+|m{lW{9gR*5Bk2kQ*cbtBYY4%x zZg)otH}Pn+YroHS(Kybhah!v3^u{2tDJ_OAYgmB)?Rq3~;k@|&guE56OON4;r#UUs zWPJA&lVHYj0(Gj82h|KQeMi)Ub`4SzOpC|RyBT=j5Sl+s1xGm01;6Cl z=ji^6YjW`w*^ScVM*6&S;pN|txLsST4CXldHZRyNGcqpH%OCPq_VH>>Q*WL zEdOPaqRG6v#NEjD#9@p0s4f<-Sl7^SvMv$u77)BwyhHGc*mF^ucfYF zosjOn5yBG?7`poodPT#dC01+QjqKmE3M$^j1ny2W!g+c%lPsD6$onCAU>JT zg-Uq3TWkim#;bB>8xfFyAlZFH5;=y6YEC<)YjPWku6JAQs_14Y9 z6ZJBRzfU9}SnP^YiRk4S&6%LK>|mPM{Rj%uSTq-CCotq0G1*ES2em(g0){wtBC55e zL%Cz2Jd>*+UkbGE@xf=>n!gaAuLiNm-C&+pZ}?4co&|wM=4F-=GUyftrCb=Tpd0;q z_bL=p-QSWB;kB)`+&5^121rV2LrAZ*K^14n2kW4N1)@W**rd6k0Ir?I=!bZP`kX?2 z+5m&gy{pzC|IVj0H2oU#Td;kaEaC5qzl*7eXr(&G=UoMV({Al%6kmk+87z|bV@V+$ zOi|%jRNC!d1;iag`-Ldpf%frzNNcVN{3DwI-w}uuuG9TDO&~h7_Mrtx!64aX50?tH zE=B$=W6eNOd)rV4O$=E*8ER8Z?86&y$J6x(92!S=1)2=(v^%p4lER*%=FOEv8s1yU z@W-*rdJb?fiUI~uc%p8@i zg={pE;DB}UJ7D2o{I0ftw~0ex9|pO9GCfzVH&M7oGf{Xc>AcLsQTx0Z@8CBVXnzlA zUrJz$gGEeqnz1U?y_-a2nymZmP@A(#Q_SX>cPydD>$YQ*c@Gl%kjx|Dr(RZs^Lwu# zU{Yk>#5QE!pFc~Jc|BRyv!k#;JDo?6QRZozzY++_ApD*KhC3$w#C(d+36Pw6h($HY zw5TCW&ZU8=%OctsQ|EYKNyCj`L-qj7-6f9e()Zx3{ z2^6$sWmu`NQW_)5yJXcxltfrTHpbSb7z#W1kaJ_L^(y2fl4lQN{(Dz{jw5i%rodS| zohrPmbG)nH<}+E4DjVy9jV9Sr*#~5TSlf2Y+2N3JY4g^WmnX5jT+Qp`@*YHWT!K2_$e#P@eC zV|>n5Xg(*6Xxw?UpDI9W7JwYYYUSk;yAMfuHh&)YzH;%0d@eRCk-_-#eSvp%ZwA2b zIHGLVMwxvBu5H}^n}n}L<8W{n)+Har8W>^5;-G1ZX>5Z!vk5Y+fz59^74K}i3#Wdk z^FT^|w9`KJk!&pjtlEstsj~IFF~sJOtrr?IK_($-PlL1gy}OW~I)!N|y)X%!E=+~2 zEVAKaJG8|9ox!Iy-a*#3^^=gTV+D|qtyjHD@RK!pHDaVfJ$?Q)O~=fl5yz4^--E^J(UK63Cj9=GiiV} z5OnM30znyM>*c`kPmrz8uu5>s*0-yPXzq8$378xL4;*61n`4z{*qhfedFEZsHX(#= z(~YoWY@Wn?2hW&uD6^a^MvV^2IY&P+XFrg45ar|-wq+p6J*=KQBnO@H53ysGe{~6# zfEnc<U@Z-~66r|4tHPb?F2vOF`fg1?0!sgO;|UjS z{MLKVt(tad9a>zGji#2iZfdmE)Rm&C{XnwO)PH_RZ|cv~looE?%&As0KGDpt>}FQA zYUT!N2ApG`aQiycgk0%aW{w_Wia%x;a^i*($LV`XQ(?}7Jd z2t^)9D}pa+mWF)TXJa;s_T&5QaL_jVuGg~dM<9_|bQ@@eR^`!s(0tQHHjs|xGV14f zSMQWZn+s_1yIZ6EWE+*KR2o93HHN-_6NM{IYpCe9r9uvyb-v#@z^X4{accrV_XyuO zf1As2TC*46o&6hf%Zz`Ag0xGB=eklN$rmGAW8AK&3u>;OxPThXUW`DEaf zh&DQ^G51g$hFF=3hN(wbNhc!34&7F3Be+e(ZSC&xgJ0c*SIBpqN{lWkFhp@Q{7ukP zk}|0zc^qwWz{;DqgXrX;If>55fgx#}VoX6~%P&eiW6V_=EG95an;p=-6-vLrM3`vw zG^Fec>FX=xem(I-Yr@-BbD#J~n~Ad=q_Tl z|Mf5?C!)qZaWHU+JR91i7U%C`SY!+%14_6WowhZ{q*2b;{cfHg7%(^OZ}BeJOJkcv zuqamm+4$7fc^^_d_S(D(c_KVmmx5EuQQ=Z>1=ftp-3Sg&E^;N$kKfas_bAclc=MGj z0mfEsAuy$k>Ml#&jR&*v0F;0=gLCkdp_HJ7n9#$rF=$OXPVjheF?MrVB!3SJgvI^dJ=36gaM6(MxYzzek2CRgMA=M|eGa18 zJ|W#Jkim;zeUxE+H}92(jG#TT`>aa9frkJI1r{x3p(&x8HOFt;R5^yx<>%r z!;0{`2)~Q)y9mEa@Vf-RLGcm5&9EST2k|?I-$7%7LxN|K&VuXIQXV`;vOmPp4aJ9m zEKXd--nuNJ(xwn{V{w(Iqp9g0d3qmQ0L66lJiSC)imf{8Wggu>bU0dunFvNK)y|;# zDCcPRlUYZIqb8fRr!NVmylfTfm1wrzQLi=rN~?o!v=>9FABvccX#1CqCUjgb3)k5%U@oJ z$df=Y<_*k~w(_$kfN=I;iZ`bWW>gV{t&tFQg6=Oyr(kCiRbOEm0ZTU(aa3Ep?&EyT z-(TWjLu$V`7kH$>3&?awbX{#|1w*oeTXwKE~YXj9@Ce9qHlw?_?A1; z-Hzin-7_PcWj2;Ba(}On_O4O*MedCBD_AeX^HPZ7ykJ zxODbvQ!#l+1TN@I*HkM|=#rOkFdkm*)!ZIYbV7!rhTfrRv{&oHZDwjqy#Ggr1}qy> zJCdQc;hVKw4)y`;e4N}~2`lNzQtqLO6MR|Tx<1gQ_{0$%->!U1$O30XPLPN{flzE# z^DKP2ZJZbYd#Q3@;gQeFIF7s5@7G+xQVA~Au;jR$*(5U@&i!TuoL98?7DAL~M&L;`5Z zp59drVPvr69m7yDWy zm$#x3(n4D<0_&Z497^O|9yK<~(iS~~GiQUDoNg2FCH&qU#4RORcL=wWyV%f_X;sIl<)P z%T8jWi38WO!H-lsnK*WQ7a__IfdAj)d+U$jJ1H2WL7U;k_XE5CAK=@A8U9o8o%s4E z;9Ea^PMIUune15IfBYr1J$Fwy{(~MJ5>efE{~0+EIs1w?W5=kH9Vy#0<2YJl;we5| zmaEqzN8--U;)grm)kT_IA5wDXCA+UV0ka|LUIWylkQEa|0Iq&WN`C`f8*wgxQx6&P zj!t7z_R>r3IG!Y_Qq%JL*fz!2v@-!l0SvYTj?MwC3hkN$QQx4-+AY51J0SC=d5mMQIVdkXUS#iR&;kZcXt|% z*mB8ZT`84jIo6dVkztfjs=LQpzmc3F{g<{6A-I@7t8znr4FKVTSqilM5Y0>H-I-s6 z!}&sM;A_Bgq9J*!bKYW^g)G_fRy+&7hV zp>Bz=EpQQb)P9DD{Zj3t_1P{67dZYY7YJf4KH8K^gGdYC&l zy+NnFNTLHA>K&}$I>lf`kvgMMOa=Vj3VbNfp&3pvG()$IPQ|d0cFr+*P5UI(-ZFg) zeW`Y1ieAx1rtZ39eUX8V-qo$?nAar}9cxX1#XvIB5!+-_=}4P&rpOm3yJO_u)i~%M zo`#m<%`EeYlvBz!{jW)R9ewje>$}cL$^kTlV=I2 zzC+J~QH%{o@Nz?-$sX^j(&SA>@p7~H4!v&k-N1hNcq%;-rOjF7U6q{#Tv!HS>v9Rs zT`1)D*ziF8p#gIa;#Zb;RY?+)K@={+E+ed<$*4_XJJVmGv;#XPboqFi&;oFz2odC~ zv0;nkPa5h*0v$wg>NEw>t5r>Fq}e= z5_x%9-UWZ5CzHdbIa{r1NT&fKH)yZ1iQP=*Y_)d(4t{FeLlF=oY|2#!O<`$~IV+4w zwq?&Gv(%UooQuod?#NZ)&Qdce1?}s8p~ao zmF&muX18j0EyDljC8!o}q%b3ZG=IQJt$|0MA?BfQDmxpq$q|FYyscQgg`1!$SmT_6 zC)0y;)l#vjD?*p`)Do^Z-?7HbmS9*EmnjAaHiiK+9HKy#pqRRvFH_&)!_>09NuAok z%Bj>B1@1$jh&0*CfH^kaIhw*deG4WSrC$WXCD%c;VTimbNL$`;~iQ8 z!Qyk#0h7>LMP&Wiw)&5=(H0~{B#(oc&4#=ctj|t@A`V`99+QC$%cqW&pQfRM{}n$8 z8IzrfpByxgCs58JEQl%DbcF@;U!v<8EL%HD*SC-v!gQVJr0Xy6!CySJQQL-0_}|j? z8|>=s09|*3QGXg;A3z6f(3Q04RA^lBAFT<99_R*}`&${6C*obo%qMzJsgSdiJ$c@* zZiG2PAR=ug0mWp;A=2hN>e+MNw1?B=KAHE*VSJw%Vj?m*m>X6iz7+3;L7DiSCqPVk z$r?1Y!K>K+;d}{$d=WXGS@p7{G)+yG8?E-{Y5m}#<}4Vcn>G+K08G%kZM((VnFqN` zxeJzChTJjL&_iAGvhkUr0zkDCNHNn>fXix!++)0smNLr3ly^?t1v8y(Hd|7fFrlE< zrfR!)@@BgWzL&7@f<0X;DMOO92!k(wh$vK}y?{be>5yG`9_x!d7F=m}=HIB?W0LHC ziEhwbjh3L2%8)Rv??F{? z#Rbm{2PO=>{w;`mCtk0j_DhHS2G0qvlW`@yPNZ^#*L&%Pft$*Z44F<3I)c~tXfPed z>m_>}czxwJ7GAra!+4FqZ{l_D(+<3DyVS<((#AIN`WJq+ReO^$!g&2PKl({{Jrh+4 zuUDdT6R%^2;@*kZY-+!BNEx0JUc2E+c+H`5gx6E)hVklSNQU&H2OYud0-{5Q@jC21 z2VQUdwT0Ig&Stz8{?o+k{HGjvy%U>SOnxod(I#HU@vE)c9L5Oabr?U&gx57eOgTfC zKn6jI)Jxvy*U@)I3t(aeasKs;{<{BM0A_?idLgO_6LCNG@{VtTRFZI(ulujRBEZMu z=t=Y!5Q!kP`KDU;UqMyXe%|wVXf5xGp#{V#IKTZJ#%KP=Is{fZU-Ouh{Z96t$SS1?2}Th*_wBG0|FlKIhR6i) zQM|88neMAlCuda^{)=MY^;%BkwC0#UtYpCf1Jz5By?70HJstp}E(JieaSDF+5(K^S zq_U0CnF!5GfY;rVz$VhtFoL?j6Axo}UTDDva^8s2mB|mm6~B3uiY{OnDiM^%!G;75 zRUD6V5(qvr5@PJGGQuVdV`SPqv45eH`gB8N}cba%ia5 z-XmqLO#zWOH_WgmaL(p0X*5~z)XhIK55RbTnLxSSfN!iBvx|^p&zK0_MwBX0~V2n9VfmgIWBZX8{d@l@yB|RQUEri-Ezuwpb}>dUkXvN$Ge~}4Gw4csw3fe zTTdsv!NA)im92}thfdADCG{U0sc$KMpe26eN@A*me$#&Wvh}eq z;x{%>nTJG~MlO@k{fa09{#+mZg1@R+>67$^h_W?Y-vkbCs5}qx?vEp74OQNX{mA44 zUthBaU-R4_k#E!@@~zEbCFqHGpK6%dE3^tzp%Q_n-NYyeap5DPQJ|WbuL`quDEg{W zPR>VgkS233LcZWRdee`gn&hDhJ*&8lx6noiM?+P@v#{Ww$n?QdJo~(a|MX~9tOK}3+M?U29 z5~g`5WTp`_Jxs^wkvaQt9`DeAo9k4m%vPM(Yct`9B0f z*cl1IlMa!@Is@|mAy5nQpHJX*$bU@pD}Wx7|15(2EpMgD|Hqy3- zj`*u~9ab=vIN405k2^=tMNk~W=z>q1LgXSaN7*o=JeBa3i!(Lh+oH1yvLTAGg{>h5 z=ExyZS7D8a3t=pRT*gAg8xeCvx;73hgiMt5Ez)U?m?M`%2ae9eL_vTt0dIdK8yPpS z2TeucKmewV(}#H23@>!W2M;ha!TqGmO3wg(;%DMtnJZd=;h6(l)B;NYacYbTiUw;1 zh=OWhUF-iu4yX&&z)}E$AypAXlr9maJ`D0#k%p>pT@UpcgE&` z@dFef=|m!C4wyEKf4ik!ACYghw=h*=!yqa9J^%=ygIdrazRONJh;9uXyf&Zs);)s+ zC#yW1d9qV*XpxrtYbHSv^fxCcH&w83+Y(VegAxI87=m*Kh+`NapT0d+^hL7^3tMQU zdAQU42k5yd9)-k2y|QL+{8xA z<12CyK6G4fH{{q1y`xgvl>6;aYDaYsS}Mj6;89|~E!CT<``xw8g9Z{!w!ey^}>mJ5Kb(~wR8bY4abFgSG-Wgis%BE@! z_yCRW-vtzgmG3H)@1*!8uGr6j?ce}>E8&Lx0)zYrM4KoN=>9^yj3}oFl*czhlx&Z^ zPHa~Afuuxw1JbS}VI=*H*jHiY_2#hmuY>cWUb5uC|D(`jh$&Ae=wZLbREi(f(kEhO zV?G3h(6?>x0ayjdNcRR!ULFh5Gf2RBeCB4&xKM+f){%wuKu2l`F)S%sNRJzBmv|m*ECkBq5 zHi}A*ib~M**X-9W=1Mqz*i^B$ywd$|htWr1Li za~{6Z%;M{Cxa`eYbmo6Kn!r}1021@@Z)BtHA0XPkieV6+{=1e3Ku(K0A7MO=fQy3& z{K_dHFc^9`iys`bS5Gok-gvB;U%;C{)XEBJds}FzrBn z7X*gF1ygPnNBHjAPf`H3{>;-jw!K5U8RlsfCMAE90cSNpRiP$TN`j+|!EZ4{oU$Mg z@@zkW5Fw9Egp7oR`WXsGXGhfAJTOp;ASGg*q`00l^E&zoWV8&ObpeXzjlemJDRgCv2_KcEx<%MPf7aQx$= zk+M%_JeY)eI2rSqPN>JkhbYIF*GLDNrk?3WWZNMSf$A|2WMIP^@{TuMXgKTzGB2WB zkH$g;AwEMRiLH+~0!iN-?zUa`KYubGMG;p1tu#_K9MFreBxx5uh*|Usv;n%onH~82mL_?JYvy4)l{kj0Y~!rr2o}(VhazR)f#lzX zm2noyc@^3Hu>NrVa%!XS%ZT^+7y0-dVzL1YyOP_2L59^n2s9CzySIdMwxh-h)S!)h zWEcn{hI5XFltV;_U7up9el%S6FYmpt!`Q*fRh|h_JT4R|=?PxbE3=UDKIB6E1hlm^Eax!+?>#XWc_3dZ4M@>x0Zy?@k>Z~m)$V0_!~k*b zQBr`zq_Skpi>c46A&qj)l7pZvk3)T@wiKoNFeP;VzfS=Am6MN>zB~vz9vyl|IcWCC z!c>}-q|of5lbB}B5Eh`>-UdN4-e`_W4YBYhEX*IiY|wwFY~6#uv!|@dA;EOu>Q(q6-j3 zSWRMm@U}LJ>BxL>;ArgGQ{>eaU0MS-t`&SEsSHO;-4MW zd|)Y>G9%QJ_Zx<82nRsLurS#eQfU3GeS*}$Bw^$6zNuVio5irv7UHF47RSqm-f<~d z?$o+8mNPw)rlhDR9IE|(0nGufo4hfF>$=}hTnAoQ4gxdlLAb%!M2~1D6X)?xgs`^~ zg7kZ-@GD8i+*!aU1p)gKKH37=g2CP#q_wGsalQ)&H_F(2||oa_NDaE!biXvQ%UVx+#%M$neO+IJTY?${dLyoMnpoi`3`&=B25aaLVfM+)1)i zIOm|015NPljK2zr-d&@WmUrPis-e0Y?j?>fR#!oCY7%O7e=Vp2h8hTI28KFrgJ7s2 zsh*(rvcd+OTj9!vI)FeZ;QE58;P(?vd zqiK49093X(u=#Kw!e*6nmx0Z2*;j2~F&qXN1Qsu`us92eL+?n1yI>?d&cuh{1^Tw4 z!vytJcr=Xd9H46@&9|@Kc5dx@+|kB z`@QF$d+xdCp2KT4znoJ?Dm`wx(6X}~)UwV9CG6`#60($e9wR>fdu+EJCn=ug^4^Mx z;umMmp_$lFJ>|b*Miyx#Q1^Fy4hGu_LwAfBX1`wW0u7uWkbv2cn53AP8Qtj)zRkc| z@n?wb={0iBk4WT^^NTwX2QaOeD;?g6I2B}FnZ)0>(ENd6G8!P`DXH`TkcNIImF}W1 z-Ypz2OjJy!)3H4BS!miDf>C$v0x{~K0%GgHhPer&4mOdWsA<05Z(|O?ka@0B44Hfy zGWm+V45QA4z{*fzv6Oar$RZ8a*l~aaZ=S<*HI)LSw=yA*0ca=I<`z=cQ%NF_kZ@uE zVMTNUnAOUs2pzrzvt!3WN6EocPyj!4#cy%sR8Q~V@a}YEY-L^HbcmG=7$gVqI_+S< zB)wLR<5~+~F;bAD$NMooo3nrVUsR`3bej`(R&t&1se+3wLI4hO8)c$n990M61IcT< zBL&k2Qjak`j)BZpd)K&*L;qFE@oQjlsU=^Xu%U2hs!XRF`RE<=bl)A&+dhyUM;nGi zoJ@f|X&vn7rQ3_fSy!g;TOZ?O^&nNxwSjJNovmaa29gKj3eO7KWz1#g;F^B~9hyi! zjT<@%hQu53e@dqRQj`WOITfuH@ts6q(EUl7$bX#aH;c6<7VRF8=oy7OcVIGr{K7Py zMUnH)UQAXsJ-3o-P@1IMIF3o(Z3sR0#s;o!7PcOouomcTCP*c(n zJFD|C$qbdD?ipMul*S<_$|d#pcaTaS#Bbcf>xI&Qs+rwzuj>!!DU^+CxMSM+O=66- zq=utph==;&a@X~ENLeG^HvN;|xCs>;lk)lR_-g+^(6U(}!;!SQ2J%`8Y>*bYVeVZ8 z5rbm^s~~z}lsjIHi>aH0t9ZIvN~fV2^n^hyu8PqeR@N^7(S5fTViP3oxV3}iP8n2L zm~BmPoAEc!-Kj7;mWM{sC2YrCD^*xK{4KB`5OANSKG>NbG~vObPK77-o>h5bcS+?b zRd+^n&ZL&!iB^D$gFs@fwD=qdE}*d;NNpsfy7BJ;y)VF1GtZ#s=YE|S!}S8J*g86& zkGIzzlg67maY+NV&g?``b-^!x5`9}UkB~JdpNBVrRLwEzqHWUZHHFgMHQ!5zHsJLt zyx#R)@DmlYoIfY*#&tY1&UB;ry$1yDp4kO|cg*a7y3h4b!4tKGfsJ^n7GJlCg7Qs6 zMM25e@MpgIIUbedn=TRew&8p772-Qpgfb@Vz_r?A(oIxe*J0w`CREf>TD{#Wt*(sU zTBp1mhb(cj9_IY}2)szFNOM24A~BxIir))X^x_IQkr*+qXg)3y4}%B0f(4Iaw4g{_ z!FAKSegg~ABmHo->jpfdyo5sNk(K-cap3pNf!|Buz>A0hAE0WO0XqjaxJyEq@7uqE z`R)SqMdQ7=mBm5cOF3?hTj1_OET!_>c{;zv34X)u7@5aFA8n|YrB#qm}>If>nsy zGWe656JqW^!+rY7m|}>cMfM&=-K24YU+w}1G2tn<8C>q~$j_rf_)L&f1~j4q61$mF z`dpU1mjC+v@_1+C{4z|@VA3Wbo?oWmA?0@TA4%G4`HhJ4%W&%ZCgzvUFEly7)IAGJ zu)0%;ns^>K8;C&?s`J2wVe>#Qbsjhbn#4R{J8YZ>0w4%)^E|K-&p=G8^FRWAiFx3i zSz;de=}?pNz#MM&c+(yH8Jq{M>qM_lXgm+hK*bT}f#e=)K}Yz-e3SSb&$9m%UlVh} zUh$o;e=EL6oD&9$dtr0Jm5`>!Il(ZtF?r}?JCDXTjcXd;Ao!$B>TkihOXCQ4s3SXB z{UqzLsXfQkMfjB*NaE+W%^ZlyZUa^s7ocs{l{oTBy;z0+N0obCilh0Ms!hk7J$^IhM-+G| zm9`kAX``G z#7)6FRsJh+5yO2jw2~p~(A4{Tx|6l>enyc5jD`$GZGID;n@9An_jlx1P%mslydN|- zZ5oYu3ypXjHAshn13FipKJHuBZ#Tfoqf6$t_uGQR=K5{O7sh@YE}jqe+xuv?mBYLf zQ22jTsleEe)^CB&BlcT8wv`!o{JD5vhlu^Q3C~26(a-(4`F`sHN+a~!Jk<8v`VD#8 zP_(Mm!e&SQo>&=RU0xT@3+cT84J)2+;v9rrz&au;Rtv!y623R5O-VF|4 z7Z_b*<@k69tz;2&ESnZdTOCzLz>#aQAHTu954j3l*x&3c`?{>L<$N};movUQBJ?;t z-AgdviASs8oVDG3koY@^*4tQHfdCB%S|MREjzX<$)@6gZ)i*j`L|P;RpJIU$Xk2*k ze(#OqG_H7;2&Y6Fh^JYlq&hs;9YCyfah{;Ho4A?_YcGZ-FF>c9UTW3hwfr6AIHuaaV395AHf)0RR;{ny=fyb z@N{;R)FnB{!!1zj{wFmw599_<#tM4FHW@PoG5Nr%7|y2p30fS>%MOqzTQL@kJO{*aRKD_I>*9EJUa ztSoSNXoxKu19K&>cWL9XlOw(c4ixZW^4yR>kcrl8a33H8l(%-#H}jDe$hIjJ1pxrK zrho-(n^HUzHsRFLR_ZP}XY<@oi9PV%5QTr}taOyBbx_jH4rc zSDx$gby)`|SjUM^9o~)(pS1zRgT84L1g7dB*cC(<{g47CgY8Kw{SL1qMn&2*SI&+Z zXM&s`z&h}PhuCUG$V^8`CHlA4y^-+6-5Y#et_l`a za?&*KVtDY1=gGVkp^)(?q$&wrfgatg7IT_!Y%H_3jrL=i!P&uiFWc$gWO;_Wpdpij4XL9nykXug}%!RBp*9PL#y|4 zNF<1_$6q%Y#n+Icp!gF02^NSMpUFhkz?3h17Ksb;1IbT|lJ+EUNiV!=ED7Q(OZg6g zy;NDq@+>4C%>&}?XYn@tM7?Sp8V{U|*`8%^&6=Rg4XOsBUhM6qe;=;DMT6FZG9}j-|%n1!$p^-T<{eko?Ne@%X+)O5cU1 zzq5gtfL*8G#j8E6{3g^x?>9Xe!|N9;<;+R4uj_I=25kg8km2Dfu81j^P|Yg`P-{HPEi;{%fCZr_a_|uVh6@e&U#&cg z{=)3qdlBlvg)N}>JP?BCy1sBHKer3tVS^x@bV5BIHNAlM@kuoI6Ewq|Lgop2Oi+0M z&zq+6b2qJ5E=L`_pg{@M;Y8BB*qFv)MjB55dZO_lI$Wjk?=^zPQ(#kEYDD8HFsDl6 z+jtbXMy7EyKL;8gLmg3RH1LG0LGXkxuON_|g&)XIg1@T}K>_?t7tf+6RK7~Yca4A5 z`5yzQJFdbDjIwAx$%80tSS&=RVu0N)23XE=1rKS#WOHW(XX$`vA9lqW;qCtZnU3m;|-BwvnqDbY9dmo3Tzcvd}? z2!|Qt0TBaV{CJYlItSE7)X$I_DG%{QOv%(7kF8UKt`8ya6<;&TX87CMF=27CX^Yc? z>JeVSqdNfOXSxj&7vGn~M}f_wE1RUoVIoqid?8k?64j3+G(53&&p;U$-EWku_f5qk z%jt2*Y)lr|vcL?yMM+u8d&kP`y9rBj&DLTg)WyOK667P0*&lB>EI1r2$Lf;`=q=#u za_DspKQw(04qBIN4CJgSLgY6Db3iF0EXH6CJ)l0(sM2v`TNUOsqyH)xIdu?R9d>he zkE2lW=HJ0F7dhY+oIljC5zfg6p#=Xy_V$2J2VC2nlJg5V;3z%0tB2)dW7^g~718x9 zj>+pB@xM4SK0$N*y(o4P;^O6H;rL@w_cA-eey^v<-vJ6Ax8MnZ3uf;u(tANZ7I_pB z5p?UmA!0q(Jl%IXy(49Z8m(ejfryTw(oNbF(fzST_ay2V@2~=$ZaIW*1k~stt-v-P z(VjxIcVntSI}`qRO9)3VW9z3Y$J6;^QcTGo&ETZC5Pwznte|ledPX>pmLd7XQ$CYR zFd(s~a@#VTB4neQ^5tFBfCi|62zWU7**FxPsQmd)`1IC8X@nv#EB}0oej>(Uc)&wh z*@9cxXobt-9I!BFE=~obV)qxmdp_FHK{yT{;#_)iWqg95Tqq*BWAIr(36jBb=%rNp zED#^5>Q9UMna`K`P+uN(Zaz9yPl;)TzKxCX6y()Q$5s~3PLwTNiV4A1=JD{nV8tTL z3X0%R>9K+mg!}VYL5UTVSV0Mb(|fE1E~utntIXj>5zB#prHWq=0(6gr0O_VEYAA|C zYz7a`B2&?*8P~v8C=Nx*o=2WW&0f<7ClVo$JjM9oS@;nCYU4+Bg$X$ffSzN27)}_( zjFU3QFK(O7p3Ag zPY3Niafr>k3xgelLFq>%@ExElDNm!oYG@0d%W=pJy*b4!m;7v&rQB0Ps}uU+$uLhG zReqJ+mjZFMXFAI0sEk1wh~A39u|78ioA@!+^gAfH4Dz;Q6_E)#PohJ;u_d(u?+%Pl zoUelO!xFj|NKURNU12uPY)3c_LmVNbZH#g0k8uhPppa6N8(q&2pgs}P13AQk;T_1dm#hJ>8Wv07-rd-=p1vXWuEO*YH%+rS+&Owx*2KMrf6BPcYFw*ecPISrJ8 zHJ%-Uah8iDr#8oE91Hp5GtK)2^Q;Hvj*zts^r3Xxsl_ls^Z2Y`>`L+&G`vgC;I{Fy zE2GAl4L);9L-E%dTpQ{70Fbos1~(!VkrTl7B1GWHO+|Q<8EGYfKoNox4Gq8xktZ#J z7IG$#ggbCn^H%xi$==7974hQT^!#mdwzn8`xlcoEFC;c-Q;k8b(s; zu`=9uAb!dagdk2uEHGMiVqWY4VKq$vndE@4m}dl%j)GBgEm)$ECXkz-v2kG)2^>$7 z|4m@p2*M!m!P5;R7%yS&2grHh1ZA$}LmUwnhUfm@^#CpJpvq?oY-&qFSr-dc+xM9Hw%Hlm8= zmDc4OolN;gZ}N>aE3Z$%=)!cbdL`5Rfmt2%a^rNPf#R@y3Iowx*%Qz45IiASMlX6m z&yoPYdy%9pQtmBHe*vF+sGH%yN&;>$t^Yf_AlEwJlw1M_#R?=}_ce`VcZ$jqHlGD* zPpwjkEf1gqDk_a)f-q@(K8N%h)m6Xbr&BP6__}^_0-ctFc!`@Z{bi3%nbDa#R_P6I zaDM@XsJG+5ta--OaK<&`IFBq1JtF$=ET{Kea;C=faAy5TIjb-p{t=s$$8G^Es_cSq zgxOMdWrqI@Y7!2klce+9$DMzsr7Xv&H;|&U2u{{y%YK5}qU$`C#yWw8;^@(8MG3s1 z26fr_9wC8^2rOijzb+y>$XS+h9M7(`2CmDbsw_ZApLC&*G#~$AiHK9MzMiDIh^w)d zz8m8d+<=D=r(ij*bNq)>i5#ckMtsUoBTm7i^a6+~P^Qw8-XRO=LQCQle6keJw>VD0 zaae}*xElW%Ps=7qr9g5g3@glgCC_Q|-kv{V@k4k_LtKrozf08P6fAw1=!1S9)R6Fl zNq>WHA^rm+f=g4CXZRA2M?NAIba^-M^+x3>O#Ea8fsu};vAVaX>KZ@G6LNwBQ)of2 zWe6T?InGj8&5n#pm05Gv%pzXR{1Lpm1VJ%z*nGQG`YZ`L-<6nT1A>>$bpIZ{pDjX` z>{7-cK9K(`>aerqa}fYQ6GgEkRH~{meL_NwL921=R4h{LlH{R@UPv8F*+ohIbHXo5 z?|%%T1z%O=A2$PBdIhSZ;kD`nIFlO``NmQ!0#D0?LY}p}o|Go67El7ian>ypg?*VokGIhe7rOJ|}bm=<;h}>k5 zBs;LdLLIOjd5q7+(cOq0;6xwBx=#&|fOGS)ASija7*EBx!8=}|HLm^&YQ+{PCQ|8hC`($=Q1>{Mx!yj2 zR8qO*)_yV7_MYG#^@WFnFWiU+ZN7WrY~_`3oW!|lvAHwe)lc@>6G>0c!j}g60(U?A2~~5q12tP38ub{hLnuc&jQ(X5A<&xh z!Tjze4e`>VPw>l`eOJF3!~7TWt5j>YJ<)wDeu?^_j-zg`Jpd&I$uZI)AM|Fd|1v&? z8vPWCsmnn>5uF;<_MS-8Et+&9kh}?xN-HLw;Jec&L!S5aLzx~+MGtV4G?3cA(QAkE zq$^;}sKQpolHF|;b!s9ZHM z9;qw*r~83DFr1F92jH4cSE1>;t|kw|cWxpRlAiAqCrV~y(n*Gix|5;CkL;C$NF?@} zFkakWtMOt35A{{W03t>zy$5|-yoyvq{P-hr0+t&a1Hu^KP8?WSH%hRFqO!*Uu*cRI z>wl8)hPC)tA)fF34&q@e@*|%0(lAsB?;G#QL z!3ou^4yYhFBDW3QvDGEzRt32R%Gv>^uL~j!+AG7@BNNJ82gDP&-=A1r!n9K*TCr-8 zC~97%oY7E(M9(g*o()dH*N`*p3miy3^f@{yr@=jxD54^Y-{lga+hRX!Q9f9Oyq1~Y zw<>AS$?C4ewGt(+D9+!N8#Wyy+O^ex9{-RYAn56WGWo{%n7ViHizu27|K|-#9xlBF z?atLe{rWjU*)kG*2A@~-v z5o*k~f9D=V2R9dG(*#m7uV?BEY|A4!#Te|9Q{&&j(yQ)t-1J|~yo7@y8^l-Xf#Jkp zV67kX!EIOI2gId2MS9LZ*)pGKtDsyum_d05RiO089oFI;*3mBMnUh zPI?2BA@!`ISa4V^PTyZ>CbD`d=8;fuoY|@F)2xWs3vHrA2pTtndThoIp2~{mC{7&n z#R_CP)&*`8?Pp@TV7rEm4yW!6Bu@g#SQCv_FSCw>b)>k8)x|jEhCh$yzTwkw9Aq}n zeZy()!J$pQdg{Q-fA;^xhvuUsvzj_@MH4nXj5!(`uLgEr6jSV(t-0?Dvsk%q6In8Ui9>>#An3LO&WI~)Sa{s(n$QD5b5Ub%8}j_;Pw z+9&tjA4vZ5r_@+kvVVqj78umtEpW+*Q}!YvD*>O$$)LjHwnwoWgDf%`AE^V&LZ#FA z>Ou%RXb}qyU^r$+R*j0^B{M7s+^JPA!N^*Q@-`||9{2&aVr_@xvROpm7UjvYxTo%L zK%io^S=XmLbiKozAryQYO4PTBZlqUfphd7-d9C&8X3P_Qs@&G8Ub9SLCWbK_(NuYR zXr^`>biM$c4DGJ_woD_S1^Vx;Tmb98hv{{zxv~BWD-P~J|E1AkP=fo5^bny+u+!W1 zAMmPk`b_12$A@>D(_?xS7hF)d5yt{LbzZ_qGziu!(^6cQy~&Tz2P|F~g&a*DBDg!< zzE|A7U%lPMcpDLQG(^}Q6xsIS{mF(yci#Gv!wJ@f2aD0Q2%>p>jio3a@tlvZNuo~# z5nuSZ(EZM@s5=$#Sz{q(VLn$yPF3yCYJe7eeh#kK?d5^W^z2~0BHAHYmM4qX% zj7UGerV~7zyvct$5|1OtaLvV`lMnX-Gbkhox>gfNuD~xpj>}@bs3bB6;u|ML3;1(`pSz7C_R7V@eLU5-je48k`{in4Uysi8e4D1FBw(T)u@HT!4 z47M6Dc!QpajKMRw)jAC7zK2sL^dtIL=f|WczN*+@mYQljkZ-{;Rt{cNXY));Ro=Wd z9`n^_lnKZCqa&~mXYYzGarz$OKnr9PnI;bCIx*L!D)&x{199`a!s3IY;qmoQDc#%U z^;H<*6o-S47SR5NWT`@@DkC36qB;mh^kF^kTkR{lkP~!58Nj|AM2RT2mngOyiY-|W zpE89!#&(i6XhA^U%OMTL$$A{NZ*;mIRVTLQf@`}|LESgA0>#=ElreD7R-Vlxk z+cmayM7-J1UAO|CxsO1mV!=EWon)OVd!ZvC!yuuNr(8Ri%n20xs(}-VRU&F0TaWUT zdvFVKM9upL(YX_h9LSr-Uq(z-CQm2vi8-Bu?~v3CdLW^mbbT)yds|d7?QLnJ9fIym zRlw_QVEa?1)^P7$(%jJWJQ_I01v=|0ERWHoOtnccyN0*j~x$R!f5>#-Ai|7XnXaJZCDnY z1q31Qr@OTMe%Z0@WMi20STE>m+ADvDlLsNJ8dNHga6mIF#L1LEpa+MB4sJVXbGdG- zqZlxx;|w~^djX$Akh(Ac=3?y%g^Kb+VQHpsm)BvtJe#-6e}#=0+s@yO@J+ZF)4^Kh zi%~SmfK#Q_ zj*@k8GW;7C1s0SN{{X=|Ng;vQ6-KR8)=zan!7sP*%x2(u<`;`qGRGXl9~t-xmpdq` zQh)FcT+YD{fxLXdKo-ry!~Ht>0b{?S0U`YYNG%!i2QNn1XGWYW zB0*;|^o=!D{1O{zw9l-yn&eM9O2q>xk+zv}0jnxUK7ODXWbDC)v)(@|sfX2Hs{Yt4 z&ta*gyE@o)R|n=fc69*HeH6lT_qdqn3Ur=>ze5C`Lwxho)0pv6bjFhe!&SG2=Tv*R z&vXx|WY{o+SyA@HDhc;MwqU&>*ed9og0%!iD=z>uBFzFk<#1$p8I%=>aV#Ec=qKsW zNO#9atyi1qs%S%C)vDHZC{=*Q>;J6Rs!XdP!xcwOKe;y`Z~IdLNvr z-<|(CET<~p>H8PTAMa9o2n(AaczSW$*_(h&+7h_&JRn zdo#HkGjS;u9fr-Y1(x)b}yv6G&}-KQqevG*&eNNSlaGL3&sTqWvv@ zA3%h(K)g@b!6(z1Hk+Fci!q@G%ve092Vn$;DPbk3vCf2yZ($kguxx~5%iE!{t6qZ6 zH~q+*7?cCQ-2OYsfjj|AL6EiDUk=E>LmeuE{a4$ecE7QXLK`+BA#5rtp&c284Q_0l z1!+@JC2km{O<^{~%`AwU3KBQ`9&XP+7!)^}Zss#BfC13J7hhtlG}8PKQJzrwMuAdD zT5n{DBHlL)XBy2-ydN%6#QTlLK@%l}O9X8^wx*15si2h+lna7@$by04fMf_C)zxw5 z!|^TFdb}#Ns6%y3h8(EG_ayq>m%f)L6`yn#C6?i=PNlzV;rbZIW!09rOW_e7Z}o0i zcrd10Ow7WQaK(>to2>u1$KM-2V5TnWU@wg=WVrfjeOqjb*NqUU#&wG`NleQY!Ab#Xu$TNTO(uJ+HsK? zHp__jTgQ*#GnFW#HRB?Dmf5rkJni&dD|kB5S{;{xFw$UZa}oXMiywv7^>LS6=qOne zM;<;M>}7GdxN-_~N5tUGL<}SOl)Z0_6|8K=j-Rc{o>POgW-D*T1Couer{dd+s#S%2 zrOH|sXRRI@!q?^2d|ju<_p;H!pR|t2!;(PHi-Qm!`d#{7=m|be9;#YFSi9Zz33@Wb zUg0yX+RE4JOhx)i3~O$mX>_A!amFF3Q63*%z(OulP>mI{!31Qk`C_c3$8Oe$RMb zAn(RfWS2agh{|Iy`WX}h; z^j7aFXotp&OQzs%)%8~*VB5(A{PBvE#m!BVT;CQ5--O@ zyi8h1LeEiWg*=Uc^||nEYCSf_NQgSjf#eY%kb6OI=SJE&hqsl1=V8{-;0TTtdP~`U z1r>q=67+c8I{ear((9QVR?ipfxSlUJG_EIThlRdMo^Sq%n4qz*jW)tJd3cI)4LmH! zA<8oj4rHmQn(T4OOHm}*{K%$l+;>#_Y8r5>1a3f*%_w5wZNb%*xXM80;V(Qhff>{w z5fYTFCuBCE->>31vZUbmTl9MweuG5(d=(Jm6_OFtG>8R8ZSFs*LUf>}sfPEUs_&*^ zboJR4+J}qRGPY$kk+8*_$KlstQ3|!_!vcp$*YLM`d8MgSWd&CIjS>$>}ln z(r;wnkjaRY=}wTnv&~B01au8{I+tdH1m83;(PpKX>>lv%#0IrgE|qnnUHkwtT&(wh zP0JVMYqIIENS&*>ANwExo8ela5Kq#*$$ z08dqxM|`CV603!nyrhi{`1=^_ z$w?LBJUq&{F-4giR`f+}Ec&|eqH&(c7%N-d_uq6=-9NS(f}Q^y7Z z??5Y$QC=IY=1uDekAnu~YW&2RG^9;~fS6B`(St4R*dmh>OLriuz}yh1Va^lH)z>fv z^`%gKSA^ELPE}DJnc?TIvp0KQccGUOVU%E%B|))R88mR_js24BME8GSwXfr_y@evR(|cG)6pxGjyI`P z8jnQB^+r1Oyvua#tqh}M{BNP-fKJDwD8?{u`Wfjsw3%@hn%`(a$F<>fY|CsC57H+Z z=@NM&jw_nFT(AcXG-drQSlWmax$zfB=3os-SZc*FsshEe+j)I(LM9Ui!= z57S{DCSXQe@$PULvHEQpcy~D3YQ~}UR4#g$g^M1RuN57+TSU=%mki@B84j(Bini!Q zV?P=@+!Q7QOxFQ0Xt?B&8rJbs6^(r5?os6PPth^(bOAm6^(O+f0_9rVTPj>#hT|)D zc`&Lza6+rC7CoOfQrF{99-=L+S+sotj{PGQd^+AqS65W@EWmPMG+co1a8!+%(c5}O zUVzSmT6rrUg@I!csK5R+WK1cwlMxI#iN$%X6@@F#-YgS!Te7bgqIRPhd^jP_2dI3J8* z7JP?XJR(UymK{SNJ0kgzP37}^2%A3|0;CsMQ6^_A79cS==|5aZkR%~NK;|%waEGS{ zoPdl(+Xd0XgcunHF_J}@-nRWrZ+Q?CY%Bx`s<2zQ3Oh9ivCT)}wrJ-t(avF7J4F@7 zHrhoShY4{4iDalE9}-3gl+DV&_k;+P50#a>b%D}*M5sX7tUQaGBt;68Slm+u%JFMR z3R6Hwnn;4)Z|Zu0#vI7y_$nEsDhd0I=6$@9-$tC3{JKK6XXTX zBI)v?BqlCYVDLLaUYv{XEH6^{1yF(Fz%nIu@EJ>zDp*9ZV_8sSU_BE>UkJ*KJy6JG zRc1T^Q-P{6m@mX-O=jFYPRNW6SOywo#%I?>l^H+d*8gUi@deZ_t<}#%BC4(KMSU19 zGp^h9|Fq1Aa26I5GgJ+sG|@R1Z443VjN#IeDij9Dq?-2pNioQ3!gVpLn5)6!k*> z+|FCQYf5Soa28j_hTc~A04if{MoWU~iEi&5ik<+`*1Yne`DNC8#d)h}vC&RU2c^v6 zx+1#^Qy=pFn)3SOKp?nPGXl$i4MjLBSxr)Os`B6t0q#`gYde)s2_+y0b&XWFeR_3z zq>7=~CE`zCvyik#G$j3EE0{77bV@^fhMNnJY7s6FON?V_5KlE8*+*$RMi$Z7ZAn>hcVfT zvQtp@?etYL$HCv}8Z#-RSQ+8`w;J-)>r6q+J6sfwsi+ygsm{qb$?c4X2dp!v_sc)T z$GFdz^-U)9IZif&w^5liNdrRPhTyhxpBw49f#|WT`PEQ6QIn~)mFv%=`m?D1Ow@lt z%j(~MlU9G|K1Iv%5IeKY;_)O-rndl(Q93U&uyR)^5{Q6H7QT6_V0=H}=d7n#0iA3Mu z{?;6QuY}TfsYYLhpl{mWo561jS;iXUcgxT){MO=7b7TBEHbWoDHb)Xn>uX5pkHCy2 z>#O7fu}X#(3SHe=K&w^<=HKq{$)bY@u(!t{Iky-4cNJT4+#H2evWGZkze;y}bv$Hl z=svyLXF{H$x}hjGlG01&3tl8UETXztR41*7^NXcAN9gNNotuOp|8yD>-@sP}{tK@M zYnPVQBhFXo_2`b}O;i>(R*UbWV|RPO;hbXDjRtTU_e=y$u{bDr1W>f9_6Hz~GpKOZ ziBVgYN%5jgWy3onc`1_ITgql770*eEp;9Qdc~PrM$>mb|q7-BoqlB3Fn-f#i33cJq zO}Wcx*TGWG$1%e9X@l+J7~_ayhh#Xn5fdC{q6>S{YR2wVwHo_tEKv9Q6&+txR ztwcH8%2t+>%jGoNP72qE$V9=$>LH9am0Qo$_tLrDn(P{cv4^e{l8dC@%(Ljis8r=H z@g-fk1z#X${suXPNX7bz(f)it!iiD%P^D;|EsqKZ@{R(wfU8hUYEL&EC% z4=zVMof=a9uJH0-9jrPOlF2_E=G};Gv21OX` z-UFyavcDr~D=(aTynzdt9kf;R132VKn*Qf!(t{(C6!%EuW>PI(*5cvlFM9}G&(h^)@M50#{zUtk7s(qX%Q`R!)bXJMFepf9gUV>-VV~T>=l8_ z*VhCXhFD(u216_EAx!#qk3gP2rVC=K<=Y@^pP2Tq&coBuM1jm-E_K2OF;-N zg{`J#>E~gzyoe%#xMWA8#irsyw3NLpaQVn##AR=CYjMd8r=@2IEpMQ}h`7}A$bv&3 zX!+%@f)+W1mXWQZp|c$TOUV?;zbdR3nCcTD(4a{H)9-Ow-sA#K_|N+l@`S? zw%+2t3U$;wTODtB-z2LvKQ0^bd*<}k;72r13#WM(iU`tt7k)*gdFww2vrxytb#IAl z4TiSm>CgF0kG~{vJ}}%!^P$sPOS3tg=Dr~`uR?(lX}(COB`Cx4BGCMidW~>C@S>pk zq7a$~HcfLAbJ9b{z$2M7SKf^>46;9-iX_>8Icj}JmHVWN9O*&$U?p}CU!a0q@B&TS zhQ0H@@&hm#x5c;QIh~%*P>g||8C0r9&;9>o6xi?Z2CfeDjK&wx^W5()&EY4i#o+OGDUfuVcbowX2swa+OxkxoWjN?CPMka#Wycg)Os*1n{J3d9+tu4Z5{e@xp?1kz}_;gT-jDU|Hjc5ma zrg2S#&t;rGh49G&If{3n5ubUeEiyjB2S-1DP0KSH>3I$>;y6Ll9wWE;30SNo5&Osx zy|S&`4uhw<1`V$6k)Awsbx+G}8;*@gD=PhPyvG861ugKgIK{F)5j)lt72o}mWQJcP z%NCAxQVoAyEYK-9zCnv(eJ(tXC>}yAP0+cUwh~~bLN-chkf75>bhea6OWB1MX~hRj zBffmB(+JZ;V6%IWu0GNX>g z1^wHF*Tx9_4!pLJGj0&CZR!VJ>!VgJ#$tEW7Ma)DEUT2REygDY1hLITgf8-#x~cfw zKOibTlYnVEjK!CP&Ix>8qXZc&t6skV_yjI7j>V&(Ffu+7$KqUdEY_1f2n*X}sbVac zWwWyIYs?HKA5drvw_h@2%miZvA@A%0m1)Cxl}V z8ZG;7Gh53O(kNujH1Y(dr>IR+MW!d1?e0vQS|%nB`aX?DXR0!yKlun_!-v-0pZO7z zF0hV9(c3)Cb-D^Lc7*qJa3{R&uh z@wxq!hR>ZKM_HX=#OEJ?EHXY3#}CaBQiYhzn6=GP1w~&{tcWW~oLSGD+0wBiYcX&SH^XI|M2JTYyNgHJz{4fl`pj|@jek-G>45=bFU#b|6 z&Cc9>46Ig`0|WUT(19tveD$7TxNS8&c&N~cdAs04(?D(V}^}yFGT$RxL@7qMjGVJHK ztm!SJ1=wHT8tnUkfaZZPa2#-_JOOeN+unmNt9pED&+JsE_j9a5lyT`i&eF64LMg~D zvie~X9qiV3h65rCcy3iLV~Ts75Zvhw@0`rxpBwdmH_eQ0cePhpKMNd@fU)E469dbPJby`#;OP3sZZbv!!g z8_(-$#f18z4N~bc`jXvWT0}w9eXdj`KMs{ZC>U%9x05GIcvbs3UaK*Y({W@KG`! zjg;&olbpWXG(?}D{sta`jUDrq3#{- z3>r^P6T;DkkmAR6IR3Mt51Tr6IIA~rWI3nI#XfF&BIkH(^@pb$Q6kY!`B zCuKhXK_+|My8(tK&(un#Mdv}7`OksU_z^X&7n(*HDf|iAl_XqY=~+#K9^2sSrtyHE z0B<&J&^Kq-N`IEdfIjB$`<@tx2KJm3jQP!I8+ayc%%}5MiZJHySggJM#F_uKF@MV` zVuO~9`IS8zkNK=;sJ-f#|E^EtF+ZYLlrjJCgAvF4a^TdGF&|C#vM-F!wCqpDexMHc z=?rv$izyTxu#DK6`{os(8zyw=W+3^E|Y>po?L+D7p?Wk7Z9YoIi z=S1R%l*Z)H$z9}|(8*bZu82eq>4Z($jax7WjQORd{P1dWV}7taZQzIfBw@nlfE)nH zSclI6^GhT0!?VDt9r)qZr^5N+$m!^SAxvR7KRm^Z9x@;WKkO~hx-S7_Md|?6w+NYX zAcoYufLET3i-D6V5^E)*8)?ocL=1AM@*A1orw?_-R|b+FUw{pdV6wKP z&}40eLp_&b)kkNIHJ1Rxsv}bFNRB33pznKdAm*cF)hek?bgViBsf&LLerp2BfX#cD z7QgxON>O59ft*t_3z-EsD9eB_1wcS#?`+v4RCQ83NM&bLB9Pqg0qQzaJ){o#=%>S4 z^U(m4xT`#H0#5w?^JvlfFA=Z9`#P)6T0%)mFiC~EkTvaWr~(4)GyG7Ec8>DdE=IJ1 zP0B|NV0XTUzVD{kQK-{%f$X^n%Zhb)X|@>GDW}>?r0_Aaf(mP}# zU1&*8vn0}3Tb$GEhC>+Wv?q`TsO!EK0|zz^%l}XXhWqjt_%5At@2k!gmJx_FSQBRpE27Kt{JFY!}U%2Zp3vbP086q zp2Bs)b^pTx*IoEzT<6mZjO&;5q<6?&bfG1<=2Jhk7}uFc47g?=(Q$on9^)FgNW*pP z_Xb?^N`km{VfrHC+MjRLDlUMzlv*=YIfE~?3D*~)HW04+9sm-m95?|&LU5f=x<&So z{kTrJF7^pr7vPg|t)>?k*N5my?~s4fg_htNLmbg!TnEDILYD&vDFFv6g&eqKF5~)2 zhKB2}M-8~fplKpHeSz{XUZ_OGbsOKRReoed7}xjtQk!rcfmaDv??ND<;#xu9jr@86 zm7hK2d0Z!42RtNj?T1gs^+9@palMo&8RDi3Ey49w>W3ENS_m&Q9aqm`9oL=rGp?g9 z)NuW4y#d#^ii5aLIGg%CBChxGty<;Jj0ode$d}rL>-T2~Tys#lhU=(sTsPw(;Ci`= z>#lNv>lS=6uIK2uenL-rhjeWzu6fiCEyi`^R|Z`Fa7f2>{(X$=iF6HD*LMb73l;=% zO@F7`B4|75oU)O>qXr-sKjU1u3(w@Mqf8)srdqeS^)x9B2 zMWW?$aL#Ql?Vkb67SjHQXtaMq=42!7@hc+JKD<-A(tcjcX?G_@!972m_Tf=*_oDT@ zCl>{GS{K72G+S54VSZ%m+*Jz+9r7so@^j*3GO!&=1h4bAO9@nT;cP9&LA3R?R@@|3 zq^YSdQu**m)#F{wdQT8+s)dfDgeW%r}9?++>a&bE|Y zqL;nlfQCt}31y!XQnool*@L#%{ip=p;5Om+9{4Y4ZgKqkTf=W{eSB_AwUv&-AU41r zW%XdNAv?h7OJbv6(A=~XN?BsPo-CbCtjuVyhFuztIAPM>j+B(hj|Vr0K(gar%4Gei z)4SD`@eu?dwypO1Z!Uh1w{l{NXmiAdU6GY2fHy>F2c*E^-OQtfY}qg`B2uGr>pn6C z@_q>A4+?y!*0KrtB&Tr|NCo7`Kr&-Wc(#Z44s1BAMjI+F?c+Wvk2&C;^se662e>FA2{uN=eEGj00K``5f z!(h1%mR_+`E=ofKq>A8r*`mU5d;?%ezmr2(4hRS0(|cMC#Py#x0%Dd5L_s7VB5u2E zig4!KLTZImlU+{1f49iCr)9eD|qjf2so*{$*>X_qQg z+7Am)ks9R;tATbOM3SIgs!(Y!h)R1TjP$(gi?#As1hL6eE*9nMdDrtq;h~sBC|4%z z4Y{w6ri184xvx*t>Kq!Z)1s_`NI{#l+}C+H_Jxhx#>ubmh3(!_dR!#GE;y5P1%0zO zgun>&>Aa!iyI6Rd)F|DI_+Bh%0dM?PeEVtT|2^2&Jmo`3YqYg#e933S(FD&mfUU`+ zM5?$>c$IkC%y;4amfbH>zugE&9Ue!YU#;_^MTr${X|~^1r5gI})t=G&4f>)CUk0*W zH*#f2729_u1u2O2E#e~oiHu03%$T;!q;HX8egBp37~?hLTr)QAhCG0VAT4Rapa8js zb`ah$r)2DNPM!u=nD6DB6GeZJOA2YU7t(0oMIH(B!NBDs*;8d$K6|Q+#0VjmpkeqA zpBP~+g~m-0m5fZO&Sjcy7u&GZ%R`&@$LUN}Lcok*0YlC)sE&jTc_DF?>|Pr@vlu*} zU3XfJKI^L3(v~6b6~Y@kldQ7jDMIp+yo@~0srJrovQK_X>4*bkOS2#srP3!cu>{YI zYgSby_gs}PhQSw*Y_A9=N)}S(P(0s|oRb|1&Uj=>bjW2KXbSSzTJFe~(h;MpkfvF< z9wemERvtSe1Y>u>mKw;j_(mjvfC9{}Ds6O}{XJf+z+Ho;yH(_d4$_?PkU~xHXFkVD z=h=67*L1FO1OsG1IZ!gO^}JL$bSyTew9<`Cj_{69>a&^BF=XlHY?Zj&T_zhdd@kV{ zVCGYk&xzf6+6=@-pf)2ng(=CI2$=pm3)Kls|04*O#W{Q()9?B)K{d*;x=>6XjicXv z9Fs>Rc*kU?Ec~2PSNue&D~dWV{E4zwNUJxyucyy+0wDKavnT0A$cAf}z5|2KB=mnwH0HA3$uEM#w+px6AKBJy(-?6WV|vHw$K z>}L>zkiIZe3B~@5SiaE`ekNR{ifYm)Xx6~uH?+u zSWJ8l#a(6TCnCdows2OdQTiMXg>^MDoP(uxb`6mdjk%SCL?mu)Ur0M)vwEpgi5%%c zDY22-*j7lJIj2y1?B6{xhph{UPs?aQfMXir~RbHs@1CTe6GIkhPr zAqu9CWHyIsEpgI!?nBM(UyI`8yzL>6rm&H=$?m8o;v#F77#gy%mh;1A6x@28e z>W}SkDIIPHf>FstBzvLElA@P0Ku7htM$;78f)uxK=crL;AD~rEJ!!LiBjsd^QN>b1 zI{mnG!)B4xFjND;3Ie{SA=na$LZ@AZZ>kG&YjoNPrxCV26eG50Q&XG4R!S$=0pZAG z4AdsUF$(SNEQU_VJwX9oGpdVwBtYt&PMRb^D>ZbABZ4PEM(J_ov?;3 zwPoR`0gg=Io@>#UQt4iNDcKSz2?X4iJFwidaDMTQ4kWNt6O1FfI1V<&Nh`A8w^fYC z{e1{NwgF^%8~mNfv+8{q8@vswS2_xlN*CfuI_5x2=xe;r{xG|=<`}^hI6$!8-wA(V z+tE$|bo1qzIv>M^C^gOB&yPsNIR5DL-jX&OZees0_J?A8SXac>p|yc`$N-^JQF2TF z^cZh-Newdd9hYqN1c2?BcH0sEA&y`6>U^)+Titn$8osk07Afye<*}~RiH^^MotUm% zM{rUnrbjVOIJ_Ug%4DGf35E_d*wKQ$rX305JrgYCD6Z#JF+Iu$l-{t{F2#aBGsfN7 z%3F(8Z;k&7wud23px1dOz=Z=gqE+n-V_oDJTRoS=Wx1YsdDW>W3wj^F-qb0%Is8c&`$s;t`x|WwG^rjZ5O2xR}K(R9UTu0LRtmXUqv!%%a~M_;xa*)Stw7~lRk z%w1<-BW|g(X@?QtonkknNxqG2MQjq=sJ52K4%ie5?yN6b1+Fk6 z{tmFNrhOyDz1;}w0aA3&)W&tcD5o;`Gs)p zz0HVgKWb((;vh1+wq@@6hV}}gfyy?-eqtZCMtNXEsEplspyiKE zewDH<{vOzw80j06-L_cYKyVc)?1T&x-GlX)6@@*e0m`@vzfu)1fMoxl$FIuzH9 zpZs5Ki}BOGlJb{YBmB$7eo0%zL{m0{DA?cIUB^C(EoM2f2u)}=eGrQM$bBv6>EGEF z^B8R{DUOXsT(6^+wvDZu!Zotppcie;36_pxiy0%fYHO6lb)m4H*z>>A7GwE=AU$4V zgfxa)*)~X<)Dt3OTHUQVOk>10ZH@Bwnovw%+}(0k{T*#FUmqh(Us!L%^mS_G?}q7g z+B|5IPhY2gr{O=TDiqUEyV?iScDBVleUvaAS#88LUu==IMWi;#r%`M%y=fb+Nj+iQ z_bRBJDnmj2>EqUc`g_@8hSn3R4eN}k#!)ld$f<3z#e6`!0!`u?pw1^|{pb6kxW4dF zJK@^SwwNQ>OIoTdTWiGiHL>~8CT`W%2~m7wqS<2Zpe;FpY!q9}Ux@(-+5G>6E^Fl- zEl2kEx5boyL-<u%@)&*c3q;eE!$#bu~S>4{QPbxxc+Ue0{3^b z#SEftIHj)A2#6sNXU}Pdq&D5 ztBtrmD>g&ggliLPu_$xb&9vnf4OF(pJS(lJ8+@^1 z%EDi%;uI40opApls&`isd3RN0`FjzF!mUfHXz+I?zpj^I%)^gDxPDO(3Mxq|U4n0# z*HlxkU1!7PDtB4@OR5kASu0}eu!YlvqgS9+j$W_P-j!1DUa)V|mD|OJOmmK2vWPuvaP?B< zP`HiFK4`lQ9iDH?#?kho-f%Z^S~Wx43q#o0WyT4>Fhg5|h3+Jr>GWKeWG~&|Hf!!* z@wG}92XkMD#f|3{5uL>7`!oSZA89#69%cplY0X+LoIIb-4H8fg>UA|sB>*YEamqax zT!Z77l%}>;Slt%OGA!q{`{26px`BMq-)*#B?RU0l)qceU!N$rV zV074(?h&dKJlE2G-*J`J=zD{;hxPjut@bGW?&Wn{)(KBDK#|9vB|=9;=DU!-AFEdzAGO+Z)oR20 z{zIT1&Tg&i`(0OReZOd^-srHtf05}T>we@hKfG0a|0#s)Z|eKH0nPO-@XceXhVYT5 zO$COD&r~$ zst^&Nt@opyo2NJx+PQh6`R402n#b{^U`3c7(?eXH)j+d)8hQ*)L#y(@XWDs1RQmlx zL+Q_GOn(hmNA!n^^|$aUpKP81VSd6W)=!0sbws?_qWtug5bHRA53xQ{73*~Vp2Rx& zwX#G9)s4^K)1H3>SsCn4`&bQ|9G;69o0Tj;jo4^z!hhE$tWdE@{kiMCP&~fiLPGkp zMtl3W_2(mFG@2I+La9F!8qpl7KfmRtBJyL;F#7v8rawuiKeRtT!ICgqe};%_?#~P1 z86k!&`6iRNZe5=u=MkBd)MWA^uEMb9<0`xjp8*j>V+5*1Y8G~jZgFgdsIoW6ODC_@ z`sF)S<3Yd76<-bGOyi|zc%qEh7r9}5v5N}{;iZSPw|^Ti*>#$8f;88#7&r2NBwpIf zFGQrj1PpT_6;R6Y1X(Vc=`Z;0N#3h- zlVYV6BXICWlq!|>B`Of#Z*eiHITTMw$y*o*lvWl!QkO`y{N}{+RA+f&R&GaW(I#$s ztdb!j)0isy7Z-@Tl=;N*Pj4bd$6{htNYCGI6zxk+w|?Cng!Niv;Y ztHq(^Y0URhmd@>qw$D#;c)>U^ei6JR6QP%jOl7$p{RwhO-x%r)OV*5zMWgGk1{b$a z?-JeqH;7Iv=q4`SO^_Mr{}tE4QV9So7U>o_uY$u{jl-ujsKk;MK_i1fuvOU_JSiEL zN_97DQP8E6&v_IgmSYU2R*)sfHwN7cQVu;y0*7x*TFEA!@Sy#X<+qwRV3w#rd7g_2 zlc>B8(1MtpvnVF#bc)H@;m;KF2pKm@)0Po7h+kxn5|eZM3Q!v^!I33|`N@45EGaCiyQL*b;qtsxZ(J9Vg6F(`Bze@u2Uo1GwyO9Vb zQCf-wHp4Ik$%Mng^W^#No8&;XRWc`}g~@lR-Xm1u&I&1AvRxLzwyFOQblpUYDpBS9rLpu?dNz4xs%Z!U|tziDi zU|yO6*=rh>hycEW5Z%jO@ATd^6?p+1%*b}{QJcrL&PAc4=51AWfGk8Qw5~#z!tyCC zT8U3v)|@0a#tX~6%Si6Iva=`%oR!30yw$tQAosfPNzP1Bf-e2yot_+9*@z^_stk9B zYWvm<|Je2|_F}gg#a;|-(U5J*W%p^lXe~dX{DNN);P+d3U~|P+8|8Qt?FpCTq9rq^ zC7JGCsLon`T&bC@)uuecRTVvJ^Sakjth^4@iJ6$b)SjKeccn!sz_*==kscyHsP<}G z_JX2zDXKiTV_Bl?vwtV&G|Y&1#3R5PBlhGh9kEFO53E)Sd4uc{?sa&2E09C}B#P>} z9w~)tkm6{ob!8`NpJ!gBtt?Bq0d6`1Q*VRP8zor1TS_Ze65HZHEEOSV+iQx>uw~Dy zbepX?r{;Ffo>wrpgVi%H219K_oVNweos9Odr%L=%qhV?mFMt)u>~N@;_p?vqt$48@Ci8z+T$iIjC?y5a`x7HWbZsA z-T2R9YHKNsH8zGa_D??SnQD!erC;-)qYnk>tkW zoQHF^7|uz?;k->7&J{6i!VlzSqJUMj(@>aGQ`7;AwN8GFgU+PR7%Iy}96zd!s+!7l z)GBApBvoqcB$9a}(dQ~9=&(SY4bQYMF=%jMc5WwW5l96LwaQa&)bk1bC|QrBS1M+& zqvC4&f|!z9$06I`he%j8PO9h$j29^1{yUcPUM;W>{3c5NtV=_RT86T1<=<+hAwkxg z#^Rn=i-WQ;Nz7sP^+G>(?Q(j@4u@)ND??;g){Vg|vq(EYTGLzTz`1J*{USbCk1}_G z559AFL$qUe1%&yVAp{LT+v@6n#egX3k``B&il2i57Lwq|+Ra@)m^D{}1dj^}aP+t; z@WQ)W_T*4^gUjZ0a=%E}<`f0Hb_Q^Ufc3UtMMwjRzyR-8=hXN|w|fJmMOY?+FKd;V zGe~8gH%jBt*U%`a$|gcj)0L6QaXlto8sv}-e9m9bMAn2pCwoRG!QSvorfP&pK1Kna zS*`nFxlyfKvs72>2EEaGUHMs2fij1S`OUiW-bY%jD_=~_h@>kgF-e-P{P~i|y7I8s zf2*#%Y=DX_>B`T)W(4&vs#Ku;8Kd1oU0Dcp8rBf#q?l2F&QvWYDPsQb!8oj7xnBJt zcUFz^BTg7XsIP_usZj2Gi}dDO7g)=?q#+01JtXmxQq5sWsg7z*hGjCP1s8fTwtLxC3&I1EfaC5#YWVDjK>>`k2%D@a#))-nbK;bH zbukaA5U3*;;r69Rs+iP?VX`kkMn%^s{2vF9hRY?Di1;Fa8YdE|1F8k+mQhM~2>rsG ziKE=9MDBunX!uVYj?SUJKv$6-UV!d~a?lNWIJe}_XcUA2u0Rwd(z4kOrQ-uq7HI7Q znXn8Wk@N)$J8oQo`R+`s)0Sag)2g34sLFVv)9YR^XrI$}bDG_I67AT+dAoiB$!l%C z+*rtdaMjofr^mgk+Fp@a=$W|IGjTOd5UI*l%Si{KomScBKCFC)Hj&DHCRibB63ct; zepTM5=u>>0cZXqyk09@<>t#Ux5D&~}=F3O!?2d&aJ}`G%54!U_y1$`GWy{TidY z+TF3*emJ9!Ht_*%`}aC^_iFsdgysq0YEGj30efPx*e*~r*(`67oS9S|DByw^f?MNLtwz36Dm@cPua`y%e z*7E&IN06WrFcbuE-9?Xr1$NoI^R|@k!(yQW77NwtGv{c}NQ>;~)Bg^AIa4}Gi$6p2 z+pp0u>UV-m%BGU4jyYA*l7;PBcsa_xa9u)XSqCWj9ps#PT2(+AGQh9ip#yyIPF)H? zOhY0Ikik1xAleU??!(%m8XUAAL+!Aw4EbbTwPJ~(ocfP8uDrXIKcSpK+e<6K_TG(R zX>k}!i~U8XXOFFQOV*tIb30{EoHf^E^_&x2SR~TI;`VZ}uwVTj)DTO51&|Gu@wJGTWD&||mj-Tiy5EpkwOtJW5eLI-HawMDL4-2aPf zi~UoAUE32l|6jSb=!HfY*A^qEw)fhiK22L&JbAyiws`t^qZ(XTs;j}fma(V^HyJl+ zhhHKpQ2KB&zjn&|uTd294ys>bD%Ea1hZy*o0qEPS12+yR1KcnWuxRU}+7Nw=|ZV^0lusj-S z#`Zex2vCZw`CDq%u0J94UIX=uG%QWL-xIj4FSJ@#P_ zJa!R1W*_to4roiI6zbpMyE0S8J|O>t@v>niJ^L4WHin*+v+qlp0rTN5<=n+sgPl#e z9+BY}>6D$G+su?wITlM2Zx!_ablgKmy^?vz{)@PXcfqtbM#S`@1n6Gjs9-=&;xK8;h7n~{bD$c?u=T)qx7hoGIP)g}Z7%Ey-8e zfK_12^D17l1k9=B1Kv|4CJp2Py&#FcCO==1z+|K=Kh87cRWySbEf47HP&<(RBvRhm z4ScIs`GyfeYo;nI_|jTR6x7;$z@*<0uFrM_5@6A3KxGu6V#M_vDj)eOah-7OhveRY zN{^`zK1IGtdVz7hh$$J;l`gad*JacXEzVc@=)(qF7tYXe-Q0n39lk=t_0Mw+xK?%v z;yUKlsJPzCw`!Fq7!k(xCce}rT>Y;ITt}dC4c7|#Zp5`34^e(qTqj(&oFQ;sk59(+ zL3)94{fM6Q4sp|kmf$K=KeQOv<30nf{io}=&NMNu-~CI&)jr37>$J{6Tz|pJCJMiv z$)wjRLjWev=Tnsqd?^yHQTOe&T#Rdbfh11t*b({fgnH2VNu%Dd4Re;NH(c`+-3Z^j zZ?=v%^*a8Fd~%|WSDrA|;l>WGTE`)-yw!C)tJh)G>&OkMqkFK92O`(OIa7821K-qD zFn(2H9)L}Cmugc@!mjjNif#yVc~2rRt`qxUbacTpb_os$JYR0V2c}TC+PG(tNfpa= zWHQ>IxP(VZjZ*LvO||grVB6~!rT!TxOSGnkWxt^TKv;{f$|&lKzZVzv#@|f$Y1Z;# zu~*J6O1LuLowyQSU)bUrROvVI&Y^QM{Kf2e+lBuyv6lQ~mZjXP;;aQlZoZ*kh?##2 zwwd>a(=EXyALE|oLjs`inbu~QDLarn-@%;9rh zhB&?(o!+&9KHp?wKj4d7hQlSz5W5c0$*>TN5BXNA4|Lz?MRl@zRy z)x?K`_W1|%7NZLpweqpNQm?z>e{^MhrS4NLfu&h5*8esaMpjU&05z@VeSR~#2Ywr{ zMxrS7h1KY)=Q&<9#;#hjqhv}tJjB@2y_)F(U3ea)?4(EMfCwZ$op-`K9fyWo`;~`h z(Mq8j-`SMX=RRRdp@w195GzG6yKb`RJG0l*-t3)eC&70$g71Va-)p+^kj8T-95Chs z8w+f%PMD1|DWjkVHbswX32mujqt%TKIrm~R(gNT&iTAd#M-B`wSDvKR-2w34^DrU3 zsT4iUyaxu|q|7~z<`CT++JeU7|JcI@zLQGHl*%^^yf+KHwwTz*gX5@Sm*n-#X&IZ05h68vjig3|-7QSZBbOe~l9i2wRxWfK;CQUley6kZ@W? z_hPUkc0F#?*zq%B$IsXS3Q@!@UW?9>QYmdrV|&9MBlxh7)Ss|LVzAp_72G9+)_LPF zas##QZ~UXcQ8dPhiJZhl^sv}sS)wdgnO2zI<9~2v`~Z&93rqwE#lqc|HIywz(-Lf|4%M5 zoE?-vM3kW88jVLZhzUW-zzp=rj6?xJ1&ND96j%^u1eK8JB$7!pg4g1W$FAbBi|+a( zctN<5AZXBa1s7Mqv*U=MtXwY4|6A{M_w<}eCgIrqub+=(rlUUt*f2HPaN6Fs{3 zH7mQ%jpCh%D7$I1f@Jr(+0DpqWFHd1@0w(`CZgjQ_B1l_!7ikz&((L;o=cNj@!ZnD zbBm$MVejK-n|anrihZSk@HPTwb9@B=z(K1u>I`%dak1G;KvG*eo5SFm(3A)eo^BI@>3iQ6Lbzb~?+1 zXvlF1Hc3P+Q0xE!*$R(XOjC5(zJ)AYN3}-!RZ68rJ51z4Q!Tw5kHDB&C{Dy~DkAe) ztj1CU74a91TkOM9>nT>QIY6mY!JbAS?CDgjaf`Ip-GC6~yF`Ov*+Ror32+|l<2_^5 z2lN#g$%#n((-p2^`HqyheU?c=<*&&3s~&=Cp1-}}`%?K!hn(GiV7v?BGm2Qq!~U!= z`I>pe`M!9npeA)_$cjPlju4P5!oaI-un#Y z2E!E;PBkZ`#M8@nD#Fn`{61f*@&Gs=p?U+xD5QGB7cA(Z6~m;)2e(lN?pz%hqbNG? z5|<$4Rl#;auLD;tYV!!SQU{9S(}8<|p`9Qd=(#GuUtbIz7{X&)8e|6|OEn#ky=~_3 zvC)BE6i-~PC_>U&IHKkxw^0K=f2nnYq~v95tNgzk7@uhJ&!q?QkGRJm`Cr)P(P^dp zi*d^Ti>+gAJK2`rIBB5T|0uj2v5ZmHAYaGgO9S)kkJZPUx3K#0%&+JC@r3Yp)mzQ- zb`Kx*2Kg`x@C@+wAz zr$2u}c-sNzotok8J;^Xc0 zEIOhUn~?TdgZ=!MW_f!yk9ld3FG4J(UT*8|JKnrai;uTQo;o2m;rDMe%iDTB>J9S8 z2$9tD_V#;^H*byc@%FhVTgzMc1B^-~m_jXa8(|1jki8AT61jdHWSv`BHILBhpO|kZ zwlC=-oECuX%^_dX-?2Ahncet7{eY=r2X?}bnj?<@&UoRhIptZyAuWqkYbI#Fqq{MI z$nedf3ZDp-$TK)uvCm@32#@_n6#MK;zaVWJJX{q1&|RdcZcl=xD$y*gsP%Qjzgx6S zaT(NTqVGJosb=CVK!7g!H5^yLTYXgo6BJ8$CxudE`EWc~|6Qs_IZo8JoC)XBLA8OT zO^D+-z4KjZ^sDN6ItbSjfE@+P; zA|TK=KV2&n{pyjdJkdYYsInIAT2K-dQpSe^3EC>ZcJ_fU&)oTwFlXLMU{l~{XK~Us z)Z^YNKfC*{MIltq$(usYkaJ&N;1s#S)+~kYv2&2=4c7DHbNSMM$bj`^mkociZRtyuMZsVce3xoyfGl}QjJQr=yR1AOR_GZBYp z;WCFdBmow$3(A)JfGM%Ei78W?7e#4XGmMH!e)@=~~yRCBb2jYmM+ zql`zhR_GqY`dEgn=0it|WJ3qWU~H zL*xVB8m2b)vZx20*wAUJP%}gV#Db z{jP`pVA@a+h$zGKp?nbiRrTjn3=_>74}5?RR$;|%y_`oGv+yJ(t`zsC1m8$=!3Njh zP-SVEk?O^4UY<|j8d=uSy(y78<%8WCW;9o&+WhsD;Q>>GP7iN~_pS-c1#`Dr_rzK)Ul1Gj=#wtJPcE3zg^WTBDdH17H+Z`+V9n^&aQLgZUO7t<6ghvch&7VmU*=2^{U6S~ zK@~=ZvqYW}kh6_X3AVz(d@Za<^l^F2)7ihdSP(!%Xe=MoDFBtw&QjkLOYEx&qhiT+ zd0Fp%>U4oQElrz#Bg%dB^^r|N;)%c#iCH9N9wwk8;zY4Y79bu9-v+3pb0#5z0q3MX z0qPJUcJbD&s(8%_B#GSDX?{DCA;~jOSu4Z9LMZx_IUnhu@)>eL8#)m%(DQ)gb zMG9!dGxeIE4o--I9Sn!O<9y&Ic@m^eZI2Co@5r231n^9h?tyiUxK?2C45%+oQM2Pn zV~a_TRI8_)g^svBhSqs`c z((FnsPg{u{gjh^L*uGh?{EMjYb>$L*kC^zUs4apw`?-{S8a5luR$!}$izvi-jOP)q zdLdEg=}w9;2`#!Plu!Sb-z1Q0UCJlEG;w2o8DO1I?-s6BxE^*93-Sz!<7UP+WB-paSNP~R$XzE8la<(nqM2>x@Ug*Vk4bF85RSz~ z)qt>JQrBN?)PbIGsKOe=(t*w#f^Cq0cVn;){P}ln9-&rZvvI~Ie8T~wmNQqkCkf`S z$TswB-$_Y|wcSUAX^6)^t$-_CxEC$~@Bdc1iXXKcbp9C$k3a{D5@>&@`?Nj~Zg z^3iL9d3$Ba@#bxuu?ep76C$^CPOcf=4(3p9gZ%t8!My$QmgCLaIAaq|D?1^){Sof% zW_bG*AN2-#SWz%+E&0;0vm?AI$ls{B-QQ)! z)e5WLHI>8V*RuG8_u(1`uUUM;Pk4&OC&=vaz%&~LW#Y&nQi>w61B*`(YHH2zu{5&Q z0@)={m;$G%$&D7HB7}XygB4hjIg2OJcM9PYIFN^Eh0Ullhhv+yCDeGLFSoA3RE}nY zBNpCWR znBUxALf&hO^d0`p6g`9|G#kOt9iDtXaIQ|Pi04huf*JaSCmXdWX<&2Im=d98ZwUo4 ztmN5$>s(#U?-JG$EtVmyy)@>52B1IyhCzd?SS&+#pd*CS2sM$z83?Y?!Wr`K5|yJ# z-_PMX9LW(c)By5`;E4w|L1({W$25$~Aium-sL=&BY?ppz(5#1`*%pL0JPt=V_|8Hb zeh)@5XqI9eRPd58NzVFaGBPTW4Ogg{c-6R`OyBt>=7M^ssl@E*U~|_cMvidU0`H6O z9bbCx_NXxq`8OF^K#nfN;WRD8A^&>tTZzY+d*@L<-gah;2nV|t2Ze3Yjrf1IXp_DQ zi*leSDdy|XRAL-b5#ylt&6!lD}j;#`bYAk;xoWi{G?D@hD^@dvm8 zRqOVBB=ran&53B#n1aFX>YQKN!F8^M;Y8;E4^VY(yQ*^^N7T7M-GZ1|uX#8`5$Rc!c!*{V zUDSApK@<-Wth@S%hzz)onsD;MPsWkOL{M-Ni-|yR5}vz~$3*1Mhw^SytqKc?=&glB zFk`7iM7%Q#gVz?#Rcxnd3}0IyH!_bmf)>UV7;!rsk*WsLB7lM;<_85w6zk_KK8=LM z%Y_9;(E32A(PkUf%%~=GE|w%vA`I^a*F*7yNZd+LSzI9!JWeqpMm{CJ7>RSC{hWo) zV;Cdh;?PxK(HF9&frN&IWVUmR#Ffwt-q8~pBQbSG+hZgmtVtCj*KSd_;Z@Q$hsVpEbYC`_O=3%9 z+^|CBLcv2&qpaBmD7ad*HCxz{Sj7TEqXU?|$rNg+yfF*HYH92Q@B&3kv}liz*oiDy z7Zy9wx1?a!Q5QEVShw-m33n5WiczRZU`u16=2JCN;BCG%-qN-JigXT(#nP{FG#>;7 zP$<6PFdngWtjZuIh=N5R#fYKU2^Xcppb~XRLl_hYE&)@<`NLVmg$|)L3G9=k@u;L= zFK17@(G>eRE)|PCm?%QnqsmY)-dT8*ZT(1qT84K%1IIWx6AMp%hmT%^eCL(H{)U^1 z_+I6t&-d^s2dAPkI5IBLF8Lm=N(&EIF!}# zi4s#$!=(rEpBEzkSG9R`S}Ff=##B7W0kamfww-Ltm4IlP;q8qaCT);=ULMTbqtLYD zi$ld3Q*jvw%#Js2zXXiX3~&8>)EnftFAL`F^TUrfZ{v)q`0VD^nS^$d+xzcsmbWkP zQE!lM>L1M8VZ)9$Z{v)qDCL0J@s`{30iiS_x5GJ1+8`(92J`lN%kk!IoG}&sCY}(v z{Sf#`GraZjQE!l6yd;>n4-7rtyp1!aV#9My(yuEbD@#bxu zF%`FsKOwyB>1vj@xi>M&8sr}@3g+#nSn(a-bwZpm6`?fWRtepyjCLu0#V}J<{644 zaT`SGZ>Y7A*$Yuac4?PRyZyytQyO5Sd@#;lNMx8p;D(XOSER-4~M_-1L z@3qDu^nWIzd*)_REc+NOx0v(;BBN&_?#3bQqB3eylfTHAyWJw~Wi`%_D9Kv6ALT$S zf@QD#BZxy?VvMb895V1#)`KH?6OFDbm5TQl#|TeHwn@-Or#UD+T}svPj=m(()3%+g1dAJ12nP{}xo#{*(u_Xx_X2MQHG zqC@T2;srv*Q|;U0#jfjo_V#9W?e`k9J-#4j-_5=XmDhvQdBcrl?b*e zM%IS*&rNJ-pH*JBlzkN{2hh;Aiy4Lf>;gmEr!lC`Bkk7M#ja64Tl>(Z@YVaB#@Fk> zV%R5@ufy3_q0-uJe1*dN+-|JE^z=tGMuO;qp0K&;2%nq6B>xs<$foJ3X$_uGwTqr= zM!POx&oxQ(9%Y_FJ;5THt2ABF>js;j>{JSj%~+&An5ulkh)8FOyIZ1fX#DN4sY<72 zFMZ2~E6Lq00d+w|$(l!Jz-HnMSPssB;T+gKBJRfgt(^s%pq&fCB+AZ$<)HqVXh0Ej zo3sY&7STJ}Bw|t#J)_xdZI^1v((Fscgq=B+R*a)fQ#vL}iOUjQ=SfIiZC*JIznKdf z=B1dk8+={wJw(MP=cgcPs0S%tk+9k$_F&Yl11)T+g=2{8bF>U}biSbbz}}o(w9!e|GY%5JfSa!FmTs8e%C;{YhB>3%Y*OQ#P7O*?%EW%t2@7|UU}nc ze%Hlx*J}J<&$=M{&SY<-VxUAm+~traK(#+G`yqgAd5OJ-cUS_p z8ElX7Td`ayamZ;uzQ_u^ATp?JG0#c+kfD& zCtGb7e@W(1sI(K2s3u&`SV4<5q4E@5<0K0eNAVuE;lh?zsQ8Gzl~TbsQLR9{Ji+xF z(K|F`8v_EgCD@`n*iNQOXW3`4-3$yH?PMAuw^sVcu}9f?@EKwc26+dcA+VC#fg3Jv zD#XzUeLKVeHrsd|bc2=tp=MaiR%)R!&?ajui*IJoBE9nBChRr+xgUqyiom#Po0bww zepx5i>9keZu|r!GWb9&mtXPYNI5~{H3AjvbU8ihex-(h87DiNLAwSj@1~x0Oh0&WW zE)HxX!^jaTUdJ%7olLtmEYw&xmE;fa5@EEc`pTSyAhn%fyg5 zG1;EMtZ<5HVlk)p`ty|Fum3` z|Nf9+(ulw-91}5EBViH9mBIQpLTS4|RV{5N%PTCfjV$^GkAae!!Z9)03xt~CoseQ; zBQp&1!uCj{oq>Ii;%Cq+=#SIc9_fBkZJ3e3y%F|EYuUZnBi+dC4%;IY=`~WX*+D9) zTl+D+RJ0K*Kepf`g(S;NN~w&;s(&8!! z@j5n<(j>}9X=dP6FK=g~^fW22A}7CRmxM|eGqIS$+h@Af>unFU>8td5>(Cvt4_R@> z&|9^8L24^3e1lJ?$*vrlMc_zen>FYoyn@gbn$rUQ!~)cvXB##us-SJy@T9dYpuX3pk zR`24e@9hO^XLl97UC*vgG{`sL`0ZxV-Qc%n*CIRp9Qxt;D*TYZeyH3pR8YbOn|BZj zo%oO5Nx$DmzwfG`2lMaBn2jt#TjjcHMBtn*R{C9iksCY>6{|DSW~IQ0!woI1sRPR^ z{Xzw80+Hc@|J0d&+N6C=qOa@h($F-~+v7aZ+a=4My+KU+(4U?-4`t42!Id6zv!x0( zjrxXMq$kb7s!xz)U;g1=3H*H`c3?yfe@R_(9zqC4Y7{={j8dcd3?}* zSOi9ZWC~7`hN)zV(%osO5yCSo*a{9Ze*TTv(uvq&%RXo?XtW7K4v7WtyZT<`?U8B& zi{;CH#WqV?&$IPr;R~-{gz}W^zcR7lAY*X91!$?JUN9K+lmG#dhy2311DVlt2aQ~i zMeG4noP>Zd&J<#Ym8l0lNHOtbIB(EXB!2<6WKJWYlD@}*Ua?>;6RqL*qh~MPr}V7y zW1-?jCSCp0L>(9QGA^7>_uftSiurdiG5b`0@-XlI{utfg?;ImaBOroD5?npC?(>mH z-%O(KjF79McaVsM1mKGgO7^|VeiS{kbpk^ynb|x`0$5O}4vNx{G@JAjlNsWNt)Q04 z%u?#(r#Qn2nW<$QD_A26L-vMfWLhO|n?biB4~I}u6{?YYEAabB_PeH${THYjd3sch zBta>KHlp~{^f1Lt8j)u6_PBubC}Usy>JhvrOowc0*%noY7Ewp*${9Z*>(DhYG!R~{ zLl%5tIm$HK^sO5WSgC0T!X9ET#eyxf^7u9Aa7PhC0ZVe{IV74ou@tlSI-~}8YFI&$O)~OE z`hD|l?t|P;|KmRJBEJm0u;&lWnBAS(Rv>i1L3_cv`%ZUM*1CGy`@!M{x692n5*n$O zITOGDQTkNw{8J1bC_RVa0R4f&Sh40Olz?N*V0Hq`Kr$&9w!q{;%oxJ2F=ntCqKlar zlFf8AVA#cMnKMNE=m@$=MlS!b-`~XYd}#*8UQo*n%-i>ZKd0dUX*hp!_miC={`6w6 zuzM!y_zrWBNoF?Oxb`26!Y-sS-|mTKTTNJ>t6U16g2=#XQ_to?elnJ}xuTSE=7bIg zF&A)Yh3Q#b!Qus8orR~Yj3tSWMyh}NE^ z-*Pw0=THalT~Fjq)JoI^(|r5**fjSHr+IjEnxAsEj^?y*n(z3rS(;be7@OvjKZo)8 z;bmlzBJx?D-8!1bvkJ*Ue130tvot3*L-UG1h0(m@rKmLbZzavk;O2r8RxcYJW6;dx zM@qT+293{bMa$)x*vOMue#XVx{~D2QHx)6#<+ANhe{fb9Td_>h#CIo&s9tD;x9xFDTd@M2V*`}~eDqBWfpYII6h0xe`(R~Ie~ z%Y6}~VRH!GKaYz|_cO1C$wT8Gqtb0^HQn99=|1p%3+Nu$4Bg+q5=M6~&>cnhrp;_E zzpo3335_?2$>5gIec#yF{2mZa_ryO$rTfhpt)=@JR{J|h0~a*K;y3G^l=A*{8r`vU z@Sc|=>0sAzGBq6}ao$NBBqh|u`2Z`U&B#=>aHkwe3q$4ktN@)vd6*u)zbs5H{Le>~ z=h44wEq@P(%f-2z@Yg|(&U}RE;q7BI{zjy`XE@!%qtpG=^w!cnB%JOU-^G#(>JHW# ze;FN{?%%!?#_vVXMV05Tr?r;u>Tr3k`L+dgA08E(?mxd6M)%fdqtbmrtLgqYAmB9K zWIz1h#G;$sC8Z1r&>c$;_x&-F9#&2bV{bEhm>nQf)x&X|Orl3G&YOXIGo)K2CmMMv z5Mhew(y2hQXKOo{CU|*7DhQqQ0m0t#@UvL>bAx>5Ai^5A(fnN~?@lI8pZ*tki#7qA z%&}BA@cjff-Lh!`ts%yf$6*?I9Gs|PsE-CH45os%QX+X|a4S`5)d={ImcWJbhJFeK zPZj7Wm>NbwEj%is^r396(q2z(>fgLMJ{}BJDd?@IpeZQ^4~jh+5fOP1*I2xRQCloZ z$Yrmmiw8A*6&`#$ zP$vx&!YR=4VEZzSh)9DG7abR?bga;G;_nGzoY2vcTNjg#R*uD=&Qv(jUr)iPa0=A1 zm{za7zJ10bTczMH`my-P5Q_&dG-yOb8jHd)$^jE(p;p4J>#en9tT2^*=TN2u>p`Bx z6=5EB$u&hvx#IamgU7nJIwcPus;vJK>#aKlvfA#++YEKq21B(KiSJy-YU|7@>(9Q9 z_=kPm_f8t_LQP}1T@Z=UAg9eEr|txJtsM>UrtM4`kNV4t}-3RHp&qRNtbCT#^XQ_s(<7y+t8e5RlQPgVD z7fWeAj+CB(>%~-y-Uh4LUGxq~5IqT&szkG}qSgmobj211p=MA4;-tC`3255&NA@nf zQ%LKu&n%QBo9M-}QskO{CcvKn2dLv9O*&v@?(=6$XF$lV(6Yr*?%jQ6!V-pkj>+TH@^qVg9^48zE>FRgpF(}qj%O>M1c=TJ!rru-LGt1 z`hJdqAh(-iFOAFVcm?4>+lb%yt}n-*$XHN_Z=O3G!3;{SNe;)65gd*M;SR?v%>&5Y z=b;YAE=_Q*(P04QT2xV_4b0U)9_wuRo!iM1Te%yl*1dz!nt~t|(r-)IZ~6CRz!T2B zlK0{qKL?TEvc4yZSMoBACdD-i=O`R~ zG90t18ijc^St_e{PST|ts~oTAZihG2QMp^Fpu-Zr)PWtG$cVkJIyo1u{VaN|nu)PC z3@IO!X>gt%eDNw_RdOaIuZe0L*pj}$lW1cK@cmn$3ZS=9Uhpo9IvLw8;qKMLJLVP{|8r#2E2=zt*}7A zh;}P{j8_iV#uy=i4xkf!jB__yq>oC>`5m3TffFSlKRF*~n}6+4p(gp75*YRGwXya% zwpBSAbR+Gd!We;(Mqq`xnnv3!eP#wIM8o|X8tzVLIN9XviV??alE+iCKLn4jC9g|P z=$MNb^Bfv;N`Qzl=g+dH=AsGbY?Zx}!A};|;hNOm5l+KFdw%kGRS3+0geH>Zs2E_t4&N z+cC6jh_I^5*%Zf7&=BW{_X-*D_px>Wk*em;sZmD!W_V=I>U;1gYBTkz|4!Mca z^_f_=)ep&F3q8+}G;$hChvB8lsY;ZWplMpvNsRzXWXIQh)tN`ruWqU!j8{Kbc&EI9 z1<thZqv!v3c$~?JY1-Qdb|4<<7J9u}?!?AHt5G_+| z9dCi36T^8;iVC+>jWFW4X0|KUT$gOh_FdP$?Oy0Qh1$K)fe37rf7&Ex{ohf)$JE16Lh!e<145S4T)oVz*!Ja8 z^kkF#@?RlG-eCuQXRZXF4Y_P`*0TECu_WomcfFE>NoYy%6$7Wt&uwZBmVa z@oEqUoNvQ2TW%=Ek`F^vBYLd$b=H-JT))k|zAvm1#CMb25nQAZdkwu7C+%;0H|ZpU zuxe;WbCuN~ZK1Wgw`89q=D#uJrArJD>GD!Tj`I|LYOdgi**TeHj0}nuz6sGhg=;4@OU+Y5nvNp2_H54afSg+LMs#!y zU>i~ZxsDpS42gb3e?2j~;SCa5&iKWDVT`Xp$OIg5ym9lpBn@V-69*!IXJ0@)brgJ} zc4a;`sz-f2t(W?J2p-^%tRiW(cuNGlYX{oiVVA5290lvav-h!jt?)aqrKYa5RChF& zA1ZTRCT1UXVtcAJY#G=k52sEL!GtZd?y7O#X?fQt%wLPOv+upU2}@kv%ym{njy>r| zU$R}A{*t+Bf&sM6LLAK|Ikk}v(O3@<0h&m9Ce&9=0R8J{!T!E4fAt|v6VB~$45Ml1uTl;A29I{bjKWQ>B^u2ORx-D zBMe?V6j?keF#QQhOUpO!c*d<@?{=zD!sh<)Nxb1a78Qwdw zoe(OQfsDeuIblb_a{^J+(x+-nf2f(`N5U_+RLwEsNO-m@r7kJk=V+(qE_4ZXNf+9^ zQ(@N|>nULKj~4^%!JUE)1g!8<7rTlfQDiQ1vE;&O%azxy&>#)sg@y^37(!@hXw*q*1gp z3%a!rBTPFZ&(17ZKqlo%aHY_E*+-nT4l~Jb79|?0lZ`aFIFfwUqqIlcdYTbdvp3OC{CJrS20n3P zf}x2{AuE>T&*%*aE*(Il3J(?a85JpfDU|w zwUJe1v`iJw z7plpp>aHcL+Te#1dmssDX-~MUykuQQ^k^aaM;gopkG*qn z8{SFwk!ebAMNY;AUe%Ai%K0S3gNcbV)S4LB4MJsa&04n`J;K6Tb-dc4d--K9_pip% zYukl9OecITvB#XWL*d$42nJII$cK-lT|dD_Gqz!wK=Wq`*yy5d&{En4VQaC(;z58l z7KqFvzC+%>Cc!|MB;V^)t>b$g19*$Q4soMpd!04#WwD3qtq^|Am-TFpy4L|jXE)6y zL=W2>yL719ach;`bP0}b%>b^7O^*Yc9y_XGOI~)^2cL^Y>Hiji0&H`Z03(slH7I+b zfkVi}hP@C4EPEYG4i5>|~i^=AMd zuAWC%LC}G1j?;JB&z=U-y^gJ5^Bx8)ZY|HpI`4Agsn@Dqwr(xQrvHIVT-JcX*u}?7U40v zzbn6AUdmg@{#EpjO~CDTN~a+o*@zS3^ei^KTUnbvToIASVbeGn% zZY`&vng0It5yM&Ut2@)S9eU$hP_c3Th16&Jwc^13|EQ&DiJh*Hqv& zcJGY|*1TMsFmgM4+_eGk)7~&)IUQq?T&u70CSN2bfm&M6|_y^)sy-M=)NN0NI-dml;spFLd+O?E>yfn z9M9edfG*JaLL@NpN9sL$PZ1F|;%|`%V+gyRCU$SCo+e06P(SAGFU&|5y(u5#?1tUj zwVwV6Kt(A)jY$Le5qND6Y-Kfm02#Ao@7IxCO@4nws8S@EI$(ik9|QEDJ*&|WndH&> z_YfQBLgPF$aSk!9i-`?&A(K8$?l2}3ma-HpjdCwMyqIYtv*pX>^WTN_R*aqKalzEN zNSF3-TfbHAM8DG+5{I80TTi~njeQ~d4`WXX6Uo>I;UH--aF9i;c@+n-ZXzQ+p3X`J z;~=`Y8>GWga;h|tf~a$clrrUfZQkHMGE4wSg7`i6ks)pmNXSA~G!jvQV_^3i>O{wHVd_WSJ?V_|2k5z($B!@Dx%roWTSGVY{1K#gi#7EKVaC4sn_yZx& zq2R+j`L)j|QQ9x+y6zcxr`}Z)p!5X4g3fKT^Ywb@AmoU%vN6N?`klHAC~zJKH4{4# zaoI$*8~-m*XoqA+uXKrnk)eX^J!f`bDUr#B3vrvhM zBCr{GK(1C6oNdYj!d&)MBLSJPPXtAl6rd7;P&tR*17ALj&ZX#sLdetrA;VQd#P%SB zC%)n1J{PQpv{~Q^#tIDrN%Mf$u|V;n1jO--t;BQalu$t@(=eg7HyqCi8rM>S7JjM_ zr4(1SUEqxe4LG@87B6iuODtyV4|-HHQK46Mqd+Z^sR;(?c}Ht zTe`tZ4o8k-Iok;-pi%#;;EQ~C7zd4(WBo>Vyl%r8S@(v%@JT|J8s%4?!2bVZ+KGLF z;R9}zH#XpyWJxh?`DspQCS~YR9pHf!DJ+9X?#Cqs4XVb9o?hy9tO{nrGiCvNSc?#? zw?L>F+lj13j(j&X)FQ0Hsc>u6nPz^<(%bcuy9rx5dPSlEXHbDIjmjJ2c6Bh9Um3ub zej#k>rz(d#2wT$DL)PWxgEFv^e4C7=*Q&d2=#s(0spu-`!T;8J9Oop5;hMZ28A8RM z@CagMCrBk$I7BO+!B^r04m`6wONDmnnnGLtDZ!jb6u^m7RfnOqo?2#&#AJ)IOGSVi zvQgw|_|kH@FA5|eOo4`F%M#{4WJ6?xbg&_+000P#gTKf89!Hp50GPHjFXD1X!3L#2 zgfD&J>uISzO!yLiK*5)ONB9yZgfUy;-rVCT_&dXw{Dd!!qLv0*s!ubQ|5}DKnAu-2 zZ=%|+WniBS*TuMpc@qOyGv@U!%)2XL{u**8zFU}A>c)z?t^)w3{p9>|VP|zENF#h{ zqx{mFgfESO?+LS#HWl+-2Z-SS(|x$f%z`G_Jbda3%)|GtrztQn{{|u_!{*C_*lQU{ zn(M94vb~m+l6ST`UmkQwhiI!clTDaEW5NV7gh+b_c2?H8(2^=ln2*WZe?G$FyN3-R zo?};rwFfx&m|XgIwwKyM;fIyEm{Tz`<>lnomf#LM{vQqXO0#>Wmn&h8cGGC@%mm~W zqp2Z}6u>UKkotihyE%7z2@soN{69KyyZ+u{*{0lUraOzG-&rL0#ho^3Km7Q4HiFX| z%_+XixUSfJ`&lVI5|45-Nw07-Jx7A0|%*8`DD| zlkD}G?fFt=Obh4oF(qjq2k$jx7+X6#WOkfI8CR_1?IuzFYa{2OC{$3g&SN#Cg?y$J zX-LNfhzSg-LXJr#hXZ~OJ_KJtS6VVA_&RWg!t!syT3=?s-xge(XDVj!-yIzO)BOn7 z5CFjIIRLm5QA0wF&)1C#bwK9{0u*Xw*p^!InTVbVE2}0f)6cu_4#B*8`e2R$t^@|S z%r1RJi9BIp8s&i)Nh`w(Dsk8rD>tZRQSgE(08L?m{5RdCix^FcFT&#?nY$2T)Ytqr z1Oh1DSn}zz&qB?}PM{SA2TSUWb523{;zvTwXWk(l3$~RdXaB?3AJ-4fNyqiA?0O2W zr)K}dy{?n*w32+gsc4QM)QpBNhaSR@^S@H?LNdh`n%)@()5(%B+BBY}i(uVBKnAli zL&?fGbVyrBca)ivlZZ}Py`vH$bd0*hvYm1KurJ4Ez6JN-QLpM?OM(%HidvI?Cgfl= z26`C=x|R*}GK%HJia?1r(fu&Y&?A$jGN=17M=ryRgiv=DZc;leuTQ2>@iKaeG~vyJWih;U*V_pJ=bL3^dN`Y$0HAHLV2R zX73|9g!{Y@O&j*0FC#dqshrs~r8n7HZz`wbx?O@>tsQlO>7@mj7<7VtCY^xPH!*Ga zd;4g^A0ZtoaBeY;_qkXHLbhXZDz(-DE+7m+Pv;u&H4X|Z8nD-zw3l$)9Fmqtyqeeh zg9kKOA+Iyn4^kH5^W17oY8Bg^X#`1j#=HmonrV_yxL<%*xnC#@`ACGxy75O8q-a8W5R&c1Iz;I1R&-afdkY;K(2C_iH$_f|C2oqjRj+F7VaG+zCQwY!;4azX>fP8qe##h$f)g4@C z`)o1kr=&HQbg{Bv-RXg5a5EMH)F~D#nRlzvm{k~K_Ir%Ml^RSFcuGsEM20FLcQgtQSd-xctJPB+M!tjVK@)Iv8 z*fKjCdchx=n}a+Tk=z`c!~7iE;OEG~-Pnup1ENw&(J88*11<>1V{|mvV|^A*4lHj! zWB9R9Q@I!%BX322QF@OQ$}YY4EdBA6E*2#g3d_A6QswKoOrMMF(w|n+XD=PQ+$g`d zOYx3mouhe2im{Y@U7om}c}HaOj@)_{yd&D&4v4{G8UR|AGpS$OJ(F=Jb#g89pD>e# zo%`f+WETePz&*U>W)xU}?}R}P%y&{K=dVHB_uy=&v3&4_a1yx^@hhRz<^J?nE>M>Z zSPBt3eaU2?6B`tCy3wEh+<6ReBMg#*!MNtdHA<#H*!0)-vu`25(;;J+3=G7T88&?x zvDs_YE?Z@WP17Enfy#u~*2-UOz=^}$-3|?z{)Al~j=dlu)7uqfn%{g0t~#V6c7#F^ zy1btBmKyQC%8=<=x}PJ{TjdmLAzRGeA!3U}xE>*4@+92Jo<&GHMIqM8*We1dVh~4g z1TLt>0E;z}@d>w}U&>)#=A`kb|2d1g5A4WQ&H?HH0H-fiz-b!?r_T-rr)%Z!Ucs9f zIGs)4v=1Xxsx@9(>yW-dOvM)F|0^HiwVY!hczUia38Xq6!+9Z~)7uH1-bU#3Hj0|l zq0`*|$`ESj1vpVHoCgua142bPgb>ROr>Q@z0QiMHs@!ldxlK2@cKeFKdFN$x1e5=S41(P0APH^u`*#gfEVUSedz+Z3kf1rDd^UTmUl&EhIve0bqc6uyGhHMDw1(1v9 z=3!AlfHvG-fO8K3&eg~vFqd}Y14=OQsbF>}VG>RtDTDzU70yEkF=UGkcV?0$p`AMK z^xw*<(;vEa3t4oF>ATaMW6sN7L^xf|4VRQ0s4= zVbcgr3=yXhR{tgR++HdMPz*Y^SK>+Zr{88GY`!v#vpR6hck{%=$JgtWGM-dRvV`P)yAFqI(z<+ZnSi&xY@O$GNdF z>jQZ8amTFdprPTIb>?`&tgq_(|1D-cwLeW?472WY7iky|D;SOZG5L{-{TMnDrWz-fSsm{it$p z^O*Hi+!>5npEj5K_f8yUz4b5L0G>>k^-ztbAk2E1tF@T*+jnyI9(&BX3usd?Ype3o z6N_2<;m%N;v?l{*JyRuzW7hwieF8CSBNqDdt(j3U>-(KF*V7rd|6$e~*2PXAuzG7T z>q|##Ajp3wWnv4({_`;FUjSYQW7c!fHANl%hglQ4s*4e$ zKoo}f5a=F`S?Ay&Y9!2h=kI8)V08{po2hcQB2LVD>kMk)cw^QV_EeqEL6~(1^ehTy zeKF`Hn|eYp>)%Z$jx(_NC6LJY)i%t!7|p;g{`X+k?yEIn)nV3`P16~+Y2YHkKh0p) zR(@OM$%9$%d+FrEtS5q|=$Q3;w})ZYKTg#Y{iMOHpFol#)`^o5vp#}QB~Dx@Wu;uzA!3;InRxC0Fze_wAlrgj&%ae;a}78iH)j29g&wn>dp~n?v&oY&V%Gb+s>_BbnDy>X z3}*|%8$ZLfcEYUBM^^X@YtC3bE&M)ZNseP--mWL6tf-xdYZ?qr&vIYx9bG@ z6AQC`4BnS`G3!C$w1#2URmGvyHiubXhbLlV*0u97FT}vCpQYaenDr%y3C9Vq*qF6( zEYG&S#xB{?cuGL!@EjuF9)5xHdTc{XEi%60@QT}*NaS8 zmuC0jn}tQmI3OZayg^95`|!2+j!#0xkHG59?(fY)1uzDK+5f&!@hfg*A0DRP{O>!9 zaYM563i{X&&n63%yXiQKWSt?pYm+IwDvJ*G7vOeSd4AcbUG7Ym)59+J!e#6eo6e!P z326!V--wI>|HgS2IwDl4#@H+emk~;de-Vt*bSdse=+tmLNohRj)yQx`Z=Zzr<9#pOh$6jt7a7W4L?G?x`DZp|^X)9!@`#>Dv}H#r2fRB6?M`I$Q9(yWG>9b13n?t6m=5#v*c`L}+8iMb z_QW?w;Y1&eL6_mxO_WLolzn4nl&k8-0}5qwl{JDmw5_lXzZK;epQl&dKwq7p^GFTIyVwbNTr zT6rvMxT%!AKjrB{#;?^RJ;Wu(dy*ezK=|NLYsicMc~u83z#qBOPSql&HW5tJYGyAD zG=^cOQsGOR9-vWke?OC-`yp`>DtK%u1nVw*PjX&Q;$>mtMF)gqNcAndG+y41J4vb! zA-Bsdg1w>XEIPI0O{pBr_|Vw2kK~AY>aNYunAibNH7)u#yK@x3b5GMoE^)d1yU>LE z<5P^c3Da8&IvGD zcJEUr%4~rmB{u2COq(=2hbL;WNne@APt?~Zfq=%ek^7d$w!!}&+sJJ=)gHf%eCB&i znqt|=sd&lBX(QJR`Y*AOJ({$UXkQ!oj!X@!HuA25HrvQkzCGD(WW#qFFQeJWn+LYk zMqbSC)NG`{ziT#fPrlwpUh{M_Hu90N?PDXu)~St8#I}(wS*QM&t|Q;fYm1G%`x{M~ zV%f;yc*)6WBcHzfzr;rF{Yo2&_O+2qSi`D~95bNJHu9?7C%cXOLtsrF%|>2vSxaqX zXMU$M8j6qBVkb1cCr6;$7$Y?!PBz#WU5j~rp?+gF#ME&(++W-HzyKRPhM zfK)M?bP0P(?t!xyq4-(?xs8Yi!0%>n5ptj)C#9n^93{&0mqFM~q)fd@OX3rDe&7(?BUM}|>aokg1}Vk4T@{KwtWMls5()_k$tXkWiDv+k#u-Ca;`C%?4Dd6v6$xxv-v-d`ymVondC zW?jjFZPZkWVA)n;_Vo3!n^8oP7l>3@vxkmsT#8WyYpA~wj%>7qH{<@%D&8D=+ez7?|T={TBJ}g*DqNWJM5Rb-)8?TDV2wO7( zVYn?iBVxG?3QO5?*QTbuZB@YWXaL7Pef z)*R^SN&uatwyE*A}dGok5xI;3v0JeqTC%Ol3n($E49T7QST6uuJ8~Ur-icuVH6K z3ou@pX{kKuevH0%%y%@ATHyW1qUb9i(3Yy{3ltCKmBSH!;&rgfOmwu=k~bqzAV5j; zk)_Xl@^>}&iLMY0^FV7wK9DcrVmi`~tollUCR7G!&sNnsO_NwIxn)-Lp!$f#GksC+ zM(Hz;L&DPHJFMLNEG^Y{Ec2Mepbh=yJfrY{i>~H74j?ss^-q*`tOFQ@tYd}MdviBp z=(Y+pgdRs@m18xFv{crv*8K?v`4322NTr7wk#-ELAjmU6kMjf3!y~wVj|C-5P;`}& zdxB;8jFzSg4AoNMY}a$MZy+Iqnn{K#76=O+a3-_%#rA3+?dA)MenGd9FV*ZmVw%^{ zlHibbzqp%fi^bFDib7|97(&Z zNlhdX5QWtcg{@2!R`ZmlZ_uCyUAIuq(kGP5NSV1B*+sXiC44M-eUP;D1^mjYgwSn_ zWE@py`9unu;e?W%jGCmTAsRv42+7%kQf3`p`P#;3wd^_y73QbDRt>=xN0GAohpJLm z@dQ8)wRI(1pOU6@wZ0Ej?P!~?ho#E8fE7Dp)Tr709Gb#7zO&>_SU|;&w(E)=ZDZ+4 zwe|+~$Z%!i4wjxYa5YUyN`~a}YkaHdO}yCAat3<5FQ*qHzWaH6QH;+TbVyMZx@z^# zMyi&HfxRQrnYuT97NwJC9BzA~E>OZ`4oef3TA!-2$h@1MVaw&uwoG6d8ej!y%l@4u|Rv(L+n+~Cm|jrC{&Fp$SlobzYCS8X`0n; zyFfLjc4#l4UYL;-radxHW7-7pk0OwHH71v2$(z0a*Zqq;=`ei7{d4l;9`a!I|i)u}M;#1$Lr zmfOg`yam1?WC1t7W5|SlS8gxsY^}UL!pkv3U` zkqx=KbNAbj;4m95*V=Pvdt@$HCsa}xR*8_c=4avhln-#^sYJMF&E1L4vJzqJ$7t%D z2ft3hwTnNx)A)J-xtYfKjgcWPvDq)w_R1Hb28A8fSznN&8-6hCoyAG;yD~4`2cl=RNvwXvR0K{_h)y9vop~ZMTIH}EchzVU z2J930X*ivje@xk}LnwY6%l5%E(B`mxaFC~vqFsdZX0C)wx!;B`d4L!|B<#}9Zy7Qb zSuj`HR9Ub}Hj;%mO;gZ_DhrZN7%3ww5lYic+Q39%4HJceH7*))8rk%vU$POeQ%3w} zb;O6C!A896<@l?NxV>N(8}WUp?oA{96C3fp`VmjS-T0+0e8l$!jrh+PacnCP*Jgg* z4nHIxa=BX%Xd(@SMf%|FY2X;rE@D7YtjC}Jz~?bX28(6g$k>rUQa>`E%1Lm8*(mHQ zFfyaBOE3so+{+4eCH99oF(dl88$cO0yofI5{{MBVpJI<56heIoJt#zhe4(h&uhbB@ zkRvBQtT-iRe5yl*UU6YN1E&aOL1IPFmX~-+mx?T@9^PH%9g!#&G~C%0c}#bFi(U4W zU$I@bV>#XB9aB%9j5{+!;Gw0m^fz9AXor#6v3mKHQF?f2seDQ?I;K3|lWx|-OG{a8 zCxVx>=WoaJfF+jVT8)w5p`}6a(CoVvc&NrWYcAY2OXYk#;G3T7M+Q^EdKSsa4>A8; zWPE5To>?H50u~8z;Gv~;a4>OzXpwx6lbl);mfG`?E!x>FAB~3rM56|fg=ao36(H)w z03wV}1cZpeAlajt3e&JhfI-4@Ohv$miq>c{(h`j5m&K9YnHolR;O`-h2kAG?J@V8W|f|R1v_4F1`bt;}}t9E@Mi( z7}0J_nh=n3(w>__rog~j$b4M{tgQE{w(#L~fTywfaDHTLWQL9p=g!plka207`0&lh zxcP9(du`#vG61Eq`EXWbY-Ca;rY996I&+4`2;q{LjA#xc`uytXjEId9b$Yige0UbH zXKX(720{`t#UT7!zE#hMp1;%hkebsbK70a?bTq}#_z(djN`6Prg;rriPXqCci4kQi z4q!wVOb5ThF{10C9RZ9e>0-u%_%Nc6?TiDM!b8`_Ve77+zz7-lVL9}YvhMo#TM8en zSlIy|LM0Yf}x?cPZ^Dmr-)Ek84fb)t7%P?lheeClnKn?#Iw4-@N;lV~Ug+@7NT9=+z!e(hf^5b*fG}2R7j>@C3}_2U?*UwB zHE^kFFAA=FO^&jl%N7E+KEP#rzT-R8brWiS@&RyE&@G{6FGdAY7qy(-_i=9}9Avk3^31Dkep6IgeJP zXmBj-1*IhfT?!!!kcRN&5Ce+g7*iSrVcj)~s zDZBHvlNU8P2JhfW*4HHG@bcZ?GjX#feM!X$3L!!~7!FnH)argnPf4PsyKXV`$Y^?2 zxePoDu(bo_{SHxizqK4Hdj-FiRPGllp2G(QWn8BWm${kY3P5G3?>7b&mm>{Wh|9Ai ze@-V>%tw^q+d!P21v^FsF#F|Pa)T*Cd%<}c+A)G7X6z*a#EgphEZ291BW5V(1Cz!e zGyMZWMYVY`pW%d=4XN&4ez?q8V9Vy1nbu+uW)`>_ikabE95g!u`6H-*rv;$dUVKsf z$G2o@R`oEhLP4A+3$v{uDrnTh_8+OReNe%baD5QMYgbImA{4c68t#nx-q%DZafPZfti%_*DtuC#S6+9F$A4rUQvqecV7U3ySeFb5$n7=0K=WxeA0Rw5>pwiMGYKB-D(Dq@o;>h+^(m@9zNi&It=P$Rd*G^C4obNUtxXs!PZ%k0Z-Co_^P{bmHw7` zqP~a4FhqfFVK8vIn}Rtw*`Wgf7&lPBLzDU@l<-hW?#?(%8;WHZm8Lf!?+oEvnyiV* zBK$xsUWiM2!s2FtD#F2ahH@rTMti1X^p6E7Hx2_+<#eoUElNckxbG;QY|u^0^6WU& z8OpKhiHbtds}od$DkouWM^ZakuYjrjNC7R?ze@W&6X2>v$s#4s+a(wQ;_hHYfZNqT zV8@V?9jH$Vo@!;I!PBsn_&4L#Rj*Pr{4q!*)}ujoxJ|nQb?BAaQaQR(rOgp1YQ$kl zZZ%w}XYaDU#9oD?J2E*(>4-dwL@>Wc(dZ<)h00vQ>I_BKDaD*Y@#R20=e~u!<{4(E z-WDFOaS2|ly!3YVQs)y;9?69Ph7l7gC>xhLF*FT0<`&9#%q^4$Z*U()jUl}qI0gpF zj50&jhiI<&@C=Q`so*v=rGkPsX@+G*8dbD(I?5|gFaQzI4xv~I5)3qpU|-J4Cx0@C z?fz+3AIYsA?pG%tz%tye^8p-uS@Qun)L?qdL8Wud2h8l~jq7}ZnkYJT2{J7RE5?)o z%FBWQnMZcaic$}s;G}(C(+CIy_V)O`QJ!;`;sE&aT{6bxN?szLIUXxzYy%k&MSB7#90Zm487#1npu4T_mP(=w+*XF;4H; zq?@utsdP0~#8i4_2bG@L;Y$*wF9;7grxv8S4;DHvK~Jne2(3$K>D(K06&37RA{U9Oi%z7{jaJ;r*5=!+IR? z@b{1qBoq(77bXjMW*8oRo`K`xr{QnQ@$j$wthN6nJUnA{bktR-i4KUDK+6~+zJuYe z5QYHm`gtA`hzN-I=FNx=kA{hNeKAZXju9pE}k3u{Iz&K1}=W%jiI=BKRghGi~j%s6PQITT>P|%xcIB^Gpo4xq%q{kkBy5L z^rE@Eb-4Jcs6GeFPQxC_vWNNbBjDnB&ucQ$5?uVBKqVtVI2!ibC2&4)`e;&|QcT8C z&raSyMTh<7_hcNBbDb3>Iot{-V5*IH1ED+2{3<{hP`!F&dJZEwM7w>f~ILC2u zBchckf-TOolYhU2@uHjrq?NdM;?lP8;V~eXvH4IJ84n(-pIwLeZIAWZ{&rh|54^@CQ zWAkAJP~ix;_((k;j$W&JIM#JWf2)OvyPbhq7GcNukBB=tn zc=7GZTI}rQ!MJ#m14FOi;+i}oV~CbTQ;rG`@(6{dr~l#NZN$aLCTT;l7^4yq7oUS4NK8&5T--ERg`q1Rgko9pZZ*0_D43^` zFeFnS!|_Q=VmQuKLE8|#z9qQ$!&j;VMaISVBt*l-Ps0chcTWaf{DUjhRqsiNi|1&> zh2!Fj4LV%B9=s-w9(!E;G`v>9#jj;A{U0tqPD=CXaxTOfxD&v|S0MF>!s0#tBQ&lp z#l)x+5ut>FP#LJOUMc0*AQF{B%!r<=|8K&Wa1d%r8mA9881!R#qrX8 zJGD`7!5QNBaCaN<()SUG9}O?P>e<+%(=1-PA^a3^x85zmOX<)!uTdaMh;IE%EO_5J zS#;l(kv7XHN*(2_42-dR5)MVz56z%NT6IbIUY3zIJKZh~PeVdBM48z9wf^*Pp8-VJ zNt9AWPm<_97t3h;>=}w%hNDWu&h=N{3&+N1kPcBhqoZTxx)kv?!kmygpn&?7GE=HH<@-VVM5pQJX~G<2WfHFPAPlGyhOq8@Mz1ATmxul%nL%+-pY@irwTpxulX`j zxS`IU?thwO_lCW!6e;q-9U_wsc(gN8;UUC7-};iXo4a%=GSDL0rh9)AGU0VjL=II^ zszD&y|&Bd>?|_md*lU*v$G4tGUw z{pmf>Rl1;b_4n;xM(6AxdTcp1#1nxxM?xe*DbSJM;U!LF2-mKhMecEQ71^xA59 z1QG^Q^xEY+DPWC-w5^s0&_#rL`O}mHD0iGn`}m27UUM`m(QBVk^x9_>4hVx361&F2 z@W}LJTB|>#=sQwHN+6wrEi#4avE0lQs_$rIv1dGP4=1_t(yc7<^lF~lQTJ>=?E184 zVf|Rxnu9|A97}@UMX2p&`3c-keb~zTuv-4pDbxpKhH$VXE?4$ovn`F33d^=sSEt^q z4ri-vQTXm3|^aT(>>*J_qR`7cz^SFe}in(+{c3S z^-%;vMKQxMZPRTSwX}O%`})HCxcfT( ztMI<&Topx*pZtB09IyO2YKLRW@xrv`I~;RO@z>J0`#bH+@czyOOo%l_kQ~4MXwcYx z@Ke;T#+2jdgyy>%TaN4J#ogEPUEzH#iqO~fj|BB~*WUR0`hVS8(AS7~_%c4rzNEwj zd~%Q4WfW~t60Wy7Mtl_DA2!MMTpXBW4`0Y8*_binb&@vAE|*>Z;lFS^iW+MPmD0_Znj8k zg_^bI16wR-n=>q(Yb_pghMG|A-kUT0Kl%N+weG`d=9QHA+ALUV@d>A`-G@6VUvO5+ zeHbMmEJ%me&0y#lcwY2elc~0a&so}MZ_0VLBYw{{C_sti$!m1irz7Gd()Bx{Tz{M* zR6-v>tvzqV>+T=0pi22>8pO7;*40^bug?R9Qd;6OPWDXs9yGF8%%7SeETH74 z2#!rynMEH84vNB%gXCmb^Cga*j7amR4+aG!eRoSK(;g=YJ7WW8>tzY9LU%)gCuJPF zmU0Oq;c0VS95}gFjAUeI;a7k9ClAt(XQZ=RyB>BU9OThXxCF-`yJRp$2$iF9JRuk`2vHa4dbnx3^f~;U< zjiSHS271_F6w7PzqbSYBvx9y#IPId;|3~$?8SJNFVNx-SNyU7UilanJv0O@bMv#i3 zNcCZo>7JSL^{h2wJkFWT#V8e0#(PEc?9%5p&y7Z#XKI?wGdq*iq0ol7L(&5WSbX{a zWA9DCqbRoa@vJ1Q6O>3$BSE7^A`%H`VhCi&jC5ooaY1lFqQQs?f-plsMIw_(CVfY^ zqSyVpUAODL0d9~05-`ZB2%-XBmyRQfq7Xos|NEZqp6P@H;`ZI||2;pRhfH^M_33lo zI#qS5>eMMz)S~@p5B(P<75Im3D!?pGF!2slZ&H3w-S5 zS-m}R_VQ_w5pFwmEOUL}H1EE_?q1E+3)3M z(SIDjBBBpZM8ubo6CzIMy&>WRURy*QL1Nj-mmrxUUTM&}#H5vL(i$rom)u8at(G&j z!XlRJGeq2hQiO6tN?T<$uKnj{Xc2~_K4%_#rJja5?|NJc6!Gi}=-*MVbyx`_)70KR zcQw`C7y5|y9)o1^IUhd2l=(&a0#oA-{N0fG9cENxckmw77izYAFE(smhT_@GG>hz) z>NJ-inKZ99XkKR0eAc8n4jJ+O{X%oKcGWDicmt0>SVkJJSZAp3MF05-T9@ch*`nE$ z%KI)`Hea+U-dT4x6>s(FB3@4q+$CsJ(L@*cFUeE}{d~$qVijYhaLA z1J_Gh>`DBv&pFXF21ZPTEpQf|1rt~J_$c`_$=Lb*ThOKWqR3|AF_KicHJJF0mC*ox{5#um+ujWFR#E>X5dG(*wgSAE&h_9i6WvC2NTN>-U*La+{~2ZALqnIXj$0N2JF7V zHLa4AVB+>rE<~=?PH87XI-$c|QZrvM3~s7l~j> zun1NF#tzv^yr;9;F+1K9CxW7{tO30Ylb(`uUc9HHBRk5|PM$B#tO*fTRC|JX1McoA zwv4Hccn=Kqbw7-FvLfv23an5C6WdonLhC{uwm72eTOJpoMTxL%rV(L7TiB5!rYjI7 zw<&xqn-JYRpo7p`jgg*{74Owt{Tm{yf6$LffO0v`j(q-CKduo`=! zwAVn%H~Vi$P<(UB`O_4#Rn&Dw_3;&sY1;)8U%j0eSX91+JpVW}53vuo4P36+t7P9# zy6#h;ev25~Lfr_}E6v&I?xv`7_$ZioF(@H|PBfxG%kpPStDoU=h1TWCK}i^*taK-0 zfH=xk5_O)xn;mP%Q(|okA_FHYDn@Jd!J;hNg2sjXYH3cL8)4j{85)2OQfv#*#ZY}L zRnPj3DEY+uRLY|G%=MUCZ?E{~m4n~F<%;iZUMPMHzWlJJVM)WPMj7U3;5rY#D7k!; zTs}%JA0?NMlFKhvd>FGK*@u=ozgYg_J>KN$KcjfljHf((nxN@0b{YmqY}`2|usBtE zrvZaiwmNPcKd9!`i{%#d3=i-EnRwuPC>fH`&Ym}mmx)Wy)Z61Utw*DDZ&yGZU-DQu@tBgV zJ&F9G;c7kuQmpNeNA+6QlZl9c^NA$MI=>7)!>0`xZ7Ld5UGWRi&aiubl?4lfq$9^e z?VNslOduL|XJIr(g|FX6J+)Qd$vo;jDsPpSHF&v~wHXWGzfmE)y+8*V;Z5OhvHp`{ z+~yg-qnPZIuc@&Y_)t*ZC(7};2%lH#w7S%2vqOD=MYi{~w-KcdD$j8gyNQPGxHtPa z*idjJuozH?TY}uaQ3;A~8v2LWSFge6GMB-287}n!HP4$B;qI%bC~EsCr6gYQ$DF6+ ztetW^1Q-=W({xduqDCox1>H~w+k%J&m_Fe6ZGshHtN0J^)QeISzqbkr3i=gHA`OE= z=cA-i=~x%ymGqU$Is{e8XIti#$`;`{O;J_7z4&fMBm>rM(rdOOXOpc0a{`#$T(GnO zHW?R^2ir*9d(GKc&ZR4e@#g!tMj~Pej)h@V6GwH8KAGh&JXO1R9`~*lo{H(^!Lpuu zDwbhSbs>o1sd&2_)DR`b6AxPWhmgEKWw8!sT;JQfWWpi`hP zRiEM-ozQKG-o|KKEBR7xcNH2obBxHxe#|c4+uzt@2gImE4?Ojy*Bnv z7^VG@ooty*I!}EIQ@PmA{yqrA!Ty4Hwgsc({5R}SOzqs(jyVI{_&UjuHp$Qn=6%io zGlQ`$4RjAsp27OE@u}KWBumNQ^TT=iM=Yi+EOsSkH*SI5|DWczJSGn~5i$v-w<4^D zlcdp*JPJdgwJTD%q zP>2IaMrc^2Ut565bwsUPhjLk4Xv@cm%BHp`s&kvm-wBn$S<_4{GBCJp7hK`FIS{v8 zS`f5;PLR~e8YDq#Mvao*=sp9bYkp^?OV?!#wnTY@iD%H#&1v)uAx9)p{PeD^RCoaJ(>jOt@%uTbZ-2Nydb13j1%D|1nm7 zyY7vIlPht6M|O&*o87xN($mE(V9d06zmLM5G#>7m+6J^;=!vItW+c{oAkvL#WU(O} zoM+n@OzbzEEwl%!B+0aN^Wev$r$U8Crns_CWQ#Kt-Q@6T(&7x27Dt7JC9MuEDhH3G zI0>z8e!MD%6K7a36&>4eDX{jI#7Brn8Yb8qH~3QwqfahU^m_=+_c?VQWGEM5548s z0hTB+IB~TrAJ5Pq-(ft)RB`xmfpwgw#@;nT)}cDkIJ*Pafev-CH{%yA7`|XGw9#G< zb}mJ_tVJaYVXj5Z8AwPvcMB!lrYCd^B}`y~)v(9<*&9s#vTr`f zW&kD&zLAgG9&xZV=b#(ogEh7VwGFccZ|MyA_B6gUMahVzTE+#lkO||0 z=a4S*fB2>9d6|#ng1dMjpF8D|u)oXhN5g8XOk)%IsT=3B3|N6M#OnRLC zp>aWQXOnS(co`$6Mw8(|5X~`%2T7b?MRz#wQhZg02Yqb|uxht?G3y(Hqvq`Mw1+*u ztYHugRCE9e`dtnij>4Ai;(`*CW~U)a&bleb+c{=HC#7~({6jEih%EK>gVr~+!+Wmm zC+(jq!E=235Am94W?JOz5T1qU18nH_;(h2OyB>`Cho zM-+5-R9zF-%s6}qX0^6|n#DMb(;jLNv-9y7Li~iVHy3>acKZvb}u}A|<%5TC$i8RYl!Zzngu*+|oUo&k+mpWmO zD_s+t(fqt=Gs3hrY{q>Pv4)zXc?OYDlF+SCx^RDkamWvzeuX>}HJiolozXpIN_Uu$ zcK-+r_X^{*v^!u!CgNG3NDRZvU>NF`wpn?!7GWn&(qKB`e4HT0JRG`7TWVWS_)Rcz z0DKfs44x=;k0588)e-s7s>B6WNP_s##%X;(LZ)d7`_k52M8y4}>FGR}n+nZMJw%Xc za_Vc!SWmto$y`&$U?UIBu7(rRT_r1&lAW+N3(|kYYI1x*G$w#_^U_xFEFje;?V^ck zt9Xd`hONkqVB+UD>a~0ppI~YO0~6i$^yk4IhVl7^WbjF5jOMYYKy8wyEsGYZWnNkOcFX+Sq4wzHpIWd-cTD*2*rTIw zLF@9z?a>=w2TXsmJz9;KAcf|?#U4E-PPa#=e#M%Jo+xaO279#49?jvat=gj#riwj! zOrdFyjzvOC_NWW#uty8|(}F$PM}K^l@wjz+ly+vRcVDC@KA8CEcubIEW_T`sIrqZJ z7ONqN^*D44810P1*aU-i-?S9*ze&m5ffXVpyU%PCgZi0zz}?5?Klx_d$Yw5}v{9S? zO#}u!u+f5bJfgf#sofS+5WP&vPV;CvJxTZ}%AHOZx^p7K5YqO))UG?3HLLhiB0eQZWMawUoHR*_Rbj2+TZLT9md6}y;PiOU{6H6?UXZuiJPyd zfpu|e3cX&I@kT||ayGZazl1J&myI$EPNVG@pNisjrzYu}kgzJXurgstyhE*ZsM}R^ zSbe7592>D`yCZ#xeVyirUShA+Vx2eC405J#%iJ6waMQOF@)lF*=bViNJwQ}Uh{h43N%%lUXiXgzt|GOZ%7KZKZo$Mo<4{jWp~ioW3x}!& z+NntnbzBVOI*J9?&Ne@;)Tb{72A;B`jJC@phUUy3nCiZv+GekErJ_T`O4>u9z zc6E*YP0mz;0M3-entNQKx(r^BX?UOdgeBPpn7;$>M>%~1Qxz3}UWvCGh5#F;1QUni zwl1PKh<@hN+iP`>&jzD-!>(?%-Lo{9xBFn;hF-7XjkmK<3#BW;zrxN0~Tf9&vxcvI>*<%R)u0j$G1+IeBQ7aOl5JeY1& z8M@d?8)l>A{JePiRXY(by7?)@iC}nAXd##^I|Yp)>~bg zvmc8vHWyAlUF9mN(WZRCCf9R*&VKiK-jzZBxsf^hZLZotF3nL0mrPakoO_=)iG>Ic$}6#9-oG zC~Q@Ufd;^^`URze&-SNeFm=MKjxF{n_7NDBc)u&6+H$4{P|(xEyC&#L-(HZu37f^> zHAg(aWN&_lc>EpTfG!gesPb;l@s&F~Nd-BdmUV$=6887eYbAFpd*A(mYv4wk$5MmT zSy-LUKsz`#H4UeeeE>*ebVz_vik7`yIood z8v}86wTE4u&RW}bA;x4dxYw4As5-`<9~l@8X~soh`YR1CFH%u=jYYlSz?dgIkD_}r zOh*b%Rb|~J8W?HoH8r+GYHFMpr5oY41rwjg>!GS?=m3i!B_FarWzH(`0px@aqF(Nm zxM{K68?dk*TBAZ6xw|&j-39(O@|j*>CD3(uZIZha+HXlZ5lKz-1|XxSXwqg%Tdr=l zEhuRBZZHmb)HcMZKb4;JL21qho-=mmx3hVAWClyKZ;A1A#os7T$I|Q^PdkTyWMrV7 zU6xs3@+@hvp&3IyK#%q7dM$P%$0#dGa~f@b`w=Kr?!K*`ZEu zZ9kYENHUTC3qVdI{2MAY@G`OsY~x^d=m? zrPiU{!clLf9Wx8lmz4T2_q7gLRyx!*o^7SRTiW4UF{QqXqDt+1w%95vb+)3_I;U-M zy)(T5dDo(?E8Xw~h~cIlC;qXO+pZpjk zBT0YSaV_8FQ+yHcw=>>UR~j!Oy&c}n2m9?y@Dv#4d_OtPbBd$lXm_ttN956-z>qm&AyhA- z0WF$U0h2xoE1-f$P{2%60ekO)0{&o!Bh}lYfsV?{qug=OzKFYZG%#u*F0lprrJYd2D5@cbe8w!Qm12daB~E*R5?+r|bT3)}BuB z|1Gr~oFOd(CkkO;48x9bV5eoSM287()s1N9Q?w3n+{PqUML)m{yU;dYH$2^)ZS((@ zdOO~%-4@@mmK_L9)S|Gn1~3l!Gz1ezTnVX)VtfE@2ri{xr(2mwXIX^AnIiB7k1yo6#`&7+-?#H1(xpdFf#wGPmForphPC?7t;S zRl2dK%R_j-LexQK`y_9(#8cm1H^KgWa0$Pt4 zw35HSF*ahcP76LVLTf2k{z;1i>{e-bV=n`Z;B+vQ{H&G8r|@J}ysZ<_%S@bHurSzp z0^ZdY@|~tIhs)O2)w?fdMt7wIK8oGSvF>*cUcprHTJ820UK3l=n=LP3=CTuCXs^Gp zLMs;DEViV-^NtCz5e->LH``?|_Vko2!6JaDe81gufigRx5u~WWLP(;#MZ4@L*wCq z#XL&(81BTOA20_(TevkVTK8r_n~=Z~Z94lGsy}bC85R$167Pa}q8SuS=58>ykDbUn z=z{403b1IqnVc2zd;ZQBAeWZ<^RJkAIN5<~00*wL>-p4T!Q)*u_GCOan?R$i%X3G{ zOX_CN(V+Gq8ieJsq!X!iUjY)neKSO|rS^bYuTqk#?61Rh@_6>WoJ?muwFq{e_YgX^%1|=P4Amoq_9W;0k;CF?bGO zN?{M`ym*<7(%eFoLu@ZrpRY8hqIKNV3osI2?~dmL%l=VljKc6p-Iscbsc8{~nKm+$ zzw?II@IyBMI#CZ#W6M{KOGgV&4 z--4o=e8&wynofOa5{%_EMY|6BZGdL-b>>6%;{IV-+LdfL^!b@VN?l$&n^7=u|J7gs z6Qa_RdP|bfq^3kH&riiOI4qzESZcnCv6RvX_j_?AgaM7+u)(_gl)*xVz72^t zGRsHE0xkIIdUJ&@Puqyjw#5~`JZ%>qxzyxk$nEkae}x|wqM~h)S2#}@_GgAJYkNeA zW8Y!MaB*1ty7f^XJa<1p&Ns8boOTTx6jSgfVuoQDmIjg0Uc^>k1vvbPPrqlNqRX!Z zk2M5`d#}_`wGQ78HQh#EFg`WK@RhMx37DA~J<_leT)1$k+b}cJ&!Jvz*a?s5c0z!5 z!WUrpU0REW7z?LCWyDy({K3%pyo%FDxe=d4jD^oH`K`u6w?mBui=6;>0I>=3_{-5S zJm&NcKWr#qcqxX0-aB)u0i!nT;se$+6gd0Wx}hK=Xk++nY*a9J7Oq-V3L_+*4VR2v z`H?)%b7o+wGMJc-fst)7e1kCKw(_L+vEpx4gHV681l5v$q3N(m*6H&&>#!@Y2J=K0 zp~{WqI{4_12~Y;NOLO`Y4-r zg`7i<=_OZciW;x=77HI4Dr-t@4#=Uin-ywOL$V(F-3#ztPY?OB@!dz{yV<4QhLiyI z#I*k1x427OdjUInYzq$a@ld})S{Y)CX{R@$rq<;fZA0RStI#mKBIM^w&d|+h6%w8OzcS_*&`Q>?RH_Y_ie9YLn{u^F#TCZo>^Q z>W@ZXXLZl={e9f+%lD_dV_r+gxq87mm->NIr7s-~&T>{ZzCyzmRi5C*qb9<#BaRYJJ2)K1N2HqfxC3blS5C z*A8@4cHMwgILbm}hm(qli9UU=W8z{h;QDw%`1muK{mw$j3OlF1zeVsqu7btbbxRXM zQ`Dul`5l9a1*oVL)lq$tz5Lfc?vD7I?vAy;F5)i5v}_fokZ@9Dq^$xSIxbReb09** zD;XF`ALBik;5j?1qbIXEKXTwSciO;Fp0vP;SsmSRi@2T=I8IORIB*nF;+fKZ5$!8j z#qec#D7BnPz;!8J*=t+W5gY3{0odyunYwh991JGDgs(wyw>=Pv3C22gZ7}hLBBa1J z8?Kam@A3~=jwe3-1$*!16l_sPhiy@#?WKeE=wLAMMi6kR@2P7shZ#(qq`&O>KvADh zt-|MB>g%cWq6ox0s&y(#w$7M)0=_#J)RmIuifvI0DkvP0(A)v>X#-J9XZEn$7Ohe1 z(ra@XCwI=!3Bk!6jDV{&~s6G7nGhi==~3) z^iX|b@v8-kKUfd!D6J#ZgGpvRz#4*H4={B86Y7BrQ|gCS0R?rS960Ay8A@5QJDB(c zkc3XA-bamI&bshepUpVFN;In) z<_SOzWKbVfw%3!l?#1t!Q0BSyf!Dk*1DD z@TTA|CO3{bUyoXZ^=*RUTHuxFM)`w`VDH@mlECQlVH1tK1-Yu+Z*bu%JTL5b<>s(m8E-plmh2fOn zo=~VRDd0$`UOcL_(Y0)t!$W~pSo!T_ThO7lCLTK>yi1}Aa@KmnvvnbRz#n_Gn0w)U zm-CiV{hF8qwgvWMO^YmWtSbjoVD2Q$Vn&wcM3tpaK1t|%Yvmi;v0XN2EfzvCqbfOV z&J}R(U5M%yu@q@09n1L8A6q8sxfhbbmqjf|(E14z!&9`=^ouktMZd_6y~-cqAU*551Jf28|s!;tn)2FzQLGj<3RAG)xq~E*+!;lW(v&p^wzN4EsAQ1}D@P z!-Ywl+hi}Qxwm0;*S6pxxx>4cCxKMP;Yl4rq;GQ~IG#J-;l!4P*y^E0f#~X?7uXin z`0}$5izwFHIP=yRhi^)jZPCz6Y>S3pXj?S$BINa)=G_-nmTJ#gRo2rE1MFCTrwbhZ zm?}qft-W>|cAE@v%tN_HFAsEq_j(4dk#r`03e`0Q*nsUHHZYj@<5136E*diu#4iT% z5rp4!qIYjpSuz$u%95NpTg#4ypR&^*qZ*V+G7cm^yURi=FPQiwXgMo(xKDxofWLy;k^N44MA|{=<4xg`TsGdb4FyGyZSX`ELRCc(VPM>do3khw$AR?k6?p z`}4WF-dv4FsTJIZqOAS`+{1dG(02YyK5mBlAcOzMx3PJn+5{!1ad$Tw8Iy z1DSy9$4Doxb1u?xosD0?brD|>T<>O1HTMN3v<26AYDAlHy#q0qLi*b9VhGo>h6t`N z{b=GEi&>zO%UOOO&2(HT|>389J5niRfrV}Fr*X|0gt+<{+ z`q{a`p*pT-U#R2S7r%n**La}gI#4*t{fG%|!S!X9Lz{8!@D@y7vmN;R3n5%T$AmoE zfss2+Twj=P!L@x67k??MXpIaJ^BIeiyF$(e)G8E%aD6a4kC**H&CN z(mu$}9fNf0>((M2*N^clxSq=wL|?yTPBnJ`6WW67D3(K;as3BJGA6Fs&xdfmW3b>F z{NBWM#2Xe|BOscIYbW8aMSVR@9@T4w08>^M$7#n%((l6c33TnmRV79Su4A0Iw&Hpl z`~U3Rp-3mLvxn=r&cv_adInz*T>r+LYHl(U+Jb8YHKNV9?%r*|b^E_VxSlacaD94* ziR*#aEV#Z5Hceb#rzOy`zOIu;_1Z6ji0JDgN%~#54#TU&HHR1(xNgnIwH4P>NIyHb z4(Y_T@A*2er{Y&|eUUE+uKk6R+=rRa7F?fUIkXwqE3LTto((LritqnpSOzZba_;-JuHX_uBDRnyKvq0nT~6q5J(uf4sMR?YGeYgry96^?9y>v zgI~e5O9&xCNjEk|(O^R0<%;VTwg6Cj$2YmD&M zVmr`F9@T43fN8V?T_mX`T${`jjj6!rRm(h42Yj9rDq5s|k)}1iEGZe<54gZQQGfIZ z)_J1W<#~(qM7sA{vyr(uBIn8zLg+};fb28jK8X>h|NJ7YHx$blT|J5<1-Wpq2E z{@37K8J1&wKbM&qZ2=QHRbGhY>$p7Mfw(Q$6up*fc=t1AQGX2U(XTFY2%c<^L zU8bI`%GCZyGMQ`Vk%qg;CqYb3HJw(*Sve9W{4nvqHjxi?z+&2=Mw_`I7uVLl=mBIx zoB1}<*%v(li&Nr%(ek_xPG67n1?h{PVNNyo?@VY*Uv&H@zp2fnLyl=X9e5nD;-eUw zo_7{66PJEvwwXPix3rmGf=#o{T>CNkYq8DTA&=nOxLHbuW7AJ0siijah`3&hSBdLH zVr1Z&KM>beT!)Z;c5XV-iR&;}oWwPsmM3vNiZ2MRMZ!sLBoo?#>oS%@oBJZ3KNQ0C z$;U#t{?uP^z2Qp}*V=zuaJ?IBnz&Be*eb5~$fJ7gO+iHZ_*s(lyKuGPRpQ!{7#X+@ z%fYo3*Ka=(Tr-hQTz6p;1#u1FS8zR+F9@!U%&F$KXF^+W9nW%TGp<+nz!?^%z7{?j z!u5en!S(3>n7Cet&Eui5sRIVGiEE1R*P_1W%AbR~UMh32d zY+PG$y$hM3uN#m~Tpxs~L|o_5WTL*l#TNwEN10R2eU=Gr!L=JTqRqHoW*zhY^3M>i zL;4A>Z*Mno?fQ%b*Pp?riR%ii|FzN%d?kX%3L+AD&H=h!#FEKK3jXoRKR$RYW zC%6XA(s2#s>A3y}zk=&}JkW97$((9#Efd;;>sXdUn{l1r%hC=w9}3}mS6{)k<5m;b ztDdyrdNH=%n7Aehe=W8H1LRS?cBvpDxSl9UE#VsS5yhgcbsu&Cw>{buoY)g4yMZ^m za+XZ#48wV7Hr#1%>kk)T1xw2X=`~8udu4pE{A@fpnGc+qJK&sStJntnxWL~(O_5!( zLHN2TIXfjh-UH0LHTr+Nsk5$<0y~|G;Sq_FE$AOzE!Y^|#&|UFnB;ot zC^(DjXQnE?kz~E9yH@rDK{4YX_j$Dyj(xBn5%L~{Qo*4BYv-{eyP~EtIzdG$ z+IWz|vhI(v&n87ik!YY0nyQDb@?l&cG=TP{e6*3i9MCMf$NBG0EoUzE18o2Z&*gHl zzM?w~$Hzjo@MtZYhb-;ncYh7O9nH+%YdM~beZ~#YeR10OrQ`?!0ix)eH)!|LDECp` z{~Vx~p8jV3B8+tCLXo6+-;p6?!end;(6>BkzQ^F)DBnbi$brxa-GtMJQn&GYw=ct} zD#AMC(9_fVhvqVQgw^HI?rH8$C1AXHfepR1i#`)klqH9ob!v-%hQW-OG3!spZ&F%&J$gtdy#39pz5@w{ooE)x2a zxZw&DECNqkoN9n4){Jw&WvRd2e^F$C_dtxzyQoV<#E@#o>F$9kwu*;4M??%vMVQl$ z_)WJ}tim@2PIY%4IL%gZJDxR6KyIhHi^NGc3*j0dK{2-JWo2Y;DFyvJ{70LIZG4Ki z23-bf2)$e;Ij|9&48bQK9_89U-i3Vr5h?W4x%vd0!vdT4bcU_sUep`&6A9c4 zOxOGwO!OC`>7S9s*jI%q<=HcB-clylIcpeA3yz-)t42SAFAY3soBc5u1|$nheWRa| zW|SVrpWl7}P3cMmT&f&~VsT^*kM|TQs*PS9+33&m3bW%Gs?p9v>7(uHy2|yp#Up|q z0t@p<Q)`=ZulX65@tNEsNP{NDkjCl1AhHFCC<{tuXomuohx9aRcRY)u zMf?EMJEMjXi$_0WFCTc^Oe2J~->E-f7~yAR??ObUqz1Bu7+=r0)Y14qqK4tS5WIG& zBdb%Atpe3k#|E==QUvxRFx^#Wnv_Vf<)V=0v1(|1AXctQv&%Yo&XN8TZFAu>+9%nV zM`hffHJW-GF4@KMX30!bH!8k7!2Lb?N9f4kn=)8=7s|KAD$$z)U9e}he;_`T9Xg5t zXZY{TV(4N>yAogMpSinYy=N?mDb-!e_sw+Mv1`nYt$<;|=X6VgX@g!6)`=xG;nt3M zSvN)AwiL&&1s3Vj@sr)jkZl%~tpIbA6CXqc#j0BblPALixL&&gN#uk4n3V)gfeZD- zfr>)*Y_q1qv77hNl`{XedPsDpVNFQls*!%FvWa9pXkYsY8DX(Iz!#=JY?0u+$cA zWngU?^%RX1#JsqbDkgwkHNym~Vl%oH2OussP}SShc*MC1sbgj+cjdQ$w-h_(Dz@W7 z_%&Xn#+l{LxAFx6Q8s!U6)K;h?ufOM)zt;L08y z?`a3u(f)xx$TTC(nf|?9{Y(}sFzi}pYTEZtK-1Dc_XI?j@V0c3wpowE6AVMs6m0}r z;f7PN!LmU8kSuKmOFAEwcQKFu9$BbBSNz&B=!)&$HIWVk-^oMec4aFGwmW2j#eYu? zfMbX4M=mU{AYYW^Q@qnK+JgC;gg5@YzD_`(mc;7#periPj;`M9j<#2Cw(qWsa-VJY zese(azPkyG#ZN{ECO=u!uK5RJg058RD*1qRlFR2`iyv4Zxe+&DWg8`a6ZAfODY&kkJsV(Y~H4 zAZLLlQ8-&X!8p!B!1zVC>L3>hC!ptNHmw9C^`^yH{i6d_D5GHjX8`>pV4JSiRvmqi z6~L7}D#3O)2GZ|VfjOO>N!qEx&K777Q+19WF2^O98a=53`<6$EDSA(_!O&CiK59a3 znlM9kWEO4XJi@X?jKT^WQG+ppt>OgGz%dFT_PPko5wKKoeqAvlDeultiuc@X)YwzY z5EB~>hv$YYG>=djv6iH3EyxbHTT!aD5k{Xv3$J+#3}0i>e^^Mi1;gQStQ&6FiVnlA zG$-C2*Wib=nOiVG!08clac%cI4-vDT`N19pPFf4qA&Hg2vj_{8#IbCOPI)nzIZT@;NEQD>& zwU`j;PkC&!z7@zIud0e&C? z2Rx7AwM7yy**_&xAjvl%>95;ptwIvA(YEj$rO==U{azWyO3`mA#F#?`a1<;`kng#V zW!>#PB?L!_$%)%59aR7o1e6ZM6ZX=9LcK4zy>95!D5U*R0XHOy&`FPK|t% zOMJ26N+^gm?GSErHhGGi6}#P|Ip*$zCJ94s{wY3m%w3F)DwZMlNiWKV5{$lO0}B+T zQR|5{Q0l~{O6Xi;0p`96%B0bqkq|F|lN+_4 z?*!J!g_ssTPdox#H==#l^rvgJjd)t1Vvnz%(jF;7OV96 zD*MF@y@%aF{%16pOPH70#`o)-DY)aTUJ7RQZa)yC&+1jK1-p#JCsQiE5p!kh^BZ}5 z#pfxvEvQs*SxOU2QQsXU$=nU*gI$F~LDkjJBlmF-@jfl`1EWqU@F#O&md0OrU!Vsb zRcT+ty#th2Xdl5PN`F+P`Cs}qh~mi0Kyi#s&5}TTax{}U`;lRnYR$sP(s;wW6AFSgsAZU4a*Z+nLyki2aAlr=%(AJ7IP5xEf=(_7~`gUH;1>?d3TU1-9W^TvYzqf9qvTivm>YgR*IX?V{!tZb&g%DZ!UpyM+TVs;m|1hhWR&ksaStuu zo9fOk-<$3}qkQjFcU<}2Y3{QEc?i&*P>}O+S;qo(4US|eh_2@$tnWK=HYi80Q=*q( zS}@v~7f4c~r5{FBjb7?RY%Mfk(N3GQzA$rzY83M;?7t%ECP+tgTB`nEuH zq56e-Wzy~csR1M2{dsPe9-G4x3q|(~w%Zb%5w_D1+7qw2anO<*{f)Zcwg9779-Ma1 zcr;Gw-*~>ar6G@OoCH|A?6#Rs)Uq!M8BGk^mN41JU_B=RVV6@-mIANE|M1vrfo!h z4Ym~~DE{N^7>+XdM0Aakj$qrO9nKmCHid`X!)S3$Ojs5*ny1;9Ya~vgD|Qz)Qmn8XWx81u-`a8d8V1Z@E_*re({P86OScWfb{_ISQ!O)SvmG ztjFMW^js7}w23iCV0yE5CVGrTY!VgU8-(BG6R^=NzERFp7Nzv|8CpAJMVG3$d>%IF z9*Q$bKa6dZ)^Gpz#f0Zbo?OK;@@zxb#ptoz;!0liR;rnbzC38ui#qB7X;VmnN!Uj!GyNpn!<8uGp_BdxJKR*!u9+v zf@>WDRteHtGG+u}b8_Lzmz)C8OdpC5UuqTCALUWKmIyHClFT^mb4h9mSK6~7YgP^& zEJ7XGWSFst9IT&TP=FB2>NW=h%>0-c^v%HX8V(cwCdyX9MI?lrv5Dd0AD*EpPz8?m zuq>U8qYn^?DE8E&Ar>N{v2`8%0vQM4VF?PtALBO(@yW3qm!G2j=P5k%<)=xPi_^l+ zKEZn2dzQ8p;{+663X)lTTC!ez+wd#J7l#M zc{;5RkxW_-;g@bocQYGSPGjHZ1!+CbjB4_8yocGh02`oPbeBLUy-IuRW;EpL0q-~Q zwhBb_WDnq(#$C)qP;dqcJ(OPX2KUGIN){Bf15b*SvN;({Rv)DcInZEi9|7{_PDZkD z7G-d@?|6f=OYuZFi`S(r;JqPbF0U<8zCdEx$)_NhQqBhrO8KHmtFuY#9VwRN1N20I zl(BO;05qk1dwMX4^Ajsp(*!UjEcPBE;Y-Ks5+2u7!Ws}oEPjtEVUjN4Ge{P$QVgz= zOs?jT3u1o?asr~6!j)c*Q+REWu%5$}?BpwuObI_cPM2^Mex)4qO!&vdLwCWQ4?t!sQ04*dVYLxD=d6GR-3Sy5}f)?zeD~ zpyZz%t4qEfzasfMdQT9=ZOo`9*YX}^du9|COQmcBk7Q!*Ds!cFr5Qbu?&2o3v(`w7Tk|O%_@hhr&B0ELs9ZV}lgatN>q` z84}KHkRo*9wDD#EBHRO*JQIYi)%Ch(0i`#$2a-j~o04?Uufwl^9$|7;hMXX^osq%)(M{4b%kT6?F7l)XKAb^V0anAsq$x$O`M z2Vn@$co5Z(f|wBx0u25DqE-oKKO+);jAY?zPIq0x+4vO+7nxk$gPb7nqHv`*NRRW{ zBH=kCmYu93nG)t3v~o;ZlTBI$%%~<`Ewol6hK;ox6^!NCWU(S0&i`~4oNI#IovXe(6nbJEAT4JA)mcyhqj2YGB{z7ZDma^I^{drS# z>Gy@3V_N?H;gJ8=Dzg?>b!=LTt7vv)=iY>5^89z$ zd?G(>Kk_^Z4|MsF5%Md%SEX@&Y_icf_h*^~hyFUvWP@g|L34mfGsdKu&x~sF&mBl} zwKge)0~=?%_p1afts9NhO#k^ATEi_(B{~BuPtI0>a_l0(7ME+b-qb!hDqxR zW>k|W2(8uH_K=wD;x4}l%T)BbjWV^JN6z`rPto?@9IDf~hNSzB5lI!_EeYDI4}q#L zKM50{FvnJFN8QVn2Hm*yWx;Squ(Qjear}$Xeot-R6pB0lB~Xk(GNHKlXdQ|<_!TJ5 z#RGxjVPpgpXYt<9{NB8_Kruw8b+kcim_aMwq_r>JpmhN=s>$E-9z5SwW7YhkNrVCw zzlk;Z^Yjo5Jq#F*ZZ9xsFFhy_gnHiqEIMmxKlg-)_@Q9|zK&li_wzLT#LndfOZzQk zgow}a-VpH-URy*QqtmKFGT|DBU(zaOHqx5N3(~rg8P()5yoZRFS6fA#FcEhMd{l(M zM;UtHqgxb9@T1UZ`p+MmJU=ajqd7L&-%eL2j!m9mH_^)2WatBeb7*Wb57w9!=lsV7 z=Ub6XY+U#yHVztX#HNH7#O6X|1kM-n-oSY%uPr$5VTYWZn}KA~ioh>vHPV11t?tZE zTJ88zH93y=z)0gr##Y8APnCsGFiinxS6!Q~x?iL;$0nb*(}grG_bbVmH+MOb zg|kv{M9!`>IeP(5gfrR{kn&;P8&dwA*A^+eij>JWBbibjYtTB%q;-`^D}@== zYOS`yYPt8Epm!Bb#wN?A9wOlexHr%gn0a3l2``RqD&ehE0dMX=Bnwv+Fd!-6tu!Qw z{YiKt64IuCgh%t3Sa$Nx7+u0FgI0z~>tj3+T7yLKlIwVna{L(Xeqoa? zcf2lP3uBW{J-VFD#wLRkMb>afcm|HT4)q7q@fEA)`W_*dxmO!p-UVxp5?9lzqr?R! zm-itfB+eEtrTcsQ2BZ5snQ0cu2k11r7&HeNG|w_={t|6S?qEhW`77Q-@*TTP$wOn4 zPu4NV;l?H#-ImhU&4G8%)l2*SyYaGRY*LBuSY`MBQ)Isp$<)Drz!szIchV-K>?2G* zA3{dR?i4=tzA&5D7MbljtrHDe=NYtyn6%oPw2GKfP2L-&%iL=RNt_}9~RMakWA6uhM`5#UZcT9(N5&&*@)C4BSbq&*wIC6$7_pd zr8=!2B6V654O-Wlv^L^_(DE>&n!KF%D9$c#TSaqU*UH!=F)WSKm)*=dk>OD%8y`HQOXZ!UQxP7tF`hbQroje%Hl<;YT)*~jZ zQ%qVfFr%7$jL=%G?S9)@j?Z7KOL)lGr0}LD&5=1aS&=W2HXWPH!$b$iCO05rB4A+1 zIMy$G&|1Db*z0<8`yg33{sq<*rQbp0i_&*9Io^+)AP^%Q>*d=R!~(oU`bSADJ9#^j zDgCnst;bASE0~`(;w5HOlNa(HTKu>7tkS<&s!QL(*rae$Gx_z#XvGi_vFX@k9ws{| zf1qhC9*A#RYw>sNkh617HhBI8Mx)42o;lw8Ln`4v4&LY1tHkmcS(ibIz zv{l>#?47f7+Zn92GgvzqXNbFlJ{4rGlP>OOyf>769j`57zM|853&|AoEzlrzubQ+T zGilW@qniA8p#>ke<<{a}Jhrv5$(S2Mb=n-8Ozj|&%Gl(Xxu9wpn@p`fbZoL{x5Px3 zX|t>Gc`Fn@-78QGGuYn`>xb;`q7}rF?`N_f3HDL)Ckp$z<{!gr3lvZ3w1U6tP`qT& zdfKG*H6948Z!n{pypi|N{7c@nLh*V@OJkE!6GAYUW0SgHk%Xr0=s5yGXlybV-?NHn z)7eTxGD|4QU@O67%f<_q_6f)c5rY<6YgaQ{Yw14(u1}FnTC4C&xRx;+;abWI(pt}q zYVvEmhlo#O>>2KT*IkXfHjYgcUr~Z_I7uGEvzj~lF#H^sf(4TT75-D7z}>{*D^4=^ zPr7_pb9M2=y1XQeb+HIu%WbIZ8r?T2{^?i|saJCH6Uw^Ks}M^JT92EsvD5}bPD$tK zzH7CIrozFw=@w8$y(l4+)RE>uJ+_b*De%|DR*%()hp(MK4g~_jQxSQ-#&f@XqpR@^ z&KJCjh{1uQ!k=MUKYod3iE&=8EXd(3;qbX4&Gx8`Pu&YgsD+%YsB=DYFi4nVZ`}k< z;?_pz9-K}p2ab4EFga)&P!_? zRq)kd+S7RJX?VgdHhuMM3p}$+voj{c56k&GcBh`Wp>ZfUf{_L8`}3 zJi1mqEYx>W=_cn=|CPEaq!AUVHMrKb0uC?Q*Jzrl7To6g;(1b1)CmR0uPPCqScnwp zW^PI5jy_H_tP~@`BY{!g6@sr^U%8js_N+Adw5ph#Y!N#$cASM!(JaiBP_bo0s+TX++Q);S+FQfzI_?XJgo9f=BcRgrcda& zH=!R?W(e`IML#BOF!dwOQB^p*P;G>A@PKC3IS~ycD(mQ(0$%2BII5~X4;nP|3DSMu3+HHW;w?B>j-FHM$Ue>! zXMY`a2gkFh2W<;Fq8U?5bABxA0dFGj5^UeB%~>+D6A#K+(CHRDn7RY<>|vpxZSgsO zDD^qF;s|fO)V6B<-0*S44j=ei(1&w5_VJ>iwrbzvlvuo2#}^gf=*8Id`ck+s(V!N- zr2YFwY)Y~v^hG&Ncm&1xl2MFfr5MYMVgy2ZDHhIl`JIm#M^#_{pNGCZWv$E*d;8QNY2 zH?8~ip0Eu#nt4B9-b4L*Cl|9J_$_|D+UZUGdfhD(?g7H^5Bxvl*ZXcB=Kf57z)7a> zqV84r-}37{4Ln(VV}GMxZ(wi>e!Ux!h_>!Xe!VXt+aL4mod60J-v1=O-tJAQ9@($= z>p^7WPw?w~SO|rEtbeOt?`XXLr~37FYR1rE{d%|N{t>_4$MANjZWNn!i#`yYgY{e!V|k#&=uu>vdm&r)}`-#TdXc-bWlD zn3INShaem~k2ugTOkYv3Zm)Y<0fzzguAE`UAiqFer}e+e805Rshb1s!K`Bh8whi*r z*D34vDLMPf5?uaHgOzBUcDOv4c*1s$q}C`ow_i8=M`GIRX%M`KQ9-ffI}X3-<~(3n$4o6kF7vB>g#uJ*>% z|5GuiPK3W3#%3v;kp6xH#uU)cvHt@xr>;MnQ>*_k=2S!=6i8)dTVqZgHRk_D%&F2J zur8&?oa){mwjVb9W5?|p?fe0zZv6i;=G6Wo?C5*}b3WmiQyP*mZ{D_;Q*R@)5e1-a zF{e%dct&0Ne=(;RQ2=ci!!GDCr@p+L)+N?g&ZLS-%&8syOu*U_b82xY_y26nsc2#N z|HYgN2OA7Em2HkW_06TBm{YI!4VQlg%l}W0IdwBWcG#Fx+g)fGEHS693GvZ9=G5R- z=!%aRbE*e&>9LRgcgCFhaTwsT+Ct2!BG7LVbLt0pksUea)JpL+W2~?LoiV4TpJ(D8fo{lvtq}+kHVwZm z$DA5szSc_2smZ4c$XQ&@fVsF7zG%i2^jht_emcn5_F{yVU^eZC0(?v&m4$*jSz}I( zS}7H=<(N~i;a$|yw#S_62Zr!MnA$X9CKPk(YMr30A+{;z)aJzIF{fSw1pnJ&PW1#& z|Am-SjptJvMoWOx|AjH9-a9Q6Wa(gUUCShP>0cFdYI~(Iq8#@)y{+_^Q&r2l5i8&R!8P$C3M-cumh&kmjS^Z06PCalcUBCawVotpTXEs=< zO=3~HYBUPqPw=z41TJ)#jz5B*l?&m-|6)HY|2D*PN8iIS-{Rlk zXEg%v{T@H7>u!VK*+=rTDj9;lxMe>p1&=I#R-5xhEq=3~Rr^;ARrvjWRzC%FwVH&+2PeYkpRf>@-!vepV3_tOY+SIRh-CP+jMuM^+(PZyYw^wL_2T}a z?YIg!V}auGrSQ$-zs#CIN86$fwKeh4wMuYFLC#vw&Rxbs_)(hKR#n@RLf5K2%W^%C z$5|{f6I6R%lY7RJf>WnE6*24`sYxRh?{_J1V3V^&enjjG_cb_EkmvomT?o&vIIH6o zZ&ri^ATCC%_NdJDh(DZ+N5w%znki5e6|uF4L@K^WM^y*AZGLS4!{kU@tP2(;xe)9q zk0Dp0m&ZjStQtab^Q(ED2W<&)bjHj8l<<>52u(La)_a$wj4}B(_#B@KpCO)yqVxHq zy_$ZgI0uoej*wt1xImCtJ0c9WjnBNQ-g&n9?+Kr{_y~OJEJjBnDv;+nTLPSrgthVD z35?|_Y8{x2YsQ}|HD3Irz?pRk4WnyvTWprX+=b z()cF>mAY8m`}Pb*7XKui*RfjrhSR9<+(3M;uJ-CqsZ|}o9W^4jEC4UO{Dksb91(6H zSC$lr!QT|VG#><92!~2v!KZWr3|3^T?o!pE0EfH;mngyIf$#8oO8Lx7a0OYsorXC4F;$M}T6^ubDDam6KQ~wO@<1235&kNXeg+)C&s#BQOA#1W z@edmqOniG818XiCGZJ(!wk;Yyg4lXa^6rf)JHgu+S=Phu-5*nSw7-+$^v9?MaZ-%~ zRmKmpkjo1ujt4ns#SZuB_J#Zmf(y-(FAp22y)pwg>jn zSh5V&1$7zqUmQ@)g<4dLaY=}1gJS;{)gf~?&ebgE$50(glOi9De*AUm@YxpoT_^#;e427%?(% z{bnVut+;-U%{ahy4bqA0H*lyWu3PC?OI+vk1!3+x=2UZ^WI|hTEoM2iIer)PFN~|b zZwS{rmk6$LlTBPlXIXHa^kEp+Zo*%SxSlDG>a`Jqh~RpHB>gU2pTjtZxb7!L2CgIN zq+!MN4rBtZgON^L=d9OposD0?^%TA!xZcg2YHlJE+Jb95HKNV9F4}LwwSkWA_$c)C ztXjeKrAa2Pv1eOwO@U~pzP>}-p=EvDERX871A>U)x?GZe7p@oKRpR;_F*0zSSchvX zu4j;bcJ4@|6W6ohIZS=+OAlh|>ma@$xDFIfa{Dl$Ex5kSa%eNI$6zfl)DHap^boF} zFBV)!PBd|SVSokKwP4f4bu5kUmT|pZ9@T443L=8*jgr(7u41E#Ws1FzJsB6ydOr8{ z_`ICYeU*NZrj61sGPL2i!1>&F!%ncC&)rR)!wH?%r^Oi7_iP+$0hm31Yv*%UpG6L& z7kw1HH6woE1fUjs|IdbFx<$JGX6JJ&M?tLS=W_>troQ-N>-AW8Etm*j75zo$KF=47 z+9Xr^)?{)^fQXJw&wWleWGZV_rmjblsfBZ&Q|N_;q704ij@T#Hq1$I&Jar9fk5Lx` zOL1+ji((t#yjP^NEbPFCO2>5=eg)T0@Ic3PlyH)}k_m0Wbrs8@&21bel)|{a zaB2wGeQyb_Q?560U72CQ^;fWIwsGZGw2JG)@~B>`6+{HrYDxNCxOT^@#C0|?GH^YP zs916R>2kp}+Q4-`EJXGlyJ#u0@A$R`7ox8b!b$FCCbR`tH_M^TxDLA8(sztFC4}qa zZwju*U1#EYQ(p_N&)0`>O`{|&_Z?1oRIgnphzPFzB&kJQFXD^_HXq?EQtsm9tOkw{ zt!nWo(noUXaL}Y7Vc!!vhtxPTROmE0hqNk6djgW^P6|AQ^wL43)WRvGP3Rftnzl&q zq9*j(w@UADXJKYOjMDurXN1c^0Q@#s^j#^ol!9s~e@)r@^%Ljr#Q3 zySTR2r^k^A?bL%vm-Zi)7VFa!w76KGX7L57PcJj4ntKxy+ESlVFGKz|*C(7%6>g_u zaaIUF3LE#r`BIzOA%+dbwrXtdk9wzB{DVud*DE_W@U~uF``?R+h+dBN z55H1g>+t}4w6e5F2#EKW@ZJaoF`w7g_y;tYGD5VIgw`7E=pU@2 zZSKX;JimCq4uuV60S-^nZcYygXBJ>DT{vw(VbcQ424$<1XX>1tXmEB8IAVqCXL8ow z=L^2Wk9;O*-eN7XMQohUlRED3C5mLS&wAN^qO{CnF zg3v8EhkIibghJaO65iMQ5DCY$(^d4MW-8hfRIL&Y*SU%}xEf_}Rb+DY17WlcUXaiT8;Y=* zub!q$>BAN_RK`ArqW*v6{R>=GRr)`UABAIh3%pQdSjWVow4$^^B|Q|>v7n*Si5g8* zKrB>3P&6+nSm2SO(z3Fm#u}!}WMxTZ6Q(7mC6T@zkT6zp0(cVxvk4y`?l6&PT^@v#hs>QLq}_3@Nl{UTk*rtX!id48%3Qg zVvBFV#E|Ob2AUvJolH>{XA#iryjfT*7br=m-zM*!Qr4v`>tvSINy+M~Wc3CR_1j&@ z+NOQ^3yH?y)=0PSdz~rk7ptlSdcOuA!i%fAK<}Rj7v2Xh%=72(r1wwBMxgp|ueE{R z-*|)Y{>W>xzQ$sjM&6I6X&ia~H8MoqPb8q%`7!Za_nYXq$@`O}tko&YL+r@76HA^3}K$E=oI>Gay3@UI-g|?U|D^Ytlmo2 zKmg%txRA9?s||4VyuJa8@}QlI1w;Sj(%wH?dW$xGIeYpN-IS}uPRLj=bRj;a#Yu-2 z3~i+aL%+O&UK0@uhNg)HLtFjP=e|gb^2n3YW3L8=VqAs@#a&3IUYh&148^UWA{0}R zA!-Eu(4E%=^@+P$q=3I)qrKL7CZ#V|gVVeGf~_no1|QP#+`4RHl;gdQj(~M)if;2% zO^Kohu>p(J?7+t9*fI?()X=IUSfr*Gb_A#Ia-s^I9HL~xeYx6atCZJ-{zaOTHZrFi-vzp zezAfCwT8?HtW&e&K2#1(uNP-x=;V#m@qqCVE#8sm@jli-BP!-a*pdsIsQ*ffWhB4^ zY@%L|oT9U6WrhrG!UTT*sHTum z!|3${35s$rX{B8Q(~ySt-ikFeBE;6`tzzr*ODK6$YPED=Ttx-u>%8}Q4Ya|J3fhq! zb9mau2(N{A4ySid#D^#es2#CW>ovRu;NW9z)BKuw^K;x)DRNd^-8sm%=cNleCqypY zhm(VlkEgFJds!8r+C$q_BdZoCqk@uQbJz|;=eJ~^fiRB%lYHV z=Ihz!q~qA!Q{UyhT8KoQ1sGn3zTm+2oxH)hJ8@R$;v_jLP%kLa4xtzG*CxpwaAQF= zfQXK&0hIg2qn%dx$=e?v=8_iG)fv$S+4&xAdA792W9eTVZoNFo!+6umE$*C4);_{l*p-$X9P z^G!#x*y^ctL4>2&&DE7a)ip}J#F44LY=Tdc9g*l7c-4XSS=Fm3+*5WAzRO(@A-lp` zsH}FAH*~=r?ITMz{GF)A25s{jG{A4uqWwfgNWQ>N8hU=g=$(esokxvH5lfo%=JdJ* zEnb7TVPD+x=Rp&%jwr@kFQU)?SAkQFJ8^vH$i=rnB3X9lQL+qohkP!iqz>N~Sv3+r zL@3k<^IvI`FKnClBA1o6RaKfAXa1mc8od_G5%wyHmma5Cb(76XT4-%t*l%2nT(sSI zgBI~R&S`!usI*PH{&&3&`ygmkhaaF0@jlT3giQoDGObl~Xs6&X*flwdJL6O%661lI z*FSaOH_6`J;;~EeYMg=T^P&dr$vVi!M&tWL2cnZv2a=D9Hp4Pt)EZ?-4lEm4y zET_crUTU7)+AXEL6-(KbeT$95=OxGzG1^b3QHj(9hQ|XQqi++w?l1yZwY(YP$lY{o zVO%X2w5ieZJJ6L-*<19@0^*4;w-L`Of@+UJrVsN`1X1i!No9xT1CBspr*IX=$h#H- z4Gg`+NSJ6~WH|B7#{X?NBZ`I~G6&&LBaDd>`^%A8+V&WVk66q}hUn=ABs<<)Ou6_- zFl__`<22#w;JaQ-u*SG4c&i_xJMn0qVrTGqnX-c2T9hy&HIpki0~PG1QEzc? zQFuAt`w*H}U}rWGdZ8FV?6?26IW2{mHvV4nr_SR1D`q-!Ix~zm{w_mA)yCg< z<2P^oy?WL>w&cCvsB0JB`aiWJYE)T+*zTL=GP_@-idC~R*ovRNO4uf*Iy_&YEx#)~ zcrleWa;`w87&rdJffCy`AL9g$uV&~3F6TGU893Os9YBVK6`$h?!>g&=w66~8Up+i` zi1r1bDH24`xQj0_srXy2-K@W{1MC79PVK^o#zAzd}Y4i1R znmdq)Ou7!cZ8Tr@Bu_&~aK9IGy-Y3YGTnl6VF8W1i%_aP=P{A5p}Bakj5&bavWre7 zw_EVe6Ss&phydO(UoK_|Em$%W7f7_7H?BC)$i*opEeyLdBN_9<$i-dJ4Sa9>Orhz% z=5Oc{y;i|3k=07xdec`Mn|N;!{*SA{i{&+#=GD=J4o!eI@vh&YF2Ct?WlcaR9aYl@=FPL zYS?p22uO6a2Rf|a?&O~&IqV^_uv9ET`vGN^}_mm=ESrCXti1 z#)sN=@%|Pq&{stzwRiE%s=DQT-g~2bgWLRAPnVYohGWUz zpVRSH)g*Kb)G&729u!BnonA$Uq}{elh+?;GvFrBCqV7p0k>#6^<~xl4QihI>i!UC+ z_X^YKUSXPdgGoExX`*bHESHt++J)c+el_1nRG@7IeFt}47k`oK*p z0;%-~08FfhiCMlV_>wksIkoV}yBhI-T9q6p%HFC0NB6)4ua~ZtSECS64FL(hZFCo@ zYBBXpbav|2q73nRnbI_`_z)X~N>M1vhl@R0n^Cm=)YW_iPNzs9Rs-SER%|te4sFrH z{f71t=PrY``eBMJG#VemRHfHw6H!Wb6}>V=JHzxt+_7EsF6LD*hTKgX-XKQKnInU^ zjE{m$%0~G&Q*)7eZrBoF-4E%N(`gTGd&AIOF?rjjy^BILAc`1?%OVPr_C{AM&qpIQ zw%w+zw8|IcaQFhpv<8WC02RaV65s))R6xm-l!_N9N1d&N=+#Kj#!F~}gw}%qN;$HX zM0mViwc!FY1KV-m9+grt5Z@Ddyj64}r@6vE=_WGf3V0uw<_dK3x0x##-%Z}+sghq! z){v4-secpG1h4ap**rx^#hekV(>Hs(Sdkp7F?;9DO7l)#AYPtXk~&$gKtAS0?eF5@ zpxj?$%n!J=A@0dx0u`3hc_b!YZ)1cg7OT^rOr~y1{ZT})EayASfOf*a-y)07PhA3E z==R7?E%j+JMLh*`r0*P@# zT>fpbxI9XYd9ejXj?+yVx3etgA@sxahvee&Pteq)h@RRE9rr}h$z}5%aui3<;_|6@ zd?r_y|4~l&c4|{`Z&NiytinETlgxNg8;uO{ZD6{eD)t*Zq(FsIHK9W3gPvQBmF0u1 zD}P!ESK@#Fz2isUh*LR6=nu5{Euu<}yRQ6*Q!U>+MyYk>OC#7Pd?i6nMUhiB-#c38 zvSQtE>S}rmmFvnU&eB^ctt;LE07ehisB}2tZ$>SzUWXZ$=~<2LtjO4r$73?GT&Ff3^&com_ zW`U<{j=W9S(|z%Q(qZQpcfx}Md`dX@vn3r{x+D!w34IsY970;US zRJAe>U);(4<0IE5cX|uO;M;n9b>CD~eDdp#Qir4edxJm9P3gZ2X&>Y!SGqgpVt!*Vcg z@8=?^Yg6RfO#(nkzvU+TH|KUel$%;fQY$mVSzihC(X*dtAUUJc+b;N;i)C5Ik$O^@ zNh%$YYq#X4gnf{^UCUknWn^yG252KDxiSLT?~BCb?cYbP-6izCle_)<-1W_oq=?*w zN?1s`!y$a;3%SYt3OePccFx3u9{NTZm*`RSJ!A}BZMh(Ez5YEUE}~E)bK~H3GG3V7 zE>2tsd!wj_s`2ytZ^%vQzkTp#q;1QsyrLroJ2rP{m&o$F$QN)YN3QLdQuU|S+{!Kl z0Pu>A$#0?-9l5E)H_SLMa%~p`v7RCX&ADNlMQ8y|?phCUy^xwbd_&>CxF|KGmVS^5 zOB-^N5eJS-3hD3KfKb5{2p!%ub60M^TRP^ZbiM(`3s>Qa$wB!YLD^8`?kXBACRcZN zfX!X}A8iW5^uW=AuTnb}hgWuJi3X}DO58j1N5|oQUD@!C#jOEf9H0&R2TVYg_`aO} z13${bUu5A=3*cqE&EH}t(Abs5AC3?FZPY+qRo#4*@;l zEgiiPm`Pma{do9cVQFOA&X!)0Ydhr*O?4EX9_4Hba{-r9t3W4^d`={TLDZ`iD-txFoh`}C6 zw86zd->88I?>5{g9<}{<&-SBY^aivWDC17hAL-&=Y_zSeCZdRezy=?V4lJ%vAFKH!f&%}W}^ z8#cHDM!_hcLDBS*jUE-W6B!q*6>t;1tWML(T?gLjSae>BH~COnRrmWbbA2NQ!O=d& zf8q;a-0!~jkajItPt}!&T29fX0V8tIp|KAWWyp725LxRt|Na3lqs7Q(>L2j9>Gu-T z@0q6Gx0rsPt^X#wqf&5Qjqz0CT3YREg(^&@ZA!j^8~$w=J_<@AM-ALf=qwoL|5uBz zMFb}g@o<}#jK(=2tczCmtYDnw9ebl54*LZtJ;ns1K*Aw9hyEfuLa#&P7fRZf@k636K@cZBKpemomd zT5Zn5Vpk^DDDTZM9=C0pG4P~ST^SBHuoMAa1k={w$|%MAm+%ztnDAuWiYfcLqufI# zxqp3f=~uZaktt27cqgMb-r7pXn&@-~@B=LiAxQDoQ!haqHP(Q@)A?j1mu++)j*FB=o%gE z?H(sm?%@=-C|NRlTNS3L)tPOm@M=9Rh{xL94})mek)c)HAEn<{;5TO4 zs0Z~?T|nC?!Buvai?=VLPi*{X8ko&4{wQ*BBCPN_e|RX|vA90+9(=Cg@HeD*f2?$V ziX<$25B0H2=o`#fA*KXq88^zPOm+Bif!F;%D@nyzk=OkjFanZBDu*8~ zcBTz)EqXf5dpOPeb<0C|sTkw`V)Ey9A;Y0f>~719c%-oaeg9INgkzY#D)-F&giJ zwyN$gfoyV~1WX60&@MnWPIl!0+$#a~Fj>RqwLpFOXt;xBL5qJV##%F*Ja;=kLkd2J z*$qO&uYQ(O{ zS@xff#h2mj%x6A>$1p6y-{@_ki@(Kqr>7PuJhP%n!d4Bd@A)|kji$<+H55+CtTKOx z;!6;ti^QWYD%KZyI-`l3aD9hDSvrQet|}wh|M6nHSXkx{E5+j2`qimQ5#_L>$>Y))jQ(Upr!vKt&nc6R~gpy%BTyWt2JI>aLC=UUAp`Jj$+ysip z=g*@dlzzMDSu`3CB+^S;S@@jjQOIgJizbi4fJ&!}bXGB2R*&hX^93sIb#@ZgIA=XT zTzOnI>8o^>Aaj%RIwW=uchqlkdhn|QWqF;y=18@ezBzy5-v$2YqjNDMcGdwWCfS+; zCJf%}kN!8(5rNBgQP1~*DZBiBfY3lCt_hvPAKiqEH%JvuHvZ_3sLaBRWII=)e?X^F zBmNytzmG0Z>Cn<+1xAhgAyGeX(;QMvA3arl%hK_ULe=GSL0V46+tk#&I{F)N-zI{l zs6WX1$H;C>LPgtAeXUf$^p={Ny&Kcb90*aqcp55gsb)5xDd|lbWK&7BZlS=XLzsg zmNxvO!YQCS!4tvh{e&YfIr*q|vKiyplaQ_u}3GX3#yohjC|fvxblE z{D{R_=>yk~qR&4zYZXYL@I-puaqG1+4>p!Hcffb{blx+~%kH1ABJCe=kLkC4I_afO z7lmD@&!nNfxRXEnZ*$aFj3&u)?k`u}LMtnqgcetp9hcznynXRgd(MA&AezVUO-0>2 z2l3jwGauP4GwHrpfWu(L?Yv#oIJ$@Mr$$8q@DMf&h)Tvt{P#NT63<6Vq^q zxQ<2?zaJ_6n>=-cSzX%jJ!zP(rB(JlFfrUQ>fp!twI9Fa2ySjN{sF7d&1LoZ2h^B; zuQdH`m~CLz=)X;sj#jh8jToUCBYdaCtAU7~yGMSY5S7D!u8-7orvM>m#&3y&Pyr@FMfB6-A=7*pQ37 zA&W$4zL!GPa!?g{EqIX(=}V8YoQ$KGM!Dfgly;W|Z;j}<41A9or9Jl(0_nZhr9CBo z#A^@ZhY00p{7|8!<{+vA7a?d5;)M%Q{&rPY81)sjp~ zd4f~GXApm{fiN}9c>~BwDdQ7-rXefXR5u)g?i_+l?P|ygXi(lIr6hk#lx~4{2{kI; z>qd(1rEGNL+``2QD8!E#Z2^9W$PB}eHjxP^Gg_6oR+Z^wD#MybvSxTDoA>%CU$3sZ zeaGv98EOpiFYpX+nAP1Awpp7`wSei8eRu34MkMFQ7(%%_)}6!MaWXz_F}mZYc;Cq! zJ&L!r0qI>bqEK|n%Si&dBcyNEg6Sm!ZO;p+DaPRuE2E1xN z$K!}hX(;>h)XHn37GKl57&m&HQ@df?tuDfmlVF;FUx{k)T50>52Oah=*;%9DE4ICqQ1wpXhD)SMjb2`Rh`@}G5XxF z$h+@YG5TS+)uQPsfiXJVyF z+C0n}qiZb|?d;YrF&fZAx9F4aj}@cK+F0~Dz6&+SD7Aeo`p&TE%41se^iXSz&M_>i zqw(k!9^3WF6^}ea@g<;i-Cghl<@$D*0Nbc@bBrbP=A ztuYF-Safd}w$L&{o?wiUdg~TdiBIE$joClqg=aB{Z&joC9d12F;wL0=dm#b9&blnnmV#7Vjk2E`+i57K@gj z)Gij4oTgj!fOthFSUdX_uR8~}{8>C(Q1hp-_OWP0AKju3$Fyit8;ic-*@Cj@j!x|o zBfnwMbox9d=!|arU~7zSv{EvLhDBm-@MQ;ki%#$?a&AA}qCI<#Wl^$jk?9&a zQ@{O_sc!#7Xm>whe~tVA&n{F?IJ12`i0rR>(D#@gEV#fL(+(DksvPYS(<6pO-YaS~Q(!4=P57{fFh1g_=M8cXNez;`A8(CcZ-nc4q!d8;d^Y*@LoZO8Zz;hJ8Y~ zCrm!3MVkj%V|1;>qMb+EB}S=d>lS^o`&hMhSsRO9=h=gbQEK~GbP~4M;TTi_yi(_==F)dnno;5~chDGA6 zbMF!R7@gpGa6I$ie2$9o zvCr&L+6xZ_p4mop+1BIeV@RD6vTfUz z2ludlP}|Uon$ggG*B_Mq5cgtae0`t&s;5*fX(G(|W(;*u-&)2%N&@kbKby+rQOa)z zOev+5@*St3jF9p1?J7=4DImnV7Q{;if^GYHnYGLW+^?c&D;d`td;`h6L&;P#F}Hl8 zvl^Kva4YG$Y`s@b-yz2txBf&&>HegDpe^qjyqj$KMn&z9*)k|cx8>`PgV{35&@Ey+ z)Zj&2Zf+l2hUMzE+!WN7x6TTT%L@iC;_|_M*;e$C^S`U3n8~^=TRsXFmx~PDA};+5 zUc@E2eQY@^Pq$@cP+Okv7Z{i21}|)R^_O;u%g=@_uWSz%m(vX0A}*Z`Uf6PO``Gf% z6g@8I1hu8g6&RO!1}|)Rj7OV4*k10xO}FLI#$a*jVCWWc`RPpEHepMT_OWH+?Yb?I zL2W5EbPHQ@3|`o>_@{PhFBj+Qw%~gXakr!GSm!%KkAtvfm%$5Lers(PTRKkFZ8?aq zLKaV7uVTZD=_^i@!jR`< z^&km;rQ!n&J2Ay21-jx7@F6}$BtUTjMP#s2#PgQ^ink~}k>_0Lcj$^o+bI?vzHxe> z$ImK0Kyj6GTVeZE#d65vs(4d={cjVIe##JzNDsva80I~pCx07f=!&nfQ!FA<+b1w0 zD->^0d?Jl+RgtcE^ZPb!Kq$UYDdNU>rsDsA;(o=t;*6k*Upp<(<7b#R9)m8kAj%d0 z2TZG-shjrd=3p^7T`A(2bXB}X@rgWr^~};0Ck0jfR7_w@?pORDQ2eE#cteAY$Ko7u zu2RI;9;X?61C10zzi*du`sETm2$!)fZO>niovH^x*j8omVt)LBuU%}Zo~_&R+$I^9 zwzl*zbPHQL7`%u}zxJ`^@j1FJX9l%p@hO3EDK>Z!m*rZ!#AWWCx-Ad97c4HnMF-mA zGk6h~&h2B%U2}C?P6}$vR71C@qj3f=Y?=36yTs+s^K@J8d^cEJzQm0%^Y!<3gBP~^ z^liJ?a`t@PmLE3;vt^8-Tf`;R;Ds$Y?PJRU!-V-xTyhucw!HmL zu(%8`bc?t|8@#aPlJ>FXQfx59^RU4|ZF#td9tUAdrNIkZ{{D5l#HCZ2Zp+{BIkNqA zLw7@WJe>=Fx4sq4*9{rv$DXX7$SnF)$l@TK%!UO_--RJX$`GEWWGmh>TR4$9W4Fb+ z;^p-+A_0mMC<2GoJI_2zZc)5N@rihR&`=y}r&xHrNGW2E3l$%rxXSs+-R)NulOT($ zqU`Np5%H;at~nyR6dz#NiCs55UZE@QVy9R{q(CX+h-4|=qWDDGz`d2a;`{z-DPqOHTHlWr;{ZK1 z3jM9n8B2A=X9rchR4HP`a}{s#_(T++R;4R`VV#Y~V$|)W6!BF-n87#DIMDx#b{U0R z4O{xLEp2DwcXtguBg`^*G5dY!^LDZ2!+Z3&EPqqRrL8T8)jQT4mllH;ap~GVw(P!F zx21DXTc#VjMV*Z|coCNcyW1r$Z(v1P9tG#U5iBlWt2ezlE*~4bh|4dZwTmqq@6&Dh z2`l)tIctkJ++^q${M808Y?<6Xwk*RU#~hcOptfvL?@DuA-ZXe&%V(dqOI)%a&~4d; zuVd}wGSbj3;&P$E3tRrwKDG?O4!Rtd^q{u9qTY|@xct@Ng)JL)wM$&Wm+Q8yUmGkg z=NP(0TuwE3VasLhW6KW1mP>-#@~C=Co8#g&cwx(PP3;nwRS)WM`TJ|Z;u2}-7IE>b zx1+0;2wTo`uI`^j8<7SZaRNo;n=t(hE%;9qZ&7?A9$)gPuDFw(V&QSVQp6sQReXTrDreKm_N$5~A&aYG z>MJ%?AtLZ`$H0heP<()4Cw5h^`!QYd{+DeOi-_E!6mdkZQM^U*iCh(Y@VKt{&Y+6* z_qkc|_6{6@KcM*9Cv?RfgDTEbidb=`;w_3#r13ei6Cl@dBUFGA}e_YQ&#zl&B}ZtGPr1rTpHgdzwYOFI68Egy*{~{APpC zApBcgUPRYJDbE=ah1CxkC4|*?GcQUA8)h3Ngbnjv4YXmZg&$||!iJ${8-#>&42i4B zre1~(!mF@0HvGCxk3%Mv_%cPep_KUT7JfbRh8M3G5=0!_D`;qo(Nw>nGHq?zwK9bx5Ajbu`I*#a1UOFr6E7?HOSya z{-ug654lLMp1g9)#E^?*$RZKig_a`fJvx*kk!ZUk%_*YB&o@$N2sf=3GY)4hEH&ePPOPKi(k_!f z;vA4atEqMR6B3 z)@Qi5;@^Lt8#CwMWyycmlAmkIUt-C>%94K@=LZ;U>9l#;y44nItComiXcf}QS?NAj zLi63vDX0o@Qkb?y?~G!gy4Bz_34e`SN&ueri1XcGl&8&-d{G2b;ukS* zY7RVzpgGW5PE{-5hV~c0%w5dfe1PwWwVw$8JMi^{A85rxe-xHW^YygV7tZAmqr8X8 zxBTOn`XU&A5O|I7Z(8xtmq_}aLWZdSswFM}eRD?Wyzu`vgD<6c+{%Nq7hiyFS6wQ9#A{{pM}js7KSWo(1V4K+T;-N2s+V``_VEZRXy7Y|k2iQ)VAU66;Uf&biSUPp=@tvT#=PiH9`fN|2BLuY_sj@t;=VQJ zlu}B0N>9;i+EbaHh(GkiRQzG>%=85OOjwvWX~xv%qHdc~T`1`Arn*`>X)QQo2EG-k ziz%DuaulC}J*-PoFkQsf7I_|;Cw}nPpe5u2$_`8fgNUTPZ-ikTyb+b@aaz?stWeSI zbrUu@uf<%)k0W3VmScnZUq|DC)8Neu6S3-TedMzBUi=Fvg?e|xZ{OArWzT0`tXR13 z-xxJyKKK~l?f^dCw>5xI^sNIg^`kw;Ln<~27>i{dIgK@)x413pIx~uTI(=N_>s_HE zy`ujME9RFAs~DB(`_#lQTrV2Lb9bwP91eIvc;7#OWorV*<|^ln7hp12>0EUsi1)G8 zWJA@BE*d@k(LbIimfK!jFD$F??5VsmhK6!1Ygt5i?1JX>U?+txo=WV5?98IH$}6M9 zNPhd1s7=wDIU7QAaw8^Ld^(FOAzkOrgG=`A|K_p&wBw-mxi z1r)fYXNkV0W>ketjquGxD%!a~`3Dr~CUQyNe0I)|6ytlmK$yys-M;@6TkYJtg!OX| zqLo9tne}Huzwg!}-Adv;3)jcfV0v(j635YkZq66e)x&y+GV=3C-h&HudBmeW6MYZL zensVdFSB4Jk4A%!rVC#vA9%O#0>c+>qqXpsW0^syh3gy0%5zzn9%SFh93AME;$nQg z#s-G!LdJ+vSwQ*bW$C4eS3&p)1=&4Gl+g>E>AO?k{d8qrGly0_qrvpJKGQ{4p_F2>a~u9-OuoYgBYo z_JfvaQ} z^QiN9^!e-os%vtXeK#1mER&AHhi=djZfOmTRR7Q=>DDlQ-?KWtnehGrkLXzL?Y`=XI>sdP79D4m z+4z)>XPH;&SRdx4?lbfcj-0| zk6MZFt>Ff5G}cb{Gm2#|TpvX}cEQa`9J>aqg|3C`X};`xLKkG@i#9ldJp!J1XifBu z(827xuck_ghu>L*^ACvYPjwAmSnD4!$jtjD@`$MN$(wH%AO@yU^IzblIZ97chVGqP zS`*>ZWCs`g!u2bV?91U{OI0Jacn@V1%+<>iFT5!*YjF|kE<+3PK*ji;xlPm$^KM^9 zsDM=BefLk(rHXtW%LlR;vlRk=!|hhQ7*#*MQzjdD?h}vCR~brU%WFu6e?a+J`WPZ_$s(=5oF@9%9Ca4f z+qsDG^f^jB`v|$*4_c`o{3%Gj@1HDB^fcjL)^&Ov5HDKE=yVZd=Hq?4r|P^f|DHfR z_snl>&iF!pU8NN-Ja}PtTMvZ3h3ka}RW|vFGIH1S;~tp>-sj=IAUtwYN*^9`5EJ5Y zI`&XZ&a?s%aKDR0>6a+D4AW0bbP=BJ(R|eW%%`QQC9S07qDkx)H=G_k=|IOXrTkyh z^nBtGRys+&eTJ%Z)ixD=%->WzkkUvOtlLb3JJ~;;`xWxDh=0J)Ks+==`2LDPK;`%1 z(Svyv2I@~rxuD2*kvu=Q8C8C=8V-0>?+evbk8fi3GW4%DJp8<`jfa%)oAHbu5n=xf zOxwr+i+v?})8)HXv|oZ-ECBFWE$Q*j!w!^Uhi%%f0-IQ1#d*1hMXHB4u-KC3U0fru zJ9!~!?G7jIY|w^TvLx0|PaC*CE%L<;*ym~sx~s<%7BToEfAlw~A!VPc5sQkL?R=9BNA9Tky3CGQ7)xEZ<|VvbO;uXc_m=-@`2W{;+z~`}hMe%G@p~ zj6%_nc2G1JAI9)A3u4wE1HXhMg!1o^977fJ`x{MPD-NY|- zMU&bn65T*Q$NSy^Rwf~)!h)1`upoMmZo!xLSzWbQ;BI5V1)QQRI>X=#C`Kb*v5!&K z3B+jcXS#(W+gPX+Ki$lVI5f)*UKsM~%YWRE7QDsA&F{5pYp9eVv4&z%N8Jow7&53G z4EcGdZpis<3_+cVjVr3Ue~-B#%&u>X$;|6dpW_tX71R2cq83;#EhY2^P| z(tpf)VB9OxcaN8LLwSbWpNjY(<18r>?)NsyJf8pA(*F-&wia`~^nXQXXe!$X|Ixk; z{~wnA-!J|5o@w|`Kgs_XX9o;^&az+YNB1oiz@KS~w# zy#P6~zKcobTm%FTvrIVL-w=8{e}|Bgs_v=eZ~P$XZ zf_}F3cMjQhXsz(Khw`_=5UKlnsF(2fW-DQC8 zXuNN=Q55IY?vEkew=yv862!n)5tufV(uxApE~m6Ca~f@QQ`LPG>8*WD#PcE*&qNE( zAN_1k5zT>ADODL++BMap#Eq&%C%we+Vnw^DLGQV(h*kYFGFDqvtlqxIh?Up*`;THQ zI6$N_db%+dj5Un~O_Xat7Mwx8ZGBZ(`j(36SVM(~J@%FCA$;=-!!i=IY4?f}dsK<@ z^b*JOZJqS(EtsV3eOmgKseJpo%J8kK`$9l{4UecNi+VfgLpH}qo9|E-)EY(E=04Kq z!=%vLsJ(fQFnO6OFv}=#Je%vJ%?ruq1h%=GviU-j9IrE-LR}So6_Lv5ZPXUz*u0f~ zw(YA&AzDkrW}&F>?#i^?OZ70YFV{th`0o*hpl$uTN|d-zm3US!aXepEN?-btFWIYP z{A+1Ha_WbZbTOD3JAeAO@a20VmC>ggzBufC>4{{m^c7**-=)ZSUz8z|eOVMKeECKB z^2NiV#1>WJ3$OD2c)qNbzVskpnxB-uq$pn=(#4Q3-$-A+kiOh~nqDtO^pnq-^^|Mh zPrijJZPm-d(o2-3Lktz{+cPH%-!_q9XmerGwM z;!?fD@qEdXzKkMY5^H2V^BAUJN+jvIgY=XH>N%P8?0C@tqJ5tx_4G9KTz#spXTO}Yne|*^ z(X*NKLkGwI0>)YDJu>0{{GLl*!tZcC*eCuLSTkCHzhU@Btvrw7Tb#oSdT!pC{h zNDDXX!JGn{b5?clg&dE!+j@_`9s+YPFY-r6($pC*CDFc=Ugz~l!#>Vk3b)f0ikj0_ zIsYyv8rP73O6QeC`~!}lg&TUEizTL$#Q1v)Oj96cg2bF9F{ev8YXUJ(Uq=yeo{YIC z8vrF}MpZJslQq3qrXPjrW_))ir2CG%U<3^QCChp3Fe0-3%sNw#cdVS74xr3R=NTZr zahQTB7peWMhgZIh_Zf*uyN>iM3)C}8>bb(u6KUvKBZF|h)N?lJ`4v+Ov!2c*b8MiV z2Pth(pq}fco@)#}BMm)iQqSd5&!wcN%A)6blDQwVMx`A7d_id&pEsp>oi|H8H<GxYS9dY+bg9w$9xEP8GxnQ4J~zNNIK zfqK?UJ#QF#HpS}xJTGThW<5V(Dr5HNNs_r2r*xGFA1}aZ$rl%>XS3Avj-h9~p(jP^ zd4V!3ozH;y7F+cANYC6rJp=k6ZQZj0y053S{Wa?D7jBHxe(-oK%y$_Ee?%Ldy0xEB zth%KS?Ri`2+)MKv?-s2IHWQ6qKra>c{eY6O2l}V7$N?0=pp)>I4Y)z^|5wNSwK&V- zn8tK8(?q6mOuH~W@S4Pb#dIUnTBggGmNG42n#*)F(-BPLnf7Gbk?GH`NqV(2i)lI2)l9!)dh%+C z8^Sc3>0L~pXS$W?KBnDXlI5;rx`OFercp1;{IN`zG2O`2@ruk(VOq{~HPdFMoz_TP z6w@=9#xqS~dKJ?QrnfMi&-8w#&oW)Z^c|+}Gu^?ond!fo9%b5%`^gzhM>D;Rse|j~ zi|3@>?=XFe=`yAjOy@JrWBO;NZl;$q9mKQ`({42*vK znZ`0*#qGzzyc!1--MwovcAR!N!atQXlW7gp-Auzj<9wzyOt&)K&(yP9;;NahW4f1V z;^#8Il<6|2>zH=?Lgo))n$7firW(_zJrd_;TETQB(~V5`Gfiri_&lc7OzW9Oe93y5 zj%B)(=?$iDneJv9{x!>EI+tlJ(?+I_y%OhPI+kfU(-lk`IX-)t9%8EM zQPHharM&uzTv4wo-AvzE_j5Y&cueiU3$u-t^j#9xo6m1<@ zedy|0|4NzT&iL(#x|{F$z!H|M-h`yCEXbr)|bx%m9L z4eo#c{$!6={@gnmWpK2P{72J2x8F2siF^B9<1^2n_cQ0miF~UcrCWCg_yNAYW#5D! zAFK5x9bLNR@f*Io?edGS{qD7^dVYKPo%KT|r+$0Oh$U$^J^gjxO(R}@qC=mW4(E;R zA&!M3$B5%yvR~LCsl)Npv^_Ht?)didXMgTq5oHC7Q{`6TQe{$i3f;{kC)G=vVQ6b5jkYA8DLC|T%Q$gwQ ztd;h-qNE-1x5;+e_?ApB`cq(f(>j^n`yQE|Y2o))$@EqWZFnQFT;1!EZ>^E|hQ}qX zVY-j$y1&ZwA*&>fWt#Sc(96iO|@S44RPrZHmlxZ_3rs#xq;uR$L&taS(k73&^&BpmF41CNxPKOJW4$3E7IA~p?Q=Iqmk&mY(j&&BC#{MkpE$#PA zm*>%Zru$Bo>2o_us_a?QLGtBYB(?fe(nF>f^ptc}xTH-EN#~v|cfs@-Q)sA$ zrSiy|BkM)=?_lHLAhu7@4Cb?$s<^p7w8U*g+qhBuVm=APr3AqxY!Ad01;J%lYr%bXgR;8ERvT69KD!GhZ$#;w=#%4$Ge7Jk{1Eq zMZ$2H}vrS6OH+^UpI~&2$aZI;QKG)-xr4>7WzU1fml$XTjeD{1sv{ zI}3jk@K-o+B7RTAZ)BJbPe*C*Uba`!G)~W8nrWd~%;zyJU|PbolxYRiYNkrHZm&by zGmGg2riEfAPV+CCk&|zdQaY8ObfT0_l(^_@lOnTjq|g z`0J0q!T6IB1KVZRLZd&BJkNW9?YHz!8LzAi*)FZ)dtG0duBaLh6(4oF%+FxDk@5Pt zuEzNwr(MYIjOnH%8(L<^|fnzpqTs zyIIn?nUXHONz#3>lI}fS(%pR|-Eo?v>tZCWIaSiR(UR)+Tl6I@lJ@MoSmK%!BrRVk z)3=sN+MDUv1(H8>fy5Qe546AANUGa!(YJB2v?n%0;-apTv>T_7E|>W$%Ou^){>F}zxPk@Jel?FeWZ~6#-1}dF z=aCXiKMaPevEWKerM}o3WWOW7Xr2?Se1oNY;zC)zmdmSh?oEOAwOYyr!^NPSi;TcQ zV^%B6ueY4%tnxjUa;1x;U1_7GUr}X}Z(_cc`K4oIeq*`JcmG-Ps~1bY{xUA_mGzZy zmrVC0%JkBuGJQ3tH(JukPZ}o=J=jV3vjTQfWa=&UT5&NA66XfB&NoP2zG?nS^5z2P z0;PlErs6yH{lI!yV}qlyzZvQ3pn2~h8yv~C&YRt^!^H)aUR(QYaCUmv+2HK-?zh3& z>2<+>t9>NT8kg)KxN1}UNd5{7F5zz39`gbY+XAN0^SymHgO! zl5z+gbu6#^CMieBE1E2EgQiHDazIkoPm*dZuatQu&s`zy>z(=E$y>#G8d%QC>!h4G zzD}!VKJg#Yj)dis#toPAJ5Qz5AHmmqM;O2K89Cmma@tl|uJt{c-oWMZPOzM6_n99X z?U~wHtEJu)&U!r)x5d7_4Ei8YURn^GRbFWjoK;?J5S&%s?jSg;yzs5|epuxt2Eke7 zWd*@m<<$nkS>-hb!CB>nBknG4%+zmHy;2=%SNUv31UWm9AHaOC6^>>F2&Q5PP z+&6KdJRQmp^Zk@OwSSl+$V4$oem|>4wn}qZbgW=h7fUwLd3;?)YcCX zm*0){M{&sv5w|)-Tm;5L7X=T8Ieyl0GAjtq+TSWd#H|hy*AybovBTaEt9|hy;xdBZ z)*xLSbpBWuB5q$0ob`T~3-_~JtiW6!*85zUL2y?4DuUpwde;QOS@rG>g0o(C#Ns-{ z>IX%x9O>lOSWvp7{XA#_C>_dQ!e#H2xFk@*Ed{m1r8G%gEGXgfLG5sHyCm)i{gWBQZnkDWC2p6}P z?PFXTcspG9HxjoS`6O=;cstx)#;pS-T(@uSaT}3Ney;>2+`b^VRo_Y6Tu{Po1+~*# zu1VZjP{KVAYKP1ANn8>r;g*8h;ZnYrxL8oa<%8Pc;#wr`2;tHkYRTnczQ+!4m@03~^G2kddXkuKr~O1SWY_PBLO z7x4on++G_TjaLna|g>+_S+-|TU#k**G!AJhfGS>-i{h>Q47 zXnGTa;H-MHLc~=B!L{+%21nz4Gt$*TyyI|NKM0YCbal|LEE^n^Utz)#UuT1(@~tKu z@i9l*>ZS4-NLL5(1r{9T;cFMs9tc@$!V}+U!K-!?W17EHdvJlz0Hs5n%6gM}8~?Z}o%l`3~bb5aIR$ zR}D&sl4qy4o8Mlq)!!5woSnZ#HaI)I8*Ol8r*(cD1;4V$2^`7~JAaFS3&r1+z`2C9 zKzUYwci7LL&hg(JJhjxhQq$&WduEsoMFkWS^YKuKP05M0Ko5*H6j zxB^fYC>=^K$?F{>ar+oI1iXttI24Zhs^|zIM zB(4aQa2rA0pmdn^&OKe?MuQS=6{wxOv9S_21e9>)pl(n)%<_`@N?dPH!exWH353Ip zi#&ooSETjdYT~29$8&E_>WMq!Vr#DB<=7!L95kaYdkn z+X(6grNivU+_NNZG$`R#fw~EV!;Bl-U*d*<60RK7O&}Z!M=dlvPWGeTpoCin>H?)h z;iz4u4UqEUK&k!ZgStTJFyms+mbm?lO9F4#f1}Qkdh3x-dfnh%Bn*dHo+Dn$YeGKZ zT;Q$p$iCf-TLVh=9RYQL(qWdj>Rc&rE-2wPg1QKVL*Y8&x3&K!p4S#fdTWtR_X`pUUI?VEFE|jPd+#$w!z`F%15a$>oaZSi4dGX-waLtUX z10`I~*hKnbS>!PQ(UaV4OHYXEhF(qZWi$3WiE~9#GN#KwSjFVaBQ(akko-?bHR6Hgf*c<3nV@u#U)J&>1OJgfRKC_y zz8-uHgQ$EC{!;Nrc+o$vHuRCbu~)Udk3e$skgg80s}y_K&GxK5W9&VHxSe3Z_!=0i#^QTm#bSzZ+fX{4SO`Alr4J zYrs_cPrSD~V4@3213YH*%~W1bxlxp4-XV($A)-9Rh2mNPd(=VW(Q5GKdL%Z)HR{m=Lr#4YJ;P8P>*zVQ2T2FpT!9jkH|TP);_)b*~CTZ zn+hjfG5BgExyILYyh6o4^ID@k#lIAMDDgi6oONDh7yrbx&~ODI;%Y<0HHV0cNe|7w z3>zH9e<{+{LGfP&zLXOv9u#LWf4I)TVZ}p-3%uKcqyAv6kEOtcQXdh23eAs{5OH%u z#H|Stw=YCoTt;YqjJ3hp)yE3pN-c4w`bfAwus$-tXV~BzmU>$UoZEt~tXr!xyaM>1|XNi12Rm+#}oj$_f!z6`XD&Xf@=+eiy0GWp9{R%z6P$pHRUqC|Eu~NJwLGi61ZKc zI9ls3!(ykxS?jMFIJ^4WYVmtV5P3%|xbQy*#z*NLgmiUKJr4IezLWIv-Zi;j5ascjwWNcv5_XMYj{hcF+;{2=CCEF_M3an40rEc2%_ zzBlvAj&97~#QEXON3pyk{9R-O^ZS`+I2MwY_LXwJm8kfSz9}&NW5K%^OtKWN+=5#g z1h>Y5TW7)1HimRquZt$AA4Zstqw3B;-I_cQtX4)s00d+e4h@$UBCa$<+?o(^S`eJ|JQj0{y&qPbCq!IHh`8DiaZMrOBK{JZABiF2 z@+>%R5{{S${x|)V&JT1>tFychwZdXY?fO5}kABRH3#g7a0Cz!m<5OEbD;_5=gX*M{TPr7b3<~Q^!5xn)jvK_A426r;b?={Km z2Y&SUw)Z#bK1xQ8p_kToD9=LtXaL7y9>=ZqF=Rq$an82Exgfg+>FU4|ypEcQZJ!&t zDBXffo75IZcD5p29W*yR1l}4yN*|PM;OM-S1m1ezg5(wD890i2DR5SKgsV5nBU}Uc zQcyZ5f4~I^X}O@P9Y&bqP53DAD?sTWJmKu>Wd(3{_2S7j>?Hp(z`H={Q1Xac?W_Q< zlt4JlIIDdPz*+6HYhU4$+qN&ecqbsPp~QPGaOQYtE)SglY>b!l9@kZp9$~)8f>ZOY zy~89<%`3AW43xJwOXl|;FKM$S-!n<3ubCjJj2y1_AChuvhsg9$zk4p?@1KLsFR2}f zcKo1x-l!oS)Lx^e*w?Rho;5l|Tt$eubwO~}abaJGxY*n5{jkbQ3lTThf>ZUQ{SWKM zZa(q6#g38{|GV~Kr$7Am(Be8aMBLI4aqDbwcIT`8z?u8C8W*kgo|NC#5A2rUC_%b9 zy5mRuRQvNmEz;FN{b)6Kt6mz%G!w2L_{?dY90goZjf*rMS4=nL(Rs2Fe4_h@S15n3FrbFbDk9=`$%83y->r@^S=f28kS>20YP>bSy_XVR8;7;uxQC2n#&a`Z?m}yS7Yu1#ayy?Y7tQz<9Nt?)=IAPN5jtM7P zfc4bBD(l0#E~Gn1di|Op_#o-^uh`=oUY6+_d&>0=Gnn5$O7iNt#_=e-;V6Ac%FA%^ z`ie|fGu^7_6*AwkK+@fup2m8Wya<*Tcg=~}b3A)XR|op-$dmF{zbMn!Tr7F*B1tQf zC2eAwaGm56{vq*E>5^YJNYecCCGC9yr%#e}4fAs^l>Ca3k`}ln%}bDU?l4KWGM_zE z^81($;`}70Y93R|d@0LY#&q{(vV2XVq!Da)Yq{jz=SjNsa!Hd0N?MX7X~hUh6JL=2 z<`0p4LA<2PxIPD6BKfV%?;9@pm?TN_IBrKczy5hCuaVoIsy9{t9?su?tt_wd|0q@R zOP-bbM|1o7U)5jC0BL^>)2&P^c%4yIe}|a2)?ZefEMLzwgYDiOV*NeO^%>9Q_MKpV zQt`_CJJ%Q2>xtI?8rG}ozoPb!wWp>u85bqb!Sd92mN{AGC;Uz7IbOexV|mKo2rd`P z?XSJsM-Tn2TqWf-{+Ip1!{-gvers7?X*-`!{@3lTlc2`?{wuzO?XUSS{mtMUoCFlyo1{#u<`N;`}4CCBJHp zqy=|MTF3cHzJulex7UBY`FgPQF=^*Mwlktc>RUEf(s)i!VOqe{!R7KJsjw_AKzuA@}SEoZuesj^FrtIDp@N2NXGY){B>;BtFee&#}{ zH=n7}r_NWpzDK0Kxr}?B@r{-0voP*0HGG3L7Q1RvTHv0HJR1D*mGS!Y#j`o?! zpNVa5hT)G;H%=&`zv4nLs&Rl0yJo^P`YWDLID6PtITPuxP%!Be3WwouQr@uSfA4f8 zvFn=&cSMLy#mUeR>iFjQvdWWx>pz|Fs6l1-RN)2B@;E-VB$er67;q;S|U z>}8WvI5Q{yTx@JJDJLg;h8<|?gp%wP`C&+UX(_YC8HF5O$r1MgyM z=zgoZAD&WV;OM^ZnjkpMBCj>_K6&AhKL2%_W1MRB|5!W09XZC~q z9aL;@pB0xC1ZTCc!Ujk8v^OGM9aJBCZEzGHbNtA@1o-a)rNbP*WnM!b;l>8RS?ybD z!3n{RX5PPRbrQXc==cA)K&I23cya4}h~K{eX{DmwkUufAR21o6wsKk&*af}D=0;NOgwZpBn!4cjXZ!JVz@7ZnjlD}@GtAqT_ zu)$G!sR>6VYHV<1pS7O%+2BZC?>UBE`ZWZ+3zQBO7ow#m9JQN@AULbuH9>GI78`m= z-u@7Av3It$kK(ru>FN;UM-W{2Tmwh`mj}UFw@q5LKLMpqg* z@^`Eaj^r&f$s^n<8yv~C_P1sm9QhHoz|c#6WPo>p(xL1ly3vFqd$(F}Jf49p@Kaej z?~%VNOXYXxX`u9caUEzDC>>_`@e2(*NPYr%7lCk?an}84@`B(t-Ww=yX^6PG5OMp0 z;H>Ks_r|#F5?%!QVZ|lc;HbW>w@q2f$>OHDYc z$7&lK;jQCWqYaMa9WlwHcH6DItzObI8tLkw^G*?X>wCzA+iJn>v%!&liI#EG4c>)x zI+VXet>bHch`5>{xHfSK0Y`Ch++}Z{Ssum39RiN(dufQc|BtdOfv;=Y-Y0E_o1yJZ znzl*D=p?AR+LWN`n#1$Bsz{Ad;pI`@Ri!b<992XW>42aJs*S1&il}O1ctjOJA2A0- zctv=I|M#7}cC+ruIj#Tt{cgU!_Fns2>)U6z=bk&H(I<}c+t6=~^>zBh`JI;{*LK&z z`Bmt*#`90TPaL;LXH8ce>*x%K)9d2y-_`zkPe8vlUTt0F6UX`9=ZQR=mkVT~-Cr2z zJzr1viKE^|bzZ05HlH}IU(MXE`nZ1=&F-F;`Mr9l`@}KNV)R?%=Q}=yUnm_`hnJ7_ zPQmqV!8YQ!|1J6paa^bNzZjQ0r+Z%Nh@syab=1K3`&>=rhORi~b>REy^~M$Z#BqBp z^6uE34zisSLnrp_;{pcB6L zd6m~^CAW0d$K$UCzBe!LCv~bgem<;bUiZ%nvrg~vHs#jt$EDs+8!*oOv=!*h>+<3M zZdjwP;@i6G^^2?ci*dC+ak{=1jB`7NZuieyj(%%yeSa}-x=$SEx1gVT>VdpIZx4u@ zS8L*?0~xmjSOBDPKCj2gS32LsMSzTp0}FsO8%KSE7nr!s80UF+BK!g%O~_6+=^G}WM8i|ZO<*f4EipIvvr*TG?F=mT3g`CdwQk)h% zf{gn{`24Q%LbU6i=<>JB1DECIYbEwa@gTo88^gW`=2U_&rucO%+st31#_Q28eI}8U zapbbxIzRcf*%oG=K|78|CmsbCboJYD zi{z#TnqC*?(^jeKWamUdO!MnHy!>T9cI{8(VI5IC zDm}c#h@;sxv~ew-IK91D1M`*mte1{!VjN=PK5=eR{9$~98gEi<#*o8Wf?iLY&Kq7G za>5s;IPIPo%R}V8RfrgCQMA3eIYutat@D##n~h;#DdyB*yuuTsM>sB ziSfoasqq%oW*j-J>Fq&YZ8~oo<`i6vW84$tjW0%wwG!24A33b)yyVrU^G068{*Upv zCq~C}n>MKNX0&~uAHZd~b$;?|voY+e!JNEHu>CzTI-dC>h_P0JwqJg7S#F)5{Mu{` z`#Pu}<8e=nj(7E|@n*FB@{`MQ>-^-`W@FeFTMPY{Lcb?Q$GiFwW32>jzx?E~+&Vw` zwb>ZYqK%zi(^hL#^atC9q&H>sqq%HxAy!;PS#A*buf=MT}K1v6jb5(_r&OU)=`WY zYo%y!tqyYf)xkX4Scg4xbYMn9jUk=t_j}Lu3vo3A+`kQO1naPc{Q>IKYgrAn?26G`n<VW*@vfMg9`L)>?_O($z#v42_I^NZ< z#zWWo>nE4x*7?b=&Bm}V`U>=eFM>yqaUYAT@fx&)?s1TVoPN)n%%e@$QHMEg7;p5% z=y=xAsmAlFw_XQ1{pw&IZMqKU)sRyG-Y6bH#yYChc-=PYAg5m)%%e@$QHD7k7;pB( z=y-C5uIu6`K-=%Sj$D>o?-%6PW@Fe_jX81fl_-84%Qo{@tMR&k{N%FSIzRcf*%Ad9C zrt?-|PCLe%JTW@n-IuQK;>bhWo15|EvfMg9`L)>?_BCQo1^A+hU&pe|{8ehaHXuK_ zEVs^2er+~}eI3+~@g`4 z>sK#0Xcsm1l>(pHGT1{*hY{2Wk8}VAi6T>lU%wLQcYcaI_ zbdk$)>-8ePHXFmf63nT?c%>&s$8*1JRO79T4_E`s$(m`p4(8FO>xiR{!kaM8M}Uzt zs>Vyv_FD&XS#F)*#jir$Ci6F`@z#L+SD?MM{gIr0>%ctPbR7+t(}?jJPmGQyXNwwdN84{5$Yr_p zI*?zRjbUH-ZM^=Si`RB|1R3+k)OZEje)-8|xpjW>YqK%zi(^hB#%nw=I-c91MU8i$ zy|w+3oPOJZd9>*|YB48z3!W$O2r}N6C{^QCXm9J-AQ!o*fu`4m`LyYJnlYyZ^)hJ(X&_4()AiD{_(BuO8;prt2wu2haZ) zZ}!CKc-9lTwTmNy_Ft)o+JZ##%AjTRoqF)9*N99&Ngg2F$6(c$p_g$CI;8jW?q0 zw+`g8+SlXZ`}jSSv=`FF&~~x6V&~Z8nB|am=a4c$p_g z$8$T>sqrSXw|YJUr{8v99&NggTFeR8;y#xrM#uB`C`6347~0#ag|X!JJ3g3Co35uB zbLuc&>50+ttfx_px1s%4>LIsZJA9V6J{OUW#_{J>BEUWw~{JmtS$)7$Jh?2l&hPRoZX3g{Hq5C9UzOrlp>EUFuf|)M z|KaZW$z{29e)4OxG3;yk0Q&F5I3EG#>R02XXg56AJ)T^aTjzKA6}OFHSM)>Z2Va%q zSD|jx)vv}|(XL(6JwLfDx6V&~Z8nB|ZJ1MYH^%QujNusL!V+B7hI2dO3IpO8PqT5c zfH=z+{|I^O@cb7LXV`H4AaQj8ah9(Iae3ft4v4dSHm<`bjxm(WPXkB5WvvOiE8w@{ zQGqs(y(rgg8^+rC98S~QgSx%OyzH%4WB=crTzkLgeQr0eF&}HoQ)7QsUqicjp6A+Y zU6r0=lKU3(&~%);znSiYzueF3RXujC6N2Dy8(Kx0m)l>P&CR}ok8%Bnb9TGp*RgCf zzEGXlz2|sxSkrmQt4-%EW8Qml{p*QgypD4|!Tle^RQtqPZf@IVH68_TJAA+ALe61s znT^9oE~`oFCcict!@erasRUn4@#|Q&8DFEu zz1I@tu%`2pSDVh;f;k-+Z}!A^<3sAYIZw42M-FQ`FL|};yz$TQ{ug+n9xiWul^XY6 zyOYD3&P!fxI2@rkoB z+`r;#ycO+g<=eQn0No8fv7AF)&1%fc$=d81>2+ki+H4H_>M*CU9^-rj7|**=HC~2x z?PK^XARc~3PQUY`n@3I1b(DR9^FMg2RUQ@UHp$tn#v`}}ZBxFD??8TQdL2SO*Q|_V z9R+I4%gNeot(uSZYO^uyi{cug3Y?{iSI4qVJ+*rLp@8dZf{R;_O@-SLqYS7;-XGqZ%*3HGez& zX4Jv!{}^y<#$(#=dXVP>uQ8u{|4WVi)m-*^kZYjV(|bK=b23+r%Hw@4XZaYvRX*q= z7fr`?C~h-q{&If{sn<<*9d!)b+%|sK?cA=~Y;N|&zQ+6i;4V@8I+kt5SF3UFYdCUP z(|X9OP3Nt{oOX;id1AW7Khnh!R&B3FVpr5bNTyGHpo zwk05cqfad7u#Ofr=H+B#PuPYsPCmzvGtIiC$wq?yGHT?62l(|ITdpZpW?X-WsoE?HcIymTl(mP~)WySaUqQ76q3ztt;Xa%Q)6i zqQ<J} zjyyHyZ<@tCRvXH4pGZ6o35iBb2>2I?1|CwZ9(4n4W`-%=nKx&5y1m`|IorwwyjFkbJ8(edPLSL55Ng`9r-8S`k zN070OxEilPyWnYD`#$48hn#+OFpoA}M?2=UVZ6~3qvKgery9?DYU_27)2|NZ(WdJN zZ^rQt-Y6bH#yYChc-=PYAg5m)%%e@$QH(hq7;pB(=y-C5upO)gZ4+`?ZhgFvUz?3# zUm51a!Cj*Gbu8OlhiWxmkM`Dn-+-Kc>%ctPbRE^0(~0pGPmGRd9pP1791*m)RtGu# z>R=vix{gN7sRD1Q%A;f1CTFc052}G&mRoNR@@um(>}z9w9B-|j7>?<<5Y7RGJT3!{ zO~$j%s2Z!q@#|Q&nZHJj2Yshp{|<}J@BJMX%g;L66>q0% zGsg0Gw1c*}8wyTtBbu&*d9>*|YN;Q*B`S}OW!pXfsquQWgRa3CPfou&m`9tg zqaAZPG2Y^d(edv2@A)o{2-;h#gPeYKFwg(Tl5%@GxfMN}jA=ObM`k!9D|K^z#rk)r}##nER^>Xs&Vyq8-H*984C!@TFlY)O0cCf`cKa32)Ffa$!f}or>I^AIzM>2#V{&Tz z<1>?;y5EzX6~CM9-43MP@`v&BGkZFj`=!NFBaXw^X2j-aK*zY$*cN{~Tq~~5ZOVyf zmxWcFVKUpE+o^taio2FvPi_bH%|l-lXl?MUWTym9)OG+DouBOf_Fox(g^c@Ti~DP2 zxPJ)-`W&Y{2qfndK;}pX7i~u%^JNbSL3>{(e@c$iuPECItxtCKxBijrZ0c#aCM26Z z>MS6+vvZT3+@g?^I=P1%>-?GQyqaRhz6H{L5kI?sDz-@q8O_3b?y^<|j&Lx8k% z#m|qVJHyNKoS{VnoSdW_C#@wVo*ct(!cRvI%%6=|a=frA*||#QoR(=gDzXgcc|h74 z@$<)ooT25p&cLF+PVe=Gk$lbII|FgKz0ADlfwbAZji0|uy0cx;K&Kz-$Y=>QrN&dr zY%Lv9%O;5p%Lki&1G2t?Y{Rt|kbY85$jOn&;%8F-}CFO>k-12lMcM7m5 z&B+}Xj%VlL7($HQ&-0uVcYYc6hfRGAS9Y%9J{CysY}B1R&Ft$@iJyV^()Y}Ip8=#@ zCw{^8R$9GD6 zW+jfF|CsngfwX^$pEMxD889X648VRnU`F4z?3O-Fz2aGAN!b6n4%q`!v8}V5tjVEx zTA4ZKTYKQW5Bx(GuGjJMk6DsJJ)F?;$b>1#!ew_buXM{U?w`H|tG`ZW;J(qCIn$16^ zIWhmk;N|a|#w6eMK-!4$CO^k%PfC7s>~%fIlp#my9wv4+kTxI4)`qTC@^M(Vt*4g;Q{_tcT{TXg-17g|#yVTO_0<+H(C(4{*6AecM$mfV- zft=&+@B8sNqWkk>tMFcf=angYoA_!VZTuvuC6DKtF1=2=sbv^)({=$KCb5?SX*U4b zx>Nk}eS~XhD$d!xthN~UkwWlQ%6L3#=3fq^hI$}1_|+c6yy|_;_$nanAN%>&T#48! z8CwpdJr87Sz4*2J%Ut()F=NZ~R-Nem8f(8qVh%jOaFhYv`uZi?^N72>nq|(uSexkw znwZ%@ntL8u-ouQQdcAp$tjfgk zUut+?1JX8&A9O8Hk2%Rx&78jjX*Y_W-M@#^>j%T#g19x(|B=Lnj}x5%*BPCdGg4yC z0@9Y+If}a=E5-R#`jd_~aS@=|#{SnZC74rlf{A$-NE>*f%u(EN#KollEFf)>jSHx| z7IPXT<`=SkhUn-CY5{u_X_RpKXw@mVZ9kKlN<=bl*5HfRS=%gKg6zs&F)2Xyr- zE#Y3!Ed7s2+(sK0prsgdIwWR~Qw;ZoKreS0;@Ec?_RR(v|F^{dCVo<%WTy}IyFTMG z`kUX5xA&1J9x9POr)7O+1!4W{nl_vFP84=i8f+m*@M!(@brr0%^OS zZhVf@?gF|TL$aJ9Yx+7vX6E1;H`^J4*9JqD=XVTf>(`?9+Xm#WuP_`h0%;q7Y>oV< ztV6)swPG&U+qtiL*xuo+nGSxFXD5Y z_NHBjK_O?*`rMA3ws2GLc(1aoNM=myFGS8o(+$^?Kw2}9t-Q0gu0MvkVaxL{+A@aRpJZ(we^~sF?XWG+yJEgVdtotIuOTg z_lvAo`i03(QQUAJ38a+)*?Lg=z1uRc54NRx?dSR<5hg=D8w_=cWq_#-nVpU=Ds%!|qR`9NCrdBO2Dh_9FN*MPLP^Mm6X zxxTZ^_-G)lxH34t4e_NiekqVvb3t%?D2(&HjK2+}wO=Ul15)u?p|6v%-n@e2_KqMv zbdec97)YxCvULHF>pfTec`|+*kn27l$TiOP;VlL48X50-vEdyFWNQMDyoZY)lkv$w z@*WKg;;jO2m5e_p^V)#q{T@i(j7yB41*G)`y88*x<;_WQ(myt@6YIgpzO!&_O_cGY zWZnffULA`qh`&nu?~%ASeBwG0*CzdcNZihsN`A#xfO|d@rT;h}?K+>h62#R?|4NDb z%qK36xXsd^bD81W7wGC%d210Dlm4?M?hf&j@-v+LnYp;vg8MB+IZpoier1zGr`-D?cnz=aw=^LK^ZtnAwBQO;!=XUh(c)+O%xlNIjWV8krHRi5x*9z9bi%kt z#{RU6lAQx2=5(2Jj-BIkuclb${p-nOXOYCcC-Xi_n3t%j4D%*lWokJcNIOS-@BOA~ z#4eVxwG#V=jrFOi0r69=Hr#W8v^&JNuMhHZEyCCHJa$_}Lq4=`mbeMmn0YIKTo?D8 zKiRzYclzQvU~np4!}fLhtj}ypkB7?0!+st&eDF9SL;UT;^ZP%MgXed09)dP)s-Wx0 zACA9-{@Ar9e>ITy5Rk3M#eV_Fd;_Y@_*5WS&jONz`Do_@S<`*uFBQL0e66oAKp*?P z`eN@S>iZw~tiJb!`)fhB5c$K}j=wnBSuAyKl=}YwQc3!CM$g_r=A0}31|Su+1IfXB zv=F$cZ+9RqD*mD3Ykjc*eeCz@i?k-{y8(Pw-&w+aiJ+TU{;;-h&|fgy)L9CoT?l0B zYVmIYGUr#~kGbCHivr2Pe6$5X>U&B2_2O?7U+b$3(8qqSzVN$=`i5Q7rSDJS?t?zB zv9`<5UnzAi1Jd3Avb9nCO+e<{dyW~u3`n+HfaG94+87}99WQ>l_-Bi+_0U3^4^$!QKwX^tp0h#kA@!tSa!Dm2n zFduCukb3SFe~I`j#n<|x)W>^-6=_bNAG+?LvfryO{+~pBzktu`dsn!>6m$!bKdfzP zRkAa9uBr1FAgvO})|KMl3}nu)#ozfBqie7o(k20^fwj>t1+w*!_>YPIjQDze zLjC0Yg!h^9@jg@540D~&epbPUU7zsBiEHs+@Y#DUp9nX?w-|LEbtPWoO1(3Iv}=HD zEffD6Aay1!FuKU=8s;+h7$9we`1^>j-;;}=-eQ@57Lazk_eBbKaHpZqYpiV+ z`rD+=ygN+&dji=yQ2Yyk%-JG-&YeczXdpS5k2VrWeMgCZg7~M4ul2PA=wrWEUx!EE zgW$8gb0FX$E`e^^`k)yd8YQs+D%Z6%Pc=f!^=$ee|Dnej7#RCFnj9Lz^s38cQy z#Q#?OPVu$A&H#Pv_v&l;EOGnog_^CtL1@!<5_BE;!`c?2e}&Zfwbb9M&gjbn(h7mh zd6D?f0Ll6WkQ~fMD*;m9Eb*(vpDn)DSJ2=8_+!6UU&H5#`u+_*tM3uven!wOMEX_EkypXwj-~_`)*QayVRet*y!sA zq>TVF=LO-0T1Z2)P#P55*(YHO29Lz_n0aDL%;x~)GUVN>uiTZqA zzp&q{FY;BQzJAqR`hFDd6!dwGwVj9lGO2R`khTiQ);jUu0y5{W518?Dfn>WDNDk(s z4F^)+k>VdGe!2KsUweQ)_Ive(zfRQm0Ql_oyH>bw7jz4eKdde7x@2dS)cLd2pI>kE zjRexh0hzN}{Fi}L^Z}3@%tu=Rq`ptY-z5G|;%j~30sh;M{a$??9({YDW~(nBZQ2fk zt|Na~+kEurK4|JZ0!TXt$kxT;&jvDQoA|>YGWvD{l7snZ2LY+?Jn=6Tzgm2)ubBFL zuFu%-)z`8qar?ajKCACx;eJ}sEkypXww>^~$>~z(VjyiTkgYew{}9NW6CO6>vw&1| zJ&+vCM{5OAUm7@RVIcj1;_K_$GU!+%^M4WE5lal8-*s&@;)`VbJRt2?@%6ej1gsnT zz3UeGHgVl~bu^!+K^ebDDM)^-{Cr%RoSfwZ+iw%!mw zWtq`;0+4nmkZj9=FMhfBXN#}(IeGs3J^Q`-n!iijevg9BZoiv_`))zE z5c$K}vghEli&AGngQj#zkNsYK6+a~E`v>@}zJrDP zSV1?j{9$eD(BCd~4qIXB-xtW%Vd5VLWX@&ce-EUBl*f%e=A+F7QqR5OFA;yG_*!3G zfIjwn^_BcLQQt4%v-;i@?k@%1LgWwiO}!EKPo&P7K-z6Uw(b^x8IU+eO_a2m!N->)H%A*)L#N*>nQP00W#+^;{ORGTi+**KIWtC2&BHL;!hKQy7*dO zUcSFR_IvfUd-N>>pWS{p3HM!sZXxoA`ufx$R_eSGNV^ZnR)hG@0+}=SDKmZykc!R# zl7snZtANz^wfLLG|3iGOFB+ha{a$^|KP7IzeOWW|F!y$7({>ef9r?rYyU|}Kb-pk4 z{|=;*(9=dwe;{*C7k?>`tgC_KU_RR6KHi!^%YVLmTm*52(tje5Hpj-f-}gzZqZILrWc+=JFJIlAw+eCf(*H4#*6)SxarKBB zDg9G{v}zmYlfMP=wKD#W#Fwq<&fAGNuGcKAm$nDsbDPnwl6h-@w4Z<+8~md1$@f3> zXQT=S8Qy0+yx%BZa{LZ|jPO(fX)A#odl|^8_FUV&?h@hedrM;7`@^?&Pmy`^fV8zh zj%~7hK6S^1cdCc?I>k$l`S5=eo|1Kj{~932764gQ@8<4x*9!kK5C4aXzfR`00ckle z8Qx+bx0T;(-e%$b(Zf4xUe|iCwh8b{gl8U*wqE!@2eR(tUY0yNq&o%p?xFcaeBXFZ zS$52RSG7a({SACGCH`q3ZLOWJKKGb6IK}zx--b8;H4`@h$kzVi9}aZ&sqbt?F^_#G zB2Sr&UnudjZM@ItU@K(a#?@y3*aT#*!7rHaH_=}t^WPV~-++wi^}1Q_5kUH*fVAy_ zJeThXbn8_5Y9!y~sFQXxuvFsCllilOWU3YaUK!6Go#~7&8tmj_trNeQY@Q1mh5KLN zY?L{_2}fFstm!CxC$T8k8L&LzyK1rNXW~4LoY`a2osmiXxh-4zHM!d|62^f0J}~&R z-ZDH#0BI+QuRn7i8iI4+yC&{HAng<&TmKUOLK)ZR*a+s$k^cK7Zl#U$*>AzdzR|3hmM*P=hJbQ;sXNRI;&XA;B z-)&J3u1_U)`}a+4wdI?NEh$TiXsT;3m(#*v=In8P11+ zw5P<^+oO}ZJ~MH518L6y*?L9%w`E*!kAk82U0CVw@wu6o17vQ$&y<%Sex!__D)CnV zIrCN^x5vZcKO*DHfZQI-fiAB)R^s4mlm6c&KIaR=R{*5#1a#+ZV|&zsD=M*9%G{gm zT%Ye5G$B6pwc#EMq(wK`8oPfNy&dsoGJXw^R{KqGe0UgsV@Sq70Ma_Xweelwg)2r} z_&ccsNIMkBR;l=>%DCRv`3f*DC!#NnB2ctT~Xj6Oe13 zKR(kLRaAiUQJ&MgBdg8(-SY-;J%xD2K8}5r_#$wk%&!x^W#YdGWQG|(2>&kloO@Ef z)2k!1ExjeJ3E!|U>lx`0<9Fg)g?oj>4*svy24tRnf$S;zaob!@L06scNS$8*<1)7u zNc%$k-+}Du_}OslW2O*X%>N7KkLfhy`vGZ3i?8Ejh%cA^3nlIbpSViIEt3A_68EC` z?rVh^<}>7Vh|9;dS~hit^I&J*8eX4@j&K%S^VT*&AKojtpv!{Eb*(vpDn)D*Gzpl z*7#co<~wLcAN#%f=A|U+TL->Uj5D3Ef@b8 zAafV}&x}6|BscTXHUQb`d7JUGfNb>_U+e1#*naHy>YJIGsP8E7S$!kWW)0(kta*Py zG`Hw$l{)i&Gx|mY**XYFeMgFaGLX4n6F>C3(Z_tWG9X(wiC-)JJ>qM9dH6jOzwO6< zuf8d1iTXYSpVhYvZR&dt$eLdlWIaX=wujVN38Y;EWb0ud^{o{Dc_4Ea|6#@-0g{{f zXq$j+4Z7X=$^U*4SY&{@;gZNL0uk}>~=wrWEUv7G$zMsKo^{qpj zHM|34&0h$z9-{`^PwJfMs=@IGU%`|r-979W0Dy!2a=olXvwuk-*_NxqWA}i zul3ag=wrWE-)4`#E5T>=9fdY)I2Fj6&lO~TtFK1tYyr|fkox}svM0TV(K7(Z+}DWT z1SB`}(Q1HfJtuy%`0K^j`WgfDvEQq2Lq_8E>odPg--qC44c`H&=TAY_WA$y4I>+=h z`t}5J>^LBMP80uJAaj2#{@`SzkNIeWfo%Ov{3+s}B)-@fwbFz99s!w&-3EH4rK00DQ3J9NN(n%)dSgjNBlPNzY<^T z3k~-_zp&q{Z(dfSzU>!u>H8VntRW43ta*SS>#_P4OP!wrX+KE)IjKh95FlfB0y6gk z@izd;&3v>XAX}%3KVAIu#n<{G0s7eQ)i<+OsuN`bvB$t?x8Id$vxZxMtnpq!hFX12 znyE7iq#X|A*x5k#Tp<3nK<55Q{0M%Jhuq9ZYXY+Mo%o&N_gHAgwZ75-eeCz@o6F8ioN`^Dcs{$Lc#l>Rbe*Jp|;~%Ru(LCH^Ns=AN8x#^(UZ&3v@OfNWhP z{#D}ND8ANL6`+s(UVTOQ-L)tah`jnownFqpf!6+k9K~>=wgY&u^s&b6(I)N=w7QL2bHAU{Y=3U1(NzYd zoekvJWkB|<0kXz$mhtxja{O{2b1*M$CXn^kiT|MZkBP6>qlN3yKiRBBnf!hW`k0Ro z=3|b6>_pvd;G%BkZ9<#a0<>BzHb`wh18FI}OwHLq_S`1^8$fc9m-Y>ix%2NZ{%|1u zG2&|tozyTYnK3DvO;@>B})>1G6THN2l>W$wi!S5W+Fuw^yEgT~cA7p0k zww&(!=&R_9V$9kP$m88d*GnJkT#hzzHPC7`3cK&4i$vRZK-%v>&PfTIH8>Q=`WA}+ zB9P;uY_lfJOZyYZeRQWgjb8+$KS_MOCM8@GZeRRvi`u^Kamz7Q!Uyv**Q4I*v&xp ztP;NiNDlJS&H%FR+r+<1{0GH1zqf?%1x?AsZ)at=zf;L$t_igl^fO!)K-&30=KNjy z^%}HqS%W?a&oAuvuE9*)w~UfdtP{1^HE2bf^|u49c8f*0$I7uwfV8J7UL6y0Z$IiXnm^}&&+0f5ZPt1U z(AID90IBZ=AZ;O#V|M}B^RxKl^NbGi(k=yZTRtTIW8yy}zSdDp9eMoyxhyARhWSlb z_IWipd5Idn0+-eBGTPMe9?;fqv0Q4qA4ppPd@64RTMgj2e9INwA-D_#g}OHhXlO3%*kHGxz>z6ORHi_EbSMx9EyXoeeJ$<0jR|h0F^UWkS z*ZLX)wjcYw`l|6Bf7E3(f363g)ptDF)ORM3HD4siNULwA)VTskTMgvc$3XUcBmVz@ z%zefXGrkx|Zsw!a0@+$8evA0;iLdpw25dj}d-YY|`60>#Vgv5&()R_pS;Nmj)|`eh ztIJ}G)R{Zf=o=2?*ug;dOcwt{AaidJ|BzuuAM?>h0@*rB{1e1KU3{(28RdWcvEQq& z1fOGyGJ)8=;IrHBT(qh0N+4^#MUbIZ-%P1<1(3EH$gz)s?DgJS_v#Db_eG;jAoeu)?Do4J zZR%SHWX%r?GSuoDBXv#((k=vYYyprx_lf@)khzDCFykix$<2JUW*}QXi|^cL{0tyX z>#GdV$9}KAc8|V;!DsaiK$|se4`j`|3o@V8S1olm18Hwd{XYTOlQh!k=>uf$OT}*l zlAHNxQ-Ew;DgGSsZxdhZs|(P_ey_e}eE&Vl1Y&Q3&+5AuZPxG@ka}JaWT@5msnj`g zl+ia1$g#;l_M9mGnLy@#PyF1`Mj!Lh+JJ2Jx!?GCK(>a9uk|$t=wrWEUwuKMzLUXc z_3eT-_3Z;>%|{5LxkX=8>Z}6NZUi!RDUdx+ioXuX+`DaW#^XS8Gaqd_kgbK{FBX5P z_*!2_fIjwn^;K_|>O`49>}&8@eNUs!8eRdi=Jy2|N}sk!>f8vVeI@m06dHZ~fQ%gh zWbV1*zX~Kb^U*qiY;FI5@gqRC_7q?1%NyN^;1)^IG4HU2{o z%`N&mrOx6VjJ|__j6EI5o^!;%49MKyieI>+(Z_tWtAK1hCjKh%*NU(8MFaG)->a_# zzj+yD0w6sdC_kaiD{W2=Ge`M3D*1DX5Koy_=EKyou5 zEevF9AMs1XpDezsPwoj;P#>-uf2CFY zkHr64e7znuTo3nkeBx_Z_A@6R%*i^6M zWDVz_O}rdvby#eXI(Oa0=-CIzu^5m&YsJrv7(L{r-3g?gm&Jcm{Qror^|VpXu#hth z|BoPhMz6NamahBP93u}OWM=N)poO{VbKA48-J@^Ehq~{p8PZ3sU!e}-uRyESDD1wk zR*SX|fV53O&e;rP&q2GIeRTnl{wqL^XOA;$!o0NIfvopb@u!P_zW91gLfdbB8*`kw z_~6}elW+FhYzc*xiC@N;D@`k{RV#4k%d01$zoQKsj&=5I~&Nc zi-7F;QvBf)OdNS>I{=ycXz@=Hze0SirIcEH?hmrxtD|OzL>;$+&u*jh(58;7fwq2& zrBdI$K-!}~jy(=!Pn-C;yBQtirQHLhj@QJ0NBob(54t~Ch1w4gt~o&30w8m4x4X%q z*Pxzj;PYI=e(xI0+c9wsdN1i(gKtq6*WeGJ)o!s=^4<@mJq_g8S|EG+790O4AUViO z8wBJU94vlJ{Nu#eI$Eg1=l&r3y*f_VDN)C@;IrHEA850NSwLIA#VV=qB_Qn`sr!8( zdy4lkI%WdNL0;NCAayi}|C0D`im!EaCg{Nba*^MJV!v0%$T5jJoJYEJw1Jy7bO3Gr z78gr>Ujb>IQul8__8hyX(Qylq9OR|-0#e5w;_oN^VdA?w2-)w|_v+4x+o|8uE`6VYn>G9hWX&lUBhoAuOf+?t18FmW z9J?LJo_oZ91jyVY_cr4Tf#haB+HfFSM~Z)(_~qhjeermw{mwy~ zHCzs4%{L3O9;>fe>I_XX`uYPowi}Q=`-*=Akhz}~zY|Dq=A$hIvh^SFKNr74e66oG zVEeJ(t8dOOiTZY4)}`-vaI=P9=wr=81X+*OH*z0SX9bXUK9FMzf$X_o{6~SzT^Kdv zcL2%Fe6&4*Z2eRG8RB0ozSh?iu>IKY)i*tosP9Se+3j~7+N@zdkTpLj$a<{4W~no@ zuWUac$94m{+fV!>fbRAazY|Dq=A*3zvh}0*e~2GyFymTZdw@Rnd-WZ*Yofjb!DsdL zLz^{>0YxTxI}$#fY0i?8*SF`D3CQjFUWeVzACA+2}pZQ>i-tVo?paIJHY5W zAIP~4Kyou5?E@fNy&g4we;`{0;%j}y0s7eQ)t5CsQQwK+v-);Mn>FkWWKDk;M01P2 zjZ){}1C72Nfs8#A$eyFcKNZN_>&4GH$mnA}+8IE$=8J!i_)Ems`pN?IvEQq2lSki| z;IsOkM4S3v0kj8nxW` zV}WcHi?8)n2k2wJSKq7nzdKRzSUV4VR^NeWvxcLAtobxS##(*zq|OaM+Q(9Vj}oJ= z7m%@ofXqEx{53#wGau~=AX|@$|CIP^#Mk;70`#%pt8dAKM14t*b?JKp+^pdvAZz?y zko8!79a3lH5TkDrkYgtS+4E2FX91b}bMXrfHTsy3)*Hyy-r^r5eoTC=uQfm)`@Q<+ z?3SqS2JqSKcOu%4 zK(<~If4%q{#n<|r9sSRr?Dy)MzI&p+{1sjLz63XG_yx$C(=ldsS*({j+kv#7r2hVg z8GYLU8M_ORx$hLe6-aL8qm2f#b+q^=iC-bUt55C`7E+(j>o)d#_025qe&3}GeNiSA zdmK56(T>^<;H}cf8qY(Ucs0=KHfp=ycS-)c(KQ7~n+D{ZbAjx65y%?*9&Y>+Ajjtc znS*(0tAV`l@~!wki=Xtk8Q1F(<9hhqBV<2w^1+qiYb5V>!_rTt=uEzxL41ZJrngj0A8!- zTC`cq?Le!;;>eh(^J*aN79htK1KBh1NaI%k$w6M)3Ly2giT|be{}o^BsiU5NdxRV# z4uQt>iqf)a2A_Qo%LxdG2AqcTJin zC9cWH#;!FP9w!H*x)^G#R)^Blx)Hmcfqhlu^$94g-XO8&IKyr|m_7IRd z-WLBu@xK&b>u5~S(RI&|{aziV(L^2npX}1{6S!GJD*9~w7CWWBNyi%@AL3ESN>1lyveyOIZfes(ugc)#LNNCi1Pl< z2>iapi1mXz2Dat3^l!?Ing1aha{pI&QvVG6ANOph|K!A9ut?f26aT|}h_fBO*W0h7 z>wjfu;-W#Tis z8TgEDx)Yk5?7olJiFKcTjai5LfwV`(H~;@E@&CvdjKTkKlkvi9P5gKu*D)wZ3FgEl z<{_E$_%`OmF{e{v##Ng{RWiOp;-9ne?*AR_uDuC!ev_Ept}{9I z1#V4CJLX&^G0)1Jm+Ty$y23l-cSB|y&M1)fH}Qkks~B^tC1$nE`M3D_h4>%!>%0FZ zKpEzo0q*eiCRYWJcK96Q=a0*9Mivcn@_tDCwF3U0Kn>>3Kx~!7ehQ>D-(cpx1*Cm0 zem;JiV7sDxr{DVST#ewGhFGrQyL8o`k9rxu1CX`t0c;eGoRx-iIFK>LK*k*Rl&Syd zXN-UPTAg#0Z_WsE=3p{;&XSy$NX{F8b;1!@XL3e?j6DX(oRvV1%z4%LOMo0V&k1Sn z{}tcb`jsNT{SA@7As_8{;KY9$o?0Mn!5fA@yutVrfXrjK{nxWf)^obVu9dlOi@ym- z4ZYtr8qzl!f8b}D+kf5aW!(c)r zu;PCv*PB4v2jUO-&D1gy$dTQ}F9-74V3zpxK<0P_$Su?JcfBszzWY-gHS#=rvBbU& zr2X_4=GMyG+CNO5AAq!+KPB&ucwYEHJuk$1M7Skr;Y37v!-FbN43nyheTYd{i^p(lH z7Ks~^Y~~#fbmwj3cY3NZ-yUP%%l?t{wApX6fi1!_CB^Wb3MB6ZK<-;_r1o_8lP}Ya ze`B`QiEG4)K2B~@fz$PAqm)>(T&uNV?$(a|X4GY$Cq|$S<{U3|?Jszg^q&g6xSz@U zC6KmR{N4JSy7mWhk2yyCD}ju>P5gC0=J*=O{dwd7(czxo{rBe%S^GMP{S`!gc;GCimq)=3WG3?qxvkpR2@Q17zeI;xCTqTKs<(s}!~Hn3-2(Vv{DATvt*f+ zi5**P<{l1o=LVdkTV&oQi92EsGw)j9*5-A}yv-7K?4D-cO~9?qD=0D=f0MYA_cHTp zfm@qbBJ+|bnz(-eX?5cBHI3i7JudUI_BQh>fVA1-o9*F$EYxBykACOPWHdoKa_~kZpozjH59zqP)>j61lo{-~Zo!~pb#-mLg6OJ);M}eGk9FTRN1>`<_ zq4-w-8F`cVM;$9V-1CM1vD=PXcn!7-pk3wJ(^tHqsg5Sj%RPj*Q>a< zG9&TzD(fo~PI7R4{m^7E_x-)&m!Xz4blwEC^Bc=eE&usPPj~EdV2J$UCs&y9?BVIo z@bY|TXi&d71xU-`p+!n&%ZuDgZzQQ&X?H1j5%VJeyoWX|b8Zl^=f6kea-bd2qe z-{g#&@r-lK{J}GfpTBdOGi+vm{H{korw{5#mVLDnb7q4#dajAvVWydvf1dG218L`p zpFbw#3@y*ab$ef@_xiLJ{AQv1dxdr2;k^mg`WAFFOZ)}roB8|AGJM&2{H}4jd#{XZ z(2RMU^9ANqUSMMG0Mfn?Kf4!xk9V3`vkr;>1@Yw{nC)>skany1IzF%1@Q-;W*@=E= z;wJ-X=ZLT4qY{5I;y259YMY541!S!{zCz;XBECt+*Gv5OHr{7jh4;XHsf&%?;g=Y_ zMVA_%|9f+E`5kJr)e`>>;^%y1*5FPc?HTcPe1pViJd4+0ADj3KfwcMJ>-biQ-y88o zpP2ZAfV9)Z*YVDtrv5nMoli}C4v@C1_(}P=XE8G!_Ygz4ADV{m=;MCkxPDFfeX%dy zWA3F!5uY?T%NbnW8|R{4_|00hC+BtaZ8s`t)#8C--o_Z&NDqk8_>ooAELrZP0@5@l_JP4)LqL zGV9a|bnR>7C*|NZ<&QCoTaN881=|7lweu%q{WE(ynbW$SJDMipJrDe5 zGk=?AJA7{g=kku;ZC$_V!f|Un|M4-WG}VdPPT;%HR|+h7z^p+nkT$;F_#CHw0pxsh zPqr`W!t(|0)rZ!b`{eD&)%u{Bv)x06V=BmF8RmqS7~ZKs+Rfqz>8Zw?W*hT}nKK!Rz|^OVp97@j0@)e>boT`x-cIlyAmi7|yd^;LJ_980d*XjC4Onm;BG+g%$c6#A;Pij1+ z%w9u=59oTmGweBYuH6O5H6_-qGxL81(!$MVyBrRr#em#CX8<`TDHYFo<9ft`@)b+I zMv2dP$?y~cxxG&pzXIrPcOZH5CuHC^>5uZg$594Oz75B?Z<|c*e+c$|-u&-~p+IID zD>w%&1ZK+13CV?_z$c%{^vl}+WXC(PAc$vApK?_<40{UuVcmm>7N9o zhHHT7z@BdzKMLgdY+wfPcJZgbZSJG|tJRFx0()WnUh%8nHTB&JZ~8Pi zz7_FpGM@UG;m`YA;}fA#txIodeCaBuaDZ@k7Jt{~Grs z9H-QMwj8&9^Qn{l+}~*2zBJoUycF}p=(|zzf@()2;D$U^d1FerxYEwc!U7oAi^ZH3P_6M~GhtBv;L^rndP&*1A&s zH9&X#f2OwiK-Ria{1<`l_;04R`9RjXO#Br1spCI$nlTGfu zQZ&y<@C@|j=>*TqsDnHk!E>?9yEfJE{0ZdPw$5b*2bpsjV~2u=Hdb(w;Nd{7_DMiS zoGo~%;Eg~YuXh5YX(sP7AZ-nh=fu~={}9OMj<3WYACl*zbi;iFkmtk`#J>p0b7Hml zPXn3j4IuZ?kHo*bx0$~l$XdSx()#o<^<=yMvo6EwGr4QODuH(DZb02-5_biVX8zws z@OF%2F1KS@*zottHuLt$k#%?XgW&B~i}~DsRT6)FUo*dFuFT)c^+6Nn^7`NaiG2!4 zds+PVfL!yh#Lwwx)_fR{Yx6pg&(}NmPj=6rJ@bvfKak_+h(BnE@s9&?n_L0pw)j*0 zi9^l!d?2^U{o=0&a@+h0x{kK3``R7{3ij1)mHze%1*5#x48-K*s+D zWWDJljbAm&=(!Wf?YFgaKs(lo=YWk8J8`sGt2=>QqdT@Yek0K3@;T`#reV z)_bz~&n`Ugam*TP<>R-C#QP0bp`H@-FW%AA@I8?B*-pmiIBhPF^MhWW$B-+$vzap$ zNV{45pgEP8(`;kLnmLn!T#KMNb(m8vF>lD6oLyv&&ugA$#FxnU*+5!VBsjhU@pUr3 z7D#K|H8?)+5WJ?B@uA~Pd}MrZd=&9989yILt1gmwpVzLwr8L z-Y@=g8D9b9e*Xl}T}vO{l0)%YP{w!K&F~%uB=3np@?I$ZH8NfeB=7Y=m)B>#z)_zd)Nn=xC4N+{5|D}j7Xa?an3-vH$JI`OlPFn$!sT2BV@IkiRnq?j2$49Mrysp3}ysbMLQ z`^>N-jXxDg{$)V!L#xC;bFz6(ojS#gUkT*CbffrxKibrH9FX&`J;vx+4CGpy>&C;e zeGc;7J`LDDJIeNnVBKg@!I)qfklb+~BdP>z1nYp**Z^#oyhj{sxTXQQ-e-w_8IbEe zTl~d9ayOM4?hc^)Y&q5V!+>1ZUBo{H$Xt~`u4A?M(UVMk97w)}K-yZMyAJMq>$y&5 z$CgimZFz6K6>DHWi?SLTXzPKwC!4v`fwcR9)N)Ii@s|KyZl7xn=P+}v@u|e@e~OuN zDbStcURUySw?lomOCjcej9iTU6t%37c}Gn%JfTxHPXT!P`|`xV!*ecqegaR4%zGC| zJEYw3B=rqBeP<@){UzK}nbN~aE5iRp%D}!jvxk$ywtEeP_r>wPOD^7b$(p88XiNC)NCZA*Ubu`<18TeHgSS zXE%|D``pv-z}#m@>RoV{>)VR*zmhWba zON}RGCgHs&oCo{i{^E=t5q{PK65a0uhVlMac(QrlDXlEk{hUW3$42nG5jhF(OWASG zb+-jRi_jOJL+CrXPfM?+%y?2-l9P4?)~0U{zR&Hxch`A}c^)f7U9@5#*RKRfy`?}N z3l+d6*O+}^HIUKGK#pw$GPVQAxyje!4=fMJeI`2>#{r&0Q|I=G^IEoG8vdvA>1ON$ zAnj}M&9x%_r!C(5>xcVE_C2r?%puoe)Db<(%>5UTc7gcUh~FXp58`)1f)GH{ww0Qi|>8!x>h*mg5!k4496@WZK?R~y3a7{ z-h}wy_BC;X_cL+-1iI_R?IUsRh%4U9^p^r@_lci`{}+*i_cC*^jdNyX#*(9ZW~Ip^`S7MWXq*S#8ZS<`D+JK6_8?nmDO+26Yv=jB}u*A77L zYtw*?xe`dbQO0iva-V!neDA*3fLs+3!?gfNdqDi`{AD?aFYk-Wq4M=-P`py5}?v=2QwP0T7)n?u(oTq4Gft)`9NIesQ)V)8D zife#eD^qjV{~_oE7yDafUD{<`I%Qo#FPU>@9&n1}ybwsc63Cphfy{X`kU49C%=rn> z&FMX7o`%;-SDLY>fV7vz_wM5*h+T4piG2@9`$~MHC1cCyoZ^UOzOks4HVIgDk(qlR zkhP{<><&3$pj#WyPs`2zSS$07k@=?sIp-Q6_1y+6yui#W0aEKcAjevOjC~(S`yA-< z^Ly|!!_J^`eh#m#wblilZz@abm#LNDY0m7i%iUkB2r zOqbd`=apj28GNc4I~Yj2OMLVBApGwRe1?br(q#j8f0=0H~avkZ1-ySBS_Krs&+??WfRf0B*hK; zHd5-8nd*jVhLr7eq{Pz!DRnPjyTjA8NZWLUPScG@1HX46Q#!lhMyC7A4?+5n*~sKhZu~){*u4iScB}K;zONQibhJdu@wQ0WQjnsvH!=bDFr?@l zg_Jn*^!doLNXl{R*JR?BZ5Fb&p38JbN*vE1WqVy84=;lh!e_N|1+VA_=+tG*{1iNZC%-;m*|hdV6|8<0E6u9QT_yv(eQ_hrLXPouu7$ zqLkAr9rx!t%|9W<-ow|s@-(F6@wrIJ(>_Sa!;6uUr(=-YTe`N^KgCV+GNkBOiIkjt z87Vrp>f_%bW!r-kod=LoPPI>U%jr0z#L)yP=OfE$o3{BH;x1|C`gsT`Tes$}+h}%} zYW7XzT@AOLu(AG{dWFZ0JT|>3#!>7na6?$md`fAbiBh26GY1PnReGX&QwS2cIPDi%NY0Oxc)Mbvdz}+aDU18+j)!Y@3324 ze;tvbya=s#;XXggwQ^{zABKN9_mrOZy@qUlv+I8grXxdk zN9hxmX+1?+&ordix)`~0oEz>a^vc!=Dd#RmN}az+yFW*U^n3l2{wCK(t@~N6`%Ppg zt#de1w(F6ibBjLi=1Hy4_)_OsyWD(gANtzqIoI7t+1}IcI!E)&5AV*Ky1h0TPEw7tn)B{KGu}ODqU5QqKGRn2(exz&%v^&!83Q1>%>-RULY(qW2VfTNw;7`hLlaxK%H%KY(-N+Ojek9W5 zSUytDO+?D}I5JfBVRKzImU-Yv_xQs|*(~kvD5QbAg?79B755(*OB3hRE-bsQF1r?P zi9U(rbmlm+osSe9mufi$DfRIdWcnp;yfcwv?{Sy9@ij$C=vGMa+gl$Wgp_R*GL(+6 zYmRi{T|L4*{v%Sh`dWYFeVmE-U4F6a_YsWs0tIK=r{$0K` z?)By(owjGOUA8y1{8-EHwA_oFH_X+)0V!Lfi`=xGiyGY5m z&ynKK8qPnq1f=B2aoT+{Qv98v-RB@fc@uG8WgWJ!>gS&O3Mo2&MoQQ}kg^&5qlew4 z)4f#BA)nE6$TyIK`nX}PM2gNEkfL)gQnq{adAIIRZ}eCfIkqL`yY;5_MmKL8AVp6r z?e2?|bdNyJPIKdY1}SlVixj`Rk+S`bw2@9XH?Nu@WowO$mNzNH-Mp)Nt`|~tR#*0&+dh4>RYigenhAtlZ^ zNIAY3Y2kiKyEh>n+}|K&PHuj{dn9MMI&MVD_99Yryn&QF`4lNSwjm`iE^Y75xo09p z$0JCYlRvNBUn3>%Xz5&q&6Ce?&p&~btx2-$PDaYsPrIXq-$r=R{W5*j%G2F&-y>yf zt@U=&di!X1KkXi*-I4vJzks!(TD!XLM9Q{TyCeN3<9D^bp85eP+XdQh{Wi52_m5%R zKbmpB4)>$^RiE}QWi^a&(yYYxy5zZR&5@GN?X*lo%K1S^@sXw7(~*+T^N>j=x;oO4 zvR#7|9nT@9thOPgto9&9$KOaP?@3MFvU&k2Wp!dR*WCpvan^4x_ot5v-JceFr(&-2 zSnEsH^m6A$8_}`gSU228q(Qi?+8sGR+lOC?OLU7|MVx)L-^+E_tGuwG`L?^ZnK$2V zQpkO-x^CFdkg^?DiT|GXPt^W`J zTZdcT^|0+Y%A5Df z)n*cA^Bgx!qq%OF08;8uxb7AB+i{zF+=qYJ&eGvx>E5F4Z;JhsZ*{}IiIgp2wvHoW zd{;$oo-JYSu_xE{bIN6Im|@6}U7&9+f_Qg`AF%<)@rx=oUE*Gc69XT2#9+c{w1CTI{sxk{#82ubx6^zKf3T}FZ3rjWvXNqMAyVwW7AZP!L`tgtLtXz3k)pc?Qu^+} zNYPz{^dlE*w=<0AO>rNCl=E3gNrO8dE`N_;qR!vTuuHZpkc;#@Yduoz`Uxpp?F(Ez zvDVdFgf1ykiEFOb_W)AdBDeH#&-LvYQm2ZMqW>Q4UWAnNFGEV0`pF5rw>?tnr`SuM zU0FvwyYVP~Ht2Z1LW(ZgM2BpWhq8IcMgEFE$-95G|HhkHBekKctDuqi=K3EgeqKOI z{%t_|k)I<)$3IBfPH(L3483#n5YHPej9QO&VK3DpWN#bdlPwh~x&|P{-VE)&94S7o zK}uQQua7^8lue}A@+wlwShiZO2t5xbyWVk;AK>pui4zdo%Ped-)PxQfzu0Df#n?c7Kl)n@(uJ^JvHvgDgHJf zW&03$H1ZFm*wT=4knLn$9%pHHP`m5%&9VBt-|Ss~%N!x8*qu`>I?m1Cqw2Xjnj)os zgnpl5PEh};^5a|m+T7=o-wRlML++uZpr`(cJhSs!*)u!daaW^T_EKhR=a(PH&LIzT zIG(=MjdKf9Hv0?LE$3zHg_Q82_wslb-Tm#XPAE200d)hN%n6r(q(xyt2t* z-?QD5n_qHmPJKCKlIn|;b3>7$dy{r=*6yFQTlC5{11V{EQoCQ!?zP$-o`z*n(jdno z(~x#&`80fuE-wuqur}(`SkvQJ}`alVR~+m|2$K0ugt*8Z(>U!$8+whcuuX6nU@>C$Rx0TlfCkrr+-xx{gSpV93Qzj zMjV_U77E=pD~m_HS}{FK{)&hM=j>e-{n?kf`5*3kRTO_C zm={NGJEHiT9^p^ypN;*iAZl4~-b??sDCw6t6Mw5{_mW<6rfie)Zn7wP9G?E3oR7Bk zjG}*i6#Y}H(7!B({;5&)&+1*?60v_<75e@6N4I}f6#eV1QO3}Zl3jOP1=wI;PishR%y`p{if3K*&`Htfdb<64E2jG|uJ6J( zN%t_qN2?dVXunfq=wA>;|N0NZwY&RkqWIer;ZORBRm2mkpOEw0IA2+wc=_R95 z^DSeX-x0;m<{y<$kJvGj@UiR^-(EZ`BI4P%w9Kx;l|Hp`7thy*9eeya&T}lj$H1BJ5I|+TDH)#otEco*;C7OEr)BFqvb>`r)fD$%LQ64)^fR) ztF(Mg%k^4*tmQXa{;1_{E%$5bd(5@}Xe}FQd8(G}v`o=5UCZHG=4d%b%X_taM9Y<0 zzO3asEw^d8Q_Fo?)_UBvCrQheT6Wa3ipKOU*Hqx@CmYuZhu4THG!?m2E7huW9*?mYcQQrsXeM z9?-J3ZYPbj9HixCS}I*GUq0&E{hF4GwVbErOf9EqnWN%)wy$WaAIb#uq3x2Kc%=ND zx75^>VZ+rWoNE(tZm>Rgq!H*kapItyqJjzCywKcLilr^JTbCipZBkPBi8zR#GgB@~ z9hTlLbr=U8bH)b876(gMC#RCo1G;rd>xu7LM&}7R86_zd%<7Vwju)(MT7?#|A=Ak& z3I=&|#K|kj%*YF77UYj-4Lu{!BUn`Fx>TY|bp4 z#C4$&2n5EC3S^Ee3XIK|7|0%%pNW3Lwk4<}G0;b1Y>7WIta}m4duHTkCcKeiyhBnQ{AlAci+S%J)q;*zielH`i!i#Xq={CTgCc*8t; z*s|idlN=bW7Rvu@#fM8XMims5s7sve!f_>;>e7%bR)erUw_ub`-OEl9DF|TOqmmC} z3&sakx|2ORNC?9>EZ5KRxL&((jLhp=Ft#wGC`g9q=8sNQ%R;3iIoY99Q(m4}oLT6GOLN2Z?bNoGL|B{%m-=U7$J-%*+X9 zjmrxT$|xR_(lw-`#5-10KIA}pwf0`mMtO4oMRmR?H<*_t{z`%qBkaIGJ=Vq2f-|Y@ zxs(L8Bs0_%M>SZTF6qn7AD@wzn-$2*%^wpe$ta@1mA50*gX<3BJPARMRxD+ZUr-Xr zE*O^|vZrEx)Dyqbm&8%+(<#yRqRN&e5&@F+O2DE8qHkQxo^Sfw$ZcUqSQX_ z>}0x=TkH-DD;XEcMy;tFcL$Gd75dtt>RQeg7iMGz)jy0fuD_pT#40Js;%$K_z4}nq zZ{U!w7q&@GK0S-!ja%29OM*pXbMxueWjK`LY(%)Of#SkoW^Q(FrZ-sf6tFjgP(ATM z@Pho{#M~0K*PUmmd#r#ya-A8-&Ce|n&FcEH{XkIdX4J};0VykxKMn)#H3Au9i$^nH zqDVECm1lUVkrZ@y zQy$0)jv6<5bg(FpQ&2EQrO45|Uqzf+ZD>?^fN!q)6}TT&r&|V|)egq^Vmd3(^ZhNm1{jSF*%&=UV;7<(1@inN$)S zR4_C*E7&zBqewMzyxlI%VOpl!F>Ax5 zkqWKv#xbsZV!FJ`Bpuq>JDH zJy*q)l7Ul0xizV{Bsg{u^Ow|O5*m~})u-Eq{k!!&v#oAD`XX-Nv%CAr?#MrVq2GdL zap@@R;GR#?CZFoL)2FsxZKzq#@O}gA_={)7_wlRIsfJpJ+tB<~axL4c7P)Q9qo@%JR=cAM3gU%y73q!6PM*MSl zR|opu%G$uU@Z7{5thE9ui>XKV^Nhzrp6h{Gjfm?a;%Q;1*&_`#uPOFiVyF>K47GhY z>wB~`)QSvjZEmQ-OR-^tp+>$>I^Q$Yw(p4dThcFlMqYhNdSF{G^yQF_LPIs_L)d)m zhN-3U6o%_S+5kHHTbEWb)_cwp%nQDHg-a)x}l(Z%t zunN}KC2hzxl*M|=dI5RApFGd`o4igl)VdTyrJ>Vf(48EAfUEa=%Z_>HJh_B?*iM?$ zAn^?9OtPUSB3qIE>F@A-$Oh(To5+h6NYYt|ep&NI-EOJnq_-96+(#PMlg8v@%f};W zka#3-mQda+siPaOBmb#`!c^+kWa^s`ethydlt)(=3H!f7%E;5*k@P12WvDHGQz!mG z9zgQT>ukeROC0XzCzxu4-&Cu3K6s~Ts-Cu~Hq*9}D1+vdhfw!$Q#I*is&(g?s(EK* z3bKo-rgk;eydI|7)zegcdz)%mA5(2jGgZg#CTshd<#gEnOtt=aQgh7 zYSq+KDJ@L3@MKf%YiX)MEG{;)nW?rnH&tQ%@;X+WhRrjn*Edp@Hz9AKzRYrEgE<_V zOMcHQZ~NpsD3jX_RdOr!Xg2NZdPC(sPCb0WP$^F$pQhbC<;tDU(U&|=U-CkE9qBI_ zYTt{7>huhLpCvyYqn$0Jo!(2?--ldmDF6Mg9QmN3RzHNFhb!vHdxZA<0DhO!cifG= zl+8%m$w;L7hj$Wuta%BG5_q>3RzM+74bDamgI)Xiz8&XRK}-D4v>fG!1qTTGH_tZ0 zGU)gh-!%FYx6S)s4r_HHC&E7VJHo!h2vdu=U?X~!pXZp7Ezv)laI0V$I#$7&6R?H& zBIP3N@(7(5Kd`nv`P+fC!w%Z@0&znp+HwlAuIyh$dCaFjxJ`wg0nb}1zh%-cIlgBK z=V2TB>5Ksj*iZbGdJ846oMY=@HEf^7JuvdG=M0{Yr|%g--?N@$;vPw#v+YgfTg=aq zt*+-ie)_oS_gHGy`}AY5D`2T*e4i$dzG>uR+zYV}Q#;YO^FEh_O9%raIKRIoeUfGg ze(fae(#SWBG*x9D>i1LPTRc}`uPtCwfwZTkC-0s8w9w5{QW+BK3o zaS46%`?%@T*U^V}%q0&B=&Qyu2GpS~WBW?j34ZKLnMvM(d^1$OJ15^;lfC#jA<>XJMk zC(kO%vwod+F;4$ZpRn5qeK$_NqbA=}T9aw1k}=2yjF*r_K6-4Fv4$}x1*St5EP*XN zU(yO{@5UyOZ@tNS?XteStnE2*FJt#;-myVWMT(wnFcoC{+Dv?suc7hnHR`ZtHnw`q zhvuB>(GIg~%qzk;sn7iO8179Aw(X)Z_=lH?RX<3lV0z6W))Ic zAHh!Cc0ZTR>~DibAbv%z(Ck8rztzY!ur5sQV81r!gpKTPh2?sn^8Hl#rmuXPSH80=-^`sg)KZg&q30sPHnfy{4_CfzE8iU+v6JzC zFY}Jyn7>@Y+#%Ox|4++Ygo1Q{FB7^^%fy5`NLAB7!PGLMmx07>%?8|!r z3u${E%^3%l;}&F0OqX`Vb&P)n`K1|woUEx!+v`M_&GdJISHHY^@3n()nL-VLuY_G3o6fOx`j1)cuWrOO;Qz?)e%f%!JIq^QP7nN_wwyk8(GM>=plWK^fxPENhPw$AAYri zImA9#_m^L-fE3t;+s`~A5z=uFgB(}`3t%eDghFV-7&Q^8kbX#nRk(LSGW#8&C;J;= zHjIRhuncyx*BLi=YB}SEr@&OG}`}kmnQ6Y zVt*^LC2>n$w>pPB;NFxc_m9G_ChdCW0-8ela}UYGP#%#_;rk`@s~+p=Yin~}Xm^~c zmJc)4unzR0`0-dnKd}-zVu#0m_M_2?a8bd2v0bRm^@)ePFCp)J2zb<0ucUnq$|gMbnv%wp$+UXhQ@Mz-q9gY@ zsNYgIJ)}NIV=8663(}+Y0hFgVU*zV!%6ne%a&LLLlDTWUy5#o|^7|}PH9404dMJH$ zd)(MCk!xSi29eQhkYnlCutwwg@%&4@lKy-<@BFlT#bp)!jLZpyZS1EZg;hPcx6+e5 zjga&HVJy}xBMlunA1S@_BDdsOY9g#-y!Ti}TD<#RB1@=CGhzQ<^b6D%VK#o&QKuSE zA9pfVtpiW4;amgiM`Ab5N62mD;~EgT1%FG(>vXOc1Zg`SUfzg|Ctqiir;4&28KrzT z6UV$4To!erzNWb}XDn?%-4-TezlW^RAZsn`yOw?n|8-MclFlQ~*%u0F%PTc&vpAON zvI5x=w-A23)47Zvon4Z+u5QxHC?!0t8y}@#X1r<1^;G&ut~V%SkG_yb@xCvp#Ay~BN!>F8(7_3Rsj zOW@YhC?k&{i zZ%HTaeaKGVQ?Bec$>yHiY_0)zFyH2Sc?2>E*%Ep}$q)3O$YGFoy`@&(#5KfR?giY; zy&>-JH&|$?VXz2YDK=Zl&fz+X{X+KDPV7L=hFKh6flU9IaRIpo5}AK(pNT&1 zH>88ye~^0*YtW~V5@s9w{$I%p%O6Ti==A?*Nc$EJ^HcymUIg&hd1Fbxb!cd znge^G)!#lf4?c#@`+e#f_z_P3$EWUwtyYS6P`%-1*a}Gr391K-hTGv;_zn{M z392m&f~(2gLQa<%7SO1UhM>x3-3YGBNEgk_#93@GC|FN z@8O)I64X5S6D~+hP)~wgCqZ2dFG1qb3F3#==eT2)qT~fmts>odD;;P`DhH!l!Ul5_t*Nz-rh7 zt?DPJY`7ObhQ#Bs3#P*w_!9QR2`40|v)}@_3|@d*4JcQ54E}-ih6(C^*bUtqk;kwD zIyFvE!(kqL0!KGVQ0*Wc3ScHIg6Ci(`~d321l0)6g&|N3v*1B^5B5M((*!jVmcec~ zyIF#o3U5PVbJ7os;Cndzqy#kq9)eFGp+$n~0Hfg!cnki3lTRibTo2E|4yfOfJb^1= z8GH=RDd>d3Fby7q58+Q}cq-)$GvF;a?6d^c2X2N9P^VRb8VK{@GicbFbif0!2XanN zP_M!nXHb6dH2e-7lBtXEBK!lL+fbJ9CLGo_LG^`OVH*rO6Pw_Kb_pr~55V_u+F9f| zJPm)qx$UW!@FpDIfii*lumeVRBra%ub^_m}N>Go&k8sL4*a#27cW~0Vv>kW`PU%E` zz%FQeUV+=&hCmo zcoFu)d1(o13cLcU8+i-wLW}MRY5{D8MmxWI zdtmm)2FQn{uoaH&lc18J4~&K>FdrU+zo187>J@BXtqX$+)tqbTcU;^9* z55p_)F|_zsdskPmPNJOb<7>@(~JPK0F7X!{0FMQtAO58=y~tF)$mRg^NZ~{;)J7 zL45?yDEfAo0dIntnV`;xE8%AdWRXYEJ(!?YLF;V#2RLRlc?0{QASXe63-xo+1;ua= zyawOGVV5PS<#53mY=^t^Xoq8IFEBqpL3JshT%bi^f?5lsF2}AS(hR2*)2G5ukXVwS z&V&hYC#-{?p!GOxfT!Vi=rBG(O@^02O~59&5jH@*iS!TfE}SxnbFdVgD`=l^6D)<@ zkbEWMF1!ebPbR-%GCT%fLG7z(S1=mhf!0$J)LhsL7hO%+LbIv#i|`9vcnxI$4W}^{ zLIzw5i{M509_n73pi<#-xF3#~&Nu)oAZZ4D8N3E{uA^Uo?QqWZlpkz`(`M2x;8Qr| z2FejO!ryStjf966VE9e6Iao9+L46JNZ>9~vHLwD{1ph6RJ4}bQ@D(K7O1{8_a0M)c z7vWR*6Ov|Aey|L7Lh@~l4KNuVfOlXQB+a3oK^9yAOW^}JVlH;T_3$x50;y zG#}gHZukNk-%fn+5c~kG?;u~`8Q2HsEuhW7#di`fOudVGbT{!qr-kT*!h3MTT1dZ_ ze1!b_7<=GTFc#4t!6=vvcfl$+;(m_73-AflTTEKvPIwkJ!jE7-K;FXuD1o`~Fsy?g z;IJjsYq$UkVII5(&QjV0^n;o3D(r`|9%PJw$KYM~2@ZRRb_6%TMyS7xx(!p{H^_RJ zeg^DE7=vLLOoQd{F{noq)TuBCrob}T1P9>M<@Bd84VJL zg&!F3bb^`)C#KnN3Rr2jM@(QNBPPwclA7K0&j4^LgSE1-F;$BBRg8a9stM4$Efs%LW zuh&!8V8(mI|33Wz+`WOhHtiFM`07}h2}fS6BrN6U<3GmrX9mLxC7S0cW}%v)OpB;neaB$`ZYoIhg)GI z)ZInff&nlNUVwk0`)~C1uo3F*#t)RhGw>%g`7d=1@?k!#hW|jVJ>)eEgR9{|cn|hM zeNA?t^X6=r76` z7Q%Z_=WqIJ*bN=`V-GwJ|G?OP=)v4t;6dna_|48|;9Opmke*kAnB1?wQ=Pfd^q1bZSQ)!zO5amS6RU8(=M% z?RmZeu7h`AHyq!=ue!ixFb7_MZ=hC3ziJD^VH!LFo8bVoJex9s7oqt%esvo>4Ijfk zIN@BsIv+;CJlF(@ov0fy0p`PM_zdjx&;e<18QcP|!`I-P?^h>7cNhaV!ISV29Ds(M zu^obNGdu%dfRjQV!C<%wmco0m7aFH>UkfgW+3*Z}3RV~D4`jpr@Fn~Ut-Dg^;6_*h zAH#lVnuZ?8hk5VRsSh=Rg+R0;}LtsGsiV zJG6dvH+&3r2GEwE5EjBq@B{D-5p^z1fT!U%Xf@EUM#B==1`P&bGu#7TL9@Y>Av_Gf zz!^i(3oGHj&~_+kfXCqnXfX``@F4sEM_fc0m4%6SHea( z=@O2^2hj3T+BmF*V*}Jdm=B*o(n!(`_dxp$zuEvfqx{OqKBy2eee$a0nMlQ)c}|R zPs1qz65fWoGyN(6kHI^z15Uev_5my53utm9{Ua=e@1gZg zq#N#qt&lX!uZF-J*aV3;Gj75l7zcO4Cvfa7qz&$Zui)fc=||x)*bR+mGseIqco04W zbsO=)aJU{;!q;%*9KSjb^5AZG2M(J{dBP2_9*&wvd@v6_gk$HU2kwLKq1Nr>8O()E zQ0ET%LYM=apzZ?PFdH_)F?W(qxEsEJ#&`KuCM<>T;k3K`ssNsbKcT@w>L1L34N&_Y z>ITe&6|fDAd#QVH89WUKpvQg037MVGeADq=#sqFb3{}&2Ypr@)Q=sR!Dl7 zdJVJSO)wu}T!)G9B>V_19wi-60Jp#@*bdHe`XlHHH^GO{=rPg&OW+4^9`~z0a2tFE zO`f0+geTw`cnQ|RdiW5wz!$I`eum%S0QgprJ~$eZpb@ly*3b^lg+4F{E`@9;fve#r zm;vN|>Il?@M$iIe6*`mW7-gkkpW+@1 ztL)Zd72w*u6U%Dsti4-@-+qo^MS^41ajG6`zSUR9s}ocM)lfBJ*4{*&sG6!~s<}Ez zwNNLkmg*FBsya=zQmuLC`3%14+=lm#&*WW;vs8Q4fi?5aR_Ca5RVQ_xI$w2GDJoTU zQC(G<>ZZD@9;&D6rFyGAs;}y&`l}06x*DJ^R0Da(cd#0whN@w#n;>sqkKos+OI1LP zR2gcN%2Zh@sIt{)m7{XGju@lz)L1?nRKR-!m#ZRGtV+~4HC|0n6V)VEh`3TsR#&Mh z>S{GrU8AO{Yt?i$LtUq?S2NWO>PB^wnx$@5x2RjyY;_y&iOyB?)O>Zjx$^@LiXo>Wh%r`1aJjCxi* z$1|hPs~6OZYPEVvy{ukQYt*aiHTAk$tKLv=s<+fS^|pG4cl*|>_tg7pgW9M*;0?kL zdAIf>^|AUyZBd`9&(!B?tNKEHslHO%)Ys}8^&hoeeXG7x->V(!2lb=+iJvfk=8eH$ z)h_j$+O7Vp_Nd?0UiF9Cr~XubslU~J^^ZEB{-yX0!!#^DqwE+yK3MBF4l`=;aqim2 z5yp|mQAVOs$2i(J#;9u?YaD0PGm?z@#_`4pMgyav(a30QG%-#zni|cF=Eg}z3*%&? zrE!XJs&Sgp%4ltzZk%Bx8*PlX#+gPt<1C}S(ZT3woNb(AoNIJ4&NI$8IvXiQs?o*h zYNQ$6jP6Dcqo>i!=xy{d`WpR={>BAHx-r1G&=_b8G6ow%jG@Lb<050Yaj`MNxWu^B z2pA)c3}cj$X=E8eBik5lg_jj6^p#x&zvW4bZJxX!rVm}%T#+-Tfn%rb5^ZZU2(W*fH|bBwvhJY&9b zyK#rHz_`=6%edQEXxwAmYuslnGVV7P8xI&ujHSkd#zV$3<6+|w<56R|@tE^A;u>@j{f_8NZ}`;0%0zl^_){l-7W0pnj9 zzhRoDW!k1=`pg8=ZysjWG7mRvn@5;Onn#(5W*zfr^BA+Pd8~PySzl`$CzuV) zhGrwPvDw5t(QIlqGn<Ud9Qh&xyZcVTx>pIE-{yy z51J2|%gl$(N6bgf<>q7N~BboouzVPO(n4PP1BB zt*z6oGpuB*jn&pV(`sj(Wwp0DSRJjit#hn%txnc?*7;UvE5%B+x>#MUG^?A{-RfcW zw0c>+tv*&?tDn{1y1+`e23Qwb1Fb>UU~7mq)EZ`8WDU12wnkW&SeIG>YowK7jj}SW zEGuYbTcfQUE7!Wr8e`>IW37Cvz$&yZw~DM{tHc^-jkhLP6Rko#kSHP@PF&9`p1?yweEcUpH@ zcUudsd#rn{`>aLQ{nldZ0c(l1)Oyf*$XaGSY&~Kt*W|YmN1)^_um%wbpvWdeeH#T4%j&y<@#=t+(E@-nTYb z8?6tlP1c9jX6qyCW9t)Zi}k7Xnf1A~)%wEv()!BUW_@jaWBte4ZhdQgXMJz&uzs+9 zw0^R7T0dLASif4ktlzBN)_<)%*6-F{>kn(6^{4fh^|!U(`o}t8{mZ~_*rsjSw(ZzH zJHhtbhuO94!|mGk5%!VxQFfwT$3EIV#;$80YaeIVvy<%l_VM-!b_2Vi-N%5H6+Zl7T%+imQ&_L+7&`z*V?-NEi?pKYIGpKEur z&$G|BJKHICs@=uzYNy%V?Cy3CyQkgD?rrz6``Z2N{`LiSx;?m?`am?aB64_7wYSd#ZhnJo^H>uud}bWXWBQ|H`+JZv+SGgTkKoy z+4gPr9DA-k&z^7JZr@=qu*IsYGXTNW6us7Nt*qiJR?alT__Q&=o_7)a6{>=W|-fDkge`$YZ zZ?nI)zp?*gZ@0g-zq7x$ci2DJKiWUpJMEwCU+iD)UG{JGZu`IX9{YEDulZA{V>qT`Ikw|CJ}1HPJBK;7oWq^k&JoU$&QVUHQ^z^lImW5$9P1qC z)N_)Y`p)sr2~Gp2q0`7|>@;ysbecNNoaW9+P7CK`r=@d>bEY>obH_ABs*=K zw$7POJLfE?z0<+z=$!4GFT68-JI@D52vTo%jxa(ar!#_ zoc_)QPP#L|xzHKt3~~lLL!6<`Fy|s?xO1^H!nwq`)Co8voeXD`lj&qRK_}Z8?c_MQ z&SlORC(jw{w%Y0lS2pbVIa-Mfy za9(s)J1;peJFhrvoL8OKoY$ST&Ku5~&Rfnp=WXX5=Ur#L^Pcm*v%%TueBf+yK6Exa zA2}a8pEz5bPo2-4&z-H#7tWW?SI#!)Yv&v1KhAdNTjx9HduNC9gY%>Fle5$L+4;r! z)!F6z=InO<>+ErUclJ7eIQyJGoxhyFo&C-~&H?9NNBIn&>9c&c&+++u2|mB?Fkda- z;lA3wBYa2tj`Ah?>iCZK9pkI(JJxrcubwZ-SKoKM?*v~1UqfFbUt?br--*7azGlAW zzLR_{d?))_`cCnk>O0NX%GcU=y6+5MvagM=t?x`F7yrb4e|~44e<^2 z4f9>(8}7TY~N^KjxX1DnQx3Q&o|bW?X`Y!hs z`HFodzHz?sz6rjGzDd3-d{_D=`>yg$@m=kk>bu4_&3CPDx^IT>I^XranZ6r*H~Mb! z$$BU0-g+l24%E4M;G&G8{FJn?mE6i!V~gQIujCZtEGv$L&Wy?^X1$c$g5s34!JV6D zvT8?wrHgV)aJ!MY|9f>y4Xy51oiKVmsnj8zo4c{F$V)I|EQ_|qj4QO*Uql=|BJAoB zhh3G1sl={W;iB0U7#d+%U}zkcRS~KZ+hT`|VqGYwV`g*Nf{o3a6Qf>av3$PAtAW)D zYgWK+M6q%{ep9Q~5Z%;R;bP`$?1G5j*m!hAH#c_33I@CFtp>FqZUxazjn$*HNeT3C z!!lFh74Cl+)xpLzv>gkI$(m5^(n?t=u`)PT4XV02=u}s&AbJMJ3Rg)6#|lzrYxSdw zp21be6n6&4uJdJP$Lh&+7RRF^ddg#mtR$Ob2P(7pV4{nj)$znuWo8$|-2`J~b^HpV zr@XT6qI|mJk10y31O1~btGd2ck?yJ$L{E3Da8kVv*3@dGJD$o@Matt*5k2j(L&i>h z>@a0*D~|NL)xEOWXh>aX<{L5Dj(0j+rXY$v<%_mD)|8*SmxYW~k;=@dA{|vLs75-f zRu(HARTOYMB@&N{YNRC|bzW*@S;WloB`IkvJziEJCFcVyZzwA`GLZEAPFcy1>xt>5K3mrcf59^ez@dmoKrCTEQ#VsNDnoBbDhZym*v_dyQ1%U5~}1 zr<~t1Mfu~#21c_AaT=F6xup#)z!Kvu6R5dU;wW!8OWud7hn|x#o*d%paVR3FXtI=EG?NcW^$?S58bL*qLq!+O z=0j9H#Ogy3N15TWj(knKkI-NzxAG3H+RFRHLt%I*hA2kYpxdY{FKVDR&b+8;3{mo; zW}Q)G*-^KCybJaO=2qN(+h^)(=!fz`9E*OJhvlRDIAyE3Uj*`ITDx6*UqMGUGOww)-ye!-f!@h%K15McuIiO*X1Q7A&Xw!&%D3xKFxQ^)CrZcC(&iE> z_vrStiS|oxNGoRu631m?iGFI zSMkwo)#0jbst#9uOXIrX9mP^FTy-t=!d2hW(wijJ&egJz)wQ-PWOeMVaO16C=`#*A z9Pe!DeYB`&%kGMmKOKIfE6N#t*DA^x?>$SWabVSg7I6#^?$URXLYhcnAwRr1Magbg9BaRn(;l4_R4PdRfq>;?!Q| z3NbGW!iv0N$ZA{^xU-CK70e#x+f|juUN-Fs*&XY0K`W1a70@O$KaaQy2v3^K{^h5W zvMIXO8GVM{Ci=X)O<+)kX|dK5W6Ikm5OvDiCdvdhZ2YY_B@I6*qeIwiHSAQy>1f!= z^7G2D6QLSfHlZv#(~jw6uY%%HDPg}v$()?2X!yl|c#C|#uzKD~FQrgbI!%pZA}Et% z9aH(Am@De3bW%_aJylE*;?SgLaj_m(D6OGYBuuNRX}YwU8Yjz}^3^a|o|+ma%TrV1 zWR=YgD@~a6W6`U;Tb;|)RGP5()l`wN*ov4828*~3%*!1sPw93o>k*^dAK^dj5#qS~ zcdMrU?$z`k_5*HQ_Eg!EMWnY^HSOtLP5*tW>Azn!{r9h?{|l<=KTzESbLS+ohl?0X zB1(Y!bJYx>pQ8@zip#|iWBTM)pSI<18@7wMiJ4r^7P*Cq>e4%zk-o!kd7{3e_h=KE zXw^8-p9cxbMrfU~iu%M`c3we7NxYu<8fQFSE6jSL8d+fq6xCP6d?=cq@-vkf<#<-< zR6!46m3GkOl%#eK-KVUHh8{K6;NF<6i4F37>du{OqM>((wpBkF@w&urn#waOxMVY8s;Y-qc3gL`rVM4B}*$TZSR%5Y$W}0>X5ebr2Qd@ zv$Q8Vbczq=eU}QwS?Me?uD-4s8e;Ty)zA>5ud9ZJ7=2weG{k6I)zA>5ud9ZJ7=2we zG{k6|)zA>5ud9ZJ*nM3!l*H=ms-vRj1)_iaJ?QoocV8E8vZC~Lv4U3FCB+IDIi1#V z#^2Y)tvG657b~=WJ*q-kRB@>rS8rD>9Wna5YUzm4<5f#Xj6SbgI%4#C)zT58nN>?i zjGnJrI%4#F)zT58_p6qU*!^F%)WqrmtEc2p6i%#OFn&wo?gy)_GJMt-J8+eKVeFta z?hUJ%R}V7>AHIf7K@Tn!;*T{AjO>qNbYk%akI{+68$3oQ7H{wvomjlVV{~Hi29Hs4 z@dl64iNzZ{Mkf|;@EDy~yust{#Hy(kwpq7CUg2om=dnk!_;d-CH$ONA2KZ zhmY08#ST#+F)=&2_;tkU=BlBg#!WWBjk3JRgvQn2+ zxmglA7w8aPzhcdoLy@)blEROQ_o62}8)F+)|?ytv*us1RE`zjVh6 zR6PrIOx4W9xNcBH8t4V8mVsWNY8qHJ*Qr(oEDKgGBg=xtZKn6$K-UC>YU{7GzprS2 zEblSxj};&^4=U5|*&oY$O#M|1JY_NH<%uet_{S)Nxa06r5j#MIbW~9KfFG;Z~R;7WeWuO;RdIY8aXx&48SPZqGy1xY zNu;4r-bAx}utPA}jIOW-Ql;{d;Fh_~5ux8Vg@;XBki5gBqV`w~N zrCn;{p)BswNuMkZsGIe{to~Nkq)Ig&vH~yFc<4$z+T=96{3{Fy&|T6_g-8eH7g4vxRYDq)mM2$npaawSGN(bWQ6N8;vHK2 zriuYW+?;BAre1nf!pPjF{MErg*RNi52S4L8@-u@e;co@ID;356VsR+L1F6Nuxuf%I zrbu1_YC{6#!SFyT>+i5YP0B@B++Lr{ zNs}9!fvy>OdD;2J5m^z+xS{P>Lx-hpybS3|#VABwv_>HHA!J{-RFtL#GmC=RDbeev z=Q9+2T0wqBQBlDJY;?y|J>2%{MrwMbh0;~Qk2mgyJTb6qUlVe(g89)DmFg+)BV3KA zrQ6uTl1b4r%)Naa=$<{cgwry{*Ko9>K&36E39LpdWf7sg1|4D5%4^mqM$@00pG8qd zsYq0&!SW_HXEP>AZPKaDEhwbCLf06fZe=iEVv!Z8Q>1eCjKfopcsvD$dcLH<4q4H4 zmY<-jGw`)d8FHq)pO-qMwAX2yLg~xmmS>)Mrdt8Zn^?mN5Yxn1^Y*AF_NdgvnpdNk zR>r24yIydIbu=tHZ<96j#HfSMOfX)6_bNIQ3hxr*USFJKNbeAyLFbrpnP0v%LX z-Dw|GSg#kZX5d3`m35FY)?i|OkTKS5;(id3)^KP$$QWzT-5q3%HS4{q8EI(JJ-TM= zsUK{V|95q^G?o9SmA3R!uLhT+2a~tbQ2y_#ZD}h159@8kIeb_?$pwjwY2I~-jEMz# zc@?hOx}XO>ii##i1QpL;5unQyX%g)GS<=TpI%0$CclbxNHrDv5jY}+!Y=BhR|P3DNz z`{F{|lr|9)OZ2!CXh}wS6Kl{H#9?CWxn(s>tXba?hn1DhEvvF~;{OkdJKV`7b$Nj(&Hz2ve;SCPC<$K;5 z)Sfu{v{*lpGC1@k_>j`8P_Mc~SXYDU5{GrMD@}aX)vSKRVPIvoB$kQX9PQk^w7u1| zcn@aWPwq+CmJRi+h!k#7O^YSeTt#RLm4M)`tZ`w}Yiy5A@Ps*a|IfMR!uxp@eR~4=TTkXDFQe zLj#k&60szjwTwNbzd@BLEGZgSx!w60<1=#es8o?l0mkZXLOr7N$AR8%HyBextU46G zhS&vOQ3v0pt9~j{L$~O4I-+Z+A{8MGJu1~uWhz2C0zUK&6;-QTp~O^Do#ez+QmqvA3{K3-9Ze-W1otLG16CSaXt1uJxuzX^54Px<4?zbV z7@C}%9Maw^E0D>E6!A-Ra!>hP>h}1(^0W99OX)94p>9v-uYAI!+TFJMd%2`me$nlj%o>sM z%VcQP$%9cA>a${mt~Bdq4OsctP|@#qV;r^0VYp%|%n zWuHyeKO5=1^a7OrXcTXNvR{d+3=#gbP<)S>(yE<%-Ln3S^JtG9rrl zuCn~8`y+{Kg?#Bbs=v^Kb)}D_g*<0f$1~Y5rZ^y9*+>b^FT%f&*R}kkcp>Gk|49{g83Ntcud3r4T zfx-A<<6_x;sH9iErz|g+LB522r!YQEsq*ZJHdj{DjWDrbEH=9LATr&DT>|myl?(_y z*AjZXC@TI?nyOOg_jH7^hpH7H`NXrrPAMO{Jy0Rl-Cm9|);Qbk1@Ew*T} zrj;uFm0DD^Xi=Zf+?UPX-TSg3wB7!n(jLDjch8(TGjrzk&dk*v4RvzYb9nakNSp2$ z5%o^pHyCo~s5oMBj76nh=GwgvKBaRpcDIo|QDByQZ>cVZ>ZcBE*Q?~_df#OAUJBm?v*5N< zSLfgkbIk|^ak&R}wDy5_^2cCbFNT3~(Z+IXsa1}fZY*U3td)Je8$#NeXkDtNM#|l< zAuF?$ZwlKTj0kzP+`kijJVH~Tj?L6R44;Ax3ofVmq57_cQ^#ob;gs$`im*FJT559I zV~TPUKkq-JiE`UGZNIu6s)y?~Fto>~`w&MD#iNvWr@}66hn*;fM zGPNlq5oV#Sd{bH4a|~_mHfG@?zZ2JO7$2+-Wp3yj(4k^iPoc;JILN6>v6g}qmO&zv zWKA(Fj9nnTTJ14$z|cmzNoZnNW39}%yk?_i-XYLY(_0-? zHNHZBsh>k-bldE$SH6SpuhC90>4U>bh!N9lN1-zRJTn=F9^|Du(3HBW%bc~zFPX~? zehePNzEvLK=*lm?X{tI$Iknz6X;WR;T559n9RHm1iuoZgRwf!(R0N9i%Y5uYQATHB zUz`m4wv$?mtwm`{mBv7h3^duwuu+Zn62rb3a|mnOh&hCHXu`Tf=UBd3wrzpK-c4I; zuuVWr8XS^g(9zr1pgCpc8XQs)_HNorgJUYf-%aOKRrm`0oB>nZ>jK%Z=|g2FryA^4 z4h||BO0+{^1_e%I?QjL9BgB1EL&*Iscgn7v_vNHRnG# zzrrTp^lqc%mYYL#=+|;q+LJO1x92}rXkk7SZqI+V)WVEnxwJaq*Ne}ZTZhQgWdoeZTSy+K&bV-s;tPDKeN2T zFl(3Z0n`dL#a}ds7ZX0NA7Cn1KC8#e)|Yp$4ovY^*=+Jx-T^8OIfdnMr%4#I(P_-I zY~>9zF++g)yqz^JM0ZDdY0jBlit0gXBplh zkx?4LmngcY%emP_`E%yvhc|_jlpE!JCcjKpu8KGNpI|yrx)%AzmlCrt^r6q;F{E8yJ@Xl*SrS#8i#N=mIOTts8>1C`=li;h!FDvq!1S+5Fui%T*yqS`wn+Q}?mnlAI{(FAu zJPwii%F30)1ZMX*=`WmRW*Ob?O5RVXcghqv-O*DZuOTY+<`>C~3GE0dnD6zu zp=$pHJ(zRTps)ZR8P0jCEyI<{y%hz9*8+0EUcL=bj!tL-zAAbD23@V8O`5_=-I#H* zBUazM>vfJiiecR;OMRPmFhP~CKtu<$t)WU4vW(L?s70I(wc0+`SOSN|8|iyA-paze zniFQfhG((K@0#w=LQ4;wt~@Yb-Ttk;NR6e~X{e=dX)Y%F)dCYWj>Qq1ni%reT2XX< znaPHhn0l6X@^HIPJ>7n2&YsDZXw>NGL%UDSD@~q!A+Mc0GLNzs9sR*Z*}Gvt46*vl z$}8sh=Flc8(|vzMNrA7lWKIcP*jTlhoHBZ>Y__;*%u-StcTE|sToPa>d6};BGAEtd zwQf^JD~+a|&d^;v%Aq4W$t*o~WxJo9ODf;`u+w>kE_sEMPQGfQ3`V&2!ZA&(7wNK&siJ|^WgV>zyCX?C1;#NWT#`L_ zR$e+Ji`)OKr8Ez9kkY){7T+c+ zA6blV6P1l*@ol2AtQX%VD(j2!ZKAT!8Q&)A``zN(O7+8#ac<*(LA6%bHX5Ym091P|Q*-dP3{BK?l(n#-E+$9b)|1Y2GWwpPlA)DE{m;?;qpOPV-JO z{_HgGDdW#h%kDD%Ott7URe&VP+NnAjWF9${i{{5!>bd12juOSche zY+SoTq_Oes3(DSEZ5M}Ee%V&pH7564C||0O2Rh^zmLxJB&AdS2M=NK!7~~m38}|B` zg&wbk*7sf*LL2w~m<9Ju*TSpk3}y6{xUk_v#L{qVm0v+s?x7y14VTO%eUrz9Z0S~| zZ{O5(DPJm8?~yjq7A}mskZJ1q7inJP_eQn69iv=$G*~^eOj@D>L}sUBfT$dd3^6C4 z8(WlfIbn7gqfxfkmitU4BlJg$+HVGm_;R$iuTbWOc5gnJU@fCM;FwI0;)YtOMu=)XvtNWySc`#d+&Ox53$56x9nDqtbc(hGlUy4WD z^aa{@v`t@>jYr$4WXg3_v?}pvUTE<(E{%)l3K3dlHRhZgW}1B?#A>IFGIZ)5Kv}kO zR^d&riX3m=%EgCm=7ky`wwbqb@nM^JD;FQOS+;WVVV*@R7bnKWbA_;I<&^oUSu1F4~D$eTvo{F6^MZs+_BExL{3R0W~bBX$IKH zV5qV_CDB4U%v$yPljK08#xNO~QLGUwYp)g>qmqQYwptcL2<{kDZkSWds15`i6K^SF zns}phSi-^*Zz*G%c%yVIiMI|BmiX$j%Hon)PV0%IwT1i8VtsTlvvtH6f|;%#MunSK z!i^5L-f1bJ77vTP@|A<;7)Ign(s7gsSEsM{8`VV=jcj(6$V2{SEgo7FF7un+`6RD% zQ}2B8D<8eluTF~bx-xn1w9Ev#*lwl3YB@paku(L$?IKnRtcU60D%39;`-|j9t$ULS z3k9*K!a_lhQlWg1YSaMVHTe}~UajTqzT->%UKQ8T=W`d9k_S`dnzeRSmd)X3sX{sY z?C0`JHRLtQXEF2)bG}$xE^-eUtXxFsq{J%A26O9D%u1}YEmDp0833=hcL}mp5^I8N zmBgAL?w*Xi`ANUy%^&EklUpdTN?>?BQ>&_l0_((utI(=ypt7WtuQyrUI-}i<UGhTgUDaK?_YpB$FkE{hlsZc*+Xe}5rQ0u>}{6z0OxpGOl z?kIG}Z)H>k(=W2K7f}wl*obIt?5zBf(x@2pDm<%%k8bF#F7@2`oQn94l^P72MkRNCgGuT>Bcb-|0G3CA2csQyTjs^U$`& zJBD_!JuJMxOu4j>&#C!~!ag6QT$Qfe&l~z>uyReSdR>in4X0XE&Mlkfzko7T>ZK0W z#bWgo^*-%?XMDX*pqy-}3#+{`Th(&##wc?LGXos_GSp&H*2+w`mpV+!+R6g!vbGAL z)x>a{dlX-<6G$nIw=U~g%~rJ>yfMl^*Q}fzTM>txvkq@vR@Px+D=q8rL%ix@O0QFT zDAZ79tfwh&8+&Jurl?mog*kxyW{otc7pJ;p=c4R9F}r`?{^f(+RcUHA#if19bRmxI z3v;u$%s=HFT^#41a_&8j?NeU3$FY6N>-IRdk2X}8>*eChz<963>Si)yFQROIGVA87=gP`lJBE(VT0>8}y7)8Fq`!+lBTYKJ z_%qU^(!`&UCf#5B8EMi7#-EWU9bx<#Y2F*gpPgo1VjNoeUs1)wJ4a;$v+O71XQdOD z4BKk0_Th1Crre@9RPK$8urz)KY_=I1e`b=QEG&G=BXc($YA0 z0h4>w;A}x(yFNm}>zFnZSgacLjR>K%Kb%r)XF-Y#- z#dioa={xyMRaw1mjNCDWQQh0k4uK|p%bBSv>!o26v%>a59U5h!{ZO2t3Ke3EJBzhf zyV~iiA^Nsgb4?C;uWW^iO_O7WS?Y>DMa2FqW2B|!GxJNm+B2m9^aEvbr!PC)mBB@M zd7F)1 zYr4L=#9!r`oIlxGRfLGOuF8_4vV3Z8k-a_>F>{?Ig;d(8iz|m*?Fo7HXfetl6go+4 zC)9mz*j%c#lx^_(X-qp2?U0zA2rZCO2TWy@v-sNaHfsU9SQHI*am?{oR^}J^Id^1j zM@YsdrjU$nWaVZ4`MupZYL*eqBsImo7}nS;?%gQFT6Zt+@UT}bbHnU%5ZmU|#u1ck zmKH0yVW!!kp>@z5s4gpDt$l#U6RY58Z%mToV^xV@j4FN8W7R1o?UWvZoqA?&psPK^ zRYF&L=qrTO{4b~uy4pir8FaOWzAB(us|eP<9g?wWlS49gZStX5$ujf4klsV=ej&Yw z-v1QU{{jaL={>|A7}9%aU9hOEyyD=ew7!YTaY;scW?F1Jp0Ja&zKP1L(L#$uCu5># z&dlntB*k*fI(%*){zDT6tDuID1sH;MPa~|t zMtmutq|BE;S6&vNFDG^9zU};3IW@1G61LG`TYfQVa45EL9opp6;YN?p6f{cCc%_H6 zobgT%YdPbk9@cVNXR6)mVJ&C8*27xPc&~@GTe0gp}ORFw}x`m zpS4euv%OZekF%o6R=cQ(Y%7~hL{{M}BXX#dHc?rJ?2fETHbu(ZI%;C3tTdIBnMj0Y zIu;^fQ!#4^Cne#NFAFV(S(hrKO;~-Q%L)`{l^0};x!P}bjX_CZR!z4Dsm-zTJqEtX znp>EKwkl%96r~Nd3vFF$(c!zl6r(k#vKx}yu_VbW>y*#Lgne-)OpH&&DDM)&e%0DW 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 literal 0 HcmV?d00001 diff --git a/Builds/MSVC/VS2010/FractoriumInstaller/msvcr100.dll b/Builds/MSVC/VS2010/FractoriumInstaller/msvcr100.dll new file mode 100644 index 0000000000000000000000000000000000000000..0318fb0964a94c90c05ca306aa419725e8cce81b GIT binary patch literal 829264 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 z
* var2) +{ + bool success = true; + + if (var1 && var2) + { + if (var1->NeedPrecalcSumSquares() != var2->NeedPrecalcSumSquares()) + { + cout << "NeedPrecalcSumSquares value of " << var1->NeedPrecalcSumSquares() << " for variation " << var1->Name() << " != NeedPrecalcSumSquares value of " << var2->NeedPrecalcSumSquares() << " for variation " << var2->Name() << endl; + success = false; + } + + if (var1->NeedPrecalcSqrtSumSquares() != var2->NeedPrecalcSqrtSumSquares()) + { + cout << "NeedPrecalcSqrtSumSquares value of " << var1->NeedPrecalcSqrtSumSquares() << " for variation " << var1->Name() << " != NeedPrecalcSqrtSumSquares value of " << var2->NeedPrecalcSqrtSumSquares() << " for variation " << var2->Name() << endl; + success = false; + } + + if (var1->NeedPrecalcAngles() != var2->NeedPrecalcAngles()) + { + cout << "NeedPrecalcAngles value of " << var1->NeedPrecalcAngles() << " for variation " << var1->Name() << " != NeedPrecalcAngles value of " << var2->NeedPrecalcAngles() << " for variation " << var2->Name() << endl; + success = false; + } + + if (var1->NeedPrecalcAtanXY() != var2->NeedPrecalcAtanXY()) + { + cout << "NeedPrecalcAtanXY value of " << var1->NeedPrecalcAtanXY() << " for variation " << var1->Name() << " != NeedPrecalcAtanXY value of " << var2->NeedPrecalcAtanXY() << " for variation " << var2->Name() << endl; + success = false; + } + + if (var1->NeedPrecalcAtanYX() != var2->NeedPrecalcAtanYX()) + { + cout << "NeedPrecalcAtanYX value of " << var1->NeedPrecalcAtanYX() << " for variation " << var1->Name() << " != NeedPrecalcAtanYX value of " << var2->NeedPrecalcAtanYX() << " for variation " << var2->Name() << endl; + success = false; + } + } + + return success; +} + +template +bool TestVarEqual(Variation* var1, Variation
* var2) +{ + bool success = true; + + if (!var1 || !var2) + { + cout << "Variations were null." << endl; + return false; + } + + if (var1->VariationId() != var2->VariationId()) + { + cout << "Variation IDs were not equal: " << var1->VariationId() << " != " << var2->VariationId() << endl; + success = false; + } + + if (var1->VarType() != var2->VarType()) + { + cout << "Variation types were not equal: " << var1->VarType() << " != " << var2->VarType() << endl; + success = false; + } + + if (var1->Name() != var2->Name()) + { + cout << "Variation names were not equal: " << var1->Name() << " != " << var2->Name() << endl; + success = false; + } + + if (var1->Prefix() != var2->Prefix()) + { + cout << "Variation prefixes were not equal: " << var1->Prefix() << " != " << var2->Prefix() << endl; + success = false; + } + + if (!TestVarPrecalcEqual(var1, var2)) + { + cout << "Variation precalcs were not equal: " << var1->Name() << " and " << var2->Name() << "." << endl; + success = false; + } + + ParametricVariation* parVar1 = dynamic_cast*>(var1); + ParametricVariation
* parVar2 = dynamic_cast*>(var2); + + if (parVar1 && parVar2) + { + if (parVar1->ParamCount() != parVar2->ParamCount()) + { + cout << "Variation ParamCount were not equal: " << parVar1->ParamCount() << " != " << parVar2->ParamCount() << endl; + success = false; + } + + vector> params1 = parVar1->ParamsVec(); + vector> params2 = parVar2->ParamsVec(); + + for (size_t i = 0; i < params1.size(); i++) + { + if (params1[i].Name() != params2[i].Name()) + { + cout << "Param Names were not equal: " << params1[i].Name() << " != " << params2[i].Name() << endl; + success = false; + } + + if (params1[i].Type() != params2[i].Type()) + { + cout << "Param " << params1[i].Name() << " Types were not equal: " << params1[i].Type() << " != " << params2[i].Type() << endl; + success = false; + } + + if (params1[i].IsPrecalc() != params2[i].IsPrecalc()) + { + cout << "Param " << params1[i].Name() << " IsPrecalc were not equal: " << params1[i].IsPrecalc() << " != " << params2[i].IsPrecalc() << endl; + success = false; + } + + if (!IsClose(params1[i].Def(), (sT)params2[i].Def())) + { + cout << "Param " << params1[i].Name() << " Def were not equal: " << params1[i].Def() << " != " << params2[i].Def() << endl; + success = false; + } + + if (typeid(sT) == typeid(dT))//Min and max can be different for float and double. + { + if (!IsClose(params1[i].Min(), (sT)params2[i].Min())) + { + cout << "Param " << params1[i].Name() << " Min were not equal: " << params1[i].Min() << " != " << params2[i].Min() << endl; + success = false; + } + + if (!IsClose(params1[i].Max(), (sT)params2[i].Max())) + { + cout << "Param " << params1[i].Name() << " Max were not equal: " << params1[i].Max() << " != " << params2[i].Max() << endl; + success = false; + } + } + + if (!IsClose(params1[i].ParamVal(), (sT)params2[i].ParamVal(), sT(1e-4))) + { + cout << "Param " << params1[i].Name() << " Val were not equal: " << params1[i].ParamVal() << " != " << params2[i].ParamVal() << endl; + success = false; + } + } + } + + return success; +} + +bool TestVarPrePostNames() +{ + bool success = true; + VariationList vlf; + + for (size_t i = 0; i < vlf.Size(); i++) + { + Variation* var = vlf.GetVariation(i); + string name = var->Name(); + + if (var->VarType() == VARTYPE_REG) + { + if (name.find("pre_") == 0) + { + cout << "Regular variation " << name << " must not start with pre_." << endl; + success = false; + } + + if (name.find("post_") == 0) + { + cout << "Regular variation " << name << " must not start with post_." << endl; + success = false; + } + } + else if (var->VarType() == VARTYPE_PRE) + { + if (name.find("pre_") != 0) + { + cout << "Pre variation " << name << " must start with pre_." << endl; + success = false; + } + } + else if (var->VarType() == VARTYPE_POST) + { + if (name.find("post_") != 0) + { + cout << "Post variation " << name << " must start with post_." << endl; + success = false; + } + } + else + { + cout << "Invalid variation type." << endl; + success = false; + break; + } + + if (ParametricVariation* parVar = dynamic_cast*>(var)) + { + vector> params = parVar->ParamsVec(); + + for (size_t p = 0; p < params.size(); p++) + { + if (params[p].Name().find(name.c_str()) != 0) + { + cout << "Param " << params[p].Name() << " must start with " << name << endl; + success = false; + } + } + } + } + + return success; +} + +template +bool TestVarCopy() +{ + bool success = true; + VariationList vlf; + + for (size_t i = 0; i < vlf.Size(); i++) + { + Variation* var = vlf.GetVariation(i); + Variation
* destVar = NULL; + auto_ptr> copyVar(var->Copy());//Test Copy(). + + if (!TestVarEqual(var, copyVar.get())) + { + cout << "Variations " << var->Name() << "<" << typeid(sT).name() << "> and " << copyVar->Name() << "<" << typeid(sT).name() << "> (same template type) were not equal after Copy()." << endl; + success = false; + } + + var->Copy(destVar);//Test Copy(var*); + auto_ptr> destPtr(destVar);//Just for deletion. + + if (!TestVarEqual(var, destPtr.get())) + { + cout << "Variations " << var->Name() << "<" << typeid(sT).name() << "> and " << destPtr->Name() << "<" << typeid(dT).name() << "> (different template types) were not equal after Copy(Variation*)." << endl; + success = false; + } + } + + return success; +} + +bool TestParVars() +{ + bool success = true; + VariationList vlf; + + for (size_t i = 0; i < vlf.ParametricSize(); i++) + { + if (ParametricVariation* parVar = vlf.GetParametricVariation(i)) + { + if (parVar->ParamCount() < 1) + { + cout << "Parametric variation " << parVar->Name() << " does not have any parameters." << endl; + success = false; + } + + vector names; + vector addresses; + ParamWithName* params = parVar->Params(); + + names.reserve(parVar->ParamCount()); + addresses.reserve(parVar->ParamCount()); + + for (unsigned int j = 0; j < parVar->ParamCount(); j++) + { + if (std::find(names.begin(), names.end(), params[j].Name()) != names.end()) + { + cout << "Param " << params[j].Name() << " for variation " << parVar->Name() << " was a duplicate name entry." << endl; + success = false; + } + else + { + names.push_back(params[j].Name()); + } + + if (std::find(addresses.begin(), addresses.end(), params[j].Param()) != addresses.end()) + { + cout << "Param address" << params[j].Param() << " for variation " << parVar->Name() << " was a duplicate name entry." << endl; + success = false; + } + else + { + addresses.push_back(params[j].Param()); + } + } + } + else + { + cout << "Parametric variation at index " << i << " was NULL." << endl; + success = false; + } + } + + return success; +} + +bool TestVarRegPrePost() +{ + bool success = true; + VariationList vlf; + + for (size_t i = 0; i < vlf.RegSize(); i++) + { + Variation* regVar = vlf.GetVariation(i, VARTYPE_REG); + + if (regVar) + { + if (regVar->Name().find("dc_") != 0) + { + string name = regVar->Name(); + + Variation* preVar = vlf.GetVariation("pre_" + name); + Variation* postVar = vlf.GetVariation("post_" + name); + + if (!preVar) + { + cout << "Pre variation equivalent of " << name << " could not be found." << endl; + success = false; + } + + if (!postVar) + { + cout << "Post variation equivalent of " << name << " could not be found." << endl; + success = false; + } + + if (!TestVarPrecalcEqual(regVar, preVar)) + { + cout << "Regular and pre variation precalc test failed for " << regVar->Name() << " and " << preVar->Name() << "." << endl; + success = false; + } + + if (!TestVarPrecalcEqual(regVar, postVar)) + { + cout << "Regular and post variation precalc test failed for " << regVar->Name() << " and " << postVar->Name() << "." << endl; + success = false; + } + } + } + else + { + cout << "Regular variation " << i << " was NULL." << endl; + success = false; + } + } + + return success; +} + +bool TestVarPrecalcUsedCL() +{ + bool success = true; + VariationList vlf; + + for (size_t i = 0; i < vlf.Size(); i++) + { + Variation* var = vlf.GetVariation(i); + string s = var->OpenCLString(); + + if (var->NeedPrecalcAngles()) + { + if (s.find("precalcSina") == string::npos) + { + cout << "Variation " << var->Name() << " needed precalcSina, but it wasn't found in the OpenCL string." << endl; + success = false; + } + + if (s.find("precalcCosa") == string::npos) + { + cout << "Variation " << var->Name() << " needed precalcCosa, but it wasn't found in the OpenCL string." << endl; + success = false; + } + } + else + { + if (s.find("precalcSina") != string::npos) + { + cout << "Variation " << var->Name() << " didn't need precalcSina, but it was found in the OpenCL string." << endl; + success = false; + } + + if (s.find("precalcCosa") != string::npos) + { + cout << "Variation " << var->Name() << " didn't need precalcCosa, but it was found in the OpenCL string." << endl; + success = false; + } + + if (var->NeedPrecalcSqrtSumSquares()) + { + if (s.find("precalcSqrtSumSquares") == string::npos) + { + cout << "Variation " << var->Name() << " needed precalcSqrtSumSquares, but it wasn't found in the OpenCL string." << endl; + success = false; + } + } + else + { + if (s.find("precalcSqrtSumSquares") != string::npos) + { + cout << "Variation " << var->Name() << " didn't need precalcSqrtSumSquares, but it was found in the OpenCL string." << endl; + success = false; + } + + if (var->NeedPrecalcSumSquares()) + { + if (s.find("precalcSumSquares") == string::npos) + { + cout << "Variation " << var->Name() << " needed precalcSumSquares, but it wasn't found in the OpenCL string." << endl; + success = false; + } + } + else + { + if (s.find("precalcSumSquares") != string::npos) + { + cout << "Variation " << var->Name() << " didn't need precalcSumSquares, but it was found in the OpenCL string." << endl; + success = false; + } + } + } + } + + if (var->NeedPrecalcSumSquares()) + { + if (s.find("SQR(vIn.x) + SQR(vIn.y)") != string::npos || s.find("vIn.x * vIn.x + vIn.y * vIn.y") != string::npos) + { + cout << "Variation " << var->Name() << " needed precalcSumSquares, but is not using it properly." << endl; + success = false; + } + } + else + { + if (s.find("precalcSumSquares") != string::npos) + { + cout << "Variation " << var->Name() << " didn't need precalcSumSquares, but it was found in the OpenCL string." << endl; + success = false; + } + + if (s.find("SQR(vIn.x) + SQR(vIn.y)") != string::npos || s.find("vIn.x * vIn.x + vIn.y * vIn.y") != string::npos) + { + cout << "Variation " << var->Name() << " did not specify precalcSumSquares, but could benefit from it." << endl; + success = false; + } + } + + if (var->NeedPrecalcSqrtSumSquares()) + { + if (s.find("sqrt(SQR(vIn.x) + SQR(vIn.y))") != string::npos || s.find("sqrt(vIn.x * vIn.x + vIn.y * vIn.y)") != string::npos) + { + cout << "Variation " << var->Name() << " needed precalcSqrtSumSquares, but is not using it properly." << endl; + success = false; + } + } + else + { + if (s.find("precalcSqrtSumSquares") != string::npos) + { + cout << "Variation " << var->Name() << " didn't need precalcSqrtSumSquares, but it was found in the OpenCL string." << endl; + success = false; + } + + if (s.find("sqrt(SQR(vIn.x) + SQR(vIn.y))") != string::npos || s.find("sqrt(vIn.x * vIn.x + vIn.y * vIn.y)") != string::npos) + { + cout << "Variation " << var->Name() << " did not specify precalcSqrtSumSquares, but could benefit from it." << endl; + success = false; + } + } + + if (var->NeedPrecalcAtanXY()) + { + if (s.find("precalcAtanxy") == string::npos) + { + cout << "Variation " << var->Name() << " needed precalcAtanxy, but it wasn't found in the OpenCL string." << endl; + success = false; + } + } + else + { + if (s.find("precalcAtanxy") != string::npos) + { + cout << "Variation " << var->Name() << " didn't need precalcAtanxy, but it was found in the OpenCL string." << endl; + success = false; + } + + if (s.find("atan2(vIn.x, vIn.y)") != string::npos) + { + cout << "Variation " << var->Name() << " did not specify precalcAtanxy, but could benefit from it." << endl; + success = false; + } + } + + if (var->NeedPrecalcAtanYX()) + { + if (s.find("precalcAtanyx") == string::npos) + { + cout << "Variation " << var->Name() << " needed precalcAtanyx, but it wasn't found in the OpenCL string." << endl; + success = false; + } + } + else + { + if (s.find("precalcAtanyx") != string::npos) + { + cout << "Variation " << var->Name() << " didn't need precalcAtanyx, but it was found in the OpenCL string." << endl; + success = false; + } + + if (s.find("atan2(vIn.y, vIn.x)") != string::npos) + { + cout << "Variation " << var->Name() << " did not specify precalcAtanyx, but could benefit from it." << endl; + success = false; + } + } + } + + return success; +} + +bool TestVarAssignTypes() +{ + bool success = true; + VariationList vlf; + vector vset, vsum; + + vset.push_back("vIn.x"); + vset.push_back("vIn.y"); + vset.push_back("vIn.z"); + vset.push_back("precalcSumSquares"); + vset.push_back("precalcSqrtSumSquares"); + vset.push_back("precalcSina"); + vset.push_back("precalcCosa"); + vset.push_back("precalcAtanxy"); + vset.push_back("precalcAtanyx"); + + vsum.push_back("vIn.x"); + vsum.push_back("vIn.y"); + //vsum.push_back("vIn.z"); + vsum.push_back("precalcSumSquares"); + vsum.push_back("precalcSqrtSumSquares"); + vsum.push_back("precalcSina"); + vsum.push_back("precalcCosa"); + vsum.push_back("precalcAtanxy"); + vsum.push_back("precalcAtanyx"); + + for (size_t i = 0; i < vlf.Size(); i++) + { + Variation* var = vlf.GetVariation(i); + string s = var->OpenCLString(); + + //Only test pre and post. The assign type for regular is ignored, and will always be summed. + if (var->VarType() != VARTYPE_REG) + { + if (var->AssignType() == ASSIGNTYPE_SET) + { + if (!SearchVar(var, vset, false)) + { + cout << "Variation " << var->Name() << " had an assign type of SET, but did not use its input points. It should have an assign type of SUM." << endl; + success = false; + } + } + else if (var->AssignType() == ASSIGNTYPE_SUM) + { + if (SearchVar(var, vsum, false)) + { + cout << "Variation " << var->Name() << " had an assign type of SUM, but used its input points. It should have an assign type of SET." << endl; + success = false; + } + } + else + { + cout << "Variation " << var->Name() << " had an invalid assign type of " << var->AssignType() << endl; + } + } + } + + return success; +} + +bool TestVarAssignVals() +{ + bool success = true; + VariationList vlf; + vector xout, yout; + + xout.push_back("vOut.x ="); + xout.push_back("vOut.x +="); + xout.push_back("vOut.x -="); + xout.push_back("vOut.x *="); + xout.push_back("vOut.x /="); + + yout.push_back("vOut.y ="); + yout.push_back("vOut.y +="); + yout.push_back("vOut.y -="); + yout.push_back("vOut.y *="); + yout.push_back("vOut.y /="); + + for (size_t i = 0; i < vlf.Size(); i++) + { + Variation* var = vlf.GetVariation(i); + + if (!SearchVar(var, xout, false)) + { + cout << "Variation " << var->Name() << " did not set its x output point. If unused, at least pass through or set to 0." << endl; + success = false; + } + + if (!SearchVar(var, yout, false)) + { + cout << "Variation " << var->Name() << " did not set its y output point. If unused, at least pass through or set to 0." << endl; + success = false; + } + } + + return success; +} + +bool TestConstants() +{ + bool success = true; + VariationList vlf; + vector stringVec; + + stringVec.push_back("2 * M_PI"); + stringVec.push_back("2*M_PI"); + stringVec.push_back("M_PI*2"); + stringVec.push_back("M_PI * 2"); + + for (size_t i = 0; i < vlf.Size(); i++) + { + Variation* var = vlf.GetVariation(i); + + if (SearchVar(var, stringVec, false)) + { + cout << "Variation " << var->Name() << " should be using M_2PI." << endl; + success = false; + } + } + + return success; +} + +void PrintAllVars() +{ + unsigned int i = 0; + VariationList vlf; + + while(Variation* var = vlf.GetVariation(i++)) + cout << var->Name() << endl; +} + +void TestXformsInOutPoints() +{ + unsigned int index = 0; + VariationList varList; + PaletteList paletteList; + QTIsaac rand; + + paletteList.Init("flam3-palettes.xml"); + + while (index < varList.RegSize()) + { + vector> xforms; + auto_ptr> regVar (varList.GetVariationCopy(index, VARTYPE_REG)); + string s = regVar->OpenCLString() + regVar->OpenCLFuncsString(); + + if (s.find("MwcNext") == string::npos) + { + auto_ptr> preVar (varList.GetVariationCopy("pre_" + regVar->Name())); + auto_ptr> postVar(varList.GetVariationCopy("post_" + regVar->Name())); + + Xform xform0(0.25f, rand.Frand01(), rand.Frand11(), 1, rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11()); + Xform xform1(0.25f, rand.Frand01(), rand.Frand11(), 1, rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11()); + Xform xform2(0.25f, rand.Frand01(), rand.Frand11(), 1, rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11()); + Xform xform3(0.25f, rand.Frand01(), rand.Frand11(), 1, rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11()); + Xform xform4(0.25f, rand.Frand01(), rand.Frand11(), 1, rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11()); + Xform xform5(0.25f, rand.Frand01(), rand.Frand11(), 1, rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11()); + Xform xform6(0.25f, rand.Frand01(), rand.Frand11(), 1, rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11()); + Xform xform7(0.25f, rand.Frand01(), rand.Frand11(), 1, rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11()); + + if (preVar.get() && postVar.get()) + { + xform1.AddVariation(preVar->Copy()); + xform2.AddVariation(regVar->Copy()); + xform3.AddVariation(postVar->Copy()); + + xform4.AddVariation(preVar->Copy()); + xform4.AddVariation(regVar->Copy()); + + xform5.AddVariation(preVar->Copy()); + xform5.AddVariation(postVar->Copy()); + + xform6.AddVariation(regVar->Copy()); + xform6.AddVariation(postVar->Copy()); + + xform7.AddVariation(preVar->Copy()); + xform7.AddVariation(regVar->Copy()); + xform7.AddVariation(postVar->Copy()); + + xforms.push_back(xform0); + xforms.push_back(xform1); + xforms.push_back(xform2); + xforms.push_back(xform3); + xforms.push_back(xform4); + xforms.push_back(xform5); + xforms.push_back(xform6); + xforms.push_back(xform7); + } + else + { + xform1.AddVariation(regVar->Copy()); + + xforms.push_back(xform0); + xforms.push_back(xform1); + } + + for (size_t i = 0; i < xforms.size(); i++) + { + bool badVals = false; + Point orig; + + orig.m_X = rand.Frand11(); + orig.m_Y = rand.Frand11(); + orig.m_Z = rand.Frand11(); + orig.m_ColorX = rand.Frand01(); + orig.m_VizAdjusted = rand.Frand01(); + + Point p1 = orig, p2 = orig, p3; + + xforms[i].Apply(&p1, &p1, rand); + xforms[i].Apply(&p2, &p3, rand); + + badVals |= (p1.m_X != p1.m_X); + badVals |= (p1.m_Y != p1.m_Y); + badVals |= (p1.m_Z != p1.m_Z); + badVals |= (p1.m_ColorX != p1.m_ColorX); + badVals |= (p1.m_VizAdjusted != p1.m_VizAdjusted); + + badVals |= (p3.m_X != p3.m_X); + badVals |= (p3.m_Y != p3.m_Y); + badVals |= (p3.m_Z != p3.m_Z); + badVals |= (p3.m_ColorX != p3.m_ColorX); + badVals |= (p3.m_VizAdjusted != p3.m_VizAdjusted); + + if (badVals) + cout << "Variation " << regVar->Name() << ": Bad value detected" << endl; + + if (!badVals) + { + if (p1.m_X != p3.m_X) + cout << "Variation " << regVar->Name() << ": p1.m_X " << p1.m_X << " != p3.m_X " << p3.m_X << endl; + + if (p1.m_Y != p3.m_Y) + cout << "Variation " << regVar->Name() << ": p1.m_Y " << p1.m_Y << " != p3.m_Y " << p3.m_Y << endl; + + if (p1.m_Z != p3.m_Z) + cout << "Variation " << regVar->Name() << ": p1.m_Z " << p1.m_Z << " != p3.m_Z " << p3.m_Z << endl; + + if (p1.m_ColorX != p3.m_ColorX) + cout << "Variation " << regVar->Name() << ": p1.m_ColorX " << p1.m_ColorX << " != p3.m_ColorX " << p3.m_ColorX << endl; + + if (p1.m_VizAdjusted != p3.m_VizAdjusted) + cout << "Variation " << regVar->Name() << ": p1.m_VizAdjusted " << p1.m_VizAdjusted << " != p3.m_VizAdjusted " << p3.m_VizAdjusted << endl; + } + } + } + + index++; + } +} + +static int SortPairByTime(const pair& a, pair& b) +{ + return a.second < b.second; +} + +template +void TestVarTime() +{ + int i = 0, iters = 10; + Timing t; + VariationList vlf; + IteratorHelper helper; + QTIsaac rand; + vector> times; + + times.reserve(vlf.RegSize()); + + while (i < vlf.RegSize()) + { + double sum = 0; + Xform xform; + Variation* var = vlf.GetVariationCopy(i, VARTYPE_REG); + + xform.AddVariation(var); + + for (int iter = 0; iter < iters; iter++) + { + Point p; + + xform.m_Affine.A(rand.Frand(-5, 5)); + xform.m_Affine.B(rand.Frand(-5, 5)); + xform.m_Affine.C(rand.Frand(-5, 5)); + xform.m_Affine.D(rand.Frand(-5, 5)); + xform.m_Affine.E(rand.Frand(-5, 5)); + xform.m_Affine.F(rand.Frand(-5, 5)); + p.m_X = rand.Frand(-20, 20); + p.m_Y = rand.Frand(-20, 20); + p.m_Z = rand.Frand(-20, 20); + helper.In.x = helper.m_TransX = (xform.m_Affine.A() * p.m_X) + (xform.m_Affine.B() * p.m_Y) + xform.m_Affine.C(); + helper.In.y = helper.m_TransY = (xform.m_Affine.D() * p.m_X) + (xform.m_Affine.E() * p.m_Y) + xform.m_Affine.F(); + helper.In.z = helper.m_TransZ = p.m_Z; + helper.m_Color.x = p.m_ColorX = rand.Frand01(); + p.m_VizAdjusted = rand.Frand01(); + + helper.m_PrecalcSumSquares = SQR(helper.m_TransX) + SQR(helper.m_TransY); + helper.m_PrecalcSqrtSumSquares = sqrt(helper.m_PrecalcSumSquares); + helper.m_PrecalcSina = helper.m_TransX / helper.m_PrecalcSqrtSumSquares; + helper.m_PrecalcCosa = helper.m_TransY / helper.m_PrecalcSqrtSumSquares; + helper.m_PrecalcAtanxy = atan2(helper.m_TransX, helper.m_TransY); + helper.m_PrecalcAtanyx = atan2(helper.m_TransY, helper.m_TransX); + + var->Random(rand); + t.Tic(); + var->Func(helper, p, rand); + sum += t.Toc(); + } + + i++; + times.push_back(pair(var->Name(), sum / iters)); + } + + std::sort(times.begin(), times.end(), &SortPairByTime); + //std::for_each(times.begin(), times.end(), [&](pair& p) { cout << p.first << "\t" << p.second << "" << endl; }); +} + +template +void TestOperations() +{ + vector stringVec; + vector*> varVec; + + //stringVec.push_back("%"); + //varVec = FindVarsWith(stringVec); + // + //for (size_t i = 0; i < varVec.size(); i++) + //{ + // cout << "Variation " << varVec[i]->Name() << " contained a modulo operation. Ensure its right hand operand is not zero." << endl; + //} + // + //stringVec.clear(); + //ClearVec>(varVec); + + stringVec.push_back("MwcNext(mwc) %"); + stringVec.push_back("MwcNext(mwc)%"); + + varVec = FindVarsWith(stringVec); + + for (size_t i = 0; i < varVec.size(); i++) + { + cout << "Variation " << varVec[i]->Name() << " contained MwcNext(mwc) %. Use MwcNextRange() instead." << endl; + } + + stringVec.clear(); + ClearVec>(varVec); + +} + +template +void TestVarsSimilar() +{ + int i = 0, compIndex = 0, iters = 10; + Timing t; + VariationList vlf; + IteratorHelper helper; + QTIsaac rand; + vector> diffs; + + diffs.reserve(vlf.RegSize()); + + while (i < vlf.RegSize()) + { + double diff = 0, highest = TMAX; + Xform xform; + Variation* var = vlf.GetVariationCopy(i, VARTYPE_REG); + pair match("", TMAX); + + compIndex = 0; + xform.AddVariation(var); + + while (compIndex < vlf.RegSize()) + { + if (compIndex == i) + { + compIndex++; + continue; + } + + double sum = 0, xdiff = 0, ydiff = 0, zdiff = 0; + Xform xformComp; + Variation* varComp = vlf.GetVariationCopy(compIndex, VARTYPE_REG); + + xformComp.AddVariation(varComp); + + ParametricVariation* parVar = dynamic_cast*>(var); + ParametricVariation* parVarComp = dynamic_cast*>(varComp); + + for (int iter = 0; iter < iters; iter++) + { + Point p, pComp; + + xform.m_Affine.A(rand.Frand(-5, 5)); + xform.m_Affine.B(rand.Frand(-5, 5)); + xform.m_Affine.C(rand.Frand(-5, 5)); + xform.m_Affine.D(rand.Frand(-5, 5)); + xform.m_Affine.E(rand.Frand(-5, 5)); + xform.m_Affine.F(rand.Frand(-5, 5)); + xformComp.m_Affine = xform.m_Affine; + + p.m_X = rand.Frand(-20, 20); + p.m_Y = rand.Frand(-20, 20); + p.m_Z = rand.Frand(-20, 20); + helper.In.x = helper.m_TransX = (xform.m_Affine.A() * p.m_X) + (xform.m_Affine.B() * p.m_Y) + xform.m_Affine.C(); + helper.In.y = helper.m_TransY = (xform.m_Affine.D() * p.m_X) + (xform.m_Affine.E() * p.m_Y) + xform.m_Affine.F(); + helper.In.z = helper.m_TransZ = p.m_Z; + helper.m_Color.x = p.m_ColorX = rand.Frand01(); + p.m_VizAdjusted = rand.Frand01(); + pComp = p; + + helper.m_PrecalcSumSquares = SQR(helper.m_TransX) + SQR(helper.m_TransY); + helper.m_PrecalcSqrtSumSquares = sqrt(helper.m_PrecalcSumSquares); + helper.m_PrecalcSina = helper.m_TransX / helper.m_PrecalcSqrtSumSquares; + helper.m_PrecalcCosa = helper.m_TransY / helper.m_PrecalcSqrtSumSquares; + helper.m_PrecalcAtanxy = atan2(helper.m_TransX, helper.m_TransY); + helper.m_PrecalcAtanyx = atan2(helper.m_TransY, helper.m_TransX); + + if (parVar) + { + for (unsigned int v = 0; v < parVar->ParamCount(); v++) + parVar->SetParamVal(v, iter); + } + + if (parVarComp) + { + for (unsigned int v = 0; v < parVarComp->ParamCount(); v++) + parVarComp->SetParamVal(v, iter); + } + + //For debugging. + if (var->VariationId() == VAR_BWRAPS && varComp->VariationId() == VAR_ECLIPSE) + { + //cout << "Break." << endl; + } + + helper.Out = v4T(0); + var->m_Weight = iter + 1; + var->Precalc(); + var->Func(helper, p, rand); + v4T varOut = helper.Out; + + helper.Out = v4T(0); + varComp->m_Weight = iter + 1; + varComp->Precalc(); + varComp->Func(helper, pComp, rand); + v4T varCompOut = helper.Out; + + xdiff += fabs(varOut.x - varCompOut.x); + ydiff += fabs(varOut.y - varCompOut.y); + zdiff += fabs(varOut.z - varCompOut.z); + } + + sum = (xdiff + ydiff + zdiff) / iters; + + if (sum < highest) + { + match.first = varComp->Name(); + match.second = highest = sum; + } + + compIndex++; + } + + if (match.second < 0.001) + cout << "The closest match to variation " << var->Name() << " is " << match.first << " with a total difference of " << match.second << endl; + + i++; + //times.push_back(pair(var->Name(), sum / iters)); + } + + //std::sort(times.begin(), times.end(), &SortPairByTime); +} + +template +void TestCpuGpuResults() +{ + bool breakOnBad = true; + int i = 0;//(int)VAR_TARGET;//Start at the one you want to test. + int iters = 10; + int skipped = 0; + T thresh = T(1e-3); + Timing t; + VariationList vlf; + QTIsaac rand; + vector> points; + RendererCL renderer; + + if (!renderer.Init(1, 0, false, 0)) + return; + + points.resize(renderer.TotalIterKernelCount()); + + while (i < vlf.RegSize()) + { + bool bad = false; + double sum = 0; + Variation* var = vlf.GetVariation(i, VARTYPE_REG); + string s = var->OpenCLString() + var->OpenCLFuncsString(); + + if (s.find("MwcNext") != string::npos) + { + i++; + skipped++; + continue; + } + + cout << "Testing cpu-gpu equality for variation: " << var->Name() << " (" << (int)var->VariationId() << ")" << endl; + + for (int iter = 0; iter < iters; iter++) + { + bool newAlloc = false; + Point p, p2; + Ember ember; + Xform xform; + Variation* varCopy = var->Copy(); + + p.m_X = rand.Frand(-5, 5); + p.m_Y = rand.Frand(-5, 5); + p.m_Z = rand.Frand(-5, 5); + p.m_ColorX = rand.Frand01(); + p.m_VizAdjusted = rand.Frand01(); + + varCopy->Random(rand); + xform.AddVariation(varCopy); + ember.AddXform(xform); + ember.CacheXforms(); + renderer.SetEmber(ember); + renderer.CreateSpatialFilter(newAlloc); + renderer.CreateDEFilter(newAlloc); + renderer.ComputeBounds(); + renderer.ComputeCamera(); + renderer.AssignIterator(); + + if (!renderer.Alloc()) + return; + + points[0].m_X = p.m_X; + points[0].m_Y = p.m_Y; + points[0].m_Z = p.m_Z; + points[0].m_ColorX = p.m_ColorX; + xform.Apply(&p, &p2, rand); + renderer.WritePoints(points); + renderer.Iterate(1, 0, 1); + renderer.ReadPoints(points); + + T xdiff = fabs(p2.m_X - points[0].m_X); + T ydiff = fabs(p2.m_Y - points[0].m_Y); + T zdiff = fabs(p2.m_Z - points[0].m_Z); + + if (xdiff > thresh || ydiff > thresh || zdiff > thresh) + { + bad = true; + cout << __FUNCTION__ << ": Variation cpu-gpu diff for iter " << iter << ": " << varCopy->Name() << " (" << (int)varCopy->VariationId() << ") xdiff: " << xdiff << endl; + cout << __FUNCTION__ << ": Variation cpu-gpu diff for iter " << iter << ": " << varCopy->Name() << " (" << (int)varCopy->VariationId() << ") ydiff: " << ydiff << endl; + cout << __FUNCTION__ << ": Variation cpu-gpu diff for iter " << iter << ": " << varCopy->Name() << " (" << (int)varCopy->VariationId() << ") zdiff: " << zdiff << endl; + cout << varCopy->ToString() << endl; + } + else + { + //cout << "Variation " << var->Name() << " had no difference between cpu and gpu for iter " << iter << endl; + } + } + + if (breakOnBad && bad) + break; + + i++; + bad = false; + } + + cout << "Skipped " << skipped << endl; +} + +int _tmain(int argc, _TCHAR* argv[]) +{ + Timing t(4); + + //cout << pow(-1, 5.1) << endl; + + //cout << "sizeof(Ember): " << sizeof(Ember) << endl; + //cout << "sizeof(Ember): " << sizeof(Ember) << endl; + // + //cout << "sizeof(Renderer): " << sizeof(Renderer) << endl; + //cout << "sizeof(Renderer): " << sizeof(Renderer) << endl; + // + //cout << "sizeof(RendererCL): " << sizeof(RendererCL) << endl; + //cout << "sizeof(RendererCL): " << sizeof(RendererCL) << endl; + + /*auto_ptr> linV(new LinearVariation()); + auto_ptr> preLinV(new PreLinearVariation()); + auto_ptr> postLinV(new PostLinearVariation()); + + cout << linV->BaseName() << endl; + cout << preLinV->BaseName() << endl; + cout << postLinV->BaseName() << endl;*/ + + //auto_ptr> preFarblurV(new PreFarblurVariation()); + + //MakeTestAllVarsRegPrePostComboFile("testallvarsout.flame"); + + //std::complex cd, cd2; + + //cd2 = sin(cd); + + t.Tic(); + VariationList vlf; + t.Toc("Creating VariationList"); + + cout << "There are " << vlf.Size() << " variations present." << endl; + +#ifdef DO_DOUBLE + t.Tic(); + VariationList vld; + t.Toc("Creating VariationList"); +#endif + + t.Tic(); + TestVarCounts(); + t.Toc("TestVarCounts()"); + + t.Tic(); + TestVarUnique(); + t.Toc("TestVarUnique()"); + +#ifdef DO_DOUBLE + t.Tic(); + TestVarUnique(); + t.Toc("TestVarUnique()"); +#endif + + t.Tic(); + TestVarCopy(); + t.Toc("TestVarCopy()"); + +#ifdef DO_DOUBLE + t.Tic(); + TestVarCopy(); + t.Toc("TestVarCopy()"); + + t.Tic(); + TestVarCopy(); + t.Toc("TestVarCopy()"); + + t.Tic(); + TestVarCopy(); + t.Toc("TestVarCopy()"); +#endif + //t.Tic(); + //TestVarRegPrePost(); + //t.Toc("TestVarRegPrePost()"); + // + //t.Tic(); + //TestParVars(); + //t.Toc("TestParVars()"); + // + //t.Tic(); + //TestVarPrePostNames(); + //t.Toc("TestVarPrePostNames()"); + // + //t.Tic(); + //TestVarPrecalcUsedCL(); + //t.Toc("TestVarPrecalcUsedCL()"); + // + //t.Tic(); + //TestVarAssignTypes(); + //t.Toc("TestVarAssignTypes()"); + // + //t.Tic(); + //TestVarAssignVals(); + //t.Toc("TestVarAssignVals()"); + // + //t.Tic(); + //TestConstants(); + //t.Toc("TestConstants()"); + // + //t.Tic(); + //TestXformsInOutPoints(); + //t.Toc("TestXformsInOutPoints()"); + // + //t.Tic(); + //TestVarTime(); + //t.Toc("TestVarTime()"); + // + //t.Tic(); + //TestOperations(); + //t.Toc("TestMod()"); + + //t.Tic(); + //TestVarsSimilar(); + //t.Toc("TestVarsSimilar()"); + + /**/t.Tic(); + TestCpuGpuResults(); + t.Toc("TestCpuGpuResults()"); + +#ifdef DO_DOUBLE + t.Tic(); + TestCpuGpuResults(); + t.Toc("TestCpuGpuResults()"); +#endif + + //PrintAllVars(); + //_CrtDumpMemoryLeaks(); + return 0; +} diff --git a/Source/EmberTester/EmberTester.h b/Source/EmberTester/EmberTester.h new file mode 100644 index 0000000..da7dba0 --- /dev/null +++ b/Source/EmberTester/EmberTester.h @@ -0,0 +1,10 @@ +#pragma once + +#include "EmberCommon.h" + +/// +/// EmberTester is a scratch area used for on the fly testing. +/// It may become a more formalized automated testing system +/// in the future. At the moment it isn't expected to build or +/// give any useful insight into the workings of Ember. +/// diff --git a/Source/Fractorium/AboutDialog.cpp b/Source/Fractorium/AboutDialog.cpp new file mode 100644 index 0000000..9ac9678 --- /dev/null +++ b/Source/Fractorium/AboutDialog.cpp @@ -0,0 +1,14 @@ +#include "FractoriumPch.h" +#include "AboutDialog.h" + +/// +/// Constructor that takes a parent widget and passes it to the base, then +/// sets up the GUI. +/// +/// The parent widget. Default: NULL. +/// The window flags. Default: 0. +FractoriumAboutDialog::FractoriumAboutDialog(QWidget* parent, Qt::WindowFlags f) + : QDialog(parent, f) +{ + ui.setupUi(this); +} diff --git a/Source/Fractorium/AboutDialog.h b/Source/Fractorium/AboutDialog.h new file mode 100644 index 0000000..eed123b --- /dev/null +++ b/Source/Fractorium/AboutDialog.h @@ -0,0 +1,22 @@ +#pragma once + +#include "ui_AboutDialog.h" + +/// +/// FractoriumAboutDialog class. +/// + +/// +/// The about dialog displays several group boxes showing the +/// code and icons used in this project and their respective authors +/// and licenses. It performs no other function. +/// +class FractoriumAboutDialog : public QDialog +{ + Q_OBJECT +public: + FractoriumAboutDialog(QWidget* parent = 0, Qt::WindowFlags f = 0); + +private: + Ui::AboutDialog ui; +}; diff --git a/Source/Fractorium/AboutDialog.ui b/Source/Fractorium/AboutDialog.ui new file mode 100644 index 0000000..04fe64e --- /dev/null +++ b/Source/Fractorium/AboutDialog.ui @@ -0,0 +1,199 @@ + + + AboutDialog + + + + 0 + 0 + 488 + 580 + + + + + 0 + 0 + + + + + 488 + 580 + + + + + 488 + 580 + + + + About + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 12 + + + + <html><head/><body><p align="center"><br/><span style=" font-size:12pt;">Fractorium 0.4.0.2 Beta</span></p><p align="center"><span style=" font-size:10pt;"><br/>A Qt-based fractal flame editor which uses a C++ re-write of the flam3 algorithm named Ember and a GPU capable version named EmberCL which implements a portion of the cuburn algorithm in OpenCL.</span></p><p align="center"><span style=" font-size:10pt;">Matt Feemster</span></p></body></html> + + + Qt::RichText + + + false + + + Qt::AlignCenter + + + true + + + + + + + Code Copied + + + + 4 + + + + + <html><head/><body><p><a href="http://code.google.com/p/flam3"><span style=" text-decoration: underline; color:#0000ff;">flam3</span></a>: Scott Draves, Erik Reckase (GPL v2)<br/><a href="http://github.com/stevenrobertson/cuburn"><span style=" text-decoration: underline; color:#0000ff;">cuburn</span></a>: Steven Robertson, Michael Semeniuk, Matthew Znoj, Nicolas Mejia (GPL v3)<br/><a href="http://fractron9000.sourceforge.net"><span style=" text-decoration: underline; color:#0000ff;">Fractron 9000</span></a>: Mike Thiesen (GPL)<br/><a href="http://sourceforge.net/projects/apophysis7x"><span style=" text-decoration: underline; color:#0000ff;">Apophysis</span></a>: Mark Townsend, Ronald Hordijk, Peter Sdobnov, Piotr Borys, Georg Kiehne (GPL)<br/><a href="http://jwildfire.org/"><span style=" text-decoration: underline; color:#0000ff;">JWildfire</span></a>: Andreas Maschke (LGPL)<br/>Numerous Apophysis plugin developers (GPL)</p></body></html> + + + Qt::RichText + + + true + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + + Libraries Linked + + + + 4 + + + + + <html><head/><body><p><a href="http://qt-project.org"><span style=" text-decoration: underline; color:#0000ff;">Qt</span></a>: Digia Plc (GPL v3, LGPL v2)<br/><a href="http://g-truc.net"><span style=" text-decoration: underline; color:#0000ff;">glm</span></a>: Christophe Riccio (MIT License)<br/><a href="http://threadingbuildingblocks.org"><span style=" text-decoration: underline; color:#0000ff;">Threading Building Blocks</span></a>: Intel Corporation (GPLv2)<br/><a href="http://libjpeg.sourceforge.net"><span style=" text-decoration: underline; color:#0000ff;">libjpeg</span></a>: Independent JPEG Group (Free Software License)<br/><a href="http://libpng.org"><span style=" text-decoration: underline; color:#0000ff;">libpng</span></a>: Glenn Randers-Pehrson et al (Libpng License)<br/><a href="http://xmlsoft.org"><span style=" text-decoration: underline; color:#0000ff;">libxml2</span></a>: Daniel Veillard (MIT License)<br/><a href="http://zlib.net"><span style=" text-decoration: underline; color:#0000ff;">zlib</span></a>: Jean-loup Gailly, Mark Adler (Zlib License)<br/><a href="http://burtleburtle.net/bob/rand/isaac.html"><span style=" text-decoration: underline; color:#0000ff;">QTIsaac</span></a>: Robert J. Jenkins, Quinn Tyler Jackson (Public Domain)<br/><a href="http://cas.ee.ic.ac.uk/people/dt10/index.html"><span style=" text-decoration: underline; color:#0000ff;">MWC64X Random Number Generator</span></a>: David Thomas (Public Domain)<br/><a href="http://code.jellycan.com/simpleopt/"><span style=" text-decoration: underline; color:#0000ff;">SimpleOpt</span></a>: Brodie Thiesfield (MIT License)</p></body></html> + + + Qt::RichText + + + true + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + + Icons Used + + + + 4 + + + + + <html><head/><body><p><a href="http://famfamfam.com"><span style=" text-decoration: underline; color:#0000ff;">Silk</span></a>: Mark James (Creative Commons Attribution 2.5 License)<br/><a href="http://momentumdesignlab.com"><span style=" text-decoration: underline; color:#0000ff;">Momentum</span></a>: Momentum Design Lab (Creative Commons Attribution-ShareAlike 3.5 License)<br/><a href="http://everaldo.com"><span style=" text-decoration: underline; color:#0000ff;">Crystal Clear</span></a>: Everaldo Coelho (LGPL)<br/><a href="http://openiconlibrary.sourceforge.net"><span style=" text-decoration: underline; color:#0000ff;">Open Icon Library</span></a>: Jeff Israel (GPL, LGPL, Creative Commons, Public Domain)<br/><a href="http://icons.mysitemyway.com/category/3d-transparent-glass-icons/"><span style=" text-decoration: underline; color:#0000ff;">3D Transparent Glass</span></a>: iconsETC (Public Domain)<br/><a href="http://p.yusukekamiyamane.com"><span style=" text-decoration: underline; color:#0000ff;">Fugue</span></a>: Yusuke Kamiyamane (Creative Commons Attribution 3.0 License)</p></body></html> + + + Qt::RichText + + + true + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + OK + + + + + + + + + + + okButton + clicked() + AboutDialog + accept() + + + 278 + 253 + + + 96 + 254 + + + + + diff --git a/Source/Fractorium/DoubleSpinBox.cpp b/Source/Fractorium/DoubleSpinBox.cpp new file mode 100644 index 0000000..c062898 --- /dev/null +++ b/Source/Fractorium/DoubleSpinBox.cpp @@ -0,0 +1,215 @@ +#include "FractoriumPch.h" +#include "DoubleSpinBox.h" + +/// +/// Constructor that passes parent to the base and sets up height and step. +/// Specific focus policy is used to allow the user to hover over the control +/// and change its value using the mouse wheel without explicitly having to click +/// inside of it. +/// +/// The parent widget. Default: NULL. +/// The height of the spin box. Default: 16. +/// The step used to increment/decrement the spin box when using the mouse wheel. Default: 0.05. +DoubleSpinBox::DoubleSpinBox(QWidget* parent, int height, double step) + : QDoubleSpinBox(parent) +{ + m_Select = false; + m_DoubleClick = false; + m_DoubleClickNonZero = 0; + m_DoubleClickZero = 1; + m_Step = step; + m_SmallStep = step / 10.0; + setSingleStep(step); + setFrame(false); + setButtonSymbols(QAbstractSpinBox::NoButtons); + setFocusPolicy(Qt::StrongFocus); + setMinimumHeight(height);//setGeometry() has no effect, so must set both of these instead. + setMaximumHeight(height); + lineEdit()->installEventFilter(this); + lineEdit()->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); + connect(this, SIGNAL(valueChanged(double)), this, SLOT(onSpinBoxValueChanged(double)), Qt::QueuedConnection); + +} + +/// +/// Set the value of the control without triggering signals. +/// +/// The value to set it to +void DoubleSpinBox::SetValueStealth(double d) +{ + blockSignals(true); + setValue(d); + blockSignals(false); +} + +/// +/// Set whether to respond to double click events. +/// +/// True if this should respond to double click events, else false. +void DoubleSpinBox::DoubleClick(bool b) +{ + m_DoubleClick = b; +} + +/// +/// Set the value to be used when the user double clicks the spinner while +/// it contains zero. +/// +/// The value to be used +void DoubleSpinBox::DoubleClickZero(double val) +{ + m_DoubleClickZero = val; +} + +/// +/// Set the value to be used when the user double clicks the spinner while +/// it contains a non-zero value. +/// +/// The value to be used +void DoubleSpinBox::DoubleClickNonZero(double val) +{ + m_DoubleClickNonZero = val; +} + +/// +/// Set the default step to be used when the user scrolls. +/// +/// The step to use for scrolling +void DoubleSpinBox::Step(double step) +{ + m_Step = step; +} + +/// +/// Set the small step to be used when the user holds down shift while scrolling. +/// The default is step / 10, so use this if something else is needed. +/// +/// The small step to use for scrolling while the shift key is down +void DoubleSpinBox::SmallStep(double step) +{ + m_SmallStep = step; +} + +/// +/// Expose the underlying QLineEdit control to the caller. +/// +/// A pointer to the QLineEdit +QLineEdit* DoubleSpinBox::lineEdit() +{ + return QDoubleSpinBox::lineEdit(); +} + +/// +/// Another workaround for the persistent text selection bug in Qt. +/// +void DoubleSpinBox::onSpinBoxValueChanged(double d) +{ + lineEdit()->deselect();//Gets rid of nasty "feature" that always has text selected. +} + +/// +/// Event filter for taking special action on double click events. +/// +/// The object +/// The eevent +/// false +bool DoubleSpinBox::eventFilter(QObject* o, QEvent* e) +{ + if (e->type() == QMouseEvent::MouseButtonPress && isEnabled()) + { + QPoint pt; + + if (QMouseEvent* me = (QMouseEvent*)e) + pt = me->localPos().toPoint(); + + int pos = lineEdit()->cursorPositionAt(pt); + + if (lineEdit()->selectedText() != "") + { + lineEdit()->deselect(); + lineEdit()->setCursorPosition(pos); + return true; + } + else if (m_Select) + { + lineEdit()->setCursorPosition(pos); + selectAll(); + m_Select = false; + return true; + } + } + else if (m_DoubleClick && e->type() == QMouseEvent::MouseButtonDblClick && isEnabled()) + { + if (IsNearZero(value())) + setValue(m_DoubleClickZero); + else + setValue(m_DoubleClickNonZero); + } + else + { + if (e->type() == QEvent::Wheel) + { + //Take special action for shift to reduce the scroll amount. Control already + //increases it automatically. + if (QWheelEvent* wheelEvent = dynamic_cast(e)) + { + Qt::KeyboardModifiers mod = wheelEvent->modifiers(); + + if (mod.testFlag(Qt::ShiftModifier)) + setSingleStep(m_SmallStep); + else + setSingleStep(m_Step); + } + } + } + + return QDoubleSpinBox::eventFilter(o, e); +} + +/// +/// Called when focus enters the spinner. +/// +/// The event +void DoubleSpinBox::focusInEvent(QFocusEvent* e) +{ + lineEdit()->setReadOnly(false); + QDoubleSpinBox::focusInEvent(e); +} + +/// +/// Called when focus leaves the spinner. +/// Qt has a nasty "feature" that leaves the text in a spinner selected +/// and the cursor visible, regardless of whether it has the focus. +/// Manually clear both here. +/// +/// The event +void DoubleSpinBox::focusOutEvent(QFocusEvent* e) +{ + lineEdit()->deselect();//Clear selection when leaving. + lineEdit()->setReadOnly(true);//Clever hack to clear the cursor when leaving. + QDoubleSpinBox::focusOutEvent(e); +} + +/// +/// Called when focus enters the spinner. +/// Must set the focus to make sure key down messages don't erroneously go to the GLWidget. +/// +/// The event +void DoubleSpinBox::enterEvent(QEvent* e) +{ + m_Select = true; + setFocus(); + QDoubleSpinBox::enterEvent(e); +} + +/// +/// Called when focus leaves the spinner. +/// Must clear the focus to make sure key down messages don't erroneously go to the GLWidget. +/// +/// The event +void DoubleSpinBox::leaveEvent(QEvent* e) +{ + m_Select = false; + clearFocus(); + QDoubleSpinBox::leaveEvent(e); +} diff --git a/Source/Fractorium/DoubleSpinBox.h b/Source/Fractorium/DoubleSpinBox.h new file mode 100644 index 0000000..1f02cd0 --- /dev/null +++ b/Source/Fractorium/DoubleSpinBox.h @@ -0,0 +1,80 @@ +#pragma once + +#include "FractoriumPch.h" + +/// +/// DoubleSpinBox and VariationTreeDoubleSpinBox classes. +/// + +/// +/// A derivation to prevent the spin box from selecting its own text +/// when editing. Also to prevent multiple spin boxes from all having +/// selected text at once. +/// +class DoubleSpinBox : public QDoubleSpinBox +{ + Q_OBJECT + +public: + explicit DoubleSpinBox(QWidget* parent = 0, int height = 16, double step = 0.05); + virtual ~DoubleSpinBox() { } + void SetValueStealth(double d); + void DoubleClick(bool b); + void DoubleClickZero(double val); + void DoubleClickNonZero(double val); + void Step(double step); + void SmallStep(double step); + QLineEdit* lineEdit(); + +public slots: + void onSpinBoxValueChanged(double d); + +protected: + bool eventFilter(QObject* o, QEvent* e); + virtual void focusInEvent(QFocusEvent* e); + virtual void focusOutEvent(QFocusEvent* e); + virtual void enterEvent(QEvent* e); + virtual void leaveEvent(QEvent* e); + +private: + bool m_Select; + bool m_DoubleClick; + double m_DoubleClickNonZero; + double m_DoubleClickZero; + double m_Step; + double m_SmallStep; +}; + +/// +/// Derivation for the double spin boxes that are in the +/// variations tree. +/// +template +class VariationTreeDoubleSpinBox : public DoubleSpinBox +{ +public: + /// + /// Constructor that passes agruments to the base and assigns the m_Param and m_Variation members. + /// + /// The parent widget + /// The variation this spinner is for + /// The name of the parameter this is for + /// The height of the spin box. Default: 16. + /// The step used to increment/decrement the spin box when using the mouse wheel. Default: 0.05. + explicit VariationTreeDoubleSpinBox(QWidget* parent, Variation* var, string param, int height = 16, double step = 0.05) + : DoubleSpinBox(parent, height, step) + { + m_Param = param; + m_Variation = var; + setDecimals(3); + } + + virtual ~VariationTreeDoubleSpinBox() { } + bool IsParam() { return !m_Param.empty(); } + string ParamName() { return m_Param; } + Variation* GetVariation() { return m_Variation; } + +private: + string m_Param; + Variation* m_Variation; +}; diff --git a/Source/Fractorium/EmberFile.h b/Source/Fractorium/EmberFile.h new file mode 100644 index 0000000..1486582 --- /dev/null +++ b/Source/Fractorium/EmberFile.h @@ -0,0 +1,145 @@ +#pragma once + +#include "FractoriumPch.h" + +/// +/// EmberFile class. +/// + +/// +/// Class for representing an ember Xml file in memory. +/// It contains a filename and a vector of embers. +/// It also provides static helper functions for creating +/// default names for the file and the embers in it. +/// +template +class EmberFile +{ +public: + /// + /// Empty constructor that does nothing. + /// + EmberFile() + { + } + + /// + /// Default copy constructor. + /// + /// The EmberFile object to copy + EmberFile(const EmberFile& emberFile) + { + EmberFile::operator=(emberFile); + } + + /// + /// Copy constructor to copy an EmberFile object of type U. + /// + /// The EmberFile object to copy + template + EmberFile(const EmberFile& emberFile) + { + EmberFile::operator=(emberFile); + } + + /// + /// Default assignment operator. + /// + /// The EmberFile object to copy + EmberFile& operator = (const EmberFile& emberFile) + { + if (this != &emberFile) + EmberFile::operator=(emberFile); + + return *this; + } + + /// + /// Assignment operator to assign a EmberFile object of type U. + /// + /// The EmberFile object to copy. + /// Reference to updated self + template + EmberFile& operator = (const EmberFile& emberFile) + { + m_Filename = emberFile.m_Filename; + CopyVec(m_Embers, emberFile.m_Embers); + return *this; + } + + /// + /// Clear the file name and the vector of embers. + /// + void Clear() + { + m_Filename.clear(); + m_Embers.clear(); + } + + /// + /// Ensure all ember names are unique. + /// + void MakeNamesUnique() + { + int x = 0; + + for (size_t i = 0; i < m_Embers.size(); i++) + { + for (size_t j = 0; j < m_Embers.size(); j++) + { + if (i != j && m_Embers[i].m_Name == m_Embers[j].m_Name) + { + m_Embers[j].m_Name = m_Embers[j].m_Name + "_" + QString::number(++x).toStdString(); + j = 0; + } + } + } + } + + /// + /// Return the default filename based on the current date/time. + /// + /// The default filename + static QString DefaultFilename() + { + return "Flame_" + QDateTime(QDateTime::currentDateTime()).toString("yyyy-MM-dd-hhmmss"); + } + + /// + /// Ensures a given input filename is unique by appending a count to the end. + /// + /// The passed in name if it was unique, else a uniquely made name. + static QString UniqueFilename(QString& filename) + { + if (!QFile::exists(filename)) + return filename; + + int counter = 2; + QString newPath; + QFileInfo original(filename); + QString base = original.completeBaseName(); + QString extension = original.suffix(); + + do + { + newPath = base + "_" + QString::number(counter++) + "." + extension; + } + while (QFile::exists(newPath)); + + return newPath; + } + + /// + /// Return the default ember name based on the current date/time and + /// the ember's index in the file. + /// + /// The index in the file of the ember + /// The default ember name + static QString DefaultEmberName(unsigned int i) + { + return DefaultFilename() + "-" + QString::number(i); + } + + QString m_Filename; + vector> m_Embers; +}; diff --git a/Source/Fractorium/EmberTreeWidgetItem.h b/Source/Fractorium/EmberTreeWidgetItem.h new file mode 100644 index 0000000..6a289ce --- /dev/null +++ b/Source/Fractorium/EmberTreeWidgetItem.h @@ -0,0 +1,120 @@ +#pragma once + +#include "FractoriumPch.h" + +/// +/// EmberTreeWidgetItem +/// + +/// +/// A thin derivation of QTreeWidgetItem for a tree of embers in an open file. +/// The tree is intended to contain one open ember file at a time. +/// This is a non-templated base for casting purposes. +/// +class EmberTreeWidgetItemBase : public QTreeWidgetItem +{ +public: + /// + /// Constructor that takes a pointer to a QTreeWidget as a parent widget. + /// This is meant to be a root level item. + /// + /// The parent widget of this item + explicit EmberTreeWidgetItemBase(QTreeWidget* parent = 0) + : QTreeWidgetItem(parent) + { + } + + /// + /// Constructor that takes a pointer to a QTreeWidgetItem as a parent widget. + /// This is meant to be the child of a root level item. + /// + /// The parent widget of this item + explicit EmberTreeWidgetItemBase(QTreeWidgetItem* parent = 0) + : QTreeWidgetItem(parent) + { + } + + /// + /// Set the preview image for the tree widget item. + /// + /// The vector containing the RGB pixels [0..255] which will make up the preview image + /// The width of the image in pixels + /// The height of the image in pixels + void SetImage(vector& v, unsigned int width, unsigned int height) + { + int size = 64; + + m_Image = QImage(width, height, QImage::Format_RGBA8888); + memcpy(m_Image.scanLine(0), v.data(), v.size() * sizeof(v[0]));//Memcpy the data in. + m_Pixmap = QPixmap::fromImage(m_Image).scaled(QSize(size, size), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);//Create a QPixmap out of the QImage, scaled to size. + setData(0, Qt::DecorationRole, m_Pixmap); + } + +protected: + QImage m_Image; + QPixmap m_Pixmap; +}; + +/// +/// A thin derivation of QTreeWidgetItem for a tree of embers in an open file. +/// The tree is intended to contain one open ember file at a time. +/// +template +class EmberTreeWidgetItem : public EmberTreeWidgetItemBase +{ +public: + /// + /// Constructor that takes a pointer to an ember and a QTreeWidget as a parent widget. + /// This is meant to be a root level item. + /// + /// A pointer to the ember this item will represent + /// The parent widget of this item + explicit EmberTreeWidgetItem(Ember* ember, QTreeWidget* parent = 0) + : EmberTreeWidgetItemBase(parent) + { + m_Ember = ember; + } + + /// + /// Constructor that takes a pointer to an ember and a QTreeWidgetItem as a parent widget. + /// This is meant to be the child of a root level item. + /// + /// A pointer to the ember this item will represent + /// The parent widget of this item + explicit EmberTreeWidgetItem(Ember* ember, QTreeWidgetItem* parent = 0) + : EmberTreeWidgetItemBase(parent) + { + m_Ember = ember; + } + + /// + /// Copy the text of the tree item to the name of the ember. + /// + void UpdateEmberName() { m_Ember->m_Name = text(0).toStdString(); } + + /// + /// Set the text of the tree item. + /// + void UpdateEditText() { setText(0, QString::fromStdString(m_Ember->m_Name)); } + + /// + /// Get a pointer to the ember held by the tree item. + /// + Ember* GetEmber() const { return m_Ember; } + + /// + /// Perform a deep copy from the passed in ember to the dereferenced + /// ember pointer of the tree item. + /// + /// The ember to copy + void SetEmber(Ember& ember) { *m_Ember = ember; } + + /// + /// Set the ember pointer member to point to the passed in ember pointer. + /// + /// The ember to point to + void SetEmberPointer(Ember* ember) { m_Ember = ember; } + +private: + Ember* m_Ember; +}; diff --git a/Source/Fractorium/FinalRenderDialog.cpp b/Source/Fractorium/FinalRenderDialog.cpp new file mode 100644 index 0000000..87bc616 --- /dev/null +++ b/Source/Fractorium/FinalRenderDialog.cpp @@ -0,0 +1,550 @@ +#include "FractoriumPch.h" +#include "FinalRenderDialog.h" +#include "Fractorium.h" + +/// +/// Constructor which sets up the GUI for the final rendering dialog. +/// Settings used to populate widgets with initial values. +/// This function contains the render function as a lambda. +/// +/// Pointer to the global settings object to use +/// The parent widget +/// The window flags. Default: 0. +FractoriumFinalRenderDialog::FractoriumFinalRenderDialog(FractoriumSettings* settings, QWidget* parent, Qt::WindowFlags f) + : QDialog(parent, f) +{ + ui.setupUi(this); + + int row = 0, spinHeight = 20; + unsigned int i; + QTableWidget* table = ui.FinalRenderGeometryTable; + QTableWidgetItem* item = NULL; + + m_Fractorium = (Fractorium*)parent; + m_Settings = settings; + ui.FinalRenderThreadCountSpin->setRange(1, Timing::ProcessorCount()); + connect(ui.FinalRenderEarlyClipCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnEarlyClipCheckBoxStateChanged(int)), Qt::QueuedConnection); + connect(ui.FinalRenderTransparencyCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnTransparencyCheckBoxStateChanged(int)), Qt::QueuedConnection); + connect(ui.FinalRenderOpenCLCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnOpenCLCheckBoxStateChanged(int)), Qt::QueuedConnection); + connect(ui.FinalRenderDoublePrecisionCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnDoublePrecisionCheckBoxStateChanged(int)), Qt::QueuedConnection); + connect(ui.FinalRenderPlatformCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(OnPlatformComboCurrentIndexChanged(int)), Qt::QueuedConnection); + connect(ui.FinalRenderDoAllCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnDoAllCheckBoxStateChanged(int)), Qt::QueuedConnection); + connect(ui.FinalRenderKeepAspectCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnKeepAspectCheckBoxStateChanged(int)), Qt::QueuedConnection); + connect(ui.FinalRenderScaleNoneRadioButton, SIGNAL(toggled(bool)), this, SLOT(OnScaleRadioButtonChanged(bool)), Qt::QueuedConnection); + connect(ui.FinalRenderScaleWidthRadioButton, SIGNAL(toggled(bool)), this, SLOT(OnScaleRadioButtonChanged(bool)), Qt::QueuedConnection); + connect(ui.FinalRenderScaleHeightRadioButton, SIGNAL(toggled(bool)), this, SLOT(OnScaleRadioButtonChanged(bool)), Qt::QueuedConnection); + + SetupSpinner(table, this, row, 1, m_WidthSpin, spinHeight, 10, 100000, 50, SIGNAL(valueChanged(int)), SLOT(OnWidthChanged(int)), true, 1980); + SetupSpinner(table, this, row, 1, m_HeightSpin, spinHeight, 10, 100000, 50, SIGNAL(valueChanged(int)), SLOT(OnHeightChanged(int)), true, 1080); + SetupSpinner(table, this, row, 1, m_QualitySpin, spinHeight, 1, 200000, 50, SIGNAL(valueChanged(int)), SLOT(OnQualityChanged(int)), true, 1000); + SetupSpinner(table, this, row, 1, m_TemporalSamplesSpin, spinHeight, 1, 5000, 50, SIGNAL(valueChanged(int)), SLOT(OnTemporalSamplesChanged(int)), true, 1000); + SetupSpinner(table, this, row, 1, m_SupersampleSpin, spinHeight, 1, 4, 1, SIGNAL(valueChanged(int)), SLOT(OnSupersampleChanged(int)), true, 2); + + row++;//Memory usage. + + TwoButtonWidget* tbw = new TwoButtonWidget("...", "Open", 22, 40, 24, table); + table->setCellWidget(row, 1, tbw); + table->item(row++, 1)->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); + connect(tbw->m_Button1, SIGNAL(clicked(bool)), this, SLOT(OnFileButtonClicked(bool)), Qt::QueuedConnection); + connect(tbw->m_Button2, SIGNAL(clicked(bool)), this, SLOT(OnShowFolderButtonClicked(bool)), Qt::QueuedConnection); + + m_PrefixEdit = new QLineEdit(table); + table->setCellWidget(row++, 1, m_PrefixEdit); + + m_SuffixEdit = new QLineEdit(table); + table->setCellWidget(row++, 1, m_SuffixEdit); + + ui.StartRenderButton->disconnect(SIGNAL(clicked(bool))); + connect(ui.StartRenderButton, SIGNAL(clicked(bool)), this, SLOT(OnRenderClicked(bool)), Qt::QueuedConnection); + connect(ui.StopRenderButton, SIGNAL(clicked(bool)), this, SLOT(OnCancelRenderClicked(bool)), Qt::QueuedConnection); + + if (m_Wrapper.CheckOpenCL()) + { + vector platforms = m_Wrapper.PlatformNames(); + + //Populate combo boxes with available OpenCL platforms and devices. + for (i = 0; i < platforms.size(); i++) + ui.FinalRenderPlatformCombo->addItem(QString::fromStdString(platforms[i])); + + //If init succeeds, set the selected platform and device combos to match what was saved in the settings. + if (m_Wrapper.Init(m_Settings->FinalPlatformIndex(), m_Settings->FinalDeviceIndex())) + { + ui.FinalRenderOpenCLCheckBox->setChecked( m_Settings->FinalOpenCL()); + ui.FinalRenderPlatformCombo->setCurrentIndex(m_Settings->FinalPlatformIndex()); + ui.FinalRenderDeviceCombo->setCurrentIndex( m_Settings->FinalDeviceIndex()); + } + else + { + OnPlatformComboCurrentIndexChanged(0); + ui.FinalRenderOpenCLCheckBox->setChecked(false); + } + } + else + { + ui.FinalRenderOpenCLCheckBox->setChecked(false); + ui.FinalRenderOpenCLCheckBox->setEnabled(false); + } + + ui.FinalRenderEarlyClipCheckBox->setChecked( m_Settings->FinalEarlyClip()); + ui.FinalRenderTransparencyCheckBox->setChecked( m_Settings->FinalTransparency()); + ui.FinalRenderDoublePrecisionCheckBox->setChecked(m_Settings->FinalDouble()); + ui.FinalRenderSaveXmlCheckBox->setChecked( m_Settings->FinalSaveXml()); + ui.FinalRenderDoAllCheckBox->setChecked( m_Settings->FinalDoAll()); + ui.FinalRenderDoSequenceCheckBox->setChecked( m_Settings->FinalDoSequence()); + ui.FinalRenderKeepAspectCheckBox->setChecked( m_Settings->FinalKeepAspect()); + ui.FinalRenderThreadCountSpin->setValue( m_Settings->FinalThreadCount()); + + m_WidthSpin->setValue(m_Settings->FinalWidth()); + m_HeightSpin->setValue(m_Settings->FinalHeight()); + m_QualitySpin->setValue(m_Settings->FinalQuality()); + m_TemporalSamplesSpin->setValue(m_Settings->FinalTemporalSamples()); + m_SupersampleSpin->setValue(m_Settings->FinalSupersample()); + + Scale((eScaleType)m_Settings->FinalScale()); + + if (m_Settings->FinalDoAllExt() == "jpg") + ui.FinalRenderJpgRadioButton->setChecked(true); + else + ui.FinalRenderPngRadioButton->setChecked(true); + + //Explicitly call these to enable/disable the appropriate controls. + OnOpenCLCheckBoxStateChanged(ui.FinalRenderOpenCLCheckBox->isChecked()); + OnDoAllCheckBoxStateChanged(ui.FinalRenderDoAllCheckBox->isChecked()); +} + +/// +/// GUI settings wrapper functions, getters only. +/// + +bool FractoriumFinalRenderDialog::EarlyClip() { return ui.FinalRenderEarlyClipCheckBox->isChecked(); } +bool FractoriumFinalRenderDialog::Transparency() { return ui.FinalRenderTransparencyCheckBox->isChecked(); } +bool FractoriumFinalRenderDialog::OpenCL() { return ui.FinalRenderOpenCLCheckBox->isChecked(); } +bool FractoriumFinalRenderDialog::Double() { return ui.FinalRenderDoublePrecisionCheckBox->isChecked(); } +bool FractoriumFinalRenderDialog::SaveXml() { return ui.FinalRenderSaveXmlCheckBox->isChecked(); } +bool FractoriumFinalRenderDialog::DoAll() { return ui.FinalRenderDoAllCheckBox->isChecked(); } +bool FractoriumFinalRenderDialog::DoSequence() { return ui.FinalRenderDoSequenceCheckBox->isChecked(); } +bool FractoriumFinalRenderDialog::KeepAspect() { return ui.FinalRenderKeepAspectCheckBox->isChecked(); } +QString FractoriumFinalRenderDialog::DoAllExt() { return ui.FinalRenderJpgRadioButton->isChecked() ? "jpg" : "png"; } +QString FractoriumFinalRenderDialog::Path() { return ui.FinalRenderGeometryTable->item(6, 1)->text(); } +void FractoriumFinalRenderDialog::Path(QString s) { ui.FinalRenderGeometryTable->item(6, 1)->setText(s); } +QString FractoriumFinalRenderDialog::Prefix() { return m_PrefixEdit->text(); } +QString FractoriumFinalRenderDialog::Suffix() { return m_SuffixEdit->text(); } +unsigned int FractoriumFinalRenderDialog::PlatformIndex() { return ui.FinalRenderPlatformCombo->currentIndex(); } +unsigned int FractoriumFinalRenderDialog::DeviceIndex() { return ui.FinalRenderDeviceCombo->currentIndex(); } +unsigned int FractoriumFinalRenderDialog::ThreadCount() { return ui.FinalRenderThreadCountSpin->value(); } +unsigned int FractoriumFinalRenderDialog::Width() { return m_WidthSpin->value(); } +unsigned int FractoriumFinalRenderDialog::Height() { return m_HeightSpin->value(); } +unsigned int FractoriumFinalRenderDialog::Quality() { return m_QualitySpin->value(); } +unsigned int FractoriumFinalRenderDialog::TemporalSamples() { return m_TemporalSamplesSpin->value(); } +unsigned int FractoriumFinalRenderDialog::Supersample() { return m_SupersampleSpin->value(); } + +/// +/// Capture the current state of the Gui. +/// Used to hold options for performing the final render. +/// +/// The state of the Gui as a struct +FinalRenderGuiState FractoriumFinalRenderDialog::State() +{ + FinalRenderGuiState state; + + state.m_EarlyClip = EarlyClip(); + state.m_Transparency = Transparency(); + state.m_OpenCL = OpenCL(); + state.m_Double = Double(); + state.m_SaveXml = SaveXml(); + state.m_DoAll = DoAll(); + state.m_DoSequence = DoSequence(); + state.m_KeepAspect = KeepAspect(); + state.m_Scale = Scale(); + state.m_Path = Path(); + state.m_DoAllExt = DoAllExt(); + state.m_Prefix = Prefix(); + state.m_Suffix = Suffix(); + state.m_PlatformIndex = PlatformIndex(); + state.m_DeviceIndex = DeviceIndex(); + state.m_ThreadCount = ThreadCount(); + state.m_Width = Width(); + state.m_Height = Height(); + state.m_Quality = Quality(); + state.m_TemporalSamples = TemporalSamples(); + state.m_Supersample = Supersample(); + + return state; +} + +/// +/// Return the type of scaling desired based on what radio button has been selected. +/// +/// The type of scaling as an eScaleType enum +eScaleType FractoriumFinalRenderDialog::Scale() +{ + if (ui.FinalRenderScaleNoneRadioButton->isChecked()) + return SCALE_NONE; + else if (ui.FinalRenderScaleWidthRadioButton->isChecked()) + return SCALE_WIDTH; + else if (ui.FinalRenderScaleHeightRadioButton->isChecked()) + return SCALE_HEIGHT; + else + return SCALE_NONE; +} + +/// +/// Set the type of scaling desired which will select the corresponding radio button. +/// +/// The type of scaling to use +void FractoriumFinalRenderDialog::Scale(eScaleType scale) +{ + if (scale == SCALE_NONE) + ui.FinalRenderScaleNoneRadioButton->setChecked(true); + else if (scale == SCALE_WIDTH) + ui.FinalRenderScaleWidthRadioButton->setChecked(true); + else if (scale == SCALE_HEIGHT) + ui.FinalRenderScaleHeightRadioButton->setChecked(true); + else + ui.FinalRenderScaleNoneRadioButton->setChecked(true); +} + +/// +/// Simple wrapper to put moving the cursor to the end in a signal +/// so it can be called from a thread. +/// +void FractoriumFinalRenderDialog::MoveCursorToEnd() +{ + ui.FinalRenderTextOutput->moveCursor(QTextCursor::End, QTextCursor::MoveAnchor); +} + +/// +/// Whether to use early clipping before spatial filtering. +/// +/// True to early clip, else don't. +void FractoriumFinalRenderDialog::OnEarlyClipCheckBoxStateChanged(int state) +{ + SetMemory(); +} + +/// +/// Whether to use transparency in png images. +/// +/// True to use transparency, else don't. +void FractoriumFinalRenderDialog::OnTransparencyCheckBoxStateChanged(int state) +{ + SetMemory(); +} + +/// +/// Set whether to use OpenCL in the rendering process or not. +/// +/// Use OpenCL if state == Qt::Checked, else don't. +void FractoriumFinalRenderDialog::OnOpenCLCheckBoxStateChanged(int state) +{ + bool checked = state == Qt::Checked; + + ui.FinalRenderPlatformCombo->setEnabled(checked); + ui.FinalRenderDeviceCombo->setEnabled(checked); + ui.FinalRenderThreadCountSpin->setEnabled(!checked); + SetMemory(); +} + +/// +/// Set whether to use double or single precision in the rendering process or not. +/// This will recreate the entire controller. +/// +/// Use double if state == Qt::Checked, else float. +void FractoriumFinalRenderDialog::OnDoublePrecisionCheckBoxStateChanged(int state) +{ + SetMemory(); +} + +/// +/// Populate the the device combo box with all available +/// OpenCL devices for the selected platform. +/// Called when the platform combo box index changes. +/// +/// The selected index of the combo box +void FractoriumFinalRenderDialog::OnPlatformComboCurrentIndexChanged(int index) +{ + vector devices = m_Wrapper.DeviceNames(index); + + ui.FinalRenderDeviceCombo->clear(); + + for (size_t i = 0; i < devices.size(); i++) + ui.FinalRenderDeviceCombo->addItem(QString::fromStdString(devices[i])); +} + +/// +/// The do all checkbox was changed. +/// If checked, render all embers available in the currently opened file, else +/// only render the current ember. +/// +/// The state of the checkbox +void FractoriumFinalRenderDialog::OnDoAllCheckBoxStateChanged(int state) +{ + ui.FinalRenderDoSequenceCheckBox->setEnabled(ui.FinalRenderDoAllCheckBox->isChecked()); + ui.FinalRenderExtensionGroupBox->setEnabled(ui.FinalRenderDoAllCheckBox->isChecked()); +} + +/// +/// Whether to keep the aspect ratio of the desired width and height the same +/// as that of the original width and height. +/// +/// The state of the checkbox +void FractoriumFinalRenderDialog::OnKeepAspectCheckBoxStateChanged(int state) +{ + if (state && m_Controller.get()) + m_HeightSpin->SetValueStealth(m_WidthSpin->value() / m_Controller->OriginalAspect()); + + SetMemory(); +} + +/// +/// The scaling method radio button selection was changed. +/// +/// The state of the radio button +void FractoriumFinalRenderDialog::OnScaleRadioButtonChanged(bool checked) +{ + if (checked) + SetMemory(); +} + +/// +/// The width spinner was changed, recompute required memory. +/// If the aspect ratio checkbox is checked, set the value of +/// the height spinner as well to be in proportion. +/// +/// Ignored +void FractoriumFinalRenderDialog::OnWidthChanged(int d) +{ + if (ui.FinalRenderKeepAspectCheckBox->isChecked() && m_Controller.get()) + m_HeightSpin->SetValueStealth(m_WidthSpin->value() / m_Controller->OriginalAspect()); + + SetMemory(); +} + +/// +/// The height spinner was changed, recompute required memory. +/// If the aspect ratio checkbox is checked, set the value of +/// the width spinner as well to be in proportion. +/// +/// Ignored +void FractoriumFinalRenderDialog::OnHeightChanged(int d) +{ + if (ui.FinalRenderKeepAspectCheckBox->isChecked() && m_Controller.get()) + m_WidthSpin->SetValueStealth(m_HeightSpin->value() * m_Controller->OriginalAspect()); + + SetMemory(); +} + +/// +/// The quality spinner was changed, recompute required memory. +/// +/// Ignored +void FractoriumFinalRenderDialog::OnQualityChanged(int d) +{ + SetMemory(); +} + +/// +/// The temporal samples spinner was changed, recompute required memory. +/// +/// Ignored +void FractoriumFinalRenderDialog::OnTemporalSamplesChanged(int d) +{ + SetMemory(); +} + +/// +/// The supersample spinner was changed, recompute required memory. +/// +/// Ignored +void FractoriumFinalRenderDialog::OnSupersampleChanged(int d) +{ + SetMemory(); +} + +/// +/// If a single ember is being rendered, show the save file dialog. +/// If a more than one is being rendered, show the save folder dialog. +/// Called when the ... file button is clicked. +/// +/// Ignored +void FractoriumFinalRenderDialog::OnFileButtonClicked(bool checked) +{ + bool doAll = ui.FinalRenderDoAllCheckBox->isChecked(); + QString filename; + + if (doAll) + filename = m_Fractorium->SetupSaveFolderDialog(); + else + filename = m_Fractorium->SetupSaveImageDialog(m_Controller->Name()); + + if (filename != "") + { + if (doAll) + { + if (!filename.endsWith(QDir::separator())) + filename += "/"; + } + + QFileInfo fileInfo(filename); + QString path = fileInfo.absolutePath(); + + m_Settings->SaveFolder(path);//Any time they exit the box with a valid value, preserve it in the settings. + Path(filename); + SetMemory(); + } +} + +/// +/// Show the folder where the last rendered image was saved to. +/// +/// Ignored +void FractoriumFinalRenderDialog::OnShowFolderButtonClicked(bool checked) +{ + QString text = Path(); + + if (text != "") + { + QFileInfo fileInfo(text); + QString path = fileInfo.absolutePath(); + QDir dir(path); + + if (dir.exists()) + QDesktopServices::openUrl(QUrl::fromLocalFile(path)); + } +} + +/// +/// Start the render process. +/// +/// Ignored +void FractoriumFinalRenderDialog::OnRenderClicked(bool checked) +{ + if (CreateControllerFromGUI(true)) + m_Controller->Render(); +} + +/// +/// Cancel the render. +/// +/// Ignored +void FractoriumFinalRenderDialog::OnCancelRenderClicked(bool checked) +{ + if (m_Controller.get()) + m_Controller->CancelRender(); +} + +/// +/// Uses the options and the ember to populate widgets. +/// Called when the dialog is initially shown. +/// +/// The event +void FractoriumFinalRenderDialog::showEvent(QShowEvent* e) +{ +#ifdef DO_DOUBLE + Ember ed; +#else + Ember ed; +#endif + + if (CreateControllerFromGUI(true)) + { + m_Fractorium->m_Controller->CopyEmber(ed);//Copy the current ember from the main window out in to a temp. + m_Controller->SetEmber(ed);//Copy the temp into the final render controller. + m_Controller->SetOriginalEmber(ed); + SetMemory(); + m_Controller->ResetProgress(); + } + + QDir dir(m_Settings->SaveFolder()); + QString name = m_Controller->Name(); + + if (dir.exists() && name != "") + Path(dir.absolutePath() + "/" + name + ".png"); + + ui.FinalRenderTextOutput->clear(); + QDialog::showEvent(e); +} + +/// +/// Close the dialog without running, or if running, cancel and exit. +/// Settings will not be saved. +/// Control will be returned to Fractorium::OnActionFinalRender(). +/// +void FractoriumFinalRenderDialog::reject() +{ + if (m_Controller.get()) + { + m_Controller->CancelRender(); + m_Controller->DeleteRenderer(); + } + + QDialog::reject(); +} + +/// +/// Create the controller from the options and optionally its underlying renderer. +/// +/// True if successful, else false. +bool FractoriumFinalRenderDialog::CreateControllerFromGUI(bool createRenderer) +{ + bool ok = true; +#ifdef DO_DOUBLE + size_t size = Double() ? sizeof(double) : sizeof(float); + Ember ed; + Ember orig; + EmberFile efd; +#else + size_t size = sizeof(float); + Ember ed; + Ember orig; + EmberFile efd; +#endif + + if (!m_Controller.get() || (m_Controller->SizeOfT() != size)) + { + //First check if a controller has already been created, and if so, save its embers and gracefully shut it down. + if (m_Controller.get()) + { + m_Controller->CopyEmber(ed);//Convert float to double or save double verbatim; + m_Controller->CopyEmberFile(efd); + m_Controller->Shutdown(); + } + + //Create a float or double controller based on the GUI. +#ifdef DO_DOUBLE + if (Double()) + m_Controller = auto_ptr(new FinalRenderEmberController(this)); + else +#endif + m_Controller = auto_ptr(new FinalRenderEmberController(this)); + + //Restore the ember and ember file. + if (m_Controller.get()) + { + m_Controller->SetEmber(ed);//Convert float to double or set double verbatim; + m_Controller->SetEmberFile(efd); + m_Fractorium->m_Controller->CopyEmber(orig);//Copy the current ember from the main window out in to a temp. + m_Controller->SetOriginalEmber(orig); + } + } + + if (m_Controller.get()) + { + if (createRenderer) + return m_Controller->CreateRendererFromGUI(); + else + return true; + } + else + return false; +} + +/// +/// Compute the amount of memory needed via call to SyncAndComputeMemory(), then +/// assign the result to the table cell as text. +/// +void FractoriumFinalRenderDialog::SetMemory() +{ + if (isVisible() && CreateControllerFromGUI(true)) + ui.FinalRenderGeometryTable->item(5, 1)->setText(QLocale(QLocale::English).toString(m_Controller->SyncAndComputeMemory())); +} diff --git a/Source/Fractorium/FinalRenderDialog.h b/Source/Fractorium/FinalRenderDialog.h new file mode 100644 index 0000000..d89a701 --- /dev/null +++ b/Source/Fractorium/FinalRenderDialog.h @@ -0,0 +1,113 @@ +#pragma once + +#include "ui_FinalRenderDialog.h" +#include "SpinBox.h" +#include "DoubleSpinBox.h" +#include "TwoButtonWidget.h" +#include "FractoriumSettings.h" +#include "FinalRenderEmberController.h" + +/// +/// FractoriumFinalRenderDialog class. +/// + +class Fractorium;//Forward declaration since Fractorium uses this dialog. + +/// +/// The final render dialog is for when the user is satisfied with the parameters they've +/// set and they want to do a final render at a higher quality and at a specific resolution +/// and save it out to a file. +/// It supports rendering either the current ember, or all of them in the opened file. +/// If a single ember is rendered, it will be saved to a single output file. +/// If multiple embers are rendered, they will all be saved to a specified directory using +/// default names. +/// The user can optionally save the Xml file with each ember. +/// They can be treated as individual images, or as an animation sequence in which case +/// motion blurring with temporal samples will be applied. +/// It keeps a pointer to the main window and the global settings object for convenience. +/// The settings used here are saved to the /finalrender portion of the settings file. +/// It has its own OpenCLWrapper member for populating the combo boxes. +/// Upon running, it will delete the main window's renderer to save memory/GPU resources and restore it to its +/// original state upon exiting. +/// This class uses a controller-based design similar to the main window. +/// +class FractoriumFinalRenderDialog : public QDialog +{ + Q_OBJECT + + friend Fractorium; + friend FinalRenderEmberControllerBase; + friend FinalRenderEmberController; +#ifdef DO_DOUBLE + friend FinalRenderEmberController; +#endif + +public: + FractoriumFinalRenderDialog(FractoriumSettings* settings, QWidget* parent, Qt::WindowFlags f = 0); + bool EarlyClip(); + bool Transparency(); + bool OpenCL(); + bool Double(); + bool SaveXml(); + bool DoAll(); + bool DoSequence(); + bool KeepAspect(); + eScaleType Scale(); + void Scale(eScaleType scale); + QString DoAllExt(); + QString Path(); + void Path(QString s); + QString Prefix(); + QString Suffix(); + unsigned int PlatformIndex(); + unsigned int DeviceIndex(); + unsigned int ThreadCount(); + unsigned int Width(); + unsigned int Height(); + unsigned int Quality(); + unsigned int TemporalSamples(); + unsigned int Supersample(); + FinalRenderGuiState State(); + +public Q_SLOTS: + void MoveCursorToEnd(); + void OnEarlyClipCheckBoxStateChanged(int state); + void OnTransparencyCheckBoxStateChanged(int state); + void OnOpenCLCheckBoxStateChanged(int state); + void OnDoublePrecisionCheckBoxStateChanged(int state); + void OnPlatformComboCurrentIndexChanged(int index); + void OnDoAllCheckBoxStateChanged(int state); + void OnKeepAspectCheckBoxStateChanged(int state); + void OnScaleRadioButtonChanged(bool checked); + void OnWidthChanged(int d); + void OnHeightChanged(int d); + void OnQualityChanged(int d); + void OnTemporalSamplesChanged(int d); + void OnSupersampleChanged(int d); + void OnFileButtonClicked(bool checked); + void OnShowFolderButtonClicked(bool checked); + void OnRenderClicked(bool checked); + void OnCancelRenderClicked(bool checked); + +protected: + virtual void reject(); + virtual void showEvent(QShowEvent* e); + +private: + bool CreateControllerFromGUI(bool createRenderer); + void SetMemory(); + + OpenCLWrapper m_Wrapper; + Timing m_RenderTimer; + SpinBox* m_WidthSpin; + SpinBox* m_HeightSpin; + SpinBox* m_QualitySpin; + SpinBox* m_TemporalSamplesSpin; + SpinBox* m_SupersampleSpin; + QLineEdit* m_PrefixEdit; + QLineEdit* m_SuffixEdit; + FractoriumSettings* m_Settings; + Fractorium* m_Fractorium; + auto_ptr m_Controller; + Ui::FinalRenderDialog ui; +}; diff --git a/Source/Fractorium/FinalRenderDialog.ui b/Source/Fractorium/FinalRenderDialog.ui new file mode 100644 index 0000000..9f77f0c --- /dev/null +++ b/Source/Fractorium/FinalRenderDialog.ui @@ -0,0 +1,904 @@ + + + FinalRenderDialog + + + + 0 + 0 + 519 + 801 + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Final Render + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + + Render all open flames instead of just the current one + + + Render All + + + + + + + Use temporal samples value to achieve motion blur effect between flames + + + Render as Animation Sequence + + + + + + + <html><head/><body><p>Checked: use 64-bit double precision numbers (slower, but better image quality).</p><p>Unchecked: use 32-bit single precision numbers (faster, but worse image quality).</p></body></html> + + + Use Double Precision + + + + + + + <html><head/><body><p>Use OpenCL to render if your video card supports it.</p><p>This is highly recommended as it will dramatically speed up render time.</p></body></html> + + + Use OpenCL + + + + + + + <html><head/><body><p>Checked: clip colors and gamma correct after density filtering.</p><p>Unchecked: do it after spatial filtering.</p></body></html> + + + Early Clip + + + + + + + Save an Xml parameter file for each flame rendered + + + Save Xml + + + + + + + <html><head/><body><p>Use transparency in the final image.</p><p>Only supported for Png format.</p></body></html> + + + Transparency + + + + + + + + 0 + 40 + + + + + 16777215 + 40 + + + + The scaling to perform from the editor to the final rendered image + + + Scale + + + + 6 + + + 4 + + + 4 + + + 6 + + + + + None + + + true + + + + + + + Width + + + + + + + Height + + + + + + + + + + + 0 + 0 + + + + + 110 + 40 + + + + + 16777215 + 40 + + + + The image type to save the final output as when rendering all open flames + + + Render All Extension + + + + 6 + + + 4 + + + 4 + + + 6 + + + + + + 0 + 0 + + + + Jpg + + + true + + + + + + + + 0 + 0 + + + + Png + + + + + + + + + + Maintain the aspect ratio between width and height to be equal to base width and base height + + + Keep Aspect Ratio + + + false + + + + + + + + + + 0 + 0 + + + + + 100 + 100 + + + + + 100 + 100 + + + + + 1 + 1 + + + + QFrame::NoFrame + + + 0 + + + + + + Qt::PlainText + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + 320 + 0 + + + + + 320 + 16777215 + + + + + + + + + 0 + 0 + + + + + 320 + 0 + + + + + 320 + 16777215 + + + + + + + + + 0 + 0 + + + + <html><head/><body><p>The number of threads to use with CPU rendering.</p><p>Decrease for a more responsive system during rendering, increase for better performance.</p></body></html> + + + Threads + + + 1 + + + 64 + + + + + + + + 0 + 0 + + + + + 0 + 218 + + + + + 16777215 + 218 + + + + Qt::NoFocus + + + QFrame::Panel + + + QFrame::Plain + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + QAbstractItemView::NoEditTriggers + + + false + + + false + + + QAbstractItemView::NoSelection + + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + true + + + Qt::SolidLine + + + false + + + 2 + + + false + + + false + + + 110 + + + false + + + 35 + + + true + + + false + + + 24 + + + false + + + 24 + + + false + + + + Width + + + + + + + + Height + + + + + Quality + + + + + Temporal Samples + + + + + Supersample + + + + + Memory Usage + + + + + Output + + + + + Prefix + + + + + Suffix + + + + + Field + + + + + + + + + + Width + + + The width in pixels of the final output image + + + + + 0 + + + + + Height + + + The height in pixels of the final output image + + + + + 0 + + + + + Quality + + + The quality in iterations per pixel of the final output image + + + + + 0 + + + + + Temporal Samples + + + The number of interpolated renders to do for each flame when rendering as an animation sequence + + + + + 0 + + + + + Supersample + + + <html><head/><body><p>The number to multiply the dimensions of the histogram and density filtering buffer by to achieve anti-aliasing.</p><p>Use this very sparingly as it increases the required memory by n squared.</p></body></html> + + + + + 0 + + + + + Memory Usage + + + The amount of memory including the final output image required to perform this render + + + + + 0 + + + + + Output + + + The output file path for rendering a single flame, or folder location for rendering multiple flames + + + + + + + + + + Prefix + + + The prefix to attach to all image filenames + + + + + + + + + + Suffix + + + The suffix to attach to all image filenames + + + + + + + + + + + + + 0 / 0 + + + Qt::AlignCenter + + + + + + + QLayout::SetDefaultConstraint + + + + + + 0 + 0 + + + + 0 + + + + + + + + 0 + 0 + + + + Iteration: + + + + + + + + 0 + 0 + + + + Final Accumulation: + + + + + + + + 0 + 0 + + + + 0 + + + + + + + + 0 + 0 + + + + 0 + + + + + + + + 0 + 0 + + + + Density Filtering: + + + + + + + + 0 + 0 + + + + Total Progress: + + + + + + + + 0 + 0 + + + + 0 + + + + + + + + + + 0 + 1 + + + + + 16777215 + 16777215 + + + + Qt::TabFocus + + + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 131 + 31 + + + + + + + + Qt::TabFocus + + + Start + + + false + + + + + + + Qt::TabFocus + + + Stop + + + + + + + Qt::TabFocus + + + Close + + + false + + + false + + + + + + + + + + TableWidget + QTableWidget +
TableWidget.h
+
+
+ + + + StartRenderButton + clicked() + FinalRenderDialog + accept() + + + 278 + 253 + + + 96 + 254 + + + + + CloseButton + clicked() + FinalRenderDialog + reject() + + + 369 + 253 + + + 179 + 282 + + + + +
diff --git a/Source/Fractorium/FinalRenderEmberController.cpp b/Source/Fractorium/FinalRenderEmberController.cpp new file mode 100644 index 0000000..e5bf103 --- /dev/null +++ b/Source/Fractorium/FinalRenderEmberController.cpp @@ -0,0 +1,553 @@ +#include "FractoriumPch.h" +#include "FractoriumEmberController.h" +#include "FinalRenderEmberController.h" +#include "FinalRenderDialog.h" +#include "Fractorium.h" + +/// +/// Constructor which accepts a pointer to the final render dialog. +/// It passes a pointer to the main window to the base and initializes members. +/// +/// Pointer to the final render dialog +FinalRenderEmberControllerBase::FinalRenderEmberControllerBase(FractoriumFinalRenderDialog* finalRender) + : FractoriumEmberControllerBase(finalRender->m_Fractorium) +{ + m_Run = false; + m_PreviewRun = false; + m_ImageCount = 0; + m_FinishedImageCount = 0; + m_FinalRender = finalRender; + m_Settings = m_Fractorium->m_Settings; +} + +/// +/// Cancel the render by calling Abort(). +/// This will block until the cancelling is actually finished. +/// It should never take longer than a few milliseconds because the +/// renderer checks the m_Abort flag in many places during the process. +/// +void FinalRenderEmberControllerBase::CancelRender() +{ + if (m_Result.isRunning()) + { + tbb::task_group g; + + g.run([&] + { + m_Run = false; + + if (m_Renderer.get()) + { + m_Renderer->Abort(); + + while (m_Renderer->InRender()) + QApplication::processEvents(); + + m_Renderer->EnterRender(); + m_Renderer->EnterFinalAccum(); + m_Renderer->LeaveFinalAccum(); + m_Renderer->LeaveRender(); + } + }); + + g.wait(); + + while (m_Result.isRunning()) + QApplication::processEvents(); + + m_FinalRender->ui.FinalRenderTextOutput->append("Render canceled."); + } +} + +/// +/// Create a new renderer based on the options selected on the GUI. +/// If a renderer matching the options has already been created, no action is taken. +/// +/// True if a valid renderer is created or if no action is taken, else false. +bool FinalRenderEmberControllerBase::CreateRendererFromGUI() +{ + bool useOpenCL = m_Wrapper.CheckOpenCL() && m_FinalRender->OpenCL(); + + return CreateRenderer(useOpenCL ? OPENCL_RENDERER : CPU_RENDERER, + m_FinalRender->PlatformIndex(), + m_FinalRender->DeviceIndex(), + false);//Not shared. +} + +/// +/// Constructor which accepts a pointer to the final render dialog and passes it to the base. +/// The main final rendering lambda function is constructed here. +/// +/// Pointer to the final render dialog +template +FinalRenderEmberController::FinalRenderEmberController(FractoriumFinalRenderDialog* finalRender) + : FinalRenderEmberControllerBase(finalRender) +{ + m_PreviewRenderer = auto_ptr>(new EmberNs::Renderer()); + m_PreviewRenderer->Callback(NULL); + m_PreviewRenderer->NumChannels(4); + m_PreviewRenderer->ReclaimOnResize(true); + + m_PreviewRenderFunc = [&]() + { + m_PreviewCs.Enter();//Thread prep. + m_PreviewRun = true; + m_PreviewRenderer->Abort(); + + QLabel* widget = m_FinalRender->ui.FinalRenderPreviewLabel; + unsigned int maxDim = 100u; + T scalePercentage; + + //Determine how to scale the scaled ember to fit in the label with a max of 100x100. + if (m_Ember.m_FinalRasW >= m_Ember.m_FinalRasH) + scalePercentage = T(maxDim) / m_Ember.m_FinalRasW; + else + scalePercentage = T(maxDim) / m_Ember.m_FinalRasH; + + m_PreviewEmber = m_Ember; + m_PreviewEmber.m_Quality = 100; + m_PreviewEmber.m_Supersample = 1; + m_PreviewEmber.m_TemporalSamples = 1; + m_PreviewEmber.m_FinalRasW = min(maxDim, unsigned int(scalePercentage * m_Ember.m_FinalRasW)); + m_PreviewEmber.m_FinalRasH = min(maxDim, unsigned int(scalePercentage * m_Ember.m_FinalRasH)); + m_PreviewEmber.m_PixelsPerUnit = scalePercentage * m_Ember.m_PixelsPerUnit; + + while (!m_PreviewRenderer->Aborted() || m_PreviewRenderer->InRender()) + QApplication::processEvents(); + + m_PreviewRenderer->EarlyClip(m_FinalRender->EarlyClip()); + m_PreviewRenderer->Transparency(m_FinalRender->Transparency()); + m_PreviewRenderer->SetEmber(m_PreviewEmber); + + if (m_PreviewRenderer->Run(m_PreviewFinalImage) == RENDER_OK) + { + QImage image(m_PreviewEmber.m_FinalRasW, m_PreviewEmber.m_FinalRasH, QImage::Format_RGBA8888);//The label wants RGBA. + memcpy(image.scanLine(0), m_PreviewFinalImage.data(), m_PreviewFinalImage.size() * sizeof(m_PreviewFinalImage[0]));//Memcpy the data in. + QPixmap pixmap = QPixmap::fromImage(image); + QMetaObject::invokeMethod(widget, "setPixmap", Qt::QueuedConnection, Q_ARG(QPixmap, pixmap)); + } + + m_PreviewRun = false; + m_PreviewCs.Leave(); + }; + + //The main rendering function which will be called in a Qt thread. + //A backup Xml is made before the rendering process starts just in case it crashes before finishing. + //If it finishes successfully, delete the backup file. + m_RenderFunc = [&]() + { + size_t i; + + m_Run = true; + m_TotalTimer.Tic();//Begin timing for progress. + m_GuiState = m_FinalRender->State();//Cache render settings from the GUI before running. + m_FinishedImageCount = 0; + + QFileInfo original(m_GuiState.m_Path); + QString backup = original.absolutePath() + QDir::separator() + m_GuiState.m_Prefix + original.completeBaseName() + m_GuiState.m_Suffix + "_backup.flame"; + + QMetaObject::invokeMethod(m_Fractorium, "OnActionSaveCurrentToOpenedFile", Qt::QueuedConnection, Q_ARG(bool, true));//First, save the current ember back to its opened file. + m_Fractorium->m_Controller->CopyEmber(m_Ember); + m_Fractorium->m_Controller->CopyEmberFile(m_EmberFile);//Copy the whole file, will take about 0.2ms per ember in the file. + + //Save backup Xml. + if (m_GuiState.m_DoAll && m_EmberFile.m_Embers.size() > 1) + m_XmlWriter.Save(backup.toStdString().c_str(), m_EmberFile.m_Embers, 0, true, false, true); + else + m_XmlWriter.Save(backup.toStdString().c_str(), m_Ember, 0, true, false, true); + + QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderTextOutput, "setText", Qt::QueuedConnection, Q_ARG(QString, "Begin rendering...")); + m_Renderer->EarlyClip(m_GuiState.m_EarlyClip); + m_Renderer->ThreadCount(m_GuiState.m_ThreadCount); + m_Renderer->Transparency(m_GuiState.m_Transparency); + + if (m_GuiState.m_Path.endsWith(".png", Qt::CaseInsensitive) || m_Renderer->RendererType() == OPENCL_RENDERER) + m_Renderer->NumChannels(4); + else + m_Renderer->NumChannels(3); + + //The rendering process is different between doing a single image, and doing multiple. + if (m_GuiState.m_DoAll && m_EmberFile.m_Embers.size() > 1) + { + m_ImageCount = m_EmberFile.m_Embers.size(); + ResetProgress(); + + //Different action required for rendering as animation or not. + if (m_GuiState.m_DoSequence) + { + //Need to loop through and set all w, h, q, ts, ss and t vals. + for (i = 0; i < m_EmberFile.m_Embers.size() && m_Run; i++) + { + Sync(m_EmberFile.m_Embers[i]); + + if (i > 0) + { + if (m_EmberFile.m_Embers[i].m_Time <= m_EmberFile.m_Embers[i - 1].m_Time) + m_EmberFile.m_Embers[i].m_Time = m_EmberFile.m_Embers[i - 1].m_Time + 1; + } + else if (i == 0) + { + m_EmberFile.m_Embers[i].m_Time = 0; + } + + m_EmberFile.m_Embers[i].m_TemporalSamples = m_GuiState.m_TemporalSamples; + } + + m_Renderer->SetEmber(m_EmberFile.m_Embers);//Copy all embers to the local storage inside the renderer. + + //Render each image, cancelling if m_Run ever gets set to false. + for (i = 0; i < m_EmberFile.m_Embers.size() && m_Run; i++) + { + m_Renderer->Reset();//Have to manually set this since the ember is not set each time through. + m_RenderTimer.Tic();//Toc() is called in the progress function. + + if (m_Renderer->Run(m_FinalImage, i) != RENDER_OK) + { + QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderTextOutput, "append", Qt::QueuedConnection, Q_ARG(QString, "Renderering failed.\n")); + m_Fractorium->ErrorReportToQTextEdit(m_Renderer->ErrorReport(), m_FinalRender->ui.FinalRenderTextOutput, false); + } + } + } + else//Render all images, but not as an animation sequence (without temporal samples motion blur). + { + //Copy widget values to all embers + for (i = 0; i < m_EmberFile.m_Embers.size() && m_Run; i++) + { + Sync(m_EmberFile.m_Embers[i]); + m_EmberFile.m_Embers[i].m_TemporalSamples = 1;//No temporal sampling. + } + + //Render each image, cancelling if m_Run ever gets set to false. + for (i = 0; i < m_EmberFile.m_Embers.size() && m_Run; i++) + { + m_Renderer->SetEmber(m_EmberFile.m_Embers[i]); + m_RenderTimer.Tic();//Toc() is called in the progress function. + + if (m_Renderer->Run(m_FinalImage) != RENDER_OK) + { + QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderTextOutput, "append", Qt::QueuedConnection, Q_ARG(QString, "Renderering failed.\n")); + m_Fractorium->ErrorReportToQTextEdit(m_Renderer->ErrorReport(), m_FinalRender->ui.FinalRenderTextOutput, false); + } + } + } + } + else//Render a single image. + { + m_ImageCount = 1; + Sync(m_Ember); + ResetProgress(); + m_Ember.m_TemporalSamples = 1; + m_Renderer->SetEmber(m_Ember); + m_RenderTimer.Tic();//Toc() is called in the progress function. + + if (m_Renderer->Run(m_FinalImage) != RENDER_OK) + { + QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderTextOutput, "append", Qt::QueuedConnection, Q_ARG(QString, "Renderering failed.\n")); + m_Fractorium->ErrorReportToQTextEdit(m_Renderer->ErrorReport(), m_FinalRender->ui.FinalRenderTextOutput, false); + } + } + + QFile::remove(backup); + m_Run = false; + }; +} + +/// +/// Setters for embers and ember files which convert between float and double types. +/// These are used to preserve the current ember/file when switching between renderers. +/// Note that some precision will be lost when going from double to float. +/// +template void FinalRenderEmberController::SetEmber(const Ember& ember, bool verbatim) { m_Ember = ember; } +template void FinalRenderEmberController::CopyEmber(Ember& ember) { ember = m_Ember; } +template void FinalRenderEmberController::SetEmberFile(const EmberFile& emberFile) { m_EmberFile = emberFile; } +template void FinalRenderEmberController::CopyEmberFile(EmberFile& emberFile) { emberFile = m_EmberFile; } +template double FinalRenderEmberController::OriginalAspect() { return double(m_OriginalEmber.m_OrigFinalRasW) / m_OriginalEmber.m_OrigFinalRasH; } +#ifdef DO_DOUBLE +template void FinalRenderEmberController::SetEmber(const Ember& ember, bool verbatim) { m_Ember = ember; } +template void FinalRenderEmberController::CopyEmber(Ember& ember) { ember = m_Ember; } +template void FinalRenderEmberController::SetEmberFile(const EmberFile& emberFile) { m_EmberFile = emberFile; } +template void FinalRenderEmberController::CopyEmberFile(EmberFile& emberFile) { emberFile = m_EmberFile; } +template void FinalRenderEmberController::SetOriginalEmber(Ember& ember) { m_OriginalEmber = ember; } +#else +template void FinalRenderEmberController::SetOriginalEmber(Ember& ember) { m_OriginalEmber = ember; } +#endif + +/// +/// Progress function. +/// Take special action to sync options upon finishing. +/// +/// The ember currently being rendered +/// An extra dummy parameter +/// The progress fraction from 0-100 +/// The stage of iteration. 1 is iterating, 2 is density filtering, 2 is final accumulation. +/// The estimated milliseconds to completion of the current stage +/// 0 if the user has clicked cancel, else 1 to continue rendering. +template +int FinalRenderEmberController::ProgressFunc(Ember& ember, void* foo, double fraction, int stage, double etaMs) +{ + static int count = 0; + + if (stage == 0) + QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderIterationProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, int(fraction))); + else if (stage == 1) + QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderFilteringProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, int(fraction))); + else if (stage == 2) + QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderAccumProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, int(fraction))); + + //Finished, so take special action. + if (stage == 2 && (int)fraction == 100) + { + string renderTimeString = m_RenderTimer.Format(m_RenderTimer.Toc()), totalTimeString; + QString status, filename = m_GuiState.m_Path; + QFileInfo original(filename); + EmberStats stats = m_Renderer->Stats(); + QString iters = QLocale(QLocale::English).toString(stats.m_Iters); + + if (m_GuiState.m_DoAll && m_EmberFile.m_Embers.size() > 1) + filename = original.absolutePath() + QDir::separator() + m_GuiState.m_Prefix + QString::fromStdString(m_EmberFile.m_Embers[m_FinishedImageCount].m_Name) + m_GuiState.m_Suffix + "." + m_GuiState.m_DoAllExt; + else + filename = original.absolutePath() + QDir::separator() + m_GuiState.m_Prefix + original.completeBaseName() + m_GuiState.m_Suffix + "." + original.suffix(); + + filename = EmberFile::UniqueFilename(filename); + + //Save whatever options were specified on the GUI to the settings. + m_Settings->FinalEarlyClip(m_GuiState.m_EarlyClip); + m_Settings->FinalTransparency(m_GuiState.m_Transparency); + m_Settings->FinalOpenCL(m_GuiState.m_OpenCL); + m_Settings->FinalDouble(m_GuiState.m_Double); + m_Settings->FinalPlatformIndex(m_GuiState.m_PlatformIndex); + m_Settings->FinalDeviceIndex(m_GuiState.m_DeviceIndex); + m_Settings->FinalSaveXml(m_GuiState.m_SaveXml); + m_Settings->FinalDoAll(m_GuiState.m_DoAll); + m_Settings->FinalDoSequence(m_GuiState.m_DoSequence); + m_Settings->FinalKeepAspect(m_GuiState.m_KeepAspect); + m_Settings->FinalScale(m_GuiState.m_Scale); + m_Settings->FinalDoAllExt(m_GuiState.m_DoAllExt); + m_Settings->FinalThreadCount(m_GuiState.m_ThreadCount); + m_Settings->FinalWidth(m_GuiState.m_Width); + m_Settings->FinalHeight(m_GuiState.m_Height); + m_Settings->FinalQuality(m_GuiState.m_Quality); + m_Settings->FinalTemporalSamples(m_GuiState.m_TemporalSamples); + m_Settings->FinalSupersample(m_GuiState.m_Supersample); + SaveCurrentRender(filename); + + if (m_GuiState.m_SaveXml) + { + QFileInfo xmlFileInfo(filename);//Create another one in case it was modified for batch rendering. + QString newPath = xmlFileInfo.absolutePath() + QDir::separator() + xmlFileInfo.completeBaseName() + ".flame"; + xmlDocPtr tempEdit = ember.m_Edits; + + ember.m_Edits = m_XmlWriter.CreateNewEditdoc(&ember, NULL, "edit", m_Settings->Nick().toStdString(), m_Settings->Url().toStdString(), m_Settings->Id().toStdString(), "", 0, 0); + m_XmlWriter.Save(newPath.toStdString().c_str(), ember, 0, true, false, true);//Note that the ember passed is used, rather than m_Ember because it's what was actually rendered. + + if (tempEdit != NULL) + xmlFreeDoc(tempEdit); + } + + m_FinishedImageCount++; + QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderTotalProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, int(((float)m_FinishedImageCount / (float)m_ImageCount) * 100))); + QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderImageCountLabel, "setText", Qt::QueuedConnection, Q_ARG(QString, QString::number(m_FinishedImageCount) + " / " + QString::number(m_ImageCount))); + + status = "Image " + QString::number(m_FinishedImageCount) + ":\nPure render time: " + QString::fromStdString(renderTimeString); + QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderTextOutput, "append", Qt::QueuedConnection, Q_ARG(QString, status)); + + totalTimeString = m_TotalTimer.Format(m_TotalTimer.Toc()); + status = "Total render time: " + QString::fromStdString(totalTimeString) + "\nTotal iters: " + iters + "\n"; + QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderTextOutput, "append", Qt::QueuedConnection, Q_ARG(QString, status)); + QMetaObject::invokeMethod(m_FinalRender, "MoveCursorToEnd", Qt::QueuedConnection); + + if (m_FinishedImageCount != m_ImageCount) + { + ResetProgress(false); + } + } + + QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderTextOutput, "update", Qt::QueuedConnection); + QApplication::processEvents(); + return m_Run ? 1 : 0; +} + +/// +/// Start the final rendering process. +/// Create the needed renderer from the GUI if it has not been created yet. +/// +/// +template +bool FinalRenderEmberController::Render() +{ + QString filename = m_FinalRender->Path(); + + if (filename == "") + { + QMessageBox::critical(m_FinalRender, "File Error", "Please enter a valid path and filename for the output."); + return false; + } + + if (CreateRendererFromGUI()) + { + m_FinalRender->ui.FinalRenderTextOutput->clear(); + + //Note that a Qt thread must be used, rather than a tbb task. + //This is because tbb does a very poor job of allocating thread resources + //and dedicates an entire core just to this thread which does nothing waiting for the + //parallel iteration loops inside of the CPU renderer to finish. The result is that + //the renderer ends up using ThreadCount - 1 to iterate, instead of ThreadCount. + //By using a Qt thread here, and tbb inside the renderer, all cores can be maxed out. + m_Result = QtConcurrent::run(m_RenderFunc); + m_Settings->sync(); + return true; + } + else + return false; +} + +/// +/// Stop rendering and initialize a new renderer, using the specified type and the options on the final render dialog. +/// +/// The type of render to create +/// The index platform of the platform to use +/// The index device of the device to use +/// The texture ID of the shared OpenGL texture if shared +/// True if shared with OpenGL, else false. Default: true. +/// True if nothing went wrong, else false. +template +bool FinalRenderEmberController::CreateRenderer(eRendererType renderType, unsigned int platform, unsigned int device, bool shared) +{ + bool ok = true; + vector errorReport; + QString filename = m_FinalRender->Path(); + unsigned int channels = filename.endsWith(".png", Qt::CaseInsensitive) ? 4 : 3; + + CancelRender(); + + if (m_Renderer.get() && + m_Renderer->Ok() && + m_Renderer->RendererType() == renderType && + m_Platform == platform && + m_Device == device && + m_Shared == shared) + { + return ok; + } + + if (!m_Renderer.get() || (m_Renderer->RendererType() != renderType)) + { + EmberReport emberReport; + vector errorReport; + + m_Platform = platform;//Store values for re-creation later on. + m_Device = device; + m_OutputTexID = 0;//Don't care about tex ID when doing final render. + m_Shared = shared; + + m_Renderer = auto_ptr(::CreateRenderer(renderType, platform, device, shared, m_OutputTexID, emberReport)); + errorReport = emberReport.ErrorReport(); + + if (!errorReport.empty()) + { + ok = false; + QMessageBox::critical(m_Fractorium, "Renderer Creation Error", "Could not create requested renderer, fallback CPU renderer created. See info tab for details."); + m_Fractorium->ErrorReportToQTextEdit(errorReport, m_Fractorium->ui.InfoRenderingTextEdit); + } + } + + if (m_Renderer.get()) + { + if (m_Renderer->RendererType() == OPENCL_RENDERER) + channels = 4;//Always using 4 since the GL texture is RGBA. + + m_Renderer->Callback(this); + m_Renderer->NumChannels(channels); + m_Renderer->ReclaimOnResize(false); + m_Renderer->EarlyClip(m_FinalRender->EarlyClip()); + m_Renderer->ThreadCount(m_FinalRender->ThreadCount()); + m_Renderer->Transparency(m_FinalRender->Transparency()); + } + else + { + ok = false; + QMessageBox::critical(m_FinalRender, "Renderer Creation Error", "Could not create renderer, aborting. See info tab for details."); + } + + return ok; +} + +/// +/// Set various parameters in the renderer and current ember with the values +/// specified in the widgets and compute the amount of memory required to render. +/// This includes the memory needed for the final output image. +/// +/// If successful, memory required in bytes, else zero. +template +unsigned __int64 FinalRenderEmberController::SyncAndComputeMemory() +{ + if (m_Renderer.get()) + { + bool b = false; + QString filename = m_FinalRender->Path(); + unsigned int channels = filename.endsWith(".png", Qt::CaseInsensitive) ? 4 : 3;//4 channels for Png, else 3. + + Sync(m_Ember); + m_Renderer->SetEmber(m_Ember); + m_Renderer->CreateSpatialFilter(b); + m_Renderer->CreateTemporalFilter(b); + m_Renderer->NumChannels(channels); + m_Renderer->ComputeBounds(); + CancelPreviewRender(); + //m_PreviewResult = QtConcurrent::run(m_PreviewRenderFunc); + //while (!m_PreviewResult.isRunning()) { QApplication::processEvents(); }//Wait for it to start up. + m_PreviewRenderFunc(); + return m_Renderer->MemoryRequired(true); + } + + return 0; +} + +/// +/// Reset the progress bars. +/// +/// True to reset render image and total progress bars, else false to only do iter, filter and accum bars. +template +void FinalRenderEmberController::ResetProgress(bool total) +{ + if (total) + { + QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderImageCountLabel, "setText", Qt::QueuedConnection, Q_ARG(QString, "0 / " + QString::number(m_ImageCount))); + QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderTotalProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, 0)); + } + + QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderIterationProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, 0)); + QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderFilteringProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, 0)); + QMetaObject::invokeMethod(m_FinalRender->ui.FinalRenderAccumProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, 0)); +} + +template +void FinalRenderEmberController::CancelPreviewRender() +{ + m_PreviewRenderer->Abort(); + + while (m_PreviewRenderer->InRender()) { QApplication::processEvents(); } + while (m_PreviewRun) { QApplication::processEvents(); } + while (m_PreviewResult.isRunning()) { QApplication::processEvents(); } +} + +/// +/// Copy widget values to the ember passed in. +/// +/// The ember whose values will be modified +template +void FinalRenderEmberController::Sync(Ember& ember) +{ + int w = m_FinalRender->m_WidthSpin->value(); + int h = m_FinalRender->m_HeightSpin->value(); + + ember.m_FinalRasW = m_OriginalEmber.m_OrigFinalRasW;//Scale is always in terms of the original dimensions of the ember in the editor. + ember.m_FinalRasH = m_OriginalEmber.m_OrigFinalRasH; + ember.m_PixelsPerUnit = m_OriginalEmber.m_PixelsPerUnit; + ember.SetSizeAndAdjustScale(w, h, false, m_FinalRender->Scale()); + ember.m_Quality = m_FinalRender->m_QualitySpin->value(); + ember.m_Supersample = m_FinalRender->m_SupersampleSpin->value(); + + if (m_FinalRender->ui.FinalRenderDoSequenceCheckBox->isChecked()) + ember.m_TemporalSamples = m_FinalRender->m_TemporalSamplesSpin->value(); +} diff --git a/Source/Fractorium/FinalRenderEmberController.h b/Source/Fractorium/FinalRenderEmberController.h new file mode 100644 index 0000000..e7f2c07 --- /dev/null +++ b/Source/Fractorium/FinalRenderEmberController.h @@ -0,0 +1,143 @@ +#pragma once + +#include "FractoriumSettings.h" +#include "FractoriumEmberController.h" + +/// +/// FinalRenderEmberControllerBase and FinalRenderEmberController classes. +/// + +/// +/// FractoriumEmberController and Fractorium need each other, but each can't include the other. +/// So Fractorium includes this file, and Fractorium is declared as a forward declaration here. +/// +class Fractorium; +class FractoriumFinalRenderDialog; + +/// +/// Used to hold the options specified in the current state of the Gui for performing the final render. +/// +struct FinalRenderGuiState +{ + bool m_EarlyClip; + bool m_AlphaChannel; + bool m_Transparency; + bool m_OpenCL; + bool m_Double; + bool m_SaveXml; + bool m_DoAll; + bool m_DoSequence; + bool m_KeepAspect; + eScaleType m_Scale; + QString m_Path; + QString m_DoAllExt; + QString m_Prefix; + QString m_Suffix; + unsigned int m_PlatformIndex; + unsigned int m_DeviceIndex; + unsigned int m_ThreadCount; + unsigned int m_Width; + unsigned int m_Height; + unsigned int m_Quality; + unsigned int m_TemporalSamples; + unsigned int m_Supersample; +}; + +/// +/// FinalRenderEmberControllerBase serves as a non-templated base class with virtual +/// functions which will be overridden in a derived class that takes a template parameter. +/// Although not meant to be used as an interactive renderer, it derives from FractoriumEmberControllerBase +/// to access a few of its members to avoid having to redefine them here. +/// +class FinalRenderEmberControllerBase : public FractoriumEmberControllerBase +{ + friend FractoriumFinalRenderDialog; + +public: + FinalRenderEmberControllerBase(FractoriumFinalRenderDialog* finalRender); + virtual ~FinalRenderEmberControllerBase() { } + + virtual unsigned __int64 SyncAndComputeMemory() { return 0; } + virtual QString Name() const { return ""; } + virtual void ResetProgress(bool total = true) { } +#ifdef DO_DOUBLE + virtual void SetOriginalEmber(Ember& ember) { } +#else + virtual void SetOriginalEmber(Ember& ember) { } +#endif + virtual double OriginalAspect() { return 1; } + + void CancelRender(); + bool CreateRendererFromGUI(); + +protected: + bool m_Run; + bool m_PreviewRun; + unsigned int m_ImageCount; + unsigned int m_FinishedImageCount; + + QFuture m_Result; + QFuture m_PreviewResult; + std::function m_RenderFunc; + std::function m_PreviewRenderFunc; + vector m_PreviewFinalImage; + + FractoriumSettings* m_Settings; + FractoriumFinalRenderDialog* m_FinalRender; + FinalRenderGuiState m_GuiState; + OpenCLWrapper m_Wrapper; + CriticalSection m_PreviewCs; + Timing m_RenderTimer; + Timing m_TotalTimer; +}; + +/// +/// Templated derived class which implements all interaction functionality between the embers +/// of a specific template type and the final render dialog; +/// +template +class FinalRenderEmberController : public FinalRenderEmberControllerBase +{ +public: + FinalRenderEmberController(FractoriumFinalRenderDialog* finalRender); + virtual ~FinalRenderEmberController() { } + + virtual void SetEmber(const Ember& ember, bool verbatim = false); + virtual void CopyEmber(Ember& ember); + virtual void SetEmberFile(const EmberFile& emberFile); + virtual void CopyEmberFile(EmberFile& emberFile); +#ifdef DO_DOUBLE + virtual void SetEmber(const Ember& ember, bool verbatim = false); + virtual void CopyEmber(Ember& ember); + virtual void SetEmberFile(const EmberFile& emberFile); + virtual void CopyEmberFile(EmberFile& emberFile); + virtual void SetOriginalEmber(Ember& ember); +#else + virtual void SetOriginalEmber(Ember& ember); +#endif + virtual double OriginalAspect(); + virtual int ProgressFunc(Ember& ember, void* foo, double fraction, int stage, double etaMs); + virtual bool Render(); + virtual bool CreateRenderer(eRendererType renderType, unsigned int platform, unsigned int device, bool shared = true); + virtual unsigned int SizeOfT() { return sizeof(T); } + virtual unsigned __int64 SyncAndComputeMemory(); + virtual QString Name() const { return QString::fromStdString(m_Ember.m_Name); } + virtual void ResetProgress(bool total = true); + void CancelPreviewRender(); + +protected: + void Sync(Ember& ember); + + Ember m_Ember; + Ember m_OriginalEmber; + Ember m_PreviewEmber; + EmberFile m_EmberFile; + EmberToXml m_XmlWriter; + auto_ptr> m_PreviewRenderer; +}; + +template class FinalRenderEmberController; + +#ifdef DO_DOUBLE + template class FinalRenderEmberController; +#endif \ No newline at end of file diff --git a/Source/Fractorium/Fractorium.aps b/Source/Fractorium/Fractorium.aps new file mode 100644 index 0000000000000000000000000000000000000000..2c2f8b4ec9af953520e4b07dbe3a2efd3696f3d1 GIT binary patch literal 158048 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%0A;Yd@>QTtBE7}g7 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 (Q<{VfQmXM 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

RQtW-Q?Fcne#@&zhfdd&g>^>k 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 +/// Constructor that initializes the entire program. +/// The setup process is very lengthy because it requires many custom modifications +/// to the GUI widgets that are not possible to do through the designer. So if something +/// is present here, it's safe to assume it can't be done in the designer. +/// +Fractorium::Fractorium(QWidget* parent) + : QMainWindow(parent) +{ + int spinHeight = 20; + size_t i, j; + Timing t; + ui.setupUi(this); + qRegisterMetaType>("QVector");//For previews. + qRegisterMetaType>("vector"); + qRegisterMetaType("EmberTreeWidgetItemBase*"); + + m_FontSize = 9; + m_VarSortMode = 1;//Sort by weight by default. + m_ColorDialog = new QColorDialog(this); + m_Settings = new FractoriumSettings(this); + + m_FileDialog = NULL;//Use lazy instantiation upon first use. + m_FolderDialog = NULL; + m_FinalRenderDialog = new FractoriumFinalRenderDialog(m_Settings, this); + m_OptionsDialog = new FractoriumOptionsDialog(m_Settings, this); + m_AboutDialog = new FractoriumAboutDialog(this); + + //The options dialog should be a fixed size without a size grip, however even if it's here, it still shows up. Perhaps Qt will fix it some day. + m_OptionsDialog->layout()->setSizeConstraint(QLayout::SetFixedSize); + m_OptionsDialog->setSizeGripEnabled(false); + connect(m_ColorDialog, SIGNAL(colorSelected(const QColor&)), this, SLOT(OnColorSelected(const QColor&)), Qt::QueuedConnection); + + InitToolbarUI(); + InitParamsUI(); + InitXformsUI(); + InitXformsColorUI(); + InitXformsAffineUI(); + InitXformsVariationsUI(); + InitXformsXaosUI(); + InitPaletteUI(); + InitLibraryUI(); + InitMenusUI(); + + //This will init the controller and fill in the variations and palette tables with template specific instances + //of their respective objects. +#ifdef DO_DOUBLE + if (m_Settings->Double()) + m_Controller = auto_ptr(new FractoriumEmberController(this)); + else +#endif + m_Controller = auto_ptr(new FractoriumEmberController(this)); + + if (m_Wrapper.CheckOpenCL() && m_Settings->OpenCL() && m_QualitySpin->value() < 30) + m_QualitySpin->setValue(30); + + int statusBarHeight = 20; + ui.statusBar->setMinimumHeight(statusBarHeight); + ui.statusBar->setMaximumHeight(statusBarHeight); + + m_RenderStatusLabel = new QLabel(this); + m_RenderStatusLabel->setMinimumWidth(200); + m_RenderStatusLabel->setAlignment(Qt::AlignRight); + ui.statusBar->addPermanentWidget(m_RenderStatusLabel); + + m_CoordinateStatusLabel = new QLabel(this); + m_CoordinateStatusLabel->setMinimumWidth(300); + m_CoordinateStatusLabel->setMaximumWidth(300); + m_CoordinateStatusLabel->setAlignment(Qt::AlignLeft); + ui.statusBar->addWidget(m_CoordinateStatusLabel); + + int progressBarHeight = 15; + int progressBarWidth = 300; + m_ProgressBar = new QProgressBar(this); + m_ProgressBar->setRange(0, 100); + m_ProgressBar->setValue(0); + m_ProgressBar->setMinimumHeight(progressBarHeight); + m_ProgressBar->setMaximumHeight(progressBarHeight); + m_ProgressBar->setMinimumWidth(progressBarWidth); + m_ProgressBar->setMaximumWidth(progressBarWidth); + ui.statusBar->addPermanentWidget(m_ProgressBar); + + showMaximized(); + + connect(ui.DockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(dockLocationChanged(Qt::DockWidgetArea))); + connect(ui.DockWidget, SIGNAL(topLevelChanged(bool)), this, SLOT(OnDockTopLevelChanged(bool))); + + //Always ensure the library tab is selected, which will show preview renders. + ui.ParamsTabWidget->setCurrentIndex(0); + ui.XformsTabWidget->setCurrentIndex(2);//Make variations tab the currently selected one under the Xforms tab. + + //Setting certain values will completely throw off the GUI, doing everything + //from setting strange margins, to arbitrarily changing the fonts used. + //For these cases, the only way to fix the problem is to use style sheets. + ui.ColorTable->setStyleSheet("QTableWidget::item { padding: 1px; }"); + ui.GeometryTable->setStyleSheet("QTableWidget::item { padding: 1px; }"); + ui.FilterTable->setStyleSheet("QTableWidget::item { padding: 1px; }"); + ui.IterationTable->setStyleSheet("QTableWidget::item { padding: 1px; }"); + ui.AffineTab->setStyleSheet("QTableWidget::item { padding: 1px; }"); + ui.XformWeightNameTable->setStyleSheet("QTableWidget::item { padding: 0px; }"); + ui.XformColorIndexTable->setStyleSheet("QTableWidget::item { padding: 1px; }"); + ui.XformColorValuesTable->setStyleSheet("QTableWidget::item { padding: 1px; }"); + ui.XformPaletteRefTable->setStyleSheet("QTableWidget::item { padding: 0px; border: none; margin: 0px; }"); + ui.PaletteAdjustTable->setStyleSheet("QTableWidget::item { padding: 1px; }");//Need this to avoid covering the top border pixel with the spinners. + ui.statusBar->setStyleSheet("QStatusBar QLabel { padding-left: 2px; padding-right: 2px; }"); + + m_PreviousPaletteRow = -1;//Force click handler the first time through. + + //Setup pointer in the GL window to point back to here. + ui.GLDisplay->SetMainWindow(this); + SetCoordinateStatus(0, 0, 0, 0); + + //At this point, everything has been setup except the renderer. Shortly after + //this constructor exits, GLWidget::initializeGL() will create the initial flock and start the rendering timer + //which executes whenever the program is idle. Upon starting the timer, the renderer + //will be initialized. +} + +///

+/// Destructor which saves out the settings file. +/// All other memory is cleared automatically through the use of STL. +/// +Fractorium::~Fractorium() +{ + m_Settings->sync(); +} + +/// +/// Stop the render timer and start the delayed restart timer. +/// This is a massive hack because Qt has no way of detecting when a resize event +/// is started and stopped. Detecting if mouse buttons down is also not an option +/// because the documentation says it gives the wrong result. +/// +void Fractorium::Resize() +{ + if (!QCoreApplication::closingDown()) + m_Controller->DelayedStartRenderTimer(); +} + +/// +/// Set the coordinate text in the status bar. +/// +/// The raster x coordinate +/// The raster y coordinate +/// The cartesian world x coordinate +/// The cartesian world y coordinate +void Fractorium::SetCoordinateStatus(int x, int y, float worldX, float worldY) +{ + //Use sprintf rather than allocating and concatenating 6 QStrings for efficiency since this is called on every mouse move. + sprintf_s(m_CoordinateString, 128, "Window: %4d, %4d World: %2.2f, %2.2f", x, y, worldX, worldY); + m_CoordinateStatusLabel->setText(QString(m_CoordinateString)); +} + +/// +/// Apply the settings for saving an ember to an Xml file to an ember (presumably about to be saved). +/// +/// The ember to apply the settings to +template +void FractoriumEmberController::ApplyXmlSavingTemplate(Ember& ember) +{ + ember.m_FinalRasW = m_Fractorium->m_Settings->XmlWidth(); + ember.m_FinalRasH = m_Fractorium->m_Settings->XmlHeight(); + ember.m_TemporalSamples = m_Fractorium->m_Settings->XmlTemporalSamples(); + ember.m_Quality = m_Fractorium->m_Settings->XmlQuality(); + ember.m_Supersample = m_Fractorium->m_Settings->XmlSupersample(); +} + +/// +/// Return whether the current ember contains a final xform and the GUI is aware of it. +/// +/// True if the current ember contains a final xform, else false. +bool Fractorium::HaveFinal() +{ + QComboBox* combo = ui.CurrentXformCombo; + + return (combo->count() > 0 && combo->itemText(combo->count() - 1) == "Final"); +} + +/// +/// Slots. +/// + +/// +/// Empty placeholder for now. +/// Qt has a severe bug where the dock gets hidden behind the window. +/// Perhaps this will be used in the future if Qt ever fixes that bug. +/// Called when the top level dock is changed. +/// +/// True if top level, else false. +void Fractorium::OnDockTopLevelChanged(bool topLevel) +{ + //if (topLevel) + //{ + // if (ui.DockWidget->y() <= 0) + // ui.DockWidget->setGeometry(ui.DockWidget->x(), ui.DockWidget->y() + 100, ui.DockWidget->width(), ui.DockWidget->height()); + // + // ui.DockWidget->setFloating(true); + //} + //else + // ui.DockWidget->setFloating(false); +} + +/// +/// Empty placeholder for now. +/// Qt has a severe bug where the dock gets hidden behind the window. +/// Perhaps this will be used in the future if Qt ever fixes that bug. +/// Called when the dock location is changed. +/// +/// The dock widget area +void Fractorium::dockLocationChanged(Qt::DockWidgetArea area) +{ + //ui.DockWidget->resize(500, ui.DockWidget->height()); + //ui.DockWidget->update(); + //ui.dockWidget->setFloating(true); + //ui.dockWidget->setFloating(false); +} + +/// +/// Virtual event overrides. +/// + +/// +/// Resize event, just pass to base. +/// +/// The event +void Fractorium::resizeEvent(QResizeEvent* e) +{ + QMainWindow::resizeEvent(e); +} + +/// +/// Stop rendering and block before exiting. +/// Called on program exit. +/// +/// The event +void Fractorium::closeEvent(QCloseEvent* e) +{ + if (m_Controller.get()) + { + m_Controller->StopRenderTimer(true);//Will wait until fully exited and stopped. + m_Controller->StopPreviewRender(); + } + + if (e) + e->accept(); +} + +/// +/// Examine the files dragged when it first enters the window area. +/// Ok if at least one file is .flam3, .flam3 or .xml, else not ok. +/// Called when the user first drags files in. +/// +/// The event +void Fractorium::dragEnterEvent(QDragEnterEvent* e) +{ + if (e->mimeData()->hasUrls()) + { + foreach (QUrl url, e->mimeData()->urls()) + { + QString localFile = url.toLocalFile(); + QFileInfo fileInfo(localFile); + QString suf = fileInfo.suffix(); + + if (suf == "flam3" || suf == "flame" || suf == "xml") + { + e->accept(); + break; + } + } + } +} + +/// +/// Always accept drag when moving, so that the drop event will correctly be called. +/// +/// The event +void Fractorium::dragMoveEvent(QDragMoveEvent* e) +{ + e->accept(); +} + +/// +/// Examine and open the dropped files. +/// Called when the user drops a file in. +/// +/// The event +void Fractorium::dropEvent(QDropEvent* e) +{ + QStringList filenames; + Qt::KeyboardModifiers mod = e->keyboardModifiers(); + bool append = mod.testFlag(Qt::ControlModifier) ? true : false; + + if (e->mimeData()->hasUrls()) + { + foreach (QUrl url, e->mimeData()->urls()) + { + QString localFile = url.toLocalFile(); + QFileInfo fileInfo(localFile); + QString suf = fileInfo.suffix(); + + if (suf == "flam3" || suf == "flame" || suf == "xml") + filenames << localFile; + } + } + + if (!filenames.empty()) + m_Controller->OpenAndPrepFiles(filenames, append); +} + +/// +/// Setup a combo box to be placed in a table cell. +/// +/// The table the combo box belongs to +/// The receiver object +/// The row in the table where this combo box resides +/// The col in the table where this combo box resides +/// Double pointer to combo box which will hold the spinner upon exit +/// The string values to populate the combo box with +/// The signal the combo box emits +/// The slot to receive the signal +/// Type of the connection. Default: Qt::QueuedConnection. +void Fractorium::SetupCombo(QTableWidget* table, const QObject* receiver, int& row, int col, StealthComboBox*& comboBox, vector& vals, const char* signal, const char* slot, Qt::ConnectionType connectionType) +{ + comboBox = new StealthComboBox(table); + std::for_each(vals.begin(), vals.end(), [&](string s) { comboBox->addItem(s.c_str()); }); + table->setCellWidget(row, col, comboBox); + connect(comboBox, signal, receiver, slot, connectionType); + row++; +} + +/// +/// Set the header of a table to be fixed. +/// +/// The header to set +/// The resizing mode to use. Default: QHeaderView::Fixed. +void Fractorium::SetFixedTableHeader(QHeaderView* header, QHeaderView::ResizeMode mode) +{ + header->setVisible(true);//For some reason, the designer keeps clobbering this value, so force it here. + header->setSectionsClickable(false); + header->setSectionResizeMode(mode); +} + +/// +/// Setup and show the open XML dialog. +/// This will perform lazy instantiation. +/// +/// The filename selected +QStringList Fractorium::SetupOpenXmlDialog() +{ + //Lazy instantiate since it takes a long time. + if (!m_FileDialog) + { + m_FileDialog = new QFileDialog(this); + m_FileDialog->setViewMode(QFileDialog::List); + } + + if (!m_FileDialog) + return QStringList(""); + + QStringList filenames; + m_FileDialog->disconnect(SIGNAL(filterSelected(const QString&))); + connect(m_FileDialog, &QFileDialog::filterSelected, [=](const QString &filter) { m_Settings->OpenXmlExt(filter); }); + + m_FileDialog->setFileMode(QFileDialog::ExistingFiles); + m_FileDialog->setAcceptMode(QFileDialog::AcceptOpen); + m_FileDialog->setNameFilter("Flam3 (*.flam3);;Flame (*.flame);;Xml (*.xml)"); + m_FileDialog->setWindowTitle("Open flame"); + m_FileDialog->setDirectory(m_Settings->OpenFolder()); + m_FileDialog->selectNameFilter(m_Settings->OpenXmlExt()); + + if (m_FileDialog->exec() == QDialog::Accepted) + { + filenames = m_FileDialog->selectedFiles(); + + if (!filenames.empty()) + m_Settings->OpenFolder(QFileInfo(filenames[0]).canonicalPath()); + } + + return filenames; +} + +/// +/// Setup and show the save XML dialog. +/// This will perform lazy instantiation. +/// +/// The default filename to populate the text box with +/// The filename selected +QString Fractorium::SetupSaveXmlDialog(QString defaultFilename) +{ + //Lazy instantiate since it takes a long time. + if (!m_FileDialog) + { + m_FileDialog = new QFileDialog(this); + m_FileDialog->setViewMode(QFileDialog::List); + } + + if (!m_FileDialog) + return ""; + + QString filename; + + m_FileDialog->disconnect(SIGNAL(filterSelected(const QString&))); + connect(m_FileDialog, &QFileDialog::filterSelected, [=](const QString &filter) { m_Settings->SaveXmlExt(filter); }); + + //This must come first because it clears various internal states which allow the file text to be properly set. + //This is most likely a bug in QFileDialog. + m_FileDialog->setAcceptMode(QFileDialog::AcceptSave); + m_FileDialog->selectFile(defaultFilename); + m_FileDialog->setNameFilter("Flam3 (*.flam3);;Flame (*.flame);;Xml (*.xml)"); + m_FileDialog->setWindowTitle("Save flame as xml"); + m_FileDialog->setDirectory(m_Settings->SaveFolder()); + m_FileDialog->selectNameFilter(m_Settings->SaveXmlExt()); + + if (m_FileDialog->exec() == QDialog::Accepted) + filename = m_FileDialog->selectedFiles().value(0); + + return filename; +} + +/// +/// Setup and show the save image dialog. +/// This will perform lazy instantiation. +/// +/// The default filename to populate the text box with +/// The filename selected +QString Fractorium::SetupSaveImageDialog(QString defaultFilename) +{ + //Lazy instantiate since it takes a long time. + if (!m_FileDialog) + { + m_FileDialog = new QFileDialog(this); + m_FileDialog->setViewMode(QFileDialog::List); + } + + if (!m_FileDialog) + return ""; + + QString filename; + + m_FileDialog->disconnect(SIGNAL(filterSelected(const QString&))); + connect(m_FileDialog, &QFileDialog::filterSelected, [=](const QString &filter) { m_Settings->SaveImageExt(filter); }); + + //This must come first because it clears various internal states which allow the file text to be properly set. + //This is most likely a bug in QFileDialog. + m_FileDialog->setAcceptMode(QFileDialog::AcceptSave); + m_FileDialog->selectFile(defaultFilename); + m_FileDialog->setFileMode(QFileDialog::AnyFile); + m_FileDialog->setOption(QFileDialog::ShowDirsOnly, false); + m_FileDialog->setOption(QFileDialog::DontUseNativeDialog, false); + m_FileDialog->setNameFilter("Jpeg (*.jpg);;Png (*.png);;Bmp (*.bmp)"); + m_FileDialog->setWindowTitle("Save image"); + m_FileDialog->setDirectory(m_Settings->SaveFolder()); + m_FileDialog->selectNameFilter(m_Settings->SaveImageExt()); + + if (m_FileDialog->exec() == QDialog::Accepted) + filename = m_FileDialog->selectedFiles().value(0); + + return filename; +} + +/// +/// Setup and show the save folder dialog. +/// This will perform lazy instantiation. +/// +/// The folder selected +QString Fractorium::SetupSaveFolderDialog() +{ + //Lazy instantiate since it takes a long time. + if (!m_FolderDialog) + { + m_FolderDialog = new QFileDialog(this); + m_FolderDialog->setViewMode(QFileDialog::List); + } + + if (!m_FolderDialog) + return ""; + + QString filename; + + //This must come first because it clears various internal states which allow the file text to be properly set. + //This is most likely a bug in QFileDialog. + m_FolderDialog->setAcceptMode(QFileDialog::AcceptSave); + m_FolderDialog->setFileMode(QFileDialog::Directory); + m_FolderDialog->setOption(QFileDialog::ShowDirsOnly, true); + m_FolderDialog->setOption(QFileDialog::DontUseNativeDialog, true); + m_FolderDialog->selectFile(""); + m_FolderDialog->setWindowTitle("Save to folder"); + m_FolderDialog->setDirectory(m_Settings->SaveFolder()); + + if (m_FolderDialog->exec() == QDialog::Accepted) + filename = m_FolderDialog->selectedFiles().value(0); + + return filename; +} + +/// +/// This is no longer needed and was used to compensate for a different bug +/// however the code is interesting, so keep it around for possible future use. +/// This was used to correct a rotation bug where matrix rotation comes out in the wrong direction +/// if x1, y1 (a & d) are on the left side of the line from 0,0 to +/// x2, y2 (b, e). In that case, the angle must be flipped. In order +/// to determine which side of the line it's on, create a mat2 +/// and find its determinant. Values > 0 are on the left side of the line. +/// +/// The affine. +/// +int Fractorium::FlipDet(Affine2D& affine) +{ + float x1 = affine.A(); + float y1 = affine.D(); + float x2 = affine.B(); + float y2 = affine.E(); + + //Just make the other end of the line be the center of the circle. + glm::mat2 mat( 0 - x1, 0 - y1,//Col 0. + x2 - x1, y2 - y1);//Col 1. + + return (glm::determinant(mat) > 0) ? -1 : 1; +} + +//template//See note at the end of Fractorium.h +//void Fractorium::SetupSpinner(QTableWidget* table, const QObject* receiver, int& row, int col, spinType*& spinBox, int height, valType min, valType max, valType step, const char* signal, const char* slot, bool incRow, valType val, valType doubleClickZero, valType doubleClickNonZero) +//{ +// spinBox = new spinType(table, height, step); +// spinBox->setRange(min, max); +// spinBox->setValue(val); +// table->setCellWidget(row, col, spinBox); +// +// if (string(signal) != "" && string(slot) != "") +// connect(spinBox, signal, receiver, slot, connectionType); +// +// if (doubleClickNonZero != -999 && doubleClickZero != -999) +// { +// spinBox->DoubleClick(true); +// spinBox->DoubleClickZero((valType)doubleClickZero); +// spinBox->DoubleClickNonZero((valType)doubleClickNonZero); +// } +// +// if (incRow) +// row++; +//} diff --git a/Source/Fractorium/Fractorium.h b/Source/Fractorium/Fractorium.h new file mode 100644 index 0000000..0f5c8a9 --- /dev/null +++ b/Source/Fractorium/Fractorium.h @@ -0,0 +1,445 @@ +#pragma once + +#include "ui_Fractorium.h" +#include "GLWidget.h" +#include "EmberTreeWidgetItem.h" +#include "VariationTreeWidgetItem.h" +#include "StealthComboBox.h" +#include "TableWidget.h" +#include "FinalRenderDialog.h" +#include "OptionsDialog.h" +#include "AboutDialog.h" + +/// +/// Fractorium class. +/// + +/// +/// Fractorium is the main window for the interactive renderer. The main viewable area +/// is a derivation of QGLWidget named GLWidget. The design uses the concept of a controller +/// to allow for both polymorphism and templating to coexist. Most processing functionality +/// is contained within the controller, and the GUI events just call the appropriate controller +/// member functions. +/// The rendering takes place on a timer with +/// a period of 0 which means it will trigger an event whenever the event input queue is idle. +/// As it's rendering, if the user changes anything on the GUI, a re-render will trigger. Since +/// certain parameters don't require a full render, the minimum necessary processing will be ran. +/// When the user changes something on the GUI, the required processing action is added to a vector. +/// Upon the next execution of the idle timer function, the most significant action will be extracted +/// and applied to the renderer. The vector is then cleared. +/// On the left side of the window is a dock widget which contains all controls needed for +/// manipulating embers. +/// Qt takes very long to create file dialog windows, so they are kept as members and initialized +/// upon first use with lazy instantiation and then kept around for the remainder of the program. +/// Additional dialogs are for the about box, options, and final rendering out to a file. +/// While all class member variables and functions are declared in this .h file, the implementation +/// for them is far too lengthy to put in a single .cpp file. So general functionality is placed in +/// Fractorium.cpp and the other functional areas are each broken out into their own files. +/// The order of the functions in each .cpp file should roughly match the order they appear in the .h file. +/// Future todo list: +/// Add all of the plugins/variations that work with Apophysis and are open source. +/// Allow specifying variations to include/exclude from random generation. +/// Allow the option to specify a different palette file rather than the default flam3-palettes.xml. +/// Implement more rendering types. +/// Add support for animation previewing. +/// Add support for full animation editing and rendering. +/// Possibly add some features from JWildFire. +/// +class Fractorium : public QMainWindow +{ + Q_OBJECT + + friend GLWidget; + friend FractoriumOptionsDialog; + friend FractoriumFinalRenderDialog; + friend FractoriumAboutDialog; + friend GLEmberControllerBase; + friend GLEmberController; + friend GLEmberController; + friend FractoriumEmberControllerBase; + friend FractoriumEmberController; + friend FractoriumEmberController; + friend FinalRenderEmberControllerBase; + friend FinalRenderEmberController; + friend FinalRenderEmberController; + +public: + Fractorium(QWidget* parent = 0); + ~Fractorium(); + + //Geometry. + void SetCenter(float x, float y); + void SetRotation(double rot, bool stealth); + void SetScale(double scale); + void Resize(); + void SetCoordinateStatus(int x, int y, float worldX, float worldY); + + //Xforms. + void CurrentXform(unsigned int i); + + //Xforms Affine. + bool DrawAllPre(); + bool DrawAllPost(); + bool LocalPivot(); + +public slots: + //Dock. + void OnDockTopLevelChanged(bool topLevel); + void dockLocationChanged(Qt::DockWidgetArea area); + + //Menu. + void OnActionNewFlock(bool checked);//File. + void OnActionNewEmptyFlameInCurrentFile(bool checked); + void OnActionNewRandomFlameInCurrentFile(bool checked); + void OnActionCopyFlameInCurrentFile(bool checked); + void OnActionOpen(bool checked); + void OnActionSaveCurrentAsXml(bool checked); + void OnActionSaveEntireFileAsXml(bool checked); + void OnActionSaveCurrentScreen(bool checked); + void OnActionSaveCurrentToOpenedFile(bool checked); + void OnActionExit(bool checked); + + void OnActionUndo(bool checked);//Edit. + void OnActionRedo(bool checked); + void OnActionCopyXml(bool checked); + void OnActionCopyAllXml(bool checked); + void OnActionPasteXmlAppend(bool checked); + void OnActionPasteXmlOver(bool checked); + + void OnActionAddReflectiveSymmetry(bool checked);//Tools. + void OnActionAddRotationalSymmetry(bool checked); + void OnActionAddBothSymmetry(bool checked); + void OnActionClearFlame(bool checked); + void OnActionRenderPreviews(bool checked); + void OnActionStopRenderingPreviews(bool checked); + void OnActionFinalRender(bool checked); + void OnFinalRenderClose(int result); + void OnActionOptions(bool checked); + void OnActionAbout(bool checked);//Help. + + //Toolbar. + void OnSaveCurrentAsXmlButtonClicked(bool checked); + void OnSaveEntireFileAsXmlButtonClicked(bool checked); + void OnSaveCurrentToOpenedFileButtonClicked(bool checked); + + //Params. + void OnBrightnessChanged(double d);//Color. + void OnGammaChanged(double d); + void OnGammaThresholdChanged(double d); + void OnVibrancyChanged(double d); + void OnHighlightPowerChanged(double d); + void OnBackgroundColorButtonClicked(bool checked); + void OnColorSelected(const QColor& color); + void OnPaletteModeComboCurrentIndexChanged(int index); + void OnWidthChanged(int d);//Geometry. + void OnHeightChanged(int d); + void OnCenterXChanged(double d); + void OnCenterYChanged(double d); + void OnScaleChanged(double d); + void OnZoomChanged(double d); + void OnRotateChanged(double d); + void OnZPosChanged(double d); + void OnPerspectiveChanged(double d); + void OnPitchChanged(double d); + void OnYawChanged(double d); + void OnDepthBlurChanged(double d); + void OnSpatialFilterWidthChanged(double d);//Filter. + void OnSpatialFilterTypeComboCurrentIndexChanged(const QString& text); + void OnTemporalFilterWidthChanged(double d); + void OnTemporalFilterTypeComboCurrentIndexChanged(const QString& text); + void OnDEFilterMinRadiusWidthChanged(double d); + void OnDEFilterMaxRadiusWidthChanged(double d); + void OnDEFilterCurveWidthChanged(double d); + void OnPassesChanged(int d);//Iteration. + void OnTemporalSamplesChanged(int d); + void OnQualityChanged(double d); + void OnSupersampleChanged(int d); + void OnAffineInterpTypeComboCurrentIndexChanged(int index); + void OnInterpTypeComboCurrentIndexChanged(int index); + + //Xforms. + void OnCurrentXformComboChanged(int index); + void OnAddXformButtonClicked(bool checked); + void OnDuplicateXformButtonClicked(bool checked); + void OnClearXformButtonClicked(bool checked); + void OnDeleteXformButtonClicked(bool checked); + void OnAddFinalXformButtonClicked(bool checked); + void OnXformWeightChanged(double d); + void OnEqualWeightButtonClicked(bool checked); + void OnXformNameChanged(int row, int col); + + //Xforms Affine. + void OnX1Changed(double d); + void OnX2Changed(double d); + void OnY1Changed(double d); + void OnY2Changed(double d); + void OnO1Changed(double d); + void OnO2Changed(double d); + + void OnFlipHorizontalButtonClicked(bool checked); + void OnFlipVerticalButtonClicked(bool checked); + void OnRotate90CButtonClicked(bool checked); + void OnRotate90CcButtonClicked(bool checked); + void OnRotateCButtonClicked(bool checked); + void OnRotateCcButtonClicked(bool checked); + void OnMoveUpButtonClicked(bool checked); + void OnMoveDownButtonClicked(bool checked); + void OnMoveLeftButtonClicked(bool checked); + void OnMoveRightButtonClicked(bool checked); + void OnScaleDownButtonClicked(bool checked); + void OnScaleUpButtonClicked(bool checked); + void OnResetAffineButtonClicked(bool checked); + + void OnAffineGroupBoxToggled(bool on); + void OnAffineDrawAllCurrentRadioButtonToggled(bool checked); + + //Xforms Color. + void OnXformColorIndexChanged(double d); + void OnXformColorIndexChanged(double d, bool updateRender); + void OnXformScrollColorIndexChanged(int d); + void OnXformColorSpeedChanged(double d); + void OnXformOpacityChanged(double d); + void OnXformDirectColorChanged(double d); + void OnSoloXformCheckBoxStateChanged(int state); + void OnXformRefPaletteResized(int logicalIndex, int oldSize, int newSize); + + //Xforms Variations. + void OnVariationSpinBoxValueChanged(double d); + void OnTreeHeaderSectionClicked(int); + void OnVariationsFilterLineEditTextChanged(const QString& text); + void OnVariationsFilterClearButtonClicked(bool checked); + + //Xforms Xaos. + void OnXaosChanged(double d); + void OnXaosFromToToggled(bool checked); + void OnClearXaosButtonClicked(bool checked); + + //Palette. + void OnPaletteAdjust(int d); + void OnPaletteCellClicked(int row, int col); + void OnPaletteCellDoubleClicked(int row, int col); + + //Library. + void OnEmberTreeItemChanged(QTreeWidgetItem* item, int col); + void OnEmberTreeItemDoubleClicked(QTreeWidgetItem* item, int col); + + //Rendering/progress. + void StartRenderTimer(); + void IdleTimer(); + bool ControllersOk(); + + //Can't have a template function be a slot. + void SetLibraryTreeItemData(EmberTreeWidgetItemBase* item, vector& v, unsigned int width, unsigned int height); + +public: + //template//See below. + //static void SetupSpinner(QTableWidget* table, const QObject* receiver, int& row, int col, spinType*& spinBox, int height, valType min, valType max, valType step, const char* signal, const char* slot, bool incRow = true, valType val = 0, valType doubleClickZero = -999, valType doubleClickNonZero = -999); + static void SetupAffineSpinner(QTableWidget* table, const QObject* receiver, int row, int col, DoubleSpinBox*& spinBox, int height, double min, double max, double step, double prec, const char* signal, const char* slot); + static void SetupCombo(QTableWidget* table, const QObject* receiver, int& row, int col, StealthComboBox*& comboBox, vector& vals, const char* signal, const char* slot, Qt::ConnectionType connectionType = Qt::QueuedConnection); + static void SetFixedTableHeader(QHeaderView* header, QHeaderView::ResizeMode mode = QHeaderView::Fixed); + static int FlipDet(Affine2D& affine); + +protected: + virtual void resizeEvent(QResizeEvent* e); + virtual void closeEvent(QCloseEvent* e); + virtual void dragEnterEvent(QDragEnterEvent* e); + virtual void dragMoveEvent(QDragMoveEvent* e); + virtual void dropEvent(QDropEvent* e); + +private: + void InitMenusUI(); + void InitToolbarUI(); + void InitParamsUI(); + void InitXformsUI(); + void InitXformsColorUI(); + void InitXformsAffineUI(); + void InitXformsVariationsUI(); + void InitXformsXaosUI(); + void InitPaletteUI(); + void InitLibraryUI(); + + //Embers. + bool HaveFinal(); + + //Params. + + //Xforms. + void FillXforms(); + + //Xforms Color. + + //Xforms Affine. + + //Xforms Variations. + + //Xforms Xaos. + void FillXaosTable(); + + //Palette. + void ResetPaletteControls(); + + //Library. + + //Info. + void UpdateHistogramBounds(); + void ErrorReportToQTextEdit(vector& errors, QTextEdit* textEdit, bool clear = true); + + //Rendering/progress. + bool CreateRendererFromOptions(); + bool CreateControllerFromOptions(); + + //Dialogs. + QStringList SetupOpenXmlDialog(); + QString SetupSaveXmlDialog(QString defaultFilename); + QString SetupSaveImageDialog(QString defaultFilename); + QString SetupSaveFolderDialog(); + QColorDialog* m_ColorDialog; + FractoriumFinalRenderDialog* m_FinalRenderDialog; + FractoriumOptionsDialog* m_OptionsDialog; + FractoriumAboutDialog* m_AboutDialog; + + //Params. + DoubleSpinBox* m_BrightnessSpin;//Color. + DoubleSpinBox* m_GammaSpin; + DoubleSpinBox* m_GammaThresholdSpin; + DoubleSpinBox* m_VibrancySpin; + DoubleSpinBox* m_HighlightSpin; + QPushButton* m_BackgroundColorButton; + StealthComboBox* m_PaletteModeCombo; + SpinBox* m_WidthSpin;//Geometry. + SpinBox* m_HeightSpin; + DoubleSpinBox* m_CenterXSpin; + DoubleSpinBox* m_CenterYSpin; + DoubleSpinBox* m_ScaleSpin; + DoubleSpinBox* m_ZoomSpin; + DoubleSpinBox* m_RotateSpin; + DoubleSpinBox* m_ZPosSpin; + DoubleSpinBox* m_PerspectiveSpin; + DoubleSpinBox* m_PitchSpin; + DoubleSpinBox* m_YawSpin; + DoubleSpinBox* m_DepthBlurSpin; + DoubleSpinBox* m_SpatialFilterWidthSpin;//Filter. + StealthComboBox* m_SpatialFilterTypeCombo; + DoubleSpinBox* m_TemporalFilterWidthSpin; + StealthComboBox* m_TemporalFilterTypeCombo; + DoubleSpinBox* m_DEFilterMinRadiusSpin; + DoubleSpinBox* m_DEFilterMaxRadiusSpin; + DoubleSpinBox* m_DECurveSpin; + SpinBox* m_PassesSpin;//Iteration. + SpinBox* m_TemporalSamplesSpin; + DoubleSpinBox* m_QualitySpin; + SpinBox* m_SupersampleSpin; + StealthComboBox* m_AffineInterpTypeCombo; + StealthComboBox* m_InterpTypeCombo; + + //Xforms. + DoubleSpinBox* m_XformWeightSpin; + SpinnerButtonWidget* m_XformWeightSpinnerButtonWidget; + + //Xforms Color. + QTableWidgetItem* m_XformColorValueItem; + QTableWidgetItem* m_PaletteRefItem; + DoubleSpinBox* m_XformColorIndexSpin; + DoubleSpinBox* m_XformColorSpeedSpin; + DoubleSpinBox* m_XformOpacitySpin; + DoubleSpinBox* m_XformDirectColorSpin; + + //Xforms Affine. + DoubleSpinBox* m_PreX1Spin;//Pre. + DoubleSpinBox* m_PreX2Spin; + DoubleSpinBox* m_PreY1Spin; + DoubleSpinBox* m_PreY2Spin; + DoubleSpinBox* m_PreO1Spin; + DoubleSpinBox* m_PreO2Spin; + + DoubleSpinBox* m_PostX1Spin;//Post. + DoubleSpinBox* m_PostX2Spin; + DoubleSpinBox* m_PostY1Spin; + DoubleSpinBox* m_PostY2Spin; + DoubleSpinBox* m_PostO1Spin; + DoubleSpinBox* m_PostO2Spin; + + //Palette. + SpinBox* m_PaletteHueSpin; + SpinBox* m_PaletteSaturationSpin; + SpinBox* m_PaletteBrightnessSpin; + SpinBox* m_PaletteContrastSpin; + SpinBox* m_PaletteBlurSpin; + SpinBox* m_PaletteFrequencySpin; + + //Files. + QFileDialog* m_FileDialog; + QFileDialog* m_FolderDialog; + QString m_LastSaveAll; + QString m_LastSaveCurrent; + //QMenu* m_FileTreeMenu; + + QProgressBar* m_ProgressBar; + QLabel* m_RenderStatusLabel; + QLabel* m_CoordinateStatusLabel; + FractoriumSettings* m_Settings; + char m_ULString[32]; + char m_URString[32]; + char m_LRString[32]; + char m_LLString[32]; + char m_WString[16]; + char m_HString[16]; + char m_DEString[16]; + char m_CoordinateString[128]; + + int m_FontSize; + int m_VarSortMode; + int m_PreviousPaletteRow; + OpenCLWrapper m_Wrapper; + auto_ptr m_Controller; + Ui::FractoriumClass ui; +}; + + +/// +/// Setup a spinner to be placed in a table cell. +/// Due to a serious compiler bug in MSVC, this must be declared as an outside function instead of a static member of Fractorium. +/// The reason is that the default arguments of type valType will not be interpreted correctly by the compiler. +/// If the bug is ever fixed, put it back as a static member function. +/// +/// The table the spinner belongs to +/// The receiver object +/// The row in the table where this spinner resides +/// The col in the table where this spinner resides +/// Double pointer to spin box which will hold the spinner upon exit +/// The height of the spinner +/// The minimum value of the spinner +/// The maximum value of the spinner +/// The step of the spinner +/// The signal the spinner emits +/// The slot to receive the signal +/// Whether to increment the row value +/// The default value for the spinner +/// When the spinner has a value of zero and is double clicked, assign this value +/// When the spinner has a value of non-zero and is double clicked, assign this value +template +static void SetupSpinner(QTableWidget* table, const QObject* receiver, int& row, int col, spinType*& spinBox, int height, valType min, valType max, valType step, const char* signal, const char* slot, bool incRow = true, valType val = 0, valType doubleClickZero = -999, valType doubleClickNonZero = -999) +{ + spinBox = new spinType(table, height, step); + spinBox->setRange(min, max); + spinBox->setValue(val); + + if (col >= 0) + table->setCellWidget(row, col, spinBox); + + if (string(signal) != "" && string(slot) != "") + receiver->connect(spinBox, signal, receiver, slot, Qt::QueuedConnection); + + if (doubleClickNonZero != -999 && doubleClickZero != -999) + { + spinBox->DoubleClick(true); + spinBox->DoubleClickZero((valType)doubleClickZero); + spinBox->DoubleClickNonZero((valType)doubleClickNonZero); + } + + if (incRow) + row++; +} + +//template void Fractorium::SetupSpinner (QTableWidget* table, const QObject* receiver, int& row, int col, SpinBox*& spinBox, int height, int min, int max, int step, const char* signal, const char* slot, bool incRow, int val, int doubleClickZero, int doubleClickNonZero); +//template void Fractorium::SetupSpinner(QTableWidget* table, const QObject* receiver, int& row, int col, DoubleSpinBox*& spinBox, int height, double min, double max, double step, const char* signal, const char* slot, bool incRow, double val, double doubleClickZero, double doubleClickNonZero); diff --git a/Source/Fractorium/Fractorium.qrc b/Source/Fractorium/Fractorium.qrc new file mode 100644 index 0000000..173695c --- /dev/null +++ b/Source/Fractorium/Fractorium.qrc @@ -0,0 +1,44 @@ + + + Icons/arrow-undo.png + Icons/arrow-redo.png + Icons/stop.png + Icons/application_side_boxes.png + Icons/page_copy.png + Icons/page_paste.png + Icons/window-close.png + Icons/068123-3d-transparent-glass-icon-alphanumeric-question-mark3.png + Icons/layer--plus.png + Icons/layers.png + Icons/layers-stack.png + Icons/monitor.png + Icons/016938-3d-transparent-glass-icon-symbols-shapes-shape-square-clear-16.png + Icons/document-hf-insert.png + Icons/010425-3d-transparent-glass-icon-animals-spiderweb2.png + Icons/database-medium.png + Icons/databases.png + Icons/drive-harddisk-5.png + Icons/folder-visiting-4.png + Icons/display-brightness-off.png + Icons/cog.png + Icons/proxy.png + Icons/shape_flip_horizontal.png + Icons/shape_flip_vertical.png + Icons/arrow_down.png + Icons/arrow_in.png + Icons/arrow_left.png + Icons/arrow_out.png + Icons/arrow_right.png + Icons/arrow_rotate_anticlockwise.png + Icons/arrow_rotate_clockwise.png + Icons/arrow_turn_left.png + Icons/arrow_turn_right.png + Icons/arrow_up.png + Icons/configure.png + Icons/infomation.png + Icons/del.png + Icons/add.png + Icons/eraser.png + Icons/editraise.png + + diff --git a/Source/Fractorium/Fractorium.rc b/Source/Fractorium/Fractorium.rc new file mode 100644 index 0000000000000000000000000000000000000000..5e89faa3d0b38d3629bbd028ae8568ce6508f4fb GIT binary patch literal 4574 zcmds*+in_35QghIQr=;f+$7p0#)q4gR*{kz2nq(rU~3~qvK-qumH;CLlB~4Pp5*(h zr-#8Fkd1PIR;{6@yQ{0~@K;q2KYwi5mL+yz*LH3byRnp~Z&T(@m`iPDXEtE;*8Zd= zc4e0~;+Zf%rN3ZqLa%)@-p+YX7>V1pjiGsIkL?ksY21-qv9ti-aZYbt)S zV;_n?+=_j-puF+aUR&M%u~R$Zw61kH;Rx^5nAx`$O?>~OB};eq;BjOPcGRrR{%3w_ zi`h1_$Mg+W5_pA~K-Xa0wNh$-z*yb3AT zE=}U&qFf;=htMeAQg#NpQ^t}+TqTe8i0go+xcxU<9ny!$mBdZ3&;&&t5U>s1sut;1 z(G=GzxlL31M608z>>=%yLuNKa66t%BMY`-A@UIM_D0pp$;Hd|yBx}c|ZIi(|_8z3} z=N^iF-K;+1C-w=N)QI68y0o!+%k`?|T8K`MJW)=W`1%{TG@OU1qs_~6=qIaY)>X_0 zi)q)+nrAHIx4SmK3D_8AGR}2bcFq$=_yxETK0=e%?}wFW;myKHQ5s38aqLyERh1Rr z#XJx-IUznyp{bGCyB;I$EK2G`iFOxPggeb^cfwl}4AySZBp~Lw?eVDJ$!HJZ-vK|z z;I?$;`Fg03w;{d&yL`Xl+lM(mj+~%uAuF|Axf4N4V`3-lgNE`dr93#~UepK#~4JyceLna@S z+ecKHHvJHZD}Gz3px2m@YEc)uc#|wYy_|ov!9IM7_s?S*~jbm1UW+*ohI zO}CFFgKVmrqS5_KwP+n_7NassQKh2o@u=T<5MOy%)q6rOd2idXyn5+?`p>ASYOBlG|>b+_4zp zJ-8{lQ;(o{Hw~Cq4h)$^UkBTcf@+pkgS>NT=Woio%PaZs`I;(L)y;aB_QfaEKUJjv z=dHJaUbp{S=zHu9!HmA9^aZr+#;cp + + FractoriumClass + + + + 0 + 0 + 1162 + 996 + + + + + 2 + 0 + + + + Qt::StrongFocus + + + true + + + Fractorium + + + + :/Fractorium/Icons/010425-3d-transparent-glass-icon-animals-spiderweb2.png:/Fractorium/Icons/010425-3d-transparent-glass-icon-animals-spiderweb2.png + + + + + + + + + + + + 255 + 255 + 255 + + + + + + + 240 + 240 + 240 + + + + + + + + + 255 + 255 + 255 + + + + + + + 240 + 240 + 240 + + + + + + + + + 240 + 240 + 240 + + + + + + + 240 + 240 + 240 + + + + + + + + ArrowCursor + + + true + + + Qt::StrongFocus + + + true + + + false + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + + 0 + 0 + 1162 + 21 + + + + + &File + + + true + + + + + + + + + + + + + + + + + + &Help + + + + + + &Tools + + + true + + + + + + + + + + + + + + + + &Edit + + + true + + + + + + + + + + + + + + + + + true + + + + + + 0 + 0 + + + + + 280 + 800 + + + + + 280 + 524287 + + + + Qt::StrongFocus + + + false + + + QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable + + + Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea + + + Parameters + + + 1 + + + + + 4 + + + 6 + + + 3 + + + 6 + + + 6 + + + + + 4 + + + + + Save the current flame as an xml file + + + + + + + :/Fractorium/Icons/database-medium.png:/Fractorium/Icons/database-medium.png + + + + + + + Save all flames as a single xml file + + + + + + + :/Fractorium/Icons/databases.png:/Fractorium/Icons/databases.png + + + + + + + <html><head/><body><p>Save the currently displayed flame back to the opened file in memory.</p><p>This overwrites the original flame but does not store the file back to disk.</p></body></html> + + + + + + + :/Fractorium/Icons/document-hf-insert.png:/Fractorium/Icons/document-hf-insert.png + + + + + + + + + + 0 + 0 + + + + + 250 + 500 + + + + + 16777215 + 16777215 + + + + QTabWidget::North + + + QTabWidget::Triangular + + + 0 + + + + Library + + + + 5 + + + 5 + + + 5 + + + 4 + + + + + Qt::CustomContextMenu + + + QFrame::Panel + + + QFrame::Plain + + + QAbstractItemView::SelectedClicked + + + 10 + + + false + + + + Current Flame File + + + + + + + + + Flame + + + + 5 + + + 5 + + + 5 + + + 4 + + + 0 + + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 20 + 5 + + + + + + + + + 0 + 0 + + + + + 0 + 278 + + + + + 16777215 + 278 + + + + Qt::NoFocus + + + QFrame::Panel + + + QFrame::Plain + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + QAbstractItemView::NoEditTriggers + + + false + + + QAbstractItemView::NoSelection + + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + true + + + Qt::SolidLine + + + false + + + 2 + + + false + + + false + + + 110 + + + false + + + 35 + + + true + + + false + + + 23 + + + false + + + 23 + + + false + + + + Width + + + + + Height + + + + + Center X + + + + + Center Y + + + + + Scale + + + + + Zoom + + + + + Rotate + + + + + Z Pos + + + + + Perspective + + + + + Pitch + + + + + Yaw + + + + + Depth Blur + + + + + Field + + + + + + + + + + Width + + + + + 0 + + + + + Height + + + + + 0 + + + + + Center X + + + + + 0 + + + + + Center Y + + + + + 0 + + + + + Scale + + + + + 0 + + + + + Zoom + + + + + 0 + + + + + Rotate + + + + + 0 + + + + + Z Pos + + + + + 0 + + + + + Perspective + + + + + 0 + + + + + Pitch + + + + + 0 + + + + + Yaw + + + + + 0 + + + + + Depth Blur + + + + + 0 + + + + + + + + + 0 + 0 + + + + + 0 + 163 + + + + + 16777215 + 163 + + + + Qt::NoFocus + + + QFrame::Panel + + + QFrame::Plain + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + QAbstractItemView::NoEditTriggers + + + false + + + QAbstractItemView::NoSelection + + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + true + + + Qt::SolidLine + + + false + + + 2 + + + false + + + false + + + 125 + + + false + + + 35 + + + true + + + false + + + 23 + + + false + + + 23 + + + false + + + + Spatial Filter Width + + + + + Spatial Filter Type + + + + + Temporal Filter Width + + + + + Temporal Filter Type + + + + + DE Filter Min Radius + + + + + DE Filter Max Radius + + + + + DE Curve + + + + + Field + + + + + + + + + + Spatial Filter Width + + + + + 0 + + + + + Spatial Filter Type + + + + + 0 + + + + + Temporal Filter Width + + + + + 0 + + + + + Temporal Filter Type + + + + + 0 + + + + + DE Filter Min Radius + + + + + 0 + + + + + DE Filter Max Radius + + + + + 0 + + + + + DE Curve + + + + + 0 + + + + + + + + true + + + + 0 + 0 + + + + + 0 + 22 + + + + + 16777215 + 22 + + + + QFrame::Panel + + + QFrame::Plain + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + false + + + false + + + false + + + 15 + + + false + + + 15 + + + true + + + false + + + 15 + + + false + + + 15 + + + + Filter + + + AlignHCenter|AlignTop + + + + + + + + true + + + + 0 + 0 + + + + + 0 + 22 + + + + + 16777215 + 22 + + + + QFrame::Panel + + + QFrame::Plain + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + false + + + false + + + false + + + 15 + + + false + + + 15 + + + true + + + false + + + 15 + + + false + + + 15 + + + + Iteration + + + AlignHCenter|AlignTop + + + + + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 20 + 5 + + + + + + + + + 0 + 0 + + + + + 0 + 163 + + + + + 16777215 + 163 + + + + Qt::NoFocus + + + true + + + QFrame::Panel + + + QFrame::Plain + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + QAbstractItemView::NoEditTriggers + + + false + + + QAbstractItemView::NoSelection + + + QAbstractItemView::SelectItems + + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + true + + + Qt::SolidLine + + + false + + + false + + + 2 + + + false + + + false + + + 110 + + + false + + + 35 + + + true + + + false + + + 23 + + + false + + + 23 + + + false + + + + Brightness + + + + + Gamma + + + + + Gamma Threshold + + + + + Vibrancy + + + + + Highlight Power + + + + + Background + + + + + Palette Mode + + + + + Field + + + + + + + + + + Brightness + + + + + 0 + + + + + Gamma + + + + + 0 + + + + + Gamma Threshold + + + + + 0 + + + + + Vibrancy + + + + + 0 + + + + + Highlight Power + + + + + 0 + + + + + Background + + + + + 0 + + + + + Palette Mode + + + + + 0 + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 300 + + + + + + + + + 0 + 0 + + + + + 0 + 140 + + + + + 16777215 + 140 + + + + Qt::NoFocus + + + QFrame::Panel + + + QFrame::Plain + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + QAbstractItemView::NoEditTriggers + + + false + + + QAbstractItemView::NoSelection + + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + true + + + Qt::SolidLine + + + false + + + 2 + + + false + + + false + + + 110 + + + false + + + 35 + + + true + + + false + + + 23 + + + false + + + 23 + + + false + + + + Passes + + + + + Temporal Samples + + + + + Quality + + + + + Supersample + + + + + Affine Interpolation + + + + + Interpolation + + + + + Field + + + + + + + + + + Passes + + + + + 0 + + + + + Temporal Samples + + + + + 0 + + + + + Quality + + + + + 0 + + + + + Supersample + + + + + 0 + + + + + Affine Interpolation + + + + + 0 + + + + + Interpolation + + + + + 0 + + + + + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 20 + 5 + + + + + + + + true + + + + 0 + 0 + + + + + 0 + 22 + + + + + 16777215 + 22 + + + + QFrame::Panel + + + QFrame::Plain + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + QAbstractItemView::ScrollPerItem + + + QAbstractItemView::ScrollPerItem + + + false + + + false + + + false + + + 15 + + + false + + + 15 + + + true + + + false + + + 15 + + + false + + + 15 + + + + Color + + + AlignHCenter|AlignTop + + + + + + + + true + + + + 0 + 0 + + + + + 0 + 22 + + + + + 16777215 + 22 + + + + QFrame::Panel + + + QFrame::Plain + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + false + + + false + + + false + + + 15 + + + false + + + 15 + + + true + + + false + + + 15 + + + false + + + 15 + + + + Geometry + + + AlignHCenter|AlignTop + + + + + + ColorTable + GeometryTable + FilterTable + IterationTable + verticalSpacer_4 + FilterTableHeader + IterationTableHeader + verticalSpacer_3 + verticalSpacer_2 + verticalSpacer + ColorTableHeader + GeometryTableHeader + + + + + 0 + 0 + + + + + + + Xforms + + + + 5 + + + 4 + + + 5 + + + 4 + + + 4 + + + + + + 2 + 0 + + + + + 0 + 45 + + + + + 16777215 + 45 + + + + + true + + + + Qt::NoFocus + + + false + + + QFrame::Panel + + + QFrame::Plain + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + false + + + QAbstractItemView::DoubleClicked|QAbstractItemView::SelectedClicked + + + QAbstractItemView::NoSelection + + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + false + + + false + + + 1 + + + 2 + + + false + + + false + + + 27 + + + true + + + false + + + 21 + + + false + + + 21 + + + + + Weight + + + + + Name + + + + + AlignLeft|AlignVCenter + + + + + AlignLeft|AlignVCenter + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + MS Shell Dlg 2 + + + + QTabWidget::pane +{ + border: 1px solid #898C95; + font: 9pt "Segoe UI"; +} + +QTabWidget::tab-bar +{ + bottom: -1px; +} + +DoubleSpinBox +{ + font: 9pt "Segoe UI"; +} + +SpinBox +{ + font: 9pt "Segoe UI"; +} + + + + QTabWidget::Triangular + + + 2 + + + + + 0 + 0 + + + + Color + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 0 + 0 + + + + + 0 + 45 + + + + + 16777215 + 68 + + + + + true + + + + Qt::NoFocus + + + + + + QFrame::Panel + + + QFrame::Plain + + + 1 + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + false + + + QAbstractItemView::NoEditTriggers + + + false + + + false + + + false + + + QAbstractItemView::NoSelection + + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + true + + + Qt::SolidLine + + + false + + + false + + + 2 + + + false + + + false + + + 110 + + + false + + + 27 + + + true + + + false + + + 22 + + + false + + + 22 + + + false + + + + Color Speed + + + + + Opacity + + + + + Direct Color + + + + + Field + + + + + + + + + + Color Speed + + + + + 0 + + + + true + + + + AlignLeft|AlignVCenter + + + + + Opacity + + + + + 0 + + + + + Direct Color + + + + + 0 + + + + + + + + + 0 + 0 + + + + + 0 + 22 + + + + + 16777215 + 22 + + + + + true + + + + Qt::NoFocus + + + false + + + + + + QFrame::Panel + + + QFrame::Plain + + + 1 + + + 0 + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + true + + + false + + + false + + + 1 + + + 2 + + + false + + + false + + + 27 + + + true + + + false + + + 21 + + + false + + + 21 + + + false + + + + + + + + + + AlignLeft|AlignVCenter + + + + + + + + + 0 + 0 + + + + + 0 + 19 + + + + + 16777215 + 19 + + + + Qt::NoFocus + + + QFrame::Panel + + + QFrame::Plain + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + false + + + false + + + false + + + 1 + + + 1 + + + false + + + 256 + + + true + + + false + + + 19 + + + false + + + + + + + + + NoItemFlags + + + + + + + + + 0 + 0 + + + + + 0 + 16 + + + + + 16777215 + 16 + + + + 255 + + + 128 + + + Qt::Horizontal + + + false + + + + + + + Solo + + + + + + + + + 0 + 0 + + + + Affine + + + + 5 + + + 6 + + + 5 + + + 6 + + + 5 + + + + + + 0 + 0 + + + + + 100 + 270 + + + + + 16777215 + 270 + + + + Pre Affine Transform + + + false + + + true + + + + 4 + + + 6 + + + 4 + + + 6 + + + 6 + + + + + + 2 + 0 + + + + + 0 + 90 + + + + + 10000 + 90 + + + + Qt::NoFocus + + + QFrame::Panel + + + QFrame::Plain + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + false + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + true + + + false + + + false + + + 3 + + + 2 + + + false + + + 100 + + + false + + + 40 + + + true + + + false + + + false + + + 22 + + + false + + + 22 + + + false + + + + X + + + + + Y + + + + + O + + + + + 1 (A, B, C) + + + + + 2 (D, E, F) + + + + + + + + QLayout::SetDefaultConstraint + + + 0 + + + 4 + + + + + Rotate xform 90 degrees counter clockwise + + + + + + + :/Fractorium/Icons/arrow_turn_left.png:/Fractorium/Icons/arrow_turn_left.png + + + + + + + Rotate xform 90 degrees clockwise + + + + + + + :/Fractorium/Icons/arrow_turn_right.png:/Fractorium/Icons/arrow_turn_right.png + + + + + + + Scale xform x percent up + + + + + + + :/Fractorium/Icons/arrow_out.png:/Fractorium/Icons/arrow_out.png + + + + + + + Move xform x units right + + + + + + + :/Fractorium/Icons/arrow_right.png:/Fractorium/Icons/arrow_right.png + + + + + + + + 0 + 0 + + + + + 75 + 22 + + + + true + + + true + + + + 110 + + + + + 125 + + + + + 150 + + + + + 175 + + + + + 200 + + + + + + + + Move xform x units up + + + + + + + :/Fractorium/Icons/arrow_up.png:/Fractorium/Icons/arrow_up.png + + + + + + + Move xform x units down + + + + + + + :/Fractorium/Icons/arrow_down.png:/Fractorium/Icons/arrow_down.png + + + + + + + + 0 + 0 + + + + + 75 + 22 + + + + true + + + true + + + + 1 + + + + + 0.5 + + + + + 0.25 + + + + + 0.1 + + + + + 0.05 + + + + + 0.025 + + + + + 0.01 + + + + + + + + Move xform x units left + + + + + + + :/Fractorium/Icons/arrow_left.png:/Fractorium/Icons/arrow_left.png + + + + + + + + 0 + 0 + + + + + 75 + 22 + + + + true + + + QComboBox::NoInsert + + + QComboBox::AdjustToContents + + + true + + + + 5 + + + + + 15 + + + + + 30 + + + + + 45 + + + + + 60 + + + + + 90 + + + + + 120 + + + + + 180 + + + + + + + + Rotate xform x degrees counter clockwise + + + + + + + :/Fractorium/Icons/arrow_rotate_anticlockwise.png:/Fractorium/Icons/arrow_rotate_anticlockwise.png + + + + + + + Rotate xform x degrees clockwise + + + + + + + :/Fractorium/Icons/arrow_rotate_clockwise.png:/Fractorium/Icons/arrow_rotate_clockwise.png + + + + + + + Scale xform x percent down + + + + + + + :/Fractorium/Icons/arrow_in.png:/Fractorium/Icons/arrow_in.png + + + + + + + Flip xform horizontally + + + + + + + :/Fractorium/Icons/shape_flip_horizontal.png:/Fractorium/Icons/shape_flip_horizontal.png + + + + + + + Flip xform vertically + + + + + + + :/Fractorium/Icons/shape_flip_vertical.png:/Fractorium/Icons/shape_flip_vertical.png + + + + + + + + 0 + 0 + + + + + 75 + 0 + + + + Reset xform to the identity matrix + + + Reset + + + + + + + + + Show + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + false + + + false + + + + 6 + + + 6 + + + 2 + + + 6 + + + 6 + + + + + Current + + + + + + + All + + + true + + + + + + + + + + + + + + 0 + 0 + + + + + 100 + 270 + + + + + 16777215 + 270 + + + + Post Affine Transform + + + false + + + true + + + false + + + + 4 + + + 6 + + + 4 + + + 6 + + + 6 + + + + + + 2 + 0 + + + + + 0 + 90 + + + + + 10000 + 90 + + + + Qt::NoFocus + + + QFrame::Panel + + + QFrame::Plain + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + false + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + true + + + false + + + false + + + 3 + + + 2 + + + false + + + 100 + + + false + + + 40 + + + true + + + false + + + false + + + 22 + + + false + + + 22 + + + false + + + + X + + + + + Y + + + + + O + + + + + 1 (A, B, C) + + + + + 2 (D, E, F) + + + + + + + + QLayout::SetDefaultConstraint + + + 0 + + + 4 + + + + + Rotate xform 90 degrees counter clockwise + + + + + + + :/Fractorium/Icons/arrow_turn_left.png:/Fractorium/Icons/arrow_turn_left.png + + + + + + + Rotate xform 90 degrees clockwise + + + + + + + :/Fractorium/Icons/arrow_turn_right.png:/Fractorium/Icons/arrow_turn_right.png + + + + + + + Scale xform x percent up + + + + + + + :/Fractorium/Icons/arrow_out.png:/Fractorium/Icons/arrow_out.png + + + + + + + Move xform x units right + + + + + + + :/Fractorium/Icons/arrow_right.png:/Fractorium/Icons/arrow_right.png + + + + + + + + 0 + 0 + + + + + 75 + 22 + + + + true + + + true + + + + 110 + + + + + 125 + + + + + 150 + + + + + 175 + + + + + 200 + + + + + + + + Move xform x units up + + + + + + + :/Fractorium/Icons/arrow_up.png:/Fractorium/Icons/arrow_up.png + + + + + + + Move xform x units down + + + + + + + :/Fractorium/Icons/arrow_down.png:/Fractorium/Icons/arrow_down.png + + + + + + + + 0 + 0 + + + + + 75 + 22 + + + + true + + + true + + + + 1 + + + + + 0.5 + + + + + 0.25 + + + + + 0.1 + + + + + 0.05 + + + + + 0.025 + + + + + 0.01 + + + + + + + + Move xform x units left + + + + + + + :/Fractorium/Icons/arrow_left.png:/Fractorium/Icons/arrow_left.png + + + + + + + + 0 + 0 + + + + + 75 + 22 + + + + true + + + QComboBox::NoInsert + + + QComboBox::AdjustToContents + + + true + + + + 5 + + + + + 15 + + + + + 30 + + + + + 45 + + + + + 60 + + + + + 90 + + + + + 120 + + + + + 180 + + + + + + + + Rotate xform x degrees counter clockwise + + + + + + + :/Fractorium/Icons/arrow_rotate_anticlockwise.png:/Fractorium/Icons/arrow_rotate_anticlockwise.png + + + + + + + Rotate xform x degrees clockwise + + + + + + + :/Fractorium/Icons/arrow_rotate_clockwise.png:/Fractorium/Icons/arrow_rotate_clockwise.png + + + + + + + Scale xform x percent down + + + + + + + :/Fractorium/Icons/arrow_in.png:/Fractorium/Icons/arrow_in.png + + + + + + + Flip xform horizontally + + + + + + + :/Fractorium/Icons/shape_flip_horizontal.png:/Fractorium/Icons/shape_flip_horizontal.png + + + + + + + Flip xform vertically + + + + + + + :/Fractorium/Icons/shape_flip_vertical.png:/Fractorium/Icons/shape_flip_vertical.png + + + + + + + + 0 + 0 + + + + + 75 + 0 + + + + Reset xform to the identity matrix + + + Reset + + + + + + + + + true + + + Show + + + + 6 + + + 2 + + + 6 + + + 6 + + + + + true + + + Current + + + true + + + + + + + All + + + + + + + + + + + + + Pivot + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + false + + + + 6 + + + 2 + + + 6 + + + 6 + + + + + Local + + + true + + + + + + + World + + + + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 20 + 40 + + + + + + + + + Variations + + + Full list of available variations and their weights for the currently selected xform. + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + 5 + + + QLayout::SetDefaultConstraint + + + 0 + + + 0 + + + 0 + + + + + + + + + 30 + 0 + + + + + 30 + 16777215 + + + + X + + + + + + + + + + 0 + 0 + + + + Qt::StrongFocus + + + Qt::DefaultContextMenu + + + QFrame::Panel + + + QFrame::Plain + + + 1 + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + QAbstractItemView::SelectItems + + + 12 + + + false + + + true + + + false + + + false + + + 2 + + + 70 + + + true + + + 10 + + + true + + + true + + + + Variation + + + + + Weight + + + + + Spherical + + + 0 + + + ItemIsSelectable|ItemIsEditable|ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled + + + + + Sinusoidal + + + 0 + + + ItemIsSelectable|ItemIsEditable|ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled + + + + + Pdj + + + 0 + + + ItemIsSelectable|ItemIsEditable|ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled + + + + pdj_d + + + 1 + + + ItemIsSelectable|ItemIsEditable|ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled + + + + + pdj_c + + + 2 + + + ItemIsSelectable|ItemIsEditable|ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled + + + + + pdj_b + + + 1.5 + + + ItemIsSelectable|ItemIsEditable|ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled + + + + + pdj_a + + + 1 + + + ItemIsSelectable|ItemIsEditable|ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled + + + + + + Linear + + + 0 + + + ItemIsSelectable|ItemIsEditable|ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled + + + + + + + + + Xaos + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 0 + 0 + + + + + 0 + 67 + + + + + 16777215 + 16777215 + + + + + true + + + + Qt::NoFocus + + + + + + QFrame::Panel + + + QFrame::Plain + + + 1 + + + Qt::ScrollBarAsNeeded + + + Qt::ScrollBarAlwaysOff + + + false + + + QAbstractItemView::NoEditTriggers + + + false + + + false + + + false + + + QAbstractItemView::NoSelection + + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + true + + + Qt::SolidLine + + + false + + + false + + + 2 + + + false + + + false + + + 110 + + + false + + + 27 + + + true + + + false + + + 22 + + + false + + + 22 + + + false + + + + Default Row + + + + + Field + + + + + + + + + + + + + + + + + + + + + + + Direction + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + false + + + + 6 + + + 2 + + + 6 + + + 6 + + + + + To + + + true + + + + + + + From + + + + + + + + + + Set all xaos values in all xforms to 1 + + + Clear Xaos + + + + + + + + + + + 4 + + + + + + 0 + 22 + + + + + 50 + 22 + + + + + 40 + 16777215 + + + + Current xform + + + false + + + 12 + + + + + + + + 0 + 0 + + + + + 30 + 0 + + + + Add xform + + + + + + + :/Fractorium/Icons/add.png:/Fractorium/Icons/add.png + + + + + + + + 0 + 0 + + + + + 30 + 0 + + + + Duplicate xform + + + + + + + :/Fractorium/Icons/editraise.png:/Fractorium/Icons/editraise.png + + + + + + + + 0 + 0 + + + + + 30 + 0 + + + + Clear xform variations + + + + + + + :/Fractorium/Icons/eraser.png:/Fractorium/Icons/eraser.png + + + + + + + + 0 + 0 + + + + + 30 + 0 + + + + Delete xform + + + + + + + :/Fractorium/Icons/del.png:/Fractorium/Icons/del.png + + + + + + + + 0 + 0 + + + + + 50 + 0 + + + + + 60 + 16777215 + + + + + 0 + 0 + + + + Add final xform + + + Qt::LeftToRight + + + Final + + + + :/Fractorium/Icons/add.png:/Fractorium/Icons/add.png + + + + + + + XformsTabWidget + XformWeightNameTable + + + + Palette + + + + QLayout::SetDefaultConstraint + + + 5 + + + 5 + + + 5 + + + 4 + + + 6 + + + + + + 2 + 0 + + + + + 0 + 67 + + + + + 16777215 + 67 + + + + Qt::NoFocus + + + QFrame::Panel + + + QFrame::Plain + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + false + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + false + + + false + + + 3 + + + 4 + + + false + + + 62 + + + 62 + + + true + + + false + + + 22 + + + false + + + 22 + + + + + + + + + + + Hue + + + + + Contrast + + + + + Saturation + + + + + Blur + + + + + Brightness + + + + + + + + + + Frequency + + + + + + + + Qt::NoFocus + + + QFrame::Panel + + + QFrame::Plain + + + Qt::ScrollBarAsNeeded + + + false + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + QAbstractItemView::ScrollPerPixel + + + false + + + 2 + + + false + + + true + + + false + + + 16 + + + false + + + 16 + + + false + + + + Name + + + AlignLeft|AlignVCenter + + + + + Palette + + + AlignLeft|AlignVCenter + + + + + + + + + 0 + 0 + + + + + 0 + 19 + + + + + 16777215 + 19 + + + + Qt::NoFocus + + + QFrame::Panel + + + QFrame::Plain + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + false + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::NoSelection + + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + false + + + false + + + 1 + + + 2 + + + false + + + true + + + false + + + 19 + + + 19 + + + + + + + + + + + Info + + + + 5 + + + 6 + + + 5 + + + 6 + + + 5 + + + + + + 0 + 0 + + + + + 0 + 250 + + + + + 16777215 + 250 + + + + Histogram Bounds + + + + 4 + + + 6 + + + 2 + + + 6 + + + 6 + + + + + + 0 + 0 + + + + + 0 + 173 + + + + + 16777215 + 173 + + + + QFrame::Box + + + QFrame::Plain + + + + 2 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + UL: + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + LR: + + + Qt::AlignBottom|Qt::AlignRight|Qt::AlignTrailing + + + + + + + UR: + + + Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing + + + + + + + H: + + + + + + + LL: + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + + + + + W: + + + Qt::AlignHCenter|Qt::AlignTop + + + + + + + + + + + 0 + 0 + + + + + 0 + 46 + + + + + 16777215 + 46 + + + + + true + + + + Qt::NoFocus + + + + + + QFrame::Panel + + + QFrame::Plain + + + 1 + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + false + + + QAbstractItemView::NoEditTriggers + + + false + + + false + + + false + + + QAbstractItemView::NoSelection + + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + true + + + Qt::SolidLine + + + false + + + false + + + 2 + + + false + + + false + + + 110 + + + false + + + 27 + + + true + + + false + + + 22 + + + false + + + 22 + + + false + + + + Gutter + + + + + DE Box Dimensions + + + + + Field + + + + + + + + + + Gutter + + + + + 0 + + + + + DE Box Dimensions + + + + + 0 + + + + true + + + + AlignLeft|AlignVCenter + + + + + + + + + + + + 0 + 0 + + + + File Opening + + + + 4 + + + 6 + + + 4 + + + 6 + + + 6 + + + + + true + + + + + + + + + + + 0 + 0 + + + + Rendering + + + + 4 + + + 6 + + + 4 + + + 6 + + + 6 + + + + + true + + + + + + + + + + + + + + + + + :/Fractorium/Icons/layers-stack.png:/Fractorium/Icons/layers-stack.png + + + New Floc&k + + + New Flock + + + Clear any existing flames and create a new file with 10 random flames in it + + + + + + :/Fractorium/Icons/folder-visiting-4.png:/Fractorium/Icons/folder-visiting-4.png + + + &Open + + + + + + :/Fractorium/Icons/window-close.png:/Fractorium/Icons/window-close.png + + + E&xit + + + + + + :/Fractorium/Icons/document-hf-insert.png:/Fractorium/Icons/document-hf-insert.png + + + &Save Current to Opened File + + + <html><body><p>Save the currently displayed flame back to the opened file in memory.</p><p>This overwrites the original flame but does not store the file back to disk.</p></body></html> + + + + + + :/Fractorium/Icons/database-medium.png:/Fractorium/Icons/database-medium.png + + + Save &Current as Xml + + + Save the current flame as an xml file + + + + + + :/Fractorium/Icons/infomation.png:/Fractorium/Icons/infomation.png + + + &About + + + + + + :/Fractorium/Icons/configure.png:/Fractorium/Icons/configure.png + + + Op&tions + + + + + + :/Fractorium/Icons/cog.png:/Fractorium/Icons/cog.png + + + Final &Render + + + + + + :/Fractorium/Icons/monitor.png:/Fractorium/Icons/monitor.png + + + Save Current Scree&n + + + Save the current screen as an image + + + + + + :/Fractorium/Icons/068123-3d-transparent-glass-icon-alphanumeric-question-mark3.png:/Fractorium/Icons/068123-3d-transparent-glass-icon-alphanumeric-question-mark3.png + + + New &Random Flame + + + Add a new random flame to the end of the current file + + + + + + :/Fractorium/Icons/databases.png:/Fractorium/Icons/databases.png + + + Save Entire &File as Xml + + + Save Entire File as Xml + + + Save all flames as a single xml file + + + + + + :/Fractorium/Icons/proxy.png:/Fractorium/Icons/proxy.png + + + Add R&eflective Symmetry + + + + + + :/Fractorium/Icons/display-brightness-off.png:/Fractorium/Icons/display-brightness-off.png + + + Add R&otational Symmetry + + + + + Add Reflective &and Rotational Symmetry + + + + + + :/Fractorium/Icons/layer--plus.png:/Fractorium/Icons/layer--plus.png + + + New &Empty Flame + + + Add a new empty flame to the end of the current file + + + + + + :/Fractorium/Icons/layers.png:/Fractorium/Icons/layers.png + + + Co&py Flame + + + Add a copy of the current flame to the end of the current file + + + + + + :/Fractorium/Icons/016938-3d-transparent-glass-icon-symbols-shapes-shape-square-clear-16.png:/Fractorium/Icons/016938-3d-transparent-glass-icon-symbols-shapes-shape-square-clear-16.png + + + &Clear Flame + + + Delete all but one xform, clear its variations and set pre and post affine to the identity matrix. + + + + + + :/Fractorium/Icons/page_copy.png:/Fractorium/Icons/page_copy.png + + + &Copy Xml + + + Copy Xml for the current flame + + + Ctrl+C + + + + + Paste Xml &Over + + + Paste Xml as a new flame in the current file + + + Ctrl+Shift+V + + + + + Copy &All Xmls + + + Copy Xmls for all flames as a single string + + + Ctrl+Shift+C + + + + + + :/Fractorium/Icons/stop.png:/Fractorium/Icons/stop.png + + + &Stop Rendering Previews + + + + + + :/Fractorium/Icons/application_side_boxes.png:/Fractorium/Icons/application_side_boxes.png + + + Render &Previews + + + + + + :/Fractorium/Icons/arrow-undo.png:/Fractorium/Icons/arrow-undo.png + + + &Undo + + + Ctrl+Z + + + + + + :/Fractorium/Icons/arrow-redo.png:/Fractorium/Icons/arrow-redo.png + + + &Redo + + + Ctrl+Y + + + + + + :/Fractorium/Icons/page_paste.png:/Fractorium/Icons/page_paste.png + + + Paste Xml &Append + + + Ctrl+V + + + + + + + TableWidget + QTableWidget +
TableWidget.h
+
+ + GLWidget + QWidget +
glwidget.h
+ 1 +
+
+ + + + +
diff --git a/Source/Fractorium/FractoriumEmberController.cpp b/Source/Fractorium/FractoriumEmberController.cpp new file mode 100644 index 0000000..a8c2b97 --- /dev/null +++ b/Source/Fractorium/FractoriumEmberController.cpp @@ -0,0 +1,244 @@ +#include "FractoriumPch.h" +#include "FractoriumEmberController.h" +#include "Fractorium.h" +#include "GLEmberController.h" + +/// +/// Constructor which initializes the non-templated members contained in this class. +/// The renderer, other templated members and GUI setup will be done in the templated derived controller class. +/// +/// Pointer to the main window. +FractoriumEmberControllerBase::FractoriumEmberControllerBase(Fractorium* fractorium) +{ + Timing t; + + m_Rendering = false; + m_Shared = true; + m_Platform = 0; + m_Device = 0; + m_FailedRenders = 0; + m_UndoIndex = 0; + m_RenderType = CPU_RENDERER; + m_OutputTexID = 0; + m_SubBatchCount = 1;//Will be ovewritten by the options on first render. + m_Fractorium = fractorium; + m_RenderTimer = NULL; + m_RenderRestartTimer = NULL; + m_Rand = QTIsaac(ISAAC_INT(t.Tic()), ISAAC_INT(t.Tic() * 2), ISAAC_INT(t.Tic() * 3));//Ensure a different rand seed on each instance. + + m_RenderTimer = new QTimer(m_Fractorium); + m_RenderTimer->setInterval(0); + m_Fractorium->connect(m_RenderTimer, SIGNAL(timeout()), SLOT(IdleTimer())); + + m_RenderRestartTimer = new QTimer(m_Fractorium); + m_Fractorium->connect(m_RenderRestartTimer, SIGNAL(timeout()), SLOT(StartRenderTimer())); +} + +/// +/// Destructor which stops rendering and deletes the timers. +/// All other memory is cleared automatically through the use of STL. +/// +FractoriumEmberControllerBase::~FractoriumEmberControllerBase() +{ + StopRenderTimer(true); + + if (m_RenderTimer) + { + m_RenderTimer->stop(); + delete m_RenderTimer; + m_RenderTimer = NULL; + } + + if (m_RenderRestartTimer) + { + m_RenderRestartTimer->stop(); + delete m_RenderRestartTimer; + m_RenderRestartTimer = NULL; + } +} + +/// +/// Constructor which passes the main window parameter to the base, initializes the templated members contained in this class. +/// Then sets up the parts of the GUI that require templated Widgets, such as the variations tree and the palette table. +/// Note the renderer is not setup here automatically. Instead, it must be manually created by the caller later. +/// +/// Pointer to the main window. +template +FractoriumEmberController::FractoriumEmberController(Fractorium* fractorium) + : FractoriumEmberControllerBase(fractorium) +{ + m_PreviewRun = false; + m_PreviewRunning = false; + m_SheepTools = auto_ptr>(new SheepTools("flam3-palettes.xml", new EmberNs::Renderer())); + m_GLController = auto_ptr>(new GLEmberController(fractorium, fractorium->ui.GLDisplay, this)); + m_PreviewRenderer = auto_ptr>(new EmberNs::Renderer()); + SetupVariationTree(); + InitPaletteTable("flam3-palettes.xml"); + BackgroundChanged(QColor(0, 0, 0));//Default to black. + ClearUndo(); + + m_PreviewRenderer->Callback(NULL); + m_PreviewRenderer->NumChannels(4); + m_PreviewRenderer->ReclaimOnResize(true); + m_PreviewRenderer->SetEmber(m_Ember);//Give it an initial ember, will be updated many times later. + //m_PreviewRenderer->ThreadCount(1);//For debugging. + + m_PreviewRenderFunc = [&](unsigned int start, unsigned int end) + { + while(m_PreviewRun || m_PreviewRunning) + { + } + + m_PreviewRun = true; + m_PreviewRunning = true; + m_PreviewRenderer->ThreadCount(max(1, Timing::ProcessorCount() - 1));//Leave one processor free so the GUI can breathe. + QTreeWidget* tree = m_Fractorium->ui.LibraryTree; + + if (QTreeWidgetItem* top = tree->topLevelItem(0)) + { + for (size_t i = start; m_PreviewRun && i < end && i < m_EmberFile.m_Embers.size(); i++) + { + Ember ember = m_EmberFile.m_Embers[i]; + + ember.SetSizeAndAdjustScale(PREVIEW_SIZE, PREVIEW_SIZE, false, SCALE_WIDTH); + ember.m_TemporalSamples = 1; + ember.m_Quality = 25; + ember.m_Supersample = 1; + m_PreviewRenderer->SetEmber(ember); + + if (m_PreviewRenderer->Run(m_PreviewFinalImage) == RENDER_OK) + { + if (EmberTreeWidgetItem* treeItem = dynamic_cast*>(top->child(i))) + { + //It is critical that Qt::BlockingQueuedConnection is passed because this is running on a different thread than the UI. + //This ensures the events are processed in order as each preview is updated, and that control does not return here + //until the update is complete. + QMetaObject::invokeMethod(m_Fractorium, "SetLibraryTreeItemData", Qt::BlockingQueuedConnection, + Q_ARG(EmberTreeWidgetItemBase*, (EmberTreeWidgetItemBase*)treeItem), + Q_ARG(vector&, m_PreviewFinalImage), + Q_ARG(unsigned int, PREVIEW_SIZE), + Q_ARG(unsigned int, PREVIEW_SIZE)); + + //treeItem->SetImage(m_PreviewFinalImage, PREVIEW_SIZE, PREVIEW_SIZE); + } + } + } + } + + m_PreviewRun = false; + m_PreviewRunning = false; + }; +} + +/// +/// Empty destructor that does nothing. +/// +template +FractoriumEmberController::~FractoriumEmberController() { } + +/// +/// Setters for embers, ember files and palettes which convert between float and double types. +/// These are used to preserve the current ember/file when switching between renderers. +/// Note that some precision will be lost when going from double to float. +/// +template void FractoriumEmberController::SetEmber(const Ember& ember, bool verbatim) { SetEmberPrivate(ember, verbatim); } +template void FractoriumEmberController::CopyEmber(Ember& ember) { ember = m_Ember; } +template void FractoriumEmberController::SetEmberFile(const EmberFile& emberFile) { m_EmberFile = emberFile; } +template void FractoriumEmberController::CopyEmberFile(EmberFile& emberFile) { emberFile = m_EmberFile; } +template void FractoriumEmberController::SetTempPalette(const Palette& palette) { m_TempPalette = palette; } +template void FractoriumEmberController::CopyTempPalette(Palette& palette) { palette = m_TempPalette; } +#ifdef DO_DOUBLE +template void FractoriumEmberController::SetEmber(const Ember& ember, bool verbatim) { SetEmberPrivate(ember, verbatim); } +template void FractoriumEmberController::CopyEmber(Ember& ember) { ember = m_Ember; } +template void FractoriumEmberController::SetEmberFile(const EmberFile& emberFile) { m_EmberFile = emberFile; } +template void FractoriumEmberController::CopyEmberFile(EmberFile& emberFile) { emberFile = m_EmberFile; } +template void FractoriumEmberController::SetTempPalette(const Palette& palette) { m_TempPalette = palette; } +template void FractoriumEmberController::CopyTempPalette(Palette& palette) { palette = m_TempPalette; } +#endif +template Ember* FractoriumEmberController::CurrentEmber() { return &m_Ember; } + +/// +/// Set the ember at the specified index from the currently opened file as the current Ember. +/// Clears the undo state. +/// Resets the rendering process. +/// +/// The index in the file from which to retrieve the ember +template +void FractoriumEmberController::SetEmber(size_t index) +{ + if (index < m_EmberFile.m_Embers.size()) + { + if (QTreeWidgetItem* top = m_Fractorium->ui.LibraryTree->topLevelItem(0)) + { + for (unsigned int i = 0; i < top->childCount(); i++) + { + if (EmberTreeWidgetItem* emberItem = dynamic_cast*>(top->child(i))) + emberItem->setSelected(i == index); + } + } + + ClearUndo(); + SetEmber(m_EmberFile.m_Embers[index]); + } +} + +/// +/// Wrapper to call a function, then optionally add the requested action to the rendering queue. +/// +/// The function to call +/// True to update renderer, else false. Default: false. +/// The action to add to the rendering queue. Default: FULL_RENDER. +template +void FractoriumEmberController::Update(std::function func, bool updateRender, eProcessAction action) +{ + func(); + + if (updateRender) + UpdateRender(action); +} + +/// +/// Wrapper to call a function on the current xform, then optionally add the requested action to the rendering queue. +/// +/// The function to call +/// True to update renderer, else false. Default: false. +/// The action to add to the rendering queue. Default: FULL_RENDER. +template +void FractoriumEmberController::UpdateCurrentXform(std::function*)> func, bool updateRender, eProcessAction action) +{ + if (Xform* xform = CurrentXform()) + { + func(xform); + + if (updateRender) + UpdateRender(action); + } +} + +/// +/// Set the current ember, but use GUI values for the fields which make sense to +/// keep the same between ember selection changes. +/// Note the extra template parameter U allows for assigning ember of different types. +/// Resets the rendering process. +/// +/// The ember to set as the current +template +template +void FractoriumEmberController::SetEmberPrivate(const Ember& ember, bool verbatim) +{ + if (ember.m_Name != m_Ember.m_Name) + m_LastSaveCurrent = ""; + + m_Ember = ember; + + if (!verbatim) + { + m_Ember.SetSizeAndAdjustScale(m_Fractorium->ui.GLDisplay->width(), m_Fractorium->ui.GLDisplay->height(), true, SCALE_WIDTH); + m_Ember.m_TemporalSamples = 1;//Change once animation is supported. + m_Ember.m_Quality = m_Fractorium->m_QualitySpin->value(); + m_Ember.m_Supersample = m_Fractorium->m_SupersampleSpin->value(); + } + + m_Fractorium->FillXforms();//Must do this first because the palette setup in FillParamTablesAndPalette() uses the xforms combo. + FillParamTablesAndPalette(); +} \ No newline at end of file diff --git a/Source/Fractorium/FractoriumEmberController.h b/Source/Fractorium/FractoriumEmberController.h new file mode 100644 index 0000000..1f0951c --- /dev/null +++ b/Source/Fractorium/FractoriumEmberController.h @@ -0,0 +1,446 @@ +#pragma once + +#include "EmberFile.h" +#include "DoubleSpinBox.h" +#include "GLEmberController.h" + +/// +/// FractoriumEmberControllerBase and FractoriumEmberController classes. +/// + +/// +/// An enum representing the type of edit being done. +/// +enum eEditUndoState : unsigned int { REGULAR_EDIT = 0, UNDO_REDO = 1, EDIT_UNDO = 2 }; + +/// +/// FractoriumEmberController and Fractorium need each other, but each can't include the other. +/// So Fractorium includes this file, and Fractorium is declared as a forward declaration here. +/// +class Fractorium; +#define PREVIEW_SIZE 256 +#define UNDO_SIZE 128 + +/// +/// FractoriumEmberControllerBase serves as a non-templated base class with virtual +/// functions which will be overridden in a derived class that takes a template parameter. +/// The controller serves as a way to access both the Fractorium GUI and the underlying ember +/// objects through an interface that doesn't require template argument, but does allow +/// templated objects to be used underneath. +/// Note that there are a few functions which access a templated object, so for those both +/// versions for float and double must be provided, then overridden in the templated derived +/// class. It's definitely a design flaw, but C++ doesn't offer any alternative since +/// templated virtual functions are not supported. +/// The functions not implemented in this file can be found in the GUI files which use them. +/// +class FractoriumEmberControllerBase : public RenderCallback +{ +public: + FractoriumEmberControllerBase(Fractorium* fractorium); + virtual ~FractoriumEmberControllerBase(); + + //Embers. + virtual void SetEmber(const Ember& ember, bool verbatim = false) { } + virtual void CopyEmber(Ember& ember) { } + virtual void SetEmberFile(const EmberFile& emberFile) { } + virtual void CopyEmberFile(EmberFile& emberFile) { } + virtual void SetTempPalette(const Palette& palette) { } + virtual void CopyTempPalette(Palette& palette) { } +#ifdef DO_DOUBLE + virtual void SetEmber(const Ember& ember, bool verbatim = false) { } + virtual void CopyEmber(Ember& ember) { } + virtual void SetEmberFile(const EmberFile& emberFile) { } + virtual void CopyEmberFile(EmberFile& emberFile) { } + virtual void SetTempPalette(const Palette& palette) { } + virtual void CopyTempPalette(Palette& palette) { } +#endif + virtual void SetEmber(size_t index) { } + virtual void Clear() { } + virtual void AddXform() { } + virtual void DuplicateXform() { } + virtual void ClearCurrentXform() { } + virtual void DeleteCurrentXform() { } + virtual void AddFinalXform() { } + virtual bool UseFinalXform() { return false; } + virtual size_t XformCount() { return 0; } + virtual size_t TotalXformCount() { return 0; } + virtual string Name() { return ""; } + virtual void Name(string s) { } + virtual unsigned int FinalRasW() { return 0; } + virtual void FinalRasW(unsigned int w) { } + virtual unsigned int FinalRasH() { return 0; } + virtual void FinalRasH(unsigned int h) { } + virtual void AddSymmetry(int sym, QTIsaac& rand) { } + virtual void CalcNormalizedWeights() { } + + //Menu. + virtual void NewFlock(unsigned int count) { }//File. + virtual void NewEmptyFlameInCurrentFile() { } + virtual void NewRandomFlameInCurrentFile() { } + virtual void CopyFlameInCurrentFile() { } + virtual void OpenAndPrepFiles(QStringList filenames, bool append) { } + virtual void SaveCurrentAsXml() { } + virtual void SaveEntireFileAsXml() { } + virtual void SaveCurrentToOpenedFile() { } + virtual void Undo() { }//Edit. + virtual void Redo() { } + virtual void CopyXml() { } + virtual void CopyAllXml() { } + virtual void PasteXmlAppend() { } + virtual void PasteXmlOver() { } + virtual void AddReflectiveSymmetry() { }//Tools. + virtual void AddRotationalSymmetry() { } + virtual void AddBothSymmetry() { } + virtual void ClearFlame() { } + + //Toolbar. + + //Params. + virtual void SetCenter(double x, double y) { } + virtual void FillParamTablesAndPalette() { } + virtual void BrightnessChanged(double d) { } + virtual void GammaChanged(double d) { } + virtual void GammaThresholdChanged(double d) { } + virtual void VibrancyChanged(double d) { } + virtual void HighlightPowerChanged(double d) { } + virtual void PaletteModeChanged(unsigned int i) { } + virtual void CenterXChanged(double d) { } + virtual void CenterYChanged(double d) { } + virtual void ScaleChanged(double d) { } + virtual void ZoomChanged(double d) { } + virtual void RotateChanged(double d) { } + virtual void ZPosChanged(double d) { } + virtual void PerspectiveChanged(double d) { } + virtual void PitchChanged(double d) { } + virtual void YawChanged(double d) { } + virtual void DepthBlurChanged(double d) { } + virtual void SpatialFilterWidthChanged(double d) { } + virtual void SpatialFilterTypeChanged(const QString& text) { } + virtual void TemporalFilterWidthChanged(double d) { } + virtual void TemporalFilterTypeChanged(const QString& text) { } + virtual void DEFilterMinRadiusWidthChanged(double d) { } + virtual void DEFilterMaxRadiusWidthChanged(double d) { } + virtual void DEFilterCurveWidthChanged(double d) { } + virtual void PassesChanged(int i) { } + virtual void TemporalSamplesChanged(int d) { } + virtual void QualityChanged(double d) { } + virtual void SupersampleChanged(int d) { } + virtual void AffineInterpTypeChanged(int i) { } + virtual void InterpTypeChanged(int i) { } + virtual void BackgroundChanged(const QColor& color) { } + + //Xforms. + virtual void CurrentXformComboChanged(int index) { } + virtual void XformWeightChanged(double d) { } + virtual void EqualizeWeights() { } + virtual void XformNameChanged(int row, int col) { } + + //Xforms Affine. + virtual void AffineSetHelper(double d, int index, bool pre) { } + virtual void FlipCurrentXform(bool horizontal, bool vertical, bool pre) { } + virtual void RotateCurrentXformByAngle(double angle, bool pre) { } + virtual void MoveCurrentXform(double x, double y, bool pre) { } + virtual void ScaleCurrentXform(double scale, bool pre) { } + virtual void ResetCurrentXformAffine(bool pre) { } + + //Xforms Color. + virtual void XformColorIndexChanged(double d, bool updateRender) { } + virtual void XformScrollColorIndexChanged(int d) { } + virtual void XformColorSpeedChanged(double d) { } + virtual void XformOpacityChanged(double d) { } + virtual void XformDirectColorChanged(double d) { } + void SetPaletteRefTable(QPixmap* pixmap); + + //Xforms Variations. + virtual void SetupVariationTree() { } + virtual void ClearVariationsTree() { } + virtual void VariationSpinBoxValueChanged(double d) { } + + //Xforms Xaos. + virtual void FillXaosWithCurrentXform() { } + virtual QString MakeXaosNameString(unsigned int i) { return ""; } + virtual void XaosChanged(DoubleSpinBox* sender) { } + virtual void ClearXaos() { } + + //Palette. + virtual bool InitPaletteTable(string s) { return false; } + virtual void ApplyPaletteToEmber() { } + virtual void PaletteAdjust() { } + virtual QRgb GetQRgbFromPaletteIndex(unsigned int i) { return QRgb(); } + virtual void PaletteCellClicked(int row, int col) { } + + //Library. + virtual void SyncNames() { } + virtual void FillLibraryTree(int selectIndex = -1) { } + virtual void UpdateLibraryTree() { } + virtual void EmberTreeItemChanged(QTreeWidgetItem* item, int col) { } + virtual void EmberTreeItemDoubleClicked(QTreeWidgetItem* item, int col) { } + virtual void RenderPreviews(unsigned int start = UINT_MAX, unsigned int end = UINT_MAX) { } + virtual void StopPreviewRender() { } + + //Info. + + //Rendering/progress. + virtual bool Render() { return false; } + virtual bool CreateRenderer(eRendererType renderType, unsigned int platform, unsigned int device, bool shared = true) { return false; } + virtual unsigned int SizeOfT() { return 0; } + virtual void ClearUndo() { } + virtual GLEmberControllerBase* GLController() { return NULL; } + bool RenderTimerRunning(); + void StartRenderTimer(); + void DelayedStartRenderTimer(); + void StopRenderTimer(bool wait); + void Shutdown(); + void UpdateRender(eProcessAction action = FULL_RENDER); + void DeleteRenderer(); + void SaveCurrentRender(QString filename); + RendererBase* Renderer() { return m_Renderer.get(); } + vector* FinalImage() { return &m_FinalImage; } + vector* PreviewFinalImage() { return &m_PreviewFinalImage; } + +protected: + //Rendering/progress. + void AddProcessAction(eProcessAction action); + eProcessAction CondenseAndClearProcessActions(); + eProcessState ProcessState() { return m_Renderer.get() ? m_Renderer->ProcessState() : NONE; } + + //Non-templated members. + bool m_Rendering; + bool m_Shared; + bool m_LastEditWasUndoRedo; + unsigned int m_Platform; + unsigned int m_Device; + unsigned int m_SubBatchCount; + unsigned int m_FailedRenders; + unsigned int m_UndoIndex; + eRendererType m_RenderType; + eEditUndoState m_EditState; + GLuint m_OutputTexID; + Timing m_RenderElapsedTimer; + QImage m_FinalPaletteImage; + QString m_LastSaveAll; + QString m_LastSaveCurrent; + CriticalSection m_Cs; + vector m_FinalImage; + vector m_PreviewFinalImage; + vector m_ProcessActions; + auto_ptr m_Renderer; + QTIsaac m_Rand; + Fractorium* m_Fractorium; + QTimer* m_RenderTimer; + QTimer* m_RenderRestartTimer; +}; + +/// +/// Templated derived class which implements all interaction functionality between the embers +/// of a specific template type and the GUI. +/// Switching between template arguments requires complete re-creation of the controller and the +/// underlying renderer. Switching between CPU and OpenCL only requires re-creation of the renderer. +/// +template +class FractoriumEmberController : public FractoriumEmberControllerBase +{ +public: + FractoriumEmberController(Fractorium* fractorium); + virtual ~FractoriumEmberController(); + + //Embers. + virtual void SetEmber(const Ember& ember, bool verbatim = false); + virtual void CopyEmber(Ember& ember); + virtual void SetEmberFile(const EmberFile& emberFile); + virtual void CopyEmberFile(EmberFile& emberFile); + virtual void SetTempPalette(const Palette& palette); + virtual void CopyTempPalette(Palette& palette); +#ifdef DO_DOUBLE + virtual void SetEmber(const Ember& ember, bool verbatim = false); + virtual void CopyEmber(Ember& ember); + virtual void SetEmberFile(const EmberFile& emberFile); + virtual void CopyEmberFile(EmberFile& emberFile); + virtual void SetTempPalette(const Palette& palette); + virtual void CopyTempPalette(Palette& palette); +#endif + virtual void SetEmber(size_t index); + virtual void Clear() { } + virtual void AddXform(); + virtual void DuplicateXform(); + virtual void ClearCurrentXform(); + virtual void DeleteCurrentXform(); + virtual void AddFinalXform(); + virtual bool UseFinalXform() { return m_Ember.UseFinalXform(); } + //virtual bool IsFinal(unsigned int i) { return false; } + virtual size_t XformCount() { return m_Ember.XformCount(); } + virtual size_t TotalXformCount() { return m_Ember.TotalXformCount(); } + virtual string Name() { return m_Ember.m_Name; } + virtual void Name(string s) { m_Ember.m_Name = s; } + virtual unsigned int FinalRasW() { return m_Ember.m_FinalRasW; } + virtual void FinalRasW(unsigned int w) { m_Ember.m_FinalRasW = w; } + virtual unsigned int FinalRasH() { return m_Ember.m_FinalRasH; } + virtual void FinalRasH(unsigned int h) { m_Ember.m_FinalRasH = h; } + virtual void AddSymmetry(int sym, QTIsaac& rand) { m_Ember.AddSymmetry(sym, rand); } + virtual void CalcNormalizedWeights() { m_Ember.CalcNormalizedWeights(m_NormalizedWeights); } + Ember* CurrentEmber(); + + //Menu. + virtual void NewFlock(unsigned int count); + virtual void NewEmptyFlameInCurrentFile(); + virtual void NewRandomFlameInCurrentFile(); + virtual void CopyFlameInCurrentFile(); + virtual void OpenAndPrepFiles(QStringList filenames, bool append); + virtual void SaveCurrentAsXml(); + virtual void SaveEntireFileAsXml(); + virtual void SaveCurrentToOpenedFile(); + virtual void Undo(); + virtual void Redo(); + virtual void CopyXml(); + virtual void CopyAllXml(); + virtual void PasteXmlAppend(); + virtual void PasteXmlOver(); + virtual void AddReflectiveSymmetry(); + virtual void AddRotationalSymmetry(); + virtual void AddBothSymmetry(); + virtual void ClearFlame(); + + //Toolbar. + + //Params. + virtual void SetCenter(double x, double y); + virtual void FillParamTablesAndPalette(); + virtual void BrightnessChanged(double d); + virtual void GammaChanged(double d); + virtual void GammaThresholdChanged(double d); + virtual void VibrancyChanged(double d); + virtual void HighlightPowerChanged(double d); + virtual void PaletteModeChanged(unsigned int i); + virtual void CenterXChanged(double d); + virtual void CenterYChanged(double d); + virtual void ScaleChanged(double d); + virtual void ZoomChanged(double d); + virtual void RotateChanged(double d); + virtual void ZPosChanged(double d); + virtual void PerspectiveChanged(double d); + virtual void PitchChanged(double d); + virtual void YawChanged(double d); + virtual void DepthBlurChanged(double d); + virtual void SpatialFilterWidthChanged(double d); + virtual void SpatialFilterTypeChanged(const QString& text); + virtual void TemporalFilterWidthChanged(double d); + virtual void TemporalFilterTypeChanged(const QString& text); + virtual void DEFilterMinRadiusWidthChanged(double d); + virtual void DEFilterMaxRadiusWidthChanged(double d); + virtual void DEFilterCurveWidthChanged(double d); + virtual void PassesChanged(int d); + virtual void TemporalSamplesChanged(int d); + virtual void QualityChanged(double d); + virtual void SupersampleChanged(int d); + virtual void AffineInterpTypeChanged(int index); + virtual void InterpTypeChanged(int index); + virtual void BackgroundChanged(const QColor& col); + + //Xforms. + virtual void CurrentXformComboChanged(int index); + virtual void XformWeightChanged(double d); + virtual void EqualizeWeights(); + virtual void XformNameChanged(int row, int col); + void FillWithXform(Xform* xform); + Xform* CurrentXform(); + + //Xforms Affine. + virtual void AffineSetHelper(double d, int index, bool pre); + virtual void FlipCurrentXform(bool horizontal, bool vertical, bool pre); + virtual void RotateCurrentXformByAngle(double angle, bool pre); + virtual void MoveCurrentXform(double x, double y, bool pre); + virtual void ScaleCurrentXform(double scale, bool pre); + virtual void ResetCurrentXformAffine(bool pre); + void FillAffineWithXform(Xform* xform, bool pre); + + //Xforms Color. + virtual void XformColorIndexChanged(double d, bool updateRender); + virtual void XformScrollColorIndexChanged(int d); + virtual void XformColorSpeedChanged(double d); + virtual void XformOpacityChanged(double d); + virtual void XformDirectColorChanged(double d); + void FillColorWithXform(Xform* xform); + + //Xforms Variations. + virtual void SetupVariationTree(); + virtual void ClearVariationsTree(); + virtual void VariationSpinBoxValueChanged(double d); + void FillVariationTreeWithXform(Xform* xform); + + //Xforms Xaos. + virtual void FillXaosWithCurrentXform(); + virtual QString MakeXaosNameString(unsigned int i); + virtual void XaosChanged(DoubleSpinBox* sender); + virtual void ClearXaos(); + + //Palette. + virtual bool InitPaletteTable(string s); + virtual void ApplyPaletteToEmber(); + virtual void PaletteAdjust(); + virtual QRgb GetQRgbFromPaletteIndex(unsigned int i) { return QRgb(); } + virtual void PaletteCellClicked(int row, int col); + + //Library. + virtual void SyncNames(); + virtual void FillLibraryTree(int selectIndex = -1); + virtual void UpdateLibraryTree(); + virtual void EmberTreeItemChanged(QTreeWidgetItem* item, int col); + virtual void EmberTreeItemDoubleClicked(QTreeWidgetItem* item, int col); + virtual void RenderPreviews(unsigned int start = UINT_MAX, unsigned int end = UINT_MAX); + virtual void StopPreviewRender(); + + //Info. + + //Rendering/progress. + virtual bool Render(); + virtual bool CreateRenderer(eRendererType renderType, unsigned int platform, unsigned int device, bool shared = true); + virtual unsigned int SizeOfT() { return sizeof(T); } + virtual int ProgressFunc(Ember& ember, void* foo, double fraction, int stage, double etaMs); + virtual void ClearUndo(); + virtual GLEmberControllerBase* GLController() { return m_GLController.get(); } + +private: + //Embers. + void ApplyXmlSavingTemplate(Ember& ember); + template void SetEmberPrivate(const Ember& ember, bool verbatim); + + //Params. + void ParamsToEmber(Ember& ember); + + //Xforms. + void SetNormalizedWeightText(Xform* xform); + bool IsFinal(Xform* xform); + + //Xforms Color. + void SetCurrentXformColorIndex(double d); + + //Palette. + void UpdateAdjustedPaletteGUI(Palette& palette); + + //Rendering/progress. + void Update(std::function func, bool updateRender = true, eProcessAction action = FULL_RENDER); + void UpdateCurrentXform(std::function*)> func, bool updateRender = true, eProcessAction action = FULL_RENDER); + + //Templated members. + bool m_PreviewRun; + bool m_PreviewRunning; + vector m_TempOpacities; + vector m_NormalizedWeights; + Ember m_Ember; + EmberFile m_EmberFile; + deque> m_UndoList; + Palette m_TempPalette; + PaletteList m_PaletteList; + VariationList m_VariationList; + auto_ptr> m_SheepTools; + auto_ptr> m_GLController; + auto_ptr> m_PreviewRenderer; + QFuture m_PreviewResult; + std::function m_PreviewRenderFunc; +}; + +template class FractoriumEmberController; + +#ifdef DO_DOUBLE + template class FractoriumEmberController; +#endif \ No newline at end of file diff --git a/Source/Fractorium/FractoriumInfo.cpp b/Source/Fractorium/FractoriumInfo.cpp new file mode 100644 index 0000000..fad2927 --- /dev/null +++ b/Source/Fractorium/FractoriumInfo.cpp @@ -0,0 +1,58 @@ +#include "FractoriumPch.h" +#include "Fractorium.h" + +/// +/// Update the histogram bounds display labels. +/// This shows the user the actual bounds of what's +/// being rendered. Mostly of engineering interest. +/// +void Fractorium::UpdateHistogramBounds() +{ + if (RendererBase* r = m_Controller->Renderer()) + { + sprintf_s(m_ULString, 32, "UL: %3.3f, %3.3f", r->LowerLeftX(), r->UpperRightY());//These bounds include gutter padding. + sprintf_s(m_URString, 32, "UR: %3.3f, %3.3f", -r->LowerLeftX(), r->UpperRightY()); + sprintf_s(m_LRString, 32, "LR: %3.3f, %3.3f", -r->LowerLeftX(), -r->UpperRightY()); + sprintf_s(m_LLString, 32, "LL: %3.3f, %3.3f", r->LowerLeftX(), -r->UpperRightY()); + sprintf_s(m_WString, 16, "W: %4d" , r->SuperRasW()); + sprintf_s(m_HString, 16, "H: %4d" , r->SuperRasH()); + + ui.InfoBoundsLabelUL->setText(QString(m_ULString)); + ui.InfoBoundsLabelUR->setText(QString(m_URString)); + ui.InfoBoundsLabelLR->setText(QString(m_LRString)); + ui.InfoBoundsLabelLL->setText(QString(m_LLString)); + ui.InfoBoundsLabelW->setText(QString(m_WString)); + ui.InfoBoundsLabelH->setText(QString(m_HString)); + + ui.InfoBoundsTable->item(0, 1)->setText(QString::number(r->GutterWidth())); + + if (r->GetDensityFilter()) + { + unsigned int deWidth = (r->GetDensityFilter()->FilterWidth() * 2) + 1; + + sprintf_s(m_DEString, 16, "%d x %d", deWidth, deWidth); + ui.InfoBoundsTable->item(1, 1)->setText(QString(m_DEString)); + } + else + ui.InfoBoundsTable->item(1, 1)->setText("N/A"); + } +} + +/// +/// Fill the passed in QTextEdit with the vector of strings. +/// Optionally clear first. +/// Serves as a convenience function because the error reports coming +/// from Ember and EmberCL use vector. +/// Use invokeMethod() in case this is called from a thread. +/// +/// The vector of error strings +/// The QTextEdit to fill +/// Clear if true, else don't. +void Fractorium::ErrorReportToQTextEdit(vector& errors, QTextEdit* textEdit, bool clear) +{ + if (clear) + QMetaObject::invokeMethod(textEdit, "clear", Qt::QueuedConnection); + + for (size_t i = 0; i < errors.size(); i++) + QMetaObject::invokeMethod(textEdit, "append", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(errors[i]) + "\n")); +} \ No newline at end of file diff --git a/Source/Fractorium/FractoriumLibrary.cpp b/Source/Fractorium/FractoriumLibrary.cpp new file mode 100644 index 0000000..555f018 --- /dev/null +++ b/Source/Fractorium/FractoriumLibrary.cpp @@ -0,0 +1,277 @@ +#include "FractoriumPch.h" +#include "Fractorium.h" + +/// +/// Initialize the library tree UI. +/// +void Fractorium::InitLibraryUI() +{ + connect(ui.LibraryTree, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this, SLOT(OnEmberTreeItemChanged(QTreeWidgetItem*, int)), Qt::QueuedConnection); + connect(ui.LibraryTree, SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)), this, SLOT(OnEmberTreeItemDoubleClicked(QTreeWidgetItem*, int)), Qt::QueuedConnection); +} + +/// +/// Slot function to be called via QMetaObject::invokeMethod() to update preview images in the preview thread. +/// +/// The item double clicked on +/// The vector holding the RGBA bitmap +/// The width of the bitmap +/// The height of the bitmap +void Fractorium::SetLibraryTreeItemData(EmberTreeWidgetItemBase* item, vector& v, unsigned int width, unsigned int height) +{ + item->SetImage(v, width, height); +} + +/// +/// Set all libary tree entries to the name of the corresponding ember they represent. +/// +template +void FractoriumEmberController::SyncNames() +{ + EmberTreeWidgetItem* item; + QTreeWidget* tree = m_Fractorium->ui.LibraryTree; + QTreeWidgetItem* top = tree->topLevelItem(0); + + tree->blockSignals(true); + + if (top) + { + for (int i = 0; i < top->childCount(); i++)//Iterate through all of the children, which will represent the open embers. + { + if ((item = dynamic_cast*>(top->child(i))) && i < m_EmberFile.m_Embers.size())//Cast the child widget to the EmberTreeWidgetItem type. + item->setText(0, QString::fromStdString(m_EmberFile.m_Embers[i].m_Name)); + } + } + + tree->blockSignals(false); +} + +/// +/// Fill the library tree with the names of the embers in the +/// currently opened file. +/// Start preview render thread. +/// +/// After the tree is filled, select this index. Pass -1 to omit selecting an index. +template +void FractoriumEmberController::FillLibraryTree(int selectIndex) +{ + unsigned int i, j, size = 64; + QTreeWidget* tree = m_Fractorium->ui.LibraryTree; + vector v(size * size * 4); + + StopPreviewRender(); + tree->clear(); + QCoreApplication::flush(); + + tree->blockSignals(true); + + QTreeWidgetItem* fileItem = new QTreeWidgetItem(tree); + QFileInfo info(m_EmberFile.m_Filename); + + fileItem->setText(0, info.fileName()); + fileItem->setToolTip(0, m_EmberFile.m_Filename); + fileItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable); + + for (j = 0; j < m_EmberFile.m_Embers.size(); j++) + { + Ember* ember = &m_EmberFile.m_Embers[j]; + EmberTreeWidgetItem* emberItem = new EmberTreeWidgetItem(ember, fileItem); + + emberItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable); + + if (ember->m_Name.empty()) + emberItem->setText(0, QString::number(j)); + else + emberItem->setText(0, ember->m_Name.c_str()); + + emberItem->setToolTip(0, emberItem->text(0)); + emberItem->SetImage(v, size, size); + } + + tree->blockSignals(false); + + if (selectIndex != -1) + if (QTreeWidgetItem* top = tree->topLevelItem(0)) + if (EmberTreeWidgetItem* emberItem = dynamic_cast*>(top->child(selectIndex))) + emberItem->setSelected(true); + + QCoreApplication::flush(); + RenderPreviews(0, m_EmberFile.m_Embers.size()); + tree->expandAll(); +} + +/// +/// Update the library tree with the newly added embers (most likely from pasting) and +/// only render previews for the new ones, without clearing the entire tree. +/// +template +void FractoriumEmberController::UpdateLibraryTree() +{ + unsigned int i, size = 64; + QTreeWidget* tree = m_Fractorium->ui.LibraryTree; + vector v(size * size * 4); + + if (QTreeWidgetItem* top = tree->topLevelItem(0)) + { + int childCount = top->childCount(); + + tree->blockSignals(true); + + for (i = childCount; i < m_EmberFile.m_Embers.size(); i++) + { + Ember* ember = &m_EmberFile.m_Embers[i]; + EmberTreeWidgetItem* emberItem = new EmberTreeWidgetItem(ember, top); + + emberItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable); + + if (ember->m_Name.empty()) + emberItem->setText(0, QString::number(i)); + else + emberItem->setText(0, ember->m_Name.c_str()); + + emberItem->setToolTip(0, emberItem->text(0)); + emberItem->SetImage(v, size, size); + } + + //When adding elements to the vector, they may have been reshuffled which will have invalidated + //the pointers contained in the EmberTreeWidgetItems. So reassign all pointers here. + for (i = 0; i < m_EmberFile.m_Embers.size(); i++) + { + if (EmberTreeWidgetItem* emberItem = dynamic_cast*>(top->child(i))) + emberItem->SetEmberPointer(&m_EmberFile.m_Embers[i]); + } + + tree->blockSignals(false); + RenderPreviews(childCount, m_EmberFile.m_Embers.size()); + } +} + +/// +/// Copy the text of the item which was changed to the name of the current ember. +/// Ensure all names are unique in the opened file. +/// This seems to be called spuriously, so we do a check inside to make sure +/// the text was actually changed. +/// We also have to wrap the dynamic_cast call in a try/catch block because this can +/// be called on a widget that has already been deleted. +/// +/// The libary tree item changed +/// The column clicked, ignored. +template +void FractoriumEmberController::EmberTreeItemChanged(QTreeWidgetItem* item, int col) +{ + try + { + QTreeWidget* tree = m_Fractorium->ui.LibraryTree; + EmberTreeWidgetItem* emberItem = dynamic_cast*>(item); + + if (emberItem) + { + string oldName = emberItem->GetEmber()->m_Name;//First preserve the previous name. + + tree->blockSignals(true); + emberItem->UpdateEmberName();//Copy edit text to the ember's name variable. + m_EmberFile.MakeNamesUnique();//Ensure all names remain unique. + SyncNames();//Copy all ember names to the tree items since some might have changed to be made unique. + string newName = emberItem->GetEmber()->m_Name;//Get the new, final, unique name. + + if (m_Ember.m_Name == oldName && oldName != newName)//If the ember edited was the current one, and the name was indeed changed, update the name of the current one. + { + m_Ember.m_Name = newName; + m_LastSaveCurrent = "";//Reset will force the dialog to show on the next save current since the user probably wants a different name. + } + + tree->blockSignals(false); + } + else if (QTreeWidgetItem* parentItem = dynamic_cast(item)) + { + QString text = parentItem->text(0); + + if (text != "") + { + m_EmberFile.m_Filename = text; + m_LastSaveAll = "";//Reset will force the dialog to show on the next save all since the user probably wants a different name. + } + } + } + catch(std::exception& e) + { + qDebug() << "FractoriumEmberController::EmberTreeItemChanged() : Exception thrown: " << e.what(); + } +} + +void Fractorium::OnEmberTreeItemChanged(QTreeWidgetItem* item, int col) { m_Controller->EmberTreeItemChanged(item, col); } + +/// +/// Set the current ember to the selected item. +/// Clears the undo state. +/// Resets the rendering process. +/// Called when the user double clicks on a library tree item. +/// +/// The item double clicked on +/// The column clicked, ignored. +template +void FractoriumEmberController::EmberTreeItemDoubleClicked(QTreeWidgetItem* item, int col) +{ + if (EmberTreeWidgetItem* emberItem = dynamic_cast*>(item)) + { + ClearUndo(); + SetEmber(*emberItem->GetEmber()); + } +} + +void Fractorium::OnEmberTreeItemDoubleClicked(QTreeWidgetItem* item, int col) { m_Controller->EmberTreeItemDoubleClicked(item, col); } + +/// +/// Stop the preview renderer if it's already running. +/// Clear all of the existing preview images, then start the preview rendering thread. +/// Optionally only render previews for a subset of all open embers. +/// +/// The 0-based index to start rendering previews for +/// The 0-based index which is one beyond the last ember to render a preview for +template +void FractoriumEmberController::RenderPreviews(unsigned int start, unsigned int end) +{ + StopPreviewRender(); + + if (start == UINT_MAX && end == UINT_MAX) + { + QTreeWidget* tree = m_Fractorium->ui.LibraryTree; + + tree->blockSignals(true); + + if (QTreeWidgetItem* top = tree->topLevelItem(0)) + { + int childCount = top->childCount(); + vector emptyPreview(PREVIEW_SIZE * PREVIEW_SIZE * 3); + + for (int i = 0; i < childCount; i++) + if (EmberTreeWidgetItem* treeItem = dynamic_cast*>(top->child(i))) + treeItem->SetImage(emptyPreview, PREVIEW_SIZE, PREVIEW_SIZE); + } + + tree->blockSignals(false); + m_PreviewResult = QtConcurrent::run(m_PreviewRenderFunc, 0, m_EmberFile.m_Embers.size()); + } + else + m_PreviewResult = QtConcurrent::run(m_PreviewRenderFunc, start, end); +} + +/// +/// Stop the preview rendering thread. +/// +template +void FractoriumEmberController::StopPreviewRender() +{ + m_PreviewRun = false; + + while (m_PreviewRunning) + QApplication::processEvents(); + + m_PreviewResult.cancel(); + + while (m_PreviewResult.isRunning()) + QApplication::processEvents(); + + QCoreApplication::sendPostedEvents(m_Fractorium->ui.LibraryTree); + QCoreApplication::flush(); +} diff --git a/Source/Fractorium/FractoriumMenus.cpp b/Source/Fractorium/FractoriumMenus.cpp new file mode 100644 index 0000000..1ef86c8 --- /dev/null +++ b/Source/Fractorium/FractoriumMenus.cpp @@ -0,0 +1,752 @@ +#include "FractoriumPch.h" +#include "Fractorium.h" + +/// +/// Initialize the menus UI. +/// +void Fractorium::InitMenusUI() +{ + //File menu. + connect(ui.ActionNewFlock, SIGNAL(triggered(bool)), this, SLOT(OnActionNewFlock(bool)), Qt::QueuedConnection); + connect(ui.ActionNewEmptyFlameInCurrentFile, SIGNAL(triggered(bool)), this, SLOT(OnActionNewEmptyFlameInCurrentFile(bool)), Qt::QueuedConnection); + connect(ui.ActionNewRandomFlameInCurrentFile, SIGNAL(triggered(bool)), this, SLOT(OnActionNewRandomFlameInCurrentFile(bool)), Qt::QueuedConnection); + connect(ui.ActionCopyFlameInCurrentFile, SIGNAL(triggered(bool)), this, SLOT(OnActionCopyFlameInCurrentFile(bool)), Qt::QueuedConnection); + connect(ui.ActionOpen, SIGNAL(triggered(bool)), this, SLOT(OnActionOpen(bool)), Qt::QueuedConnection); + connect(ui.ActionSaveCurrentAsXml, SIGNAL(triggered(bool)), this, SLOT(OnActionSaveCurrentAsXml(bool)), Qt::QueuedConnection); + connect(ui.ActionSaveEntireFileAsXml, SIGNAL(triggered(bool)), this, SLOT(OnActionSaveEntireFileAsXml(bool)), Qt::QueuedConnection); + connect(ui.ActionSaveCurrentToOpenedFile, SIGNAL(triggered(bool)), this, SLOT(OnActionSaveCurrentToOpenedFile(bool)), Qt::QueuedConnection); + connect(ui.ActionSaveCurrentScreen, SIGNAL(triggered(bool)), this, SLOT(OnActionSaveCurrentScreen(bool)), Qt::QueuedConnection); + connect(ui.ActionExit, SIGNAL(triggered(bool)), this, SLOT(OnActionExit(bool)), Qt::QueuedConnection); + + //Edit menu. + connect(ui.ActionUndo, SIGNAL(triggered(bool)), this, SLOT(OnActionUndo(bool)), Qt::QueuedConnection); + connect(ui.ActionRedo, SIGNAL(triggered(bool)), this, SLOT(OnActionRedo(bool)), Qt::QueuedConnection); + connect(ui.ActionCopyXml, SIGNAL(triggered(bool)), this, SLOT(OnActionCopyXml(bool)), Qt::QueuedConnection); + connect(ui.ActionCopyAllXml, SIGNAL(triggered(bool)), this, SLOT(OnActionCopyAllXml(bool)), Qt::QueuedConnection); + connect(ui.ActionPasteXmlAppend, SIGNAL(triggered(bool)), this, SLOT(OnActionPasteXmlAppend(bool)), Qt::QueuedConnection); + connect(ui.ActionPasteXmlOver, SIGNAL(triggered(bool)), this, SLOT(OnActionPasteXmlOver(bool)), Qt::QueuedConnection); + + //Tools menu. + connect(ui.ActionAddReflectiveSymmetry, SIGNAL(triggered(bool)), this, SLOT(OnActionAddReflectiveSymmetry(bool)), Qt::QueuedConnection); + connect(ui.ActionAddRotationalSymmetry, SIGNAL(triggered(bool)), this, SLOT(OnActionAddRotationalSymmetry(bool)), Qt::QueuedConnection); + connect(ui.ActionAddBothSymmetry, SIGNAL(triggered(bool)), this, SLOT(OnActionAddBothSymmetry(bool)), Qt::QueuedConnection); + connect(ui.ActionClearFlame, SIGNAL(triggered(bool)), this, SLOT(OnActionClearFlame(bool)), Qt::QueuedConnection); + connect(ui.ActionStopRenderingPreviews, SIGNAL(triggered(bool)), this, SLOT(OnActionStopRenderingPreviews(bool)), Qt::QueuedConnection); + connect(ui.ActionRenderPreviews, SIGNAL(triggered(bool)), this, SLOT(OnActionRenderPreviews(bool)), Qt::QueuedConnection); + connect(ui.ActionFinalRender, SIGNAL(triggered(bool)), this, SLOT(OnActionFinalRender(bool)), Qt::QueuedConnection); + connect(m_FinalRenderDialog, SIGNAL(finished(int)), this, SLOT(OnFinalRenderClose(int)), Qt::QueuedConnection); + connect(ui.ActionOptions, SIGNAL(triggered(bool)), this, SLOT(OnActionOptions(bool)), Qt::QueuedConnection); + + //Help menu. + connect(ui.ActionAbout, SIGNAL(triggered(bool)), this, SLOT(OnActionAbout(bool)), Qt::QueuedConnection); +} + +/// +/// Create a new flock of random embers, with the specified length. +/// +/// The number of embers to include in the flock +template +void FractoriumEmberController::NewFlock(unsigned int count) +{ + Ember ember; + + StopPreviewRender(); + m_EmberFile.Clear(); + m_EmberFile.m_Embers.reserve(count); + m_EmberFile.m_Filename = EmberFile::DefaultFilename(); + + for (unsigned int i = 0; i < count; i++) + { + m_SheepTools->Random(ember); + ParamsToEmber(ember); + ember.m_OrigFinalRasW = ember.m_FinalRasW; + ember.m_OrigFinalRasH = ember.m_FinalRasH; + ember.m_Name = m_EmberFile.m_Filename.toStdString() + "-" + QString::number(i + 1).toStdString(); + m_EmberFile.m_Embers.push_back(ember); + } + + m_LastSaveAll = ""; + FillLibraryTree(); +} + +/// +/// Create a new flock and assign the first ember as the current one. +/// +/// Ignored +void Fractorium::OnActionNewFlock(bool checked) +{ + m_Controller->NewFlock(10); + m_Controller->SetEmber(0); +} + +/// +/// Create and add a new empty ember in the currently opened file +/// and set it as the current one. +/// It will have one empty xform in it. +/// Resets the rendering process. +/// +template +void FractoriumEmberController::NewEmptyFlameInCurrentFile() +{ + Ember ember; + Xform xform; + QDateTime local(QDateTime::currentDateTime()); + + StopPreviewRender(); + ParamsToEmber(ember); + ember.m_OrigFinalRasW = ember.m_FinalRasW; + ember.m_OrigFinalRasH = ember.m_FinalRasH; + xform.m_Weight = T(0.25); + xform.m_ColorX = m_Rand.Frand01(); + ember.AddXform(xform); + ember.m_Palette = *m_PaletteList.GetPalette(-1); + ember.m_Name = EmberFile::DefaultEmberName(m_EmberFile.m_Embers.size() + 1).toStdString(); + m_EmberFile.m_Embers.push_back(ember);//Will invalidate the pointers contained in the EmberTreeWidgetItems, UpdateLibraryTree() will resync. + m_EmberFile.MakeNamesUnique(); + UpdateLibraryTree(); + SetEmber(m_EmberFile.m_Embers.size() - 1); +} + +void Fractorium::OnActionNewEmptyFlameInCurrentFile(bool checked) { m_Controller->NewEmptyFlameInCurrentFile(); } + +/// +/// Create and add a new random ember in the currently opened file +/// and set it as the current one. +/// Resets the rendering process. +/// +template +void FractoriumEmberController::NewRandomFlameInCurrentFile() +{ + Ember ember; + + StopPreviewRender(); + m_SheepTools->Random(ember); + ParamsToEmber(ember); + ember.m_OrigFinalRasW = ember.m_FinalRasW; + ember.m_OrigFinalRasH = ember.m_FinalRasH; + ember.m_Name = EmberFile::DefaultEmberName(m_EmberFile.m_Embers.size() + 1).toStdString(); + m_EmberFile.m_Embers.push_back(ember);//Will invalidate the pointers contained in the EmberTreeWidgetItems, UpdateLibraryTree() will resync. + m_EmberFile.MakeNamesUnique(); + UpdateLibraryTree(); + SetEmber(m_EmberFile.m_Embers.size() - 1); +} + +void Fractorium::OnActionNewRandomFlameInCurrentFile(bool checked) { m_Controller->NewRandomFlameInCurrentFile(); } + +/// +/// Create and add a a copy of the current ember in the currently opened file +/// and set it as the current one. +/// Resets the rendering process. +/// +template +void FractoriumEmberController::CopyFlameInCurrentFile() +{ + Ember ember = m_Ember; + + StopPreviewRender(); + ember.m_Name = EmberFile::DefaultEmberName(m_EmberFile.m_Embers.size() + 1).toStdString(); + m_EmberFile.m_Embers.push_back(ember);//Will invalidate the pointers contained in the EmberTreeWidgetItems, UpdateLibraryTree() will resync. + m_EmberFile.MakeNamesUnique(); + UpdateLibraryTree(); + SetEmber(m_EmberFile.m_Embers.size() - 1); +} + +void Fractorium::OnActionCopyFlameInCurrentFile(bool checked) { m_Controller->CopyFlameInCurrentFile(); } + +/// +/// Open a list of ember Xml files, apply various values from the GUI widgets. +/// Either append these newly read embers to the existing open embers, +/// or clear the current ember file first. +/// When appending, add the new embers the the end of library tree. +/// When not appending, clear and populate the library tree with the new embers. +/// Set the current ember to the first one in the newly opened list. +/// Clears the undo state. +/// Resets the rendering process. +/// +/// A list of full paths and filenames +/// True to append the embers in the new files to the end of the currently open embers, false to clear and replace them +template +void FractoriumEmberController::OpenAndPrepFiles(QStringList filenames, bool append) +{ + if (!filenames.empty()) + { + size_t i; + EmberFile emberFile; + XmlToEmber parser; + vector> embers; + unsigned int previousSize = append ? m_EmberFile.m_Embers.size() : 0; + + StopPreviewRender(); + emberFile.m_Filename = filenames[0]; + + foreach (QString filename, filenames) + { + embers.clear(); + + if (parser.Parse(filename.toStdString().c_str(), embers) && !embers.empty()) + { + //Disregard whatever size was specified in the file and fit it to the output window size. + for (i = 0; i < embers.size(); i++) + { + embers[i].SetSizeAndAdjustScale(m_Fractorium->ui.GLDisplay->width(), m_Fractorium->ui.GLDisplay->height(), true, SCALE_WIDTH); + + //Also ensure it has a name. + if (embers[i].m_Name == "" || embers[i].m_Name == "No name") + embers[i].m_Name = QString::number(i).toStdString(); + + embers[i].m_Quality = m_Fractorium->m_QualitySpin->value(); + embers[i].m_Supersample = m_Fractorium->m_SupersampleSpin->value(); + } + + m_LastSaveAll = ""; + emberFile.m_Embers.insert(emberFile.m_Embers.end(), embers.begin(), embers.end()); + } + else + { + vector errors = parser.ErrorReport(); + + m_Fractorium->ErrorReportToQTextEdit(errors, m_Fractorium->ui.InfoFileOpeningTextEdit); + QMessageBox::critical(m_Fractorium, "Open Failed", "Could not open file, see info tab for details."); + } + } + + if (append) + { + if (m_EmberFile.m_Filename == "") + m_EmberFile.m_Filename = filenames[0]; + + m_EmberFile.m_Embers.insert(m_EmberFile.m_Embers.end(), emberFile.m_Embers.begin(), emberFile.m_Embers.end()); + } + else + m_EmberFile = emberFile; + + //Resync indices and names. + for (i = 0; i < m_EmberFile.m_Embers.size(); i++) + m_EmberFile.m_Embers[i].m_Index = i; + + m_EmberFile.MakeNamesUnique(); + + if (append) + UpdateLibraryTree(); + else + FillLibraryTree(append ? previousSize - 1 : 0); + + ClearUndo(); + SetEmber(previousSize); + } +} + +/// +/// Show a file open dialog to open ember Xml files. +/// +/// Ignored +void Fractorium::OnActionOpen(bool checked) { m_Controller->OpenAndPrepFiles(SetupOpenXmlDialog(), false); } + +/// +/// Save current ember as Xml, using the Xml saving template values from the options. +/// This will first save the current ember back to the opened file in memory before +/// saving it to disk. +/// +template +void FractoriumEmberController::SaveCurrentAsXml() +{ + QString filename; + FractoriumSettings* s = m_Fractorium->m_Settings; + + if (QFile::exists(m_LastSaveCurrent)) + { + filename = m_LastSaveCurrent; + } + else + { + if (m_EmberFile.m_Embers.size() == 1) + filename = m_Fractorium->SetupSaveXmlDialog(m_EmberFile.m_Filename);//If only one ember present, just use parent filename. + else + filename = m_Fractorium->SetupSaveXmlDialog(QString::fromStdString(m_Ember.m_Name));//More than one ember present, use individual ember name. + } + + if (filename != "") + { + Ember ember = m_Ember; + EmberToXml writer; + QFileInfo fileInfo(filename); + xmlDocPtr tempEdit = ember.m_Edits; + + SaveCurrentToOpenedFile();//Save the current ember back to the opened file before writing to disk. + ApplyXmlSavingTemplate(ember); + ember.m_Edits = writer.CreateNewEditdoc(&ember, NULL, "edit", s->Nick().toStdString(), s->Url().toStdString(), s->Id().toStdString(), "", 0, 0); + + if (tempEdit != NULL) + xmlFreeDoc(tempEdit); + + if (writer.Save(filename.toStdString().c_str(), ember, 0, true, false, true)) + { + s->SaveFolder(fileInfo.canonicalPath()); + m_LastSaveCurrent = filename; + } + else + QMessageBox::critical(m_Fractorium, "Save Failed", "Could not save file, try saving to a different folder."); + } +} + +void Fractorium::OnActionSaveCurrentAsXml(bool checked) { m_Controller->SaveCurrentAsXml(); } + +/// +/// Save entire opened file Xml, using the Xml saving template values from the options on each ember. +/// This will first save the current ember back to the opened file in memory before +/// saving all to disk. +/// +template +void FractoriumEmberController::SaveEntireFileAsXml() +{ + QString filename; + FractoriumSettings* s = m_Fractorium->m_Settings; + + if (QFile::exists(m_LastSaveAll)) + filename = m_LastSaveAll; + else + filename = m_Fractorium->SetupSaveXmlDialog(m_EmberFile.m_Filename); + + if (filename != "") + { + EmberFile emberFile; + EmberToXml writer; + QFileInfo fileInfo(filename); + + SaveCurrentToOpenedFile();//Save the current ember back to the opened file before writing to disk. + emberFile = m_EmberFile; + + for (size_t i = 0; i < emberFile.m_Embers.size(); i++) + ApplyXmlSavingTemplate(emberFile.m_Embers[i]); + + if (writer.Save(filename.toStdString().c_str(), emberFile.m_Embers, 0, true, false, true)) + { + m_LastSaveAll = filename; + s->SaveFolder(fileInfo.canonicalPath()); + } + else + QMessageBox::critical(m_Fractorium, "Save Failed", "Could not save file, try saving to a different folder."); + } +} + +void Fractorium::OnActionSaveEntireFileAsXml(bool checked) { m_Controller->SaveEntireFileAsXml(); } + +/// +/// Show a file save dialog and save what is currently shown in the render window to disk as an image. +/// +/// Ignored +void Fractorium::OnActionSaveCurrentScreen(bool checked) +{ + QString filename = SetupSaveImageDialog(QString::fromStdString(m_Controller->Name())); + + m_Controller->SaveCurrentRender(filename); +} + +/// +/// Save the current ember back to its position in the opened file. +/// This does not save to disk. +/// +template +void FractoriumEmberController::SaveCurrentToOpenedFile() +{ + size_t i; + bool fileFound = false; + + for (i = 0; i < m_EmberFile.m_Embers.size(); i++) + { + if (m_Ember.m_Name == m_EmberFile.m_Embers[i].m_Name) + { + m_EmberFile.m_Embers[i] = m_Ember; + fileFound = true; + break; + } + } + + if (!fileFound) + { + StopPreviewRender(); + m_EmberFile.m_Embers.push_back(m_Ember); + m_EmberFile.MakeNamesUnique(); + UpdateLibraryTree(); + } + else + { + RenderPreviews(i, i + 1); + } +} + +void Fractorium::OnActionSaveCurrentToOpenedFile(bool checked) { m_Controller->SaveCurrentToOpenedFile(); } + +/// +/// Exit the application. +/// +/// Ignore. +void Fractorium::OnActionExit(bool checked) +{ + closeEvent(NULL); + QApplication::exit(); +} + +/// +/// Undoes this instance. +/// +template +void FractoriumEmberController::Undo() +{ + if (m_UndoList.size() > 1 && m_UndoIndex > 0) + { + int index = m_Ember.GetTotalXformIndex(CurrentXform()); + + m_LastEditWasUndoRedo = true; + m_UndoIndex = max(0u, m_UndoIndex - 1u); + SetEmber(m_UndoList[m_UndoIndex], true); + m_EditState = UNDO_REDO; + + if (index >= 0) + m_Fractorium->CurrentXform(index); + + m_Fractorium->ui.ActionUndo->setEnabled(m_UndoList.size() > 1 && (m_UndoIndex > 0)); + m_Fractorium->ui.ActionRedo->setEnabled(m_UndoList.size() > 1 && !(m_UndoIndex == m_UndoList.size() - 1)); + } +} + +void Fractorium::OnActionUndo(bool checked) { m_Controller->Undo(); } + +/// +/// Redoes this instance. +/// +template +void FractoriumEmberController::Redo() +{ + if (m_UndoList.size() > 1 && m_UndoIndex < m_UndoList.size() - 1) + { + int index = m_Ember.GetTotalXformIndex(CurrentXform()); + + m_LastEditWasUndoRedo = true; + m_UndoIndex = min(m_UndoIndex + 1, m_UndoList.size() - 1); + SetEmber(m_UndoList[m_UndoIndex], true); + m_EditState = UNDO_REDO; + + if (index >= 0) + m_Fractorium->CurrentXform(index); + + m_Fractorium->ui.ActionUndo->setEnabled(m_UndoList.size() > 1 && (m_UndoIndex > 0)); + m_Fractorium->ui.ActionRedo->setEnabled(m_UndoList.size() > 1 && !(m_UndoIndex == m_UndoList.size() - 1)); + } +} + +void Fractorium::OnActionRedo(bool checked) { m_Controller->Redo(); } + +/// +/// Copy the current ember Xml to the clipboard. +/// Apply Xml saving settings from the options first. +/// +template +void FractoriumEmberController::CopyXml() +{ + Ember ember = m_Ember; + EmberToXml emberToXml; + FractoriumSettings* settings = m_Fractorium->m_Settings; + + ember.m_FinalRasW = settings->XmlWidth(); + ember.m_FinalRasH = settings->XmlHeight(); + ember.m_Quality = settings->XmlQuality(); + ember.m_Supersample = settings->XmlSupersample(); + ember.m_TemporalSamples = settings->XmlTemporalSamples(); + QApplication::clipboard()->setText(QString::fromStdString(emberToXml.ToString(ember, "", 0, false, false, true))); +} + +void Fractorium::OnActionCopyXml(bool checked) { m_Controller->CopyXml(); } + +/// +/// Copy the Xmls for all open embers as a single string to the clipboard, enclosed with the tag. +/// Apply Xml saving settings from the options first for each ember. +/// +template +void FractoriumEmberController::CopyAllXml() +{ + ostringstream os; + EmberToXml emberToXml; + FractoriumSettings* settings = m_Fractorium->m_Settings; + + os << "\n"; + + for (size_t i = 0; i < m_EmberFile.m_Embers.size(); i++) + { + Ember ember = m_EmberFile.m_Embers[i]; + + ember.m_FinalRasW = settings->XmlWidth(); + ember.m_FinalRasH = settings->XmlHeight(); + ember.m_Quality = settings->XmlQuality(); + ember.m_Supersample = settings->XmlSupersample(); + ember.m_TemporalSamples = settings->XmlTemporalSamples(); + + os << emberToXml.ToString(ember, "", 0, false, false, true); + } + + os << "\n"; + QApplication::clipboard()->setText(QString::fromStdString(os.str())); +} + +void Fractorium::OnActionCopyAllXml(bool checked) { m_Controller->CopyAllXml(); } + +/// +/// Convert the Xml text from the clipboard to an ember, add it to the end +/// of the current file and set it as the current ember. If multiple Xmls were +/// copied to the clipboard and were enclosed in tags, then all of them will be added. +/// Clears the undo state. +/// Resets the rendering process. +/// +template +void FractoriumEmberController::PasteXmlAppend() +{ + unsigned int i, previousSize = m_EmberFile.m_Embers.size(); + string s, errors; + XmlToEmber parser; + vector> embers; + QTextCodec* codec = QTextCodec::codecForName("UTF-8"); + QByteArray b = codec->fromUnicode(QApplication::clipboard()->text()); + + s.reserve(b.size()); + + for (i = 0; i < b.size(); i++) + { + if ((unsigned int)b[i] < 128u) + s.push_back(b[i]); + } + + b.clear(); + StopPreviewRender(); + parser.Parse((unsigned char*)s.c_str(), "", embers); + errors = parser.ErrorReportString(); + + if (errors != "") + { + QMessageBox::critical(m_Fractorium, "Paste Error", QString::fromStdString(errors)); + } + + if (!embers.empty()) + { + for (i = 0; i < embers.size(); i++) + { + //Disregard whatever size was specified in the file and fit it to the output window size. + embers[i].m_Index = m_EmberFile.m_Embers.size(); + embers[i].SetSizeAndAdjustScale(m_Fractorium->ui.GLDisplay->width(), m_Fractorium->ui.GLDisplay->height(), true, SCALE_WIDTH); + + //Also ensure it has a name. + if (embers[i].m_Name == "" || embers[i].m_Name == "No name") + embers[i].m_Name = QString::number(embers[i].m_Index).toStdString(); + + m_EmberFile.m_Embers.push_back(embers[i]);//Will invalidate the pointers contained in the EmberTreeWidgetItems, UpdateLibraryTree() will resync. + } + + m_EmberFile.MakeNamesUnique(); + UpdateLibraryTree(); + SetEmber(previousSize); + } +} + +void Fractorium::OnActionPasteXmlAppend(bool checked) { m_Controller->PasteXmlAppend(); } + +/// +/// Convert the Xml text from the clipboard to an ember, overwrite the +/// current file and set the first as the current ember. If multiple Xmls were +/// copied to the clipboard and were enclosed in tags, then the current file will contain all of them. +/// +template +void FractoriumEmberController::PasteXmlOver() +{ + unsigned int i; + string s, errors; + XmlToEmber parser; + Ember backupEmber = m_EmberFile.m_Embers[0]; + QTextCodec* codec = QTextCodec::codecForName("UTF-8"); + QByteArray b = codec->fromUnicode(QApplication::clipboard()->text()); + + s.reserve(b.size()); + + for (i = 0; i < b.size(); i++) + { + if ((unsigned int)b[i] < 128u) + s.push_back(b[i]); + } + + b.clear(); + StopPreviewRender(); + m_EmberFile.m_Embers.clear();//Will invalidate the pointers contained in the EmberTreeWidgetItems, UpdateLibraryTree() will resync. + parser.Parse((unsigned char*)s.c_str(), "", m_EmberFile.m_Embers); + errors = parser.ErrorReportString(); + + if (errors != "") + { + QMessageBox::critical(m_Fractorium, "Paste Error", QString::fromStdString(errors)); + } + + if (!m_EmberFile.m_Embers.empty()) + { + for (i = 0; i < m_EmberFile.m_Embers.size(); i++) + { + //Disregard whatever size was specified in the file and fit it to the output window size. + m_EmberFile.m_Embers[i].m_Index = i; + m_EmberFile.m_Embers[i].SetSizeAndAdjustScale(m_Fractorium->ui.GLDisplay->width(), m_Fractorium->ui.GLDisplay->height(), true, SCALE_WIDTH); + + //Also ensure it has a name. + if (m_EmberFile.m_Embers[i].m_Name == "" || m_EmberFile.m_Embers[i].m_Name == "No name") + m_EmberFile.m_Embers[i].m_Name = QString::number(m_EmberFile.m_Embers[i].m_Index).toStdString(); + } + } + else + { + backupEmber.m_Index = 0; + m_EmberFile.m_Embers.push_back(backupEmber); + } + + m_EmberFile.MakeNamesUnique(); + FillLibraryTree(); + SetEmber(0); +} + +void Fractorium::OnActionPasteXmlOver(bool checked) { m_Controller->PasteXmlOver(); } + +/// +/// Add reflective symmetry to the current ember. +/// Resets the rendering process. +/// +template +void FractoriumEmberController::AddReflectiveSymmetry() +{ + QComboBox* combo = m_Fractorium->ui.CurrentXformCombo; + + m_Ember.AddSymmetry(-1, m_Rand); + m_Fractorium->FillXforms(); + combo->setCurrentIndex(combo->count() - (m_Fractorium->HaveFinal() ? 2 : 1));//Set index to the last item before final. + UpdateRender(); +} + +void Fractorium::OnActionAddReflectiveSymmetry(bool checked) { m_Controller->AddReflectiveSymmetry(); } + +/// +/// Add rotational symmetry to the current ember. +/// Resets the rendering process. +/// +template +void FractoriumEmberController::AddRotationalSymmetry() +{ + QComboBox* combo = m_Fractorium->ui.CurrentXformCombo; + + m_Ember.AddSymmetry(2, m_Rand); + m_Fractorium->FillXforms(); + combo->setCurrentIndex(combo->count() - (m_Fractorium->HaveFinal() ? 2 : 1));//Set index to the last item before final. + UpdateRender(); +} + +void Fractorium::OnActionAddRotationalSymmetry(bool checked) { m_Controller->AddRotationalSymmetry(); } + +/// +/// Add both reflective and rotational symmetry to the current ember. +/// Resets the rendering process. +/// +template +void FractoriumEmberController::AddBothSymmetry() +{ + QComboBox* combo = m_Fractorium->ui.CurrentXformCombo; + + m_Ember.AddSymmetry(-2, m_Rand); + m_Fractorium->FillXforms(); + combo->setCurrentIndex(combo->count() - (m_Fractorium->HaveFinal() ? 2 : 1));//Set index to the last item before final. + UpdateRender(); +} + +void Fractorium::OnActionAddBothSymmetry(bool checked) { m_Controller->AddBothSymmetry(); } + +/// +/// Delete all but one xform in the current ember. +/// Clear that xform's variations. +/// Resets the rendering process. +/// +template +void FractoriumEmberController::ClearFlame() +{ + while (m_Ember.TotalXformCount() > 1) + m_Ember.DeleteTotalXform(m_Ember.TotalXformCount() - 1); + + if (m_Ember.XformCount() == 1) + { + if (Xform* xform = m_Ember.GetXform(0)) + { + xform->Clear(); + xform->ParentEmber(&m_Ember); + } + } + + m_Fractorium->FillXforms(); + m_Fractorium->ui.CurrentXformCombo->setCurrentIndex(0); + UpdateRender(); +} + +void Fractorium::OnActionClearFlame(bool checked) { m_Controller->ClearFlame(); } + +/// +/// Re-render all previews. +/// +void Fractorium::OnActionRenderPreviews(bool checked) +{ + m_Controller->RenderPreviews(); +} + +/// +/// Stop all previews from being rendered. This is handy if the user +/// opens a large file with many embers in it, such as an animation sequence. +/// +void Fractorium::OnActionStopRenderingPreviews(bool checked) { m_Controller->StopPreviewRender(); } + +/// +/// Show the final render dialog as a modeless dialog to allow +/// the user to minimize the main window while doing a lengthy final render. +/// Note: The user probably should not be otherwise interacting with the main GUI +/// while the final render is taking place. +/// +/// Ignored +void Fractorium::OnActionFinalRender(bool checked) +{ + //First completely stop what the current rendering process is doing. + m_Controller->DeleteRenderer();//Delete the renderer, but not the controller. + m_RenderStatusLabel->setText("Renderer stopped."); + m_FinalRenderDialog->show(); +} + +/// +/// Called when the final render dialog has been closed. +/// +/// Ignored +void Fractorium::OnFinalRenderClose(int result) +{ + m_RenderStatusLabel->setText("Renderer starting..."); + StartRenderTimer();//Re-create the renderer and start rendering again. +} + +/// +/// Show the final options dialog. +/// Restart rendering and sync options after the options dialog is dismissed with Ok. +/// Called when the options dialog is finished with ok. +/// +/// Ignored +void Fractorium::OnActionOptions(bool checked) +{ + if (m_OptionsDialog->exec()) + { + //First completely stop what the current rendering process is doing. + m_Controller->Shutdown(); + StartRenderTimer();//This will recreate the controller and/or the renderer from the options if necessary, then start the render timer. + m_Settings->sync(); + } +} + +/// +/// Show the about dialog. +/// +/// Ignored +void Fractorium::OnActionAbout(bool checked) +{ + m_AboutDialog->exec(); +} diff --git a/Source/Fractorium/FractoriumPalette.cpp b/Source/Fractorium/FractoriumPalette.cpp new file mode 100644 index 0000000..17a92c9 --- /dev/null +++ b/Source/Fractorium/FractoriumPalette.cpp @@ -0,0 +1,235 @@ +#include "FractoriumPch.h" +#include "Fractorium.h" + +#define PALETTE_CELL_HEIGHT 16 + +/// +/// Initialize the palette UI. +/// +void Fractorium::InitPaletteUI() +{ + int spinHeight = 20, row = 0; + QTableWidget* paletteTable = ui.PaletteListTable; + QTableWidget* palettePreviewTable = ui.PalettePreviewTable; + + connect(paletteTable, SIGNAL(cellClicked(int, int)), this, SLOT(OnPaletteCellClicked(int, int)), Qt::QueuedConnection); + connect(paletteTable, SIGNAL(cellDoubleClicked(int, int)), this, SLOT(OnPaletteCellDoubleClicked(int, int)), Qt::QueuedConnection); + + //Palette adjustment table. + QTableWidget* table = ui.PaletteAdjustTable; + table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);//Split width over all columns evenly. + + SetupSpinner(table, this, row, 1, m_PaletteHueSpin, spinHeight, -180, 180, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0); + SetupSpinner(table, this, row, 1, m_PaletteSaturationSpin, spinHeight, -100, 100, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0); + SetupSpinner(table, this, row, 1, m_PaletteBrightnessSpin, spinHeight, -255, 255, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0); + row = 0; + + SetupSpinner(table, this, row, 3, m_PaletteContrastSpin, spinHeight, -100, 100, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0); + SetupSpinner(table, this, row, 3, m_PaletteBlurSpin, spinHeight, 0, 127, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0); + SetupSpinner(table, this, row, 3, m_PaletteFrequencySpin, spinHeight, 1, 10, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 1, 1, 1); +} + +/// +/// Read a palette Xml file and populate the palette table with the contents. +/// This will clear any previous contents. +/// Called upon initialization, or controller type change. +/// +/// The full path to the palette file +/// True if successful, else false. +template +bool FractoriumEmberController::InitPaletteTable(string s) +{ + QTableWidget* paletteTable = m_Fractorium->ui.PaletteListTable; + QTableWidget* palettePreviewTable = m_Fractorium->ui.PalettePreviewTable; + + paletteTable->clear(); + + if (m_PaletteList.Init(s))//Default to this, but add an option later.//TODO + { + //Preview table. + palettePreviewTable->setRowCount(1); + palettePreviewTable->setColumnWidth(1, 260);//256 plus small margin on each side. + QTableWidgetItem* previewNameCol = new QTableWidgetItem(""); + palettePreviewTable->setItem(0, 0, previewNameCol); + QTableWidgetItem* previewPaletteItem = new QTableWidgetItem(); + palettePreviewTable->setItem(0, 1, previewPaletteItem); + + //Palette list table. + paletteTable->setRowCount(m_PaletteList.Count()); + paletteTable->setColumnWidth(1, 260);//256 plus small margin on each side. + paletteTable->horizontalHeader()->setSectionsClickable(false); + + //Headers get removed when clearing, so must re-create here. + QTableWidgetItem* nameHeader = new QTableWidgetItem("Name"); + QTableWidgetItem* paletteHeader = new QTableWidgetItem("Palette"); + + nameHeader->setTextAlignment(Qt::AlignLeft|Qt::AlignVCenter); + paletteHeader->setTextAlignment(Qt::AlignLeft|Qt::AlignVCenter); + + paletteTable->setHorizontalHeaderItem(0, nameHeader); + paletteTable->setHorizontalHeaderItem(1, paletteHeader); + + //Palette list table. + for (size_t i = 0; i < m_PaletteList.Count(); i++) + { + Palette* p = m_PaletteList.GetPalette(i); + vector v = p->MakeRgbPaletteBlock(PALETTE_CELL_HEIGHT); + QTableWidgetItem* nameCol = new QTableWidgetItem(p->m_Name.c_str()); + + nameCol->setToolTip(p->m_Name.c_str()); + paletteTable->setItem(i, 0, nameCol); + + QImage image(v.data(), p->Size(), PALETTE_CELL_HEIGHT, QImage::Format_RGB888); + QTableWidgetItem* paletteItem = new QTableWidgetItem(); + + paletteItem->setData(Qt::DecorationRole, QPixmap::fromImage(image)); + paletteTable->setItem(i, 1, paletteItem); + } + + return true; + } + else + { + vector errors = m_PaletteList.ErrorReport(); + + m_Fractorium->ErrorReportToQTextEdit(errors, m_Fractorium->ui.InfoFileOpeningTextEdit); + QMessageBox::critical(m_Fractorium, "Palette Read Error", "Could not load palette file, all images will be black. See info tab for details."); + } + + return false; +} + +/// +/// Apply adjustments to the current ember's palette. +/// +template +void FractoriumEmberController::ApplyPaletteToEmber() +{ + int i, rot = 0; + unsigned int blur = m_Fractorium->m_PaletteBlurSpin->value(); + unsigned int freq = m_Fractorium->m_PaletteFrequencySpin->value(); + double sat = (double)m_Fractorium->m_PaletteSaturationSpin->value() / 100.0; + double brightness = (double)m_Fractorium->m_PaletteBrightnessSpin->value() / 255.0; + double contrast = (double)(m_Fractorium->m_PaletteContrastSpin->value() > 0 ? (m_Fractorium->m_PaletteContrastSpin->value() * 2) : m_Fractorium->m_PaletteContrastSpin->value()) / 100.0; + + m_Ember.m_Hue = (double)(m_Fractorium->m_PaletteHueSpin->value()) / 360.0;//This is the only palette adjustment value that gets saved with the ember, so just assign it here. + + //Use the temp palette as the base and apply the adjustments gotten from the GUI and save the result in the ember palette. + m_TempPalette.MakeAdjustedPalette(m_Ember.m_Palette, 0, m_Ember.m_Hue, sat, brightness, contrast, blur, freq); +} + +/// +/// Use adjusted palette to update all related GUI controls with new color values. +/// Resets the rendering process. +/// +/// The palette to use +/// Name of the palette +template +void FractoriumEmberController::UpdateAdjustedPaletteGUI(Palette& palette) +{ + Xform* xform = CurrentXform(); + QTableWidget* palettePreviewTable = m_Fractorium->ui.PalettePreviewTable; + QTableWidgetItem* previewPaletteItem = palettePreviewTable->item(0, 1); + QString paletteName = QString::fromStdString(m_Ember.m_Palette.m_Name); + + if (previewPaletteItem)//This can be null if the palette file was moved or corrupted. + { + //Use the adjusted palette to fill the preview palette control so the user can see the effects of applying the adjustements. + vector v = palette.MakeRgbPaletteBlock(PALETTE_CELL_HEIGHT);//Make the palette repeat for PALETTE_CELL_HEIGHT rows. + + m_FinalPaletteImage = QImage(palette.Size(), PALETTE_CELL_HEIGHT, QImage::Format_RGB888);//Create a QImage out of it. + memcpy(m_FinalPaletteImage.scanLine(0), v.data(), v.size() * sizeof(v[0]));//Memcpy the data in. + QPixmap pixmap = QPixmap::fromImage(m_FinalPaletteImage);//Create a QPixmap out of the QImage. + previewPaletteItem->setData(Qt::DecorationRole, pixmap.scaled(QSize(pixmap.width(), palettePreviewTable->rowHeight(0) + 2), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));//Set the pixmap on the palette tab. + SetPaletteRefTable(&pixmap);//Set the palette ref table on the xforms | color tab. + + QTableWidgetItem* previewNameItem = palettePreviewTable->item(0, 0); + previewNameItem->setText(paletteName);//Finally, set the name of the palette to be both the text and the tooltip. + previewNameItem->setToolTip(paletteName); + } + + //Update the current xform's color and reset the rendering process. + if (xform) + XformColorIndexChanged(xform->m_ColorX, true); +} + +/// +/// Apply all adjustments to the selected palette, show it +/// and assign it to the current ember. +/// Called when any adjustment spinner is modified. +/// Resets the rendering process. +/// +template +void FractoriumEmberController::PaletteAdjust() +{ + UpdateCurrentXform([&] (Xform* xform) + { + ApplyPaletteToEmber(); + UpdateAdjustedPaletteGUI(m_Ember.m_Palette); + }); +} + +void Fractorium::OnPaletteAdjust(int d) { m_Controller->PaletteAdjust(); } + +/// +/// Set the selected palette as the current one, +/// applying any adjustments previously specified. +/// Called when a palette cell is clicked. Unfortunately, +/// this will get called twice on a double click when moving +/// from one palette to another. It happens quickly so it shouldn't +/// be too much of a problem. +/// Resets the rendering process. +/// +/// The table row clicked +/// The table col clicked +template +void FractoriumEmberController::PaletteCellClicked(int row, int col) +{ + Palette* palette = m_PaletteList.GetPalette(row); + QTableWidgetItem* nameItem = m_Fractorium->ui.PaletteListTable->item(row, 0); + + if (palette) + { + m_TempPalette = *palette;//Deep copy. + ApplyPaletteToEmber();//Copy temp palette to ember palette and apply adjustments. + UpdateAdjustedPaletteGUI(m_Ember.m_Palette);//Show the adjusted palette. + } +} + +void Fractorium::OnPaletteCellClicked(int row, int col) +{ + if (m_PreviousPaletteRow != row) + { + m_Controller->PaletteCellClicked(row, col); + m_PreviousPaletteRow = row;//Save for comparison on next click. + } +} + +/// +/// Set the selected palette as the current one, +/// resetting any adjustments previously specified. +/// Called when a palette cell is double clicked. +/// Resets the rendering process. +/// +/// The table row clicked +/// The table col clicked +void Fractorium::OnPaletteCellDoubleClicked(int row, int col) +{ + ResetPaletteControls(); + m_PreviousPaletteRow = -1; + OnPaletteCellClicked(row, col); +} + +/// +/// Reset the palette controls. +/// Usually in response to a palette cell double click. +/// +void Fractorium::ResetPaletteControls() +{ + m_PaletteHueSpin->SetValueStealth(0); + m_PaletteSaturationSpin->SetValueStealth(0); + m_PaletteBrightnessSpin->SetValueStealth(0); + m_PaletteContrastSpin->SetValueStealth(0); + m_PaletteBlurSpin->SetValueStealth(0); + m_PaletteFrequencySpin->SetValueStealth(1); +} diff --git a/Source/Fractorium/FractoriumParams.cpp b/Source/Fractorium/FractoriumParams.cpp new file mode 100644 index 0000000..fed89c9 --- /dev/null +++ b/Source/Fractorium/FractoriumParams.cpp @@ -0,0 +1,627 @@ +#include "FractoriumPch.h" +#include "Fractorium.h" + +/// +/// Initialize the parameters UI. +/// +void Fractorium::InitParamsUI() +{ + int row = 0; + int spinHeight = 20; + vector comboVals; + QTableWidget* table = ui.ColorTable; + + //Because QTableWidget does not allow for a single title bar/header + //at the top of a multi-column table, the workaround hack is to just + //make another single column table with no rows, and use the single + //column header as the title bar. Then positioning it right above the table + //that holds the data. Disallow selecting and resizing of the title bar. + SetFixedTableHeader(ui.ColorTableHeader->horizontalHeader()); + SetFixedTableHeader(ui.GeometryTableHeader->horizontalHeader()); + SetFixedTableHeader(ui.FilterTableHeader->horizontalHeader()); + SetFixedTableHeader(ui.IterationTableHeader->horizontalHeader()); + + //Color. + SetupSpinner(table, this, row, 1, m_BrightnessSpin, spinHeight, 0.05, 100, 1, SIGNAL(valueChanged(double)), SLOT(OnBrightnessChanged(double)), true, 4.0, 4.0, 4.0); + SetupSpinner(table, this, row, 1, m_GammaSpin, spinHeight, 1, 9999, 0.5, SIGNAL(valueChanged(double)), SLOT(OnGammaChanged(double)), true, 4.0, 4.0, 4.0); + SetupSpinner(table, this, row, 1, m_GammaThresholdSpin, spinHeight, 0, 10, 0.1, SIGNAL(valueChanged(double)), SLOT(OnGammaThresholdChanged(double)), true, 0.1, 0.1, 0.1); + SetupSpinner(table, this, row, 1, m_VibrancySpin, spinHeight, 0, 1, 0.01, SIGNAL(valueChanged(double)), SLOT(OnVibrancyChanged(double)), true, 1.0, 1.0, 1.0); + SetupSpinner(table, this, row, 1, m_HighlightSpin, spinHeight, -1.0, 2.0, 0.1, SIGNAL(valueChanged(double)), SLOT(OnHighlightPowerChanged(double)), true, -1.0, -1.0, -1.0); + + m_BackgroundColorButton = new QPushButton("...", table); + m_BackgroundColorButton->setMinimumWidth(21); + m_BackgroundColorButton->setMaximumWidth(21); + table->setCellWidget(row, 1, m_BackgroundColorButton); + table->item(row, 1)->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); + connect(m_BackgroundColorButton, SIGNAL(clicked(bool)), this, SLOT(OnBackgroundColorButtonClicked(bool)), Qt::QueuedConnection); + row++; + + comboVals.push_back("Step"); + comboVals.push_back("Linear"); + SetupCombo(table, this, row, 1, m_PaletteModeCombo, comboVals, SIGNAL(currentIndexChanged(int)), SLOT(OnPaletteModeComboCurrentIndexChanged(int))); + + //Geometry. + row = 0; + table = ui.GeometryTable; + SetupSpinner (table, this, row, 1, m_WidthSpin, spinHeight, 10, 100000, 50, SIGNAL(valueChanged(int)), SLOT(OnWidthChanged(int))); + SetupSpinner (table, this, row, 1, m_HeightSpin, spinHeight, 10, 100000, 50, SIGNAL(valueChanged(int)), SLOT(OnHeightChanged(int))); + SetupSpinner(table, this, row, 1, m_CenterXSpin, spinHeight, -10, 10, 0.05, SIGNAL(valueChanged(double)), SLOT(OnCenterXChanged(double)), true, 0, 0, 0); + SetupSpinner(table, this, row, 1, m_CenterYSpin, spinHeight, -10, 10, 0.05, SIGNAL(valueChanged(double)), SLOT(OnCenterYChanged(double)), true, 0, 0, 0); + SetupSpinner(table, this, row, 1, m_ScaleSpin, spinHeight, 10, 3000, 20, SIGNAL(valueChanged(double)), SLOT(OnScaleChanged(double)), true, 240, 240, 240); + SetupSpinner(table, this, row, 1, m_ZoomSpin, spinHeight, 0, 5, 0.2, SIGNAL(valueChanged(double)), SLOT(OnZoomChanged(double)), true, 0, 0, 0); + SetupSpinner(table, this, row, 1, m_RotateSpin, spinHeight, -180, 180, 10, SIGNAL(valueChanged(double)), SLOT(OnRotateChanged(double)), true, 0, 0, 0); + SetupSpinner(table, this, row, 1, m_ZPosSpin, spinHeight, -1000, 1000, 1, SIGNAL(valueChanged(double)), SLOT(OnZPosChanged(double)), true, 0, 1, 0); + SetupSpinner(table, this, row, 1, m_PerspectiveSpin, spinHeight, -500, 500, 0.01, SIGNAL(valueChanged(double)), SLOT(OnPerspectiveChanged(double)), true, 0, 1, 0); + SetupSpinner(table, this, row, 1, m_PitchSpin, spinHeight, -180, 180, 1, SIGNAL(valueChanged(double)), SLOT(OnPitchChanged(double)), true, 0, 45, 0); + SetupSpinner(table, this, row, 1, m_YawSpin, spinHeight, -180, 180, 1, SIGNAL(valueChanged(double)), SLOT(OnYawChanged(double)), true, 0, 45, 0); + SetupSpinner(table, this, row, 1, m_DepthBlurSpin, spinHeight, -100, 100, 0.01, SIGNAL(valueChanged(double)), SLOT(OnDepthBlurChanged(double)), true, 0, 1, 0); + m_WidthSpin->setEnabled(false);//Will programatically change these to match the window size, but the user should never be allowed to. + m_HeightSpin->setEnabled(false); + m_CenterXSpin->setDecimals(3); + m_CenterYSpin->setDecimals(3); + m_ZPosSpin->setDecimals(3); + m_PerspectiveSpin->setDecimals(4); + m_DepthBlurSpin->setDecimals(3); + + //Filter. + row = 0; + table = ui.FilterTable; + SetupSpinner(table, this, row, 1, m_SpatialFilterWidthSpin, spinHeight, 0.1, 10, 0.1, SIGNAL(valueChanged(double)), SLOT(OnSpatialFilterWidthChanged(double)), true, 1.0, 1.0, 1.0); + + comboVals = SpatialFilterCreator::FilterTypes(); + SetupCombo(table, this, row, 1, m_SpatialFilterTypeCombo, comboVals, SIGNAL(currentIndexChanged(const QString&)), SLOT(OnSpatialFilterTypeComboCurrentIndexChanged(const QString&))); + + SetupSpinner(table, this, row, 1, m_TemporalFilterWidthSpin, spinHeight, 1, 10, 1, SIGNAL(valueChanged(double)), SLOT(OnTemporalFilterWidthChanged(double)), true, 1); + + comboVals = TemporalFilterCreator::FilterTypes(); + SetupCombo(table, this, row, 1, m_TemporalFilterTypeCombo, comboVals, SIGNAL(currentIndexChanged(const QString&)), SLOT(OnTemporalFilterTypeComboCurrentIndexChanged(const QString&))); + + SetupSpinner(table, this, row, 1, m_DEFilterMinRadiusSpin, spinHeight, 0, 25, 1, SIGNAL(valueChanged(double)), SLOT(OnDEFilterMinRadiusWidthChanged(double)), true, 0, 0, 0); + SetupSpinner(table, this, row, 1, m_DEFilterMaxRadiusSpin, spinHeight, 0, 25, 1, SIGNAL(valueChanged(double)), SLOT(OnDEFilterMaxRadiusWidthChanged(double)), true, 9.0, 9.0, 0); + SetupSpinner(table, this, row, 1, m_DECurveSpin, spinHeight, 0.15, 5, 0.1, SIGNAL(valueChanged(double)), SLOT(OnDEFilterCurveWidthChanged(double)), true, 0.4, 0.4, 0.4); + + //Iteration. + row = 0; + table = ui.IterationTable; + SetupSpinner (table, this, row, 1, m_PassesSpin, spinHeight, 1, 3, 1, SIGNAL(valueChanged(int)), SLOT(OnPassesChanged(int)), true, 1, 1, 1); + SetupSpinner (table, this, row, 1, m_TemporalSamplesSpin, spinHeight, 1, 5000, 50, SIGNAL(valueChanged(int)), SLOT(OnTemporalSamplesChanged(int)), true, 1000); + SetupSpinner(table, this, row, 1, m_QualitySpin, spinHeight, 1, 200000, 50, SIGNAL(valueChanged(double)), SLOT(OnQualityChanged(double)), true, 10, 10, 10); + SetupSpinner (table, this, row, 1, m_SupersampleSpin, spinHeight, 1, 4, 1, SIGNAL(valueChanged(int)), SLOT(OnSupersampleChanged(int)), true, 1, 1, 1); + + comboVals.clear(); + comboVals.push_back("Step"); + comboVals.push_back("Linear"); + SetupCombo(table, this, row, 1, m_AffineInterpTypeCombo, comboVals, SIGNAL(currentIndexChanged(int)), SLOT(OnAffineInterpTypeComboCurrentIndexChanged(int))); + + comboVals.clear(); + comboVals.push_back("Linear"); + comboVals.push_back("Smooth"); + SetupCombo(table, this, row, 1, m_InterpTypeCombo, comboVals, SIGNAL(currentIndexChanged(int)), SLOT(OnInterpTypeComboCurrentIndexChanged(int))); +} + +/// +/// Color. +/// + +/// +/// Set the brightness to be used for calculating K1 and K2 for filtering and final accum. +/// Called when brightness spinner is changed. +/// Resets the rendering process to the filtering stage. +/// +/// The brightness +template +void FractoriumEmberController::BrightnessChanged(double d) { Update([&] { m_Ember.m_Brightness = d; }, true, FILTER_AND_ACCUM); } +void Fractorium::OnBrightnessChanged(double d) { m_Controller->BrightnessChanged(d); } + +/// +/// Set the gamma to be used for final accum. +/// Called when gamma spinner is changed. +/// Resets the rendering process if temporal samples is greater than 1, +/// else if early clip is true, filter and accum, else final accum only. +/// +/// The gamma value +template void FractoriumEmberController::GammaChanged(double d) { Update([&] { m_Ember.m_Gamma = d; }, true, m_Ember.m_TemporalSamples > 1 ? FULL_RENDER : (m_Renderer->EarlyClip() ? FILTER_AND_ACCUM : ACCUM_ONLY)); } +void Fractorium::OnGammaChanged(double d) { m_Controller->GammaChanged(d); } + +/// +/// Set the gamma threshold to be used for final accum. +/// Called when gamma threshold spinner is changed. +/// Resets the rendering process to the final accumulation stage. +/// +/// The gamma threshold +template void FractoriumEmberController::GammaThresholdChanged(double d) { Update([&] { m_Ember.m_GammaThresh = d; }, true, m_Ember.m_TemporalSamples > 1 ? FULL_RENDER : (m_Renderer->EarlyClip() ? FILTER_AND_ACCUM : ACCUM_ONLY)); } +void Fractorium::OnGammaThresholdChanged(double d) { m_Controller->GammaThresholdChanged(d); } + +/// +/// Set the vibrancy to be used for final accum. +/// Called when vibrancy spinner is changed. +/// Resets the rendering process to the final accumulation stage if temporal samples is 1, else full reset. +/// +/// The vibrancy +template void FractoriumEmberController::VibrancyChanged(double d) { Update([&] { m_Ember.m_Vibrancy = d; }, true, m_Ember.m_TemporalSamples > 1 ? FULL_RENDER : (m_Renderer->EarlyClip() ? FILTER_AND_ACCUM : ACCUM_ONLY)); } +void Fractorium::OnVibrancyChanged(double d) { m_Controller->VibrancyChanged(d); } + +/// +/// Set the highlight power to be used for final accum. +/// Called when highlight power spinner is changed. +/// Resets the rendering process to the final accumulation stage. +/// +/// The highlight power +template void FractoriumEmberController::HighlightPowerChanged(double d) { Update([&] { m_Ember.m_HighlightPower = d; }, true, m_Ember.m_TemporalSamples > 1 ? FULL_RENDER : (m_Renderer->EarlyClip() ? FILTER_AND_ACCUM : ACCUM_ONLY)); } +void Fractorium::OnHighlightPowerChanged(double d) { m_Controller->HighlightPowerChanged(d); } + +/// +/// Show the color selection dialog. +/// Called when background color button is clicked. +/// +/// Ignored +void Fractorium::OnBackgroundColorButtonClicked(bool checked) +{ + m_ColorDialog->show(); +} + +/// +/// Set a new ember background color when the user accepts the color dialog. +/// Also change the background and foreground colors of the color cell in the +/// color params table. +/// Resets the rendering process. +/// +/// The color to set, RGB in the 0-255 range +template +void FractoriumEmberController::BackgroundChanged(const QColor& color) +{ + Update([&] + { + int itemRow = 5; + QTableWidget* colorTable = m_Fractorium->ui.ColorTable; + colorTable->item(itemRow, 1)->setBackgroundColor(color); + + QString r = QString::number(color.red()); + QString g = QString::number(color.green()); + QString b = QString::number(color.blue()); + + int threshold = 105; + int delta = (color.red() * 0.299) + //Magic numbers gotten from a Stack Overflow post. + (color.green() * 0.587) + + (color.blue() * 0.114); + + QColor textColor = (255 - delta < threshold) ? QColor(0, 0, 0) : QColor(255, 255, 255); + colorTable->item(itemRow, 1)->setTextColor(textColor); + colorTable->item(itemRow, 1)->setText("rgb(" + r + ", " + g + ", " + b + ")"); + + //Color is 0-255, normalize to 0-1. + m_Ember.m_Background.r = color.red() / 255.0; + m_Ember.m_Background.g = color.green() / 255.0; + m_Ember.m_Background.b = color.blue() / 255.0; + }); +} + +void Fractorium::OnColorSelected(const QColor& color) { m_Controller->BackgroundChanged(color); } + +/// +/// Set the palette index interpolation mode. +/// Called when palette mode combo box index is changed. +/// Resets the rendering process. +/// +/// The index of the palette mode combo box +template void FractoriumEmberController::PaletteModeChanged(unsigned int i) { Update([&] { m_Ember.m_PaletteMode = i == 0 ? PALETTE_STEP : PALETTE_LINEAR; }); } +void Fractorium::OnPaletteModeComboCurrentIndexChanged(int index) { m_Controller->PaletteModeChanged(index); } + +/// +/// Geometry. +/// + +/// +/// Placeholder, do nothing. +/// Dimensions are set automatically to match the dimensions of GLWidget. +/// +/// Ignored +void Fractorium::OnWidthChanged(int d) { } + +/// +/// Placeholder, do nothing. +/// Dimensions are set automatically to match the dimensions of GLWidget. +/// +/// Ignored +void Fractorium::OnHeightChanged(int d) { } + +/// +/// Set the x offset applied to the center of the image. +/// Resets the rendering process. +/// +/// The x offset value +template void FractoriumEmberController::CenterXChanged(double d) { Update([&] { m_Ember.m_CenterX = d; }); } +void Fractorium::OnCenterXChanged(double d) { m_Controller->CenterXChanged(d); } + +/// +/// Set the y offset applied to the center of the image. +/// Resets the rendering process. +/// +/// The y offset value +template void FractoriumEmberController::CenterYChanged(double d) { Update([&] { m_Ember.m_CenterY = d; }); } +void Fractorium::OnCenterYChanged(double d) { m_Controller->CenterYChanged(d); } + +/// +/// Set the scale (pixels per unit) value of the image. +/// Note this will not increase the number of iters ran, but will degrade quality. +/// To preserve quality, but exponentially increase iters, use zoom. +/// Called when scale spinner is changed. +/// Resets the rendering process. +/// +/// The scale value +template void FractoriumEmberController::ScaleChanged(double d) { Update([&] { m_Ember.m_PixelsPerUnit = d; }); } +void Fractorium::OnScaleChanged(double d) { m_Controller->ScaleChanged(d); } + +/// +/// Set the zoom value of the image. +/// Note this will increase the number of iters ran exponentially. +/// To zoom in without increasing iters, but sacrifice quality, use scale. +/// Called when zoom spinner is changed. +/// Resets the rendering process. +/// +/// The zoom value +template void FractoriumEmberController::ZoomChanged(double d) { Update([&] { m_Ember.m_Zoom = d; }); } +void Fractorium::OnZoomChanged(double d) { m_Controller->ZoomChanged(d); } + +/// +/// Set the angular rotation of the image. +/// Called when rotate spinner is changed. +/// Resets the rendering process. +/// +/// The rotation in angles +template void FractoriumEmberController::RotateChanged(double d) { Update([&] { m_Ember.m_Rotate = d; }); } +void Fractorium::OnRotateChanged(double d) { m_Controller->RotateChanged(d); } + +template void FractoriumEmberController::ZPosChanged(double d) { Update([&] { m_Ember.m_CamZPos = d; }); } +void Fractorium::OnZPosChanged(double d) { m_Controller->ZPosChanged(d); } + +template void FractoriumEmberController::PerspectiveChanged(double d) { Update([&] { m_Ember.m_CamPerspective = d; }); } +void Fractorium::OnPerspectiveChanged(double d) { m_Controller->PerspectiveChanged(d); } + +template void FractoriumEmberController::PitchChanged(double d) { Update([&] { m_Ember.m_CamPitch = d * DEG_2_RAD; }); } +void Fractorium::OnPitchChanged(double d) { m_Controller->PitchChanged(d); } + +template void FractoriumEmberController::YawChanged(double d) { Update([&] { m_Ember.m_CamYaw = d * DEG_2_RAD; }); } +void Fractorium::OnYawChanged(double d) { m_Controller->YawChanged(d); } + +template void FractoriumEmberController::DepthBlurChanged(double d) { Update([&] { m_Ember.m_CamDepthBlur = d; }); } +void Fractorium::OnDepthBlurChanged(double d) { m_Controller->DepthBlurChanged(d); } + +/// +/// Filter. +/// + +/// +/// Set the spatial filter width. +/// Called when the spatial filter width spinner is changed. +/// Resets the rendering process. +/// +/// The spatial filter width +template void FractoriumEmberController::SpatialFilterWidthChanged(double d) { Update([&] { m_Ember.m_SpatialFilterRadius = d; }); }//Must fully reset because it's used to create bounds. +void Fractorium::OnSpatialFilterWidthChanged(double d) { m_Controller->SpatialFilterWidthChanged(d); } + +/// +/// Set the spatial filter type. +/// Called when the spatial filter type combo box index is changed. +/// Resets the rendering process. +/// +/// The spatial filter type +template void FractoriumEmberController::SpatialFilterTypeChanged(const QString& text) { Update([&] { m_Ember.m_SpatialFilterType = SpatialFilterCreator::FromString(text.toStdString()); }); }//Must fully reset because it's used to create bounds. +void Fractorium::OnSpatialFilterTypeComboCurrentIndexChanged(const QString& text) { m_Controller->SpatialFilterTypeChanged(text); } + +/// +/// Set the temporal filter width to be used with animation. +/// Called when the temporal filter width spinner is changed. +/// Does not reset anything because this is only used for animation. +/// In the future, when animation is implemented, this will have an effect. +/// +/// The temporal filter width +template void FractoriumEmberController::TemporalFilterWidthChanged(double d) { Update([&] { m_Ember.m_TemporalFilterWidth = d; }, true, NOTHING); }//Don't do anything until animation is implemented. +void Fractorium::OnTemporalFilterWidthChanged(double d) { m_Controller->TemporalFilterWidthChanged(d); } + +/// +/// Set the temporal filter type to be used with animation. +/// Called when the temporal filter combo box index is changed. +/// Does not reset anything because this is only used for animation. +/// In the future, when animation is implemented, this will have an effect. +/// +/// The name of the temporal filter +template void FractoriumEmberController::TemporalFilterTypeChanged(const QString& text) { Update([&] { m_Ember.m_TemporalFilterType = TemporalFilterCreator::FromString(text.toStdString()); }, true, NOTHING); }//Don't do anything until animation is implemented. +void Fractorium::OnTemporalFilterTypeComboCurrentIndexChanged(const QString& text) { m_Controller->TemporalFilterTypeChanged(text); } + +/// +/// Set the density estimation filter min radius value. +/// Resets the rendering process. +/// +/// The min radius value +template +void FractoriumEmberController::DEFilterMinRadiusWidthChanged(double d) +{ + Update([&] + { + if (m_Fractorium->m_DEFilterMinRadiusSpin->value() > m_Fractorium->m_DEFilterMaxRadiusSpin->value()) + { + m_Fractorium->m_DEFilterMinRadiusSpin->setValue(m_Fractorium->m_DEFilterMaxRadiusSpin->value() - 1); + return; + } + + m_Ember.m_MinRadDE = d; + }); +} + +void Fractorium::OnDEFilterMinRadiusWidthChanged(double d) { m_Controller->DEFilterMinRadiusWidthChanged(d); } + +/// +/// Set the density estimation filter max radius value. +/// Resets the rendering process. +/// +/// The max radius value +template +void FractoriumEmberController::DEFilterMaxRadiusWidthChanged(double d) +{ + Update([&] + { + if (m_Fractorium->m_DEFilterMaxRadiusSpin->value() < m_Fractorium->m_DEFilterMinRadiusSpin->value()) + { + m_Fractorium->m_DEFilterMaxRadiusSpin->setValue(m_Fractorium->m_DEFilterMinRadiusSpin->value() + 1); + return; + } + + m_Ember.m_MaxRadDE = d; + }); +} + +void Fractorium::OnDEFilterMaxRadiusWidthChanged(double d) { m_Controller->DEFilterMaxRadiusWidthChanged(d); } + +/// +/// Set the density estimation filter curve value. +/// Resets the rendering process. +/// +/// The curve value +template void FractoriumEmberController::DEFilterCurveWidthChanged(double d) { Update([&] { m_Ember.m_CurveDE = d; }); } +void Fractorium::OnDEFilterCurveWidthChanged(double d) { m_Controller->DEFilterCurveWidthChanged(d); } + +/// +/// Iteration. +/// + +/// +/// Set the number of passes. +/// This is a feature that is mostly useless and unused, and may even be removed soon. +/// It should never be set to a value greater than 1. +/// Resets the rendering process. +/// +/// The passes value +template void FractoriumEmberController::PassesChanged(int i) { Update([&] { m_Ember.m_Passes = i; }); } +void Fractorium::OnPassesChanged(int d) { m_Controller->PassesChanged(d); } + +/// +/// Set the temporal samples to be used with animation. +/// Called when the temporal samples spinner is changed. +/// Does not reset anything because this is only used for animation. +/// In the future, when animation is implemented, this will have an effect. +/// +/// The temporal samples value +template void FractoriumEmberController::TemporalSamplesChanged(int i) { Update([&] { m_Ember.m_TemporalSamples = i; }, true, NOTHING); }//Don't do anything until animation is implemented. +void Fractorium::OnTemporalSamplesChanged(int d) { m_Controller->TemporalSamplesChanged(d); } + +/// +/// Set the quality. +/// 10 is good for interactive rendering on the CPU. +/// 20-50 is good for OpenCL. +/// Above 500 seems to offer little additional value for final renders. +/// Called when the quality spinner is changed. +/// If rendering is done, and the value is greater than the last value, +/// the rendering process is continued, else it's reset. +/// +/// The quality in terms of iterations per pixel +template void FractoriumEmberController::QualityChanged(double d) { Update([&] { m_Ember.m_Quality = d; }, true, d > m_Ember.m_Quality ? KEEP_ITERATING : FULL_RENDER); } +void Fractorium::OnQualityChanged(double d) { m_Controller->QualityChanged(d); } + +/// +/// Set the supersample. +/// Note this will dramatically degrade performance, especially in +/// OpenCL, while only giving a minor improvement in visual quality. +/// Values above 2 add no noticeable difference. +/// The user should only use this for a final render, or a quick preview, and then +/// reset it back to 1 when done. +/// Called when the supersample spinner is changed. +/// Resets the rendering process. +/// +/// The supersample value to set +template void FractoriumEmberController::SupersampleChanged(int d) { Update([&] { m_Ember.m_Supersample = d; }); } +void Fractorium::OnSupersampleChanged(int d) { m_Controller->SupersampleChanged(d); } + +/// +/// Set the affine interpolation type. +/// Does not reset anything because this is only used for animation. +/// In the future, when animation is implemented, this will have an effect. +/// Called when the affine interp type combo box index is changed. +/// +/// The index +template +void FractoriumEmberController::AffineInterpTypeChanged(int i) +{ + Update([&] + { + if (i == 0) + m_Ember.m_AffineInterp = INTERP_LINEAR; + else if (i == 1) + m_Ember.m_AffineInterp = INTERP_LOG; + else + m_Ember.m_AffineInterp = INTERP_LINEAR; + }, true, NOTHING); +} + +void Fractorium::OnAffineInterpTypeComboCurrentIndexChanged(int index) { m_Controller->AffineInterpTypeChanged(index); } + +/// +/// Set the interpolation type. +/// Does not reset anything because this is only used for animation. +/// In the future, when animation is implemented, this will have an effect. +/// Called when the interp type combo box index is changed. +/// +/// The index +template +void FractoriumEmberController::InterpTypeChanged(int i) +{ + Update([&] + { + if (i == 0) + m_Ember.m_Interp = EMBER_INTERP_LINEAR; + else if (i == 1) + m_Ember.m_Interp = EMBER_INTERP_SMOOTH; + else + m_Ember.m_Interp = EMBER_INTERP_LINEAR; + }, true, NOTHING); +} + +void Fractorium::OnInterpTypeComboCurrentIndexChanged(int index) { m_Controller->InterpTypeChanged(index); } + +/// +/// Set the center. +/// This updates the spinners as well as the current ember center. +/// Resets the renering process. +/// +/// The x offset +/// The y offset +template +void FractoriumEmberController::SetCenter(double x, double y) +{ + m_Ember.m_CenterX = x; + m_Ember.m_CenterY = y; + m_Fractorium->m_CenterXSpin->SetValueStealth(x);//Don't trigger a redraw twice. + m_Fractorium->m_CenterYSpin->SetValueStealth(y); + + if (m_Renderer.get())//On startup, this will be null at first because a resize takes place before the rendering thread is started. + CenterXChanged(m_Ember.m_CenterX);//Trigger update using both new values. +} + +/// +/// Fill the parameter tables and palette widgets with values from the current ember. +/// +template +void FractoriumEmberController::FillParamTablesAndPalette() +{ + m_Fractorium->m_BrightnessSpin->SetValueStealth(m_Ember.m_Brightness);//Color. + m_Fractorium->m_GammaSpin->SetValueStealth(m_Ember.m_Gamma); + m_Fractorium->m_GammaThresholdSpin->SetValueStealth(m_Ember.m_GammaThresh); + m_Fractorium->m_VibrancySpin->SetValueStealth(m_Ember.m_Vibrancy); + m_Fractorium->m_HighlightSpin->SetValueStealth(m_Ember.m_HighlightPower); + m_Fractorium->m_ColorDialog->setCurrentColor(QColor(m_Ember.m_Background.r * 255, m_Ember.m_Background.g * 255, m_Ember.m_Background.b * 255)); + m_Fractorium->ui.ColorTable->item(5, 1)->setBackgroundColor(m_Fractorium->m_ColorDialog->currentColor()); + m_Fractorium->m_PaletteModeCombo->SetCurrentIndexStealth((int)m_Ember.m_PaletteMode); + m_Fractorium->m_WidthSpin->SetValueStealth(m_Ember.m_FinalRasW);//Geometry. + m_Fractorium->m_HeightSpin->SetValueStealth(m_Ember.m_FinalRasH); + m_Fractorium->m_CenterXSpin->SetValueStealth(m_Ember.m_CenterX); + m_Fractorium->m_CenterYSpin->SetValueStealth(m_Ember.m_CenterY); + m_Fractorium->m_ScaleSpin->SetValueStealth(m_Ember.m_PixelsPerUnit); + m_Fractorium->m_RotateSpin->SetValueStealth(m_Ember.m_Rotate); + m_Fractorium->m_ZPosSpin->SetValueStealth(m_Ember.m_CamZPos); + m_Fractorium->m_PerspectiveSpin->SetValueStealth(m_Ember.m_CamPerspective); + m_Fractorium->m_PitchSpin->SetValueStealth(m_Ember.m_CamPitch * RAD_2_DEG_T); + m_Fractorium->m_YawSpin->SetValueStealth(m_Ember.m_CamYaw * RAD_2_DEG_T); + m_Fractorium->m_DepthBlurSpin->SetValueStealth(m_Ember.m_CamDepthBlur); + m_Fractorium->m_SpatialFilterWidthSpin->SetValueStealth(m_Ember.m_SpatialFilterRadius);//Filter. + m_Fractorium->m_SpatialFilterTypeCombo->SetCurrentIndexStealth((int)m_Ember.m_SpatialFilterType); + m_Fractorium->m_TemporalFilterWidthSpin->SetValueStealth(m_Ember.m_TemporalFilterWidth); + m_Fractorium->m_TemporalFilterTypeCombo->SetCurrentIndexStealth((int)m_Ember.m_TemporalFilterType); + m_Fractorium->m_DEFilterMinRadiusSpin->SetValueStealth(m_Ember.m_MinRadDE); + m_Fractorium->m_DEFilterMaxRadiusSpin->SetValueStealth(m_Ember.m_MaxRadDE); + m_Fractorium->m_DECurveSpin->SetValueStealth(m_Ember.m_CurveDE); + m_Fractorium->m_PassesSpin->SetValueStealth(m_Ember.m_Passes);//Iteration. + m_Fractorium->m_TemporalSamplesSpin->SetValueStealth(m_Ember.m_TemporalSamples); + m_Fractorium->m_QualitySpin->SetValueStealth(m_Ember.m_Quality); + m_Fractorium->m_SupersampleSpin->SetValueStealth(m_Ember.m_Supersample); + m_Fractorium->m_AffineInterpTypeCombo->SetCurrentIndexStealth(m_Ember.m_AffineInterp); + m_Fractorium->m_InterpTypeCombo->SetCurrentIndexStealth(m_Ember.m_Interp); + + //Palette. + m_Fractorium->ResetPaletteControls(); + m_Fractorium->m_PaletteHueSpin->SetValueStealth(NormalizeDeg180(m_Ember.m_Hue * 360.0));//Convert -0.5 to 0.5 range to -180 - 180. + + //Use -1 as a placeholder to mean either generate a random palette from the list or + //to just use the values "as-is" without looking them up in the list. + if (m_Ember.m_Palette.m_Index >= 0) + { + m_Fractorium->OnPaletteCellClicked(Clamp(m_Ember.m_Palette.m_Index, 0, m_Fractorium->ui.PaletteListTable->rowCount() - 1), 1); + } + else + { + //An ember with an embedded palette was loaded, rather than one from the list, so assign it directly to the controls without applying adjustments. + //Normally, temp palette is assigned whenever the user clicks on a palette cell. But since that is skipped here just make a copy of the ember's palette. + m_TempPalette = m_Ember.m_Palette; + UpdateAdjustedPaletteGUI(m_Ember.m_Palette);//Will clear name string since embedded palettes have no name. This will trigger a full render. + } +} + +/// +/// Copy all GUI widget values on the parameters tab to the passed in ember. +/// +/// The ember to copy values to. +template +void FractoriumEmberController::ParamsToEmber(Ember& ember) +{ + QColor color = m_Fractorium->ui.ColorTable->item(5, 1)->backgroundColor(); + + ember.m_Brightness = m_Fractorium->m_BrightnessSpin->value();//Color. + ember.m_Gamma = m_Fractorium->m_GammaSpin->value(); + ember.m_GammaThresh = m_Fractorium->m_GammaThresholdSpin->value(); + ember.m_Vibrancy = m_Fractorium->m_VibrancySpin->value(); + ember.m_HighlightPower = m_Fractorium->m_HighlightSpin->value(); + ember.m_Background.r = color.red() / 255.0; + ember.m_Background.g = color.green() / 255.0; + ember.m_Background.b = color.blue() / 255.0; + ember.m_PaletteMode = (ePaletteMode)m_Fractorium->m_PaletteModeCombo->currentIndex(); + ember.m_FinalRasW = m_Fractorium->m_WidthSpin->value();//Geometry. + ember.m_FinalRasH = m_Fractorium->m_HeightSpin->value(); + ember.m_CenterX = m_Fractorium->m_CenterXSpin->value(); + ember.m_CenterY = m_Fractorium->m_CenterYSpin->value(); + ember.m_PixelsPerUnit = m_Fractorium->m_ScaleSpin->value(); + ember.m_Rotate = m_Fractorium->m_RotateSpin->value(); + ember.m_CamZPos = m_Fractorium->m_ZPosSpin->value(); + ember.m_CamPerspective = m_Fractorium->m_PerspectiveSpin->value(); + ember.m_CamPitch = m_Fractorium->m_PitchSpin->value() * DEG_2_RAD_T; + ember.m_CamYaw = m_Fractorium->m_YawSpin->value() * DEG_2_RAD_T; + ember.m_CamDepthBlur = m_Fractorium->m_DepthBlurSpin->value(); + ember.m_SpatialFilterRadius = m_Fractorium->m_SpatialFilterWidthSpin->value();//Filter. + ember.m_SpatialFilterType = (eSpatialFilterType)m_Fractorium->m_SpatialFilterTypeCombo->currentIndex(); + ember.m_TemporalFilterWidth = m_Fractorium->m_TemporalFilterWidthSpin->value(); + ember.m_TemporalFilterType = (eTemporalFilterType)m_Fractorium->m_TemporalFilterTypeCombo->currentIndex(); + ember.m_MinRadDE = m_Fractorium->m_DEFilterMinRadiusSpin->value(); + ember.m_MaxRadDE = m_Fractorium->m_DEFilterMaxRadiusSpin->value(); + ember.m_CurveDE = m_Fractorium->m_DECurveSpin->value(); + ember.m_Passes = m_Fractorium->m_PassesSpin->value(); + ember.m_TemporalSamples = m_Fractorium->m_TemporalSamplesSpin->value(); + ember.m_Quality = m_Fractorium->m_QualitySpin->value(); + ember.m_Supersample = m_Fractorium->m_SupersampleSpin->value(); + ember.m_AffineInterp = (eAffineInterp)m_Fractorium->m_AffineInterpTypeCombo->currentIndex(); + ember.m_Interp = (eInterp)m_Fractorium->m_InterpTypeCombo->currentIndex(); +} + +/// +/// Set the rotation. +/// This updates the spinner, optionally stealth. +/// +/// The rotation value in angles to set +/// True if stealth to skip re-rendering, else false to trigger a new render +void Fractorium::SetRotation(double rot, bool stealth) +{ + if (stealth) + m_RotateSpin->SetValueStealth(rot); + else + m_RotateSpin->setValue(rot); +} + +/// +/// Set the scale. +/// This is the number of raster pixels that correspond to the distance +/// between 0-1 in the cartesian plane. The higher the number, the more +/// zoomed in the image is. +/// Resets the rendering process. +/// +/// The scale value +void Fractorium::SetScale(double scale) +{ + m_ScaleSpin->setValue(scale); +} diff --git a/Source/Fractorium/FractoriumPch.cpp b/Source/Fractorium/FractoriumPch.cpp new file mode 100644 index 0000000..3c0c5f3 --- /dev/null +++ b/Source/Fractorium/FractoriumPch.cpp @@ -0,0 +1 @@ +#include "FractoriumPch.h" diff --git a/Source/Fractorium/FractoriumPch.h b/Source/Fractorium/FractoriumPch.h new file mode 100644 index 0000000..9fc1d72 --- /dev/null +++ b/Source/Fractorium/FractoriumPch.h @@ -0,0 +1,44 @@ +#pragma once + +#define GL_GLEXT_PROTOTYPES +#define GLM_FORCE_INLINE 1 + +#include "Renderer.h" +#include "RendererCL.h" +#include "VariationList.h" +#include "OpenCLWrapper.h" +#include "XmlToEmber.h" +#include "EmberToXml.h" +#include "SheepTools.h" +#include "JpegUtils.h" +#include "EmberCommon.h" +#include +#undef QT_OPENGL_ES_2//Make absolutely sure OpenGL ES is not used. +#define QT_NO_OPENGL_ES_2 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GLM_FORCE_RADIANS + +#include "glm/glm.hpp" +#include "glm/gtc/matrix_transform.hpp" +#include "glm/gtc/type_ptr.hpp" + +using namespace std; +using namespace EmberNs; +using namespace EmberCLns; \ No newline at end of file diff --git a/Source/Fractorium/FractoriumRender.cpp b/Source/Fractorium/FractoriumRender.cpp new file mode 100644 index 0000000..19cda2f --- /dev/null +++ b/Source/Fractorium/FractoriumRender.cpp @@ -0,0 +1,630 @@ +#include "FractoriumPch.h" +#include "Fractorium.h" + +/// +/// Return whether the render timer is running. +/// +/// True if running, else false. +bool FractoriumEmberControllerBase::RenderTimerRunning() +{ + return m_RenderTimer && m_RenderTimer->isActive(); +} + +/// +/// Start the render timer. +/// If a renderer has not been created yet, it will be created from the options. +/// +void FractoriumEmberControllerBase::StartRenderTimer() +{ + if (m_RenderTimer) + { + UpdateRender(); + m_RenderTimer->start(); + m_RenderElapsedTimer.Tic(); + } +} + +/// +/// Start the render timer after a short delay. +/// If the timer is already running, stop it first. +/// This is useful for stopping and restarting the render +/// process in response to things like a window resize. +/// +void FractoriumEmberControllerBase::DelayedStartRenderTimer() +{ + DeleteRenderer(); + + if (m_RenderRestartTimer) + { + m_RenderRestartTimer->setSingleShot(true); + m_RenderRestartTimer->start(300);//Will stop the timer if it's already running, and start again. + } +} + +/// +/// Stop the render timer and abort the rendering process. +/// Optionally block until stopping is complete. +/// +/// True to block, else false. +void FractoriumEmberControllerBase::StopRenderTimer(bool wait) +{ + if (m_RenderTimer) + m_RenderTimer->stop(); + + if (m_Renderer.get()) + m_Renderer->Abort(); + + if (wait) + { + while (m_Rendering || RenderTimerRunning() || (Renderer() && (!m_Renderer->Aborted() || m_Renderer->InRender()))) + QApplication::processEvents(); + } +} + +/// +/// Stop all timers, rendering and drawing and block until they are done. +/// +void FractoriumEmberControllerBase::Shutdown() +{ + StopRenderTimer(true); + + while(m_Fractorium->ui.GLDisplay->Drawing()) + QApplication::processEvents(); +} + +/// +/// Update the state of the renderer. +/// Upon changing values, some intelligence is used to avoid blindly restarting the +/// entire iteration proceess every time a value changes. This is because some values don't affect the +/// iteration, and only affect filtering and final accumulation. They are broken into three categories: +/// 1) Restart the entire process. +/// 2) Log/density filter, then final accum. +/// 3) Final accum only. +/// 4) Continue iterating. +/// +/// The action to take +void FractoriumEmberControllerBase::UpdateRender(eProcessAction action) +{ + AddProcessAction(action); + m_RenderElapsedTimer.Tic(); +} + +/// +/// Call Shutdown() then delete the renderer and clear the textures in the output window if there is one. +/// +void FractoriumEmberControllerBase::DeleteRenderer() +{ + Shutdown(); + m_Renderer.reset(); + + if (GLController()) + GLController()->ClearWindow(); +} + +/// +/// Save the current contents of the GL window to a file. +/// This could benefit from QImageWriter, however it's compression capabilities are +/// severely lacking. A Png file comes out larger than a bitmap, so instead use the +/// Png and Jpg wrapper functions from the command line programs. +/// This will embed the id, url and nick fields from the options in the image comments. +/// +/// The full path and filename +void FractoriumEmberControllerBase::SaveCurrentRender(QString filename) +{ + if (filename != "") + { + bool b = false; + unsigned int i, j; + unsigned int width = m_Renderer->FinalRasW(); + unsigned int height = m_Renderer->FinalRasH(); + unsigned char* data = NULL; + vector vecRgb; + QFileInfo fileInfo(filename); + QString suffix = fileInfo.suffix(); + FractoriumSettings* settings = m_Fractorium->m_Settings; + RendererCLBase* rendererCL = dynamic_cast(m_Renderer.get()); + + if (rendererCL && m_Renderer->PrepFinalAccumVector(m_FinalImage)) + { + if (!rendererCL->ReadFinal(m_FinalImage.data())) + { + QMessageBox::critical(m_Fractorium, "GPU Read Error", "Could not read image from the GPU, aborting image save."); + return; + } + } + + data = m_FinalImage.data();//Png and channels = 4. + + if ((suffix == "jpg" || suffix == "bmp") && m_Renderer->NumChannels() == 4) + { + EmberNs::RgbaToRgb(m_FinalImage, vecRgb, width, height); + + data = vecRgb.data(); + } + + string s = filename.toStdString(); + string id = settings->Id().toStdString(); + string url = settings->Url().toStdString(); + string nick = settings->Nick().toStdString(); + EmberImageComments comments = m_Renderer->ImageComments(0, false, true); + + if (suffix == "png") + b = WritePng(s.c_str(), data, width, height, 1, true, comments, id, url, nick); + else if (suffix == "jpg") + b = WriteJpeg(s.c_str(), data, width, height, 100, true, comments, id, url, nick); + else if (suffix == "bmp") + b = WriteBmp(s.c_str(), data, width, height); + else + QMessageBox::critical(m_Fractorium, "Save Failed", "Unrecognized format " + suffix + ", not saving."); + + if (b) + settings->SaveFolder(fileInfo.canonicalPath()); + else + QMessageBox::critical(m_Fractorium, "Save Failed", "Could not save file, try saving to a different folder."); + } +} + +/// +/// Add a process action to the list of actions to take. +/// Called in response to the user changing something on the GUI. +/// +/// The action for the renderer to take +void FractoriumEmberControllerBase::AddProcessAction(eProcessAction action) +{ + m_Cs.Enter(); + m_ProcessActions.push_back(action); + + if (m_Renderer.get()) + m_Renderer->Abort(); + + m_Cs.Leave(); +} + +/// +/// Condense and clear the process actions into a single action and return. +/// Many actions may be specified, but only the one requiring the greatest amount +/// of processing matters. Extract and return the greatest and clear the vector. +/// +/// The most significant processing action desired +eProcessAction FractoriumEmberControllerBase::CondenseAndClearProcessActions() +{ + m_Cs.Enter(); + eProcessAction action = NOTHING; + + for (size_t i = 0; i < m_ProcessActions.size(); i++) + if (m_ProcessActions[i] > action) + action = m_ProcessActions[i]; + + m_ProcessActions.clear(); + m_Cs.Leave(); + return action; +} + +/// +/// Render progress callback function to update progress bar. +/// +/// The ember currently being rendered +/// An extra dummy parameter +/// The progress fraction from 0-100 +/// The stage of iteration. 1 is iterating, 2 is density filtering, 2 is final accumulation. +/// The estimated milliseconds to completion of the current stage +/// 0 if the user has changed anything on the GUI, else 1 to continue rendering. +template +int FractoriumEmberController::ProgressFunc(Ember& ember, void* foo, double fraction, int stage, double etaMs) +{ + QString status; + + m_Fractorium->m_ProgressBar->setValue((int)fraction);//Only really applies to iter and filter, because final accum only gives progress 0 and 100. + + if (stage == 0) + status = "Iterating"; + else if (stage == 1) + status = "Density Filtering"; + else if (stage == 2) + status = "Spatial Filtering + Final Accumulation"; + + m_Fractorium->m_RenderStatusLabel->setText(status); + return m_ProcessActions.empty() ? 1 : 0;//If they've done anything, abort. +} + +/// +/// Clear the undo list as well as the undo/redo index and state. +/// +template +void FractoriumEmberController::ClearUndo() +{ + m_UndoIndex = 0; + m_UndoList.clear(); + m_EditState = REGULAR_EDIT; + m_LastEditWasUndoRedo = false; + m_Fractorium->ui.ActionUndo->setEnabled(false); + m_Fractorium->ui.ActionRedo->setEnabled(false); +} + +/// +/// The main rendering function. +/// Called whenever the event loop is idle. +/// +template +bool FractoriumEmberController::Render() +{ + bool success = true; + + m_Rendering = true; + GLWidget* gl = m_Fractorium->ui.GLDisplay; + eProcessAction action = CondenseAndClearProcessActions(); + + //Set dimensions first. + if ((m_Ember.m_FinalRasW != gl->width() || + m_Ember.m_FinalRasH != gl->height())) + { + m_Ember.SetSizeAndAdjustScale(gl->width(), gl->height(), false, SCALE_WIDTH); + m_Fractorium->m_ScaleSpin->SetValueStealth(m_Ember.m_PixelsPerUnit); + action = FULL_RENDER; + } + + //Force temporal samples to always be 1. Perhaps change later when animation is implemented. + m_Ember.m_TemporalSamples = 1; + + //Take care of solo xforms and set the current ember and action. + if (action != NOTHING) + { + int i, solo = m_Fractorium->ui.CurrentXformCombo->property("soloxform").toInt(); + + if (solo != -1) + { + m_TempOpacities.resize(m_Ember.TotalXformCount()); + + for (i = 0; i < m_Ember.TotalXformCount(); i++) + { + m_TempOpacities[i] = m_Ember.GetTotalXform(i)->m_Opacity; + m_Ember.GetTotalXform(i)->m_Opacity = i == solo ? 1 : 0; + } + } + + m_Renderer->SetEmber(m_Ember, action); + + if (solo != -1) + { + for (i = 0; i < m_Ember.TotalXformCount(); i++) + { + m_Ember.GetTotalXform(i)->m_Opacity = m_TempOpacities[i]; + } + } + } + + //Determining if a completely new rendering process is being started. + bool iterBegin = ProcessState() == NONE; + + if (iterBegin) + { + if (m_Renderer->RendererType() == CPU_RENDERER) + m_SubBatchCount = m_Fractorium->m_Settings->CpuSubBatch(); + else if (m_Renderer->RendererType() == OPENCL_RENDERER) + m_SubBatchCount = m_Fractorium->m_Settings->OpenCLSubBatch(); + + m_Fractorium->m_ProgressBar->setValue(0); + m_Fractorium->m_RenderStatusLabel->setText("Starting"); + } + + //If the rendering process hasn't finished, render with the current specified action. + if (ProcessState() != ACCUM_DONE) + { + //if (m_Renderer->Run(m_FinalImage, 0) == RENDER_OK)//Full, non-incremental render for debugging. + if (m_Renderer->Run(m_FinalImage, 0, m_SubBatchCount, iterBegin) == RENDER_OK)//Force output on iterBegin. + { + //The amount to increment sub batch while rendering proceeds is purely empirical. + //Change later if better values can be derived/observed. + if (m_Renderer->RendererType() == OPENCL_RENDERER) + { + if (m_SubBatchCount < 3)//More than 3 with OpenCL gives a sluggish UI. + m_SubBatchCount++; + } + else + { + if (m_SubBatchCount < 5) + m_SubBatchCount++; + else if (m_SubBatchCount < 105)//More than 105 with CPU gives a sluggish UI. + m_SubBatchCount += 25; + } + + //Rendering has finished, update final stats. + if (ProcessState() == ACCUM_DONE) + { + EmberStats stats = m_Renderer->Stats(); + + m_Fractorium->m_ProgressBar->setValue(100); + QString iters = QLocale(QLocale::English).toString(stats.m_Iters); + QString scaledQuality = QString::number((unsigned int)m_Renderer->ScaledQuality()); + string renderTime = m_RenderElapsedTimer.Format(m_RenderElapsedTimer.Toc()); + + //Only certain status can be reported with OpenCL. + if (m_Renderer->RendererType() == OPENCL_RENDERER) + { + m_Fractorium->m_RenderStatusLabel->setText("Iters: " + iters + ". Scaled quality: " + scaledQuality + ". Total time: " + QString::fromStdString(renderTime)); + } + else + { + double percent = (double)stats.m_Badvals / (double)stats.m_Iters; + QString badVals = QLocale(QLocale::English).toString(stats.m_Badvals); + QString badPercent = QLocale(QLocale::English).toString(percent * 100, 'f', 2); + + m_Fractorium->m_RenderStatusLabel->setText("Iters: " + iters + ". Scaled quality: " + scaledQuality + ". Bad values: " + badVals + " (" + badPercent + "%). Total time: " + QString::fromStdString(renderTime)); + } + + if (m_LastEditWasUndoRedo && (m_UndoIndex == m_UndoList.size() - 1))//Traversing through undo list, reached the end, so put back in regular edit mode. + { + m_EditState = REGULAR_EDIT; + } + else if (m_EditState == REGULAR_EDIT)//Regular edit, just add to the end of the undo list. + { + m_UndoList.push_back(m_Ember); + m_UndoIndex = m_UndoList.size() - 1; + m_Fractorium->ui.ActionUndo->setEnabled(m_UndoList.size() > 1); + m_Fractorium->ui.ActionRedo->setEnabled(false); + + if (m_UndoList.size() >= UNDO_SIZE) + m_UndoList.pop_front(); + } + else if (!m_LastEditWasUndoRedo && m_UndoIndex != m_UndoList.size() - 1)//They were in the middle of the undo list, then did a manual edit, so clear the undo list. + { + ClearUndo(); + m_UndoList.push_back(m_Ember); + } + + m_LastEditWasUndoRedo = false; + m_Fractorium->UpdateHistogramBounds();//Mostly of engineering interest. + } + + //Update the GL window on start because the output will be forced. + //Update it on finish because the rendering process is completely done. + if (iterBegin || ProcessState() == ACCUM_DONE) + { + if (m_FinalImage.size() == m_Renderer->FinalBufferSize())//Make absolutely sure the correct amount of data is passed. + gl->repaint(); + + //Uncomment for debugging kernel build and execution errors. + //m_Fractorium->ui.InfoRenderingTextEdit->setText(QString::fromStdString(m_Fractorium->m_Wrapper.DumpInfo())); + //if (RendererCL* rendererCL = dynamic_cast*>(m_Renderer.get())) + // m_Fractorium->ui.InfoRenderingTextEdit->setText(QString::fromStdString(rendererCL->IterKernel())); + } + } + else//Something went very wrong, show error report. + { + vector errors = m_Renderer->ErrorReport(); + + success = false; + m_FailedRenders++; + m_Fractorium->m_RenderStatusLabel->setText("Rendering failed, see info tab. Try changing parameters."); + m_Fractorium->ErrorReportToQTextEdit(errors, m_Fractorium->ui.InfoRenderingTextEdit); + m_Renderer->ClearErrorReport(); + + if (m_FailedRenders >= 3) + { + m_Rendering = false; + StopRenderTimer(true); + m_Fractorium->m_RenderStatusLabel->setText("Rendering failed 3 or more times, stopping all rendering, see info tab. Try changing renderer types."); + + memset(m_FinalImage.data(), 0, m_FinalImage.size()); + + if (m_Renderer->RendererType() == OPENCL_RENDERER) + ((RendererCL*)m_Renderer.get())->ClearFinal(); + + m_GLController->ClearWindow(); + } + } + } + + //Upon finishing, or having nothing to do, rest. + if (ProcessState() == ACCUM_DONE) + QThread::msleep(1); + + m_Rendering = false; + return success; +} + +/// +/// Stop rendering and initialize a new renderer, using the specified type. +/// Rendering will be left in a stopped state. The caller is responsible for restarting the render loop again. +/// +/// The type of render to create +/// The index platform of the platform to use +/// The index device of the device to use +/// The texture ID of the shared OpenGL texture if shared +/// True if shared with OpenGL, else false. Default: true. +/// True if nothing went wrong, else false. +template +bool FractoriumEmberController::CreateRenderer(eRendererType renderType, unsigned int platform, unsigned int device, bool shared) +{ + bool ok = true; + FractoriumSettings* s = m_Fractorium->m_Settings; + GLWidget* gl = m_Fractorium->ui.GLDisplay; + + if (!m_Renderer.get() || (m_Renderer->RendererType() != renderType) || (m_Platform != platform) || (m_Device != device)) + { + EmberReport emberReport; + vector errorReport; + + DeleteRenderer();//Delete the renderer and refresh the textures. + //Before starting, must take care of allocations. + gl->Allocate(true);//Forcing a realloc of the texture is necessary on AMD, but not on nVidia. + m_Renderer = auto_ptr(::CreateRenderer(renderType, platform, device, shared, gl->OutputTexID(), emberReport)); + errorReport = emberReport.ErrorReport(); + + if (errorReport.empty()) + { + m_Platform = platform;//Store values for re-creation later on. + m_Device = device; + m_OutputTexID = gl->OutputTexID(); + m_Shared = shared; + } + else + { + ok = false; + QMessageBox::critical(m_Fractorium, "Renderer Creation Error", "Could not create requested renderer, fallback CPU renderer created. See info tab for details."); + m_Fractorium->ErrorReportToQTextEdit(errorReport, m_Fractorium->ui.InfoRenderingTextEdit); + } + } + + if (m_Renderer.get()) + { + m_RenderType = m_Renderer->RendererType(); + + if (m_RenderType == OPENCL_RENDERER && m_Fractorium->m_QualitySpin->value() < 30) + m_Fractorium->m_QualitySpin->setValue(30); + + m_Renderer->Callback(this); + m_Renderer->NumChannels(4);//Always using 4 since the GL texture is RGBA. + m_Renderer->ReclaimOnResize(true); + m_Renderer->SetEmber(m_Ember);//Give it an initial ember, will be updated many times later. + m_Renderer->EarlyClip(s->EarlyClip()); + m_Renderer->ThreadCount(s->ThreadCount()); + m_Renderer->Transparency(s->Transparency()); + + if (m_Renderer->RendererType() == CPU_RENDERER) + m_Renderer->InteractiveFilter(s->CpuDEFilter() ? FILTER_DE : FILTER_LOG); + else + m_Renderer->InteractiveFilter(s->OpenCLDEFilter() ? FILTER_DE : FILTER_LOG); + + m_FailedRenders = 0; + m_RenderElapsedTimer.Tic(); + //Leave rendering in a stopped state. The caller is responsible for restarting the render loop again. + } + else + { + ok = false; + QMessageBox::critical(m_Fractorium, "Renderer Creation Error", "Creating a basic CPU renderer failed, something is catastrophically wrong. Exiting program."); + QApplication::quit(); + } + + return ok; +} + +/// +/// Create a new renderer from the options. +/// +/// True if nothing went wrong, else false. +bool Fractorium::CreateRendererFromOptions() +{ + bool ok = true; + bool useOpenCL = m_Wrapper.CheckOpenCL() && m_Settings->OpenCL(); + + //The most important option to process is what kind of renderer is desired, so do it first. + if (!m_Controller->CreateRenderer(useOpenCL ? OPENCL_RENDERER : CPU_RENDERER, + m_Settings->PlatformIndex(), + m_Settings->DeviceIndex())) + { + //If using OpenCL, will only get here if creating RendererCL failed, but creating a backup CPU Renderer succeeded. + QMessageBox::critical(this, "Renderer Creation Error", "Error creating renderer, most likely a GPU problem. Using CPU instead."); + m_Settings->OpenCL(false); + m_OptionsDialog->ui.OpenCLCheckBox->setChecked(false); + m_FinalRenderDialog->ui.FinalRenderOpenCLCheckBox->setChecked(false); + ok = false; + } + + return ok; +} + +/// +/// Create a new controller from the options. +/// This does not create the internal renderer or start the timers. +/// +/// True if successful, else false. +bool Fractorium::CreateControllerFromOptions() +{ + bool ok = true; + + size_t size = +#ifdef DO_DOUBLE + m_Settings->Double() ? sizeof(double) : +#endif + sizeof(float); + + if (!m_Controller.get() || (m_Controller->SizeOfT() != size)) + { + double hue = m_PaletteHueSpin->value(); + double sat = m_PaletteSaturationSpin->value(); + double bright = m_PaletteBrightnessSpin->value(); + double con = m_PaletteContrastSpin->value(); + double blur = m_PaletteBlurSpin->value(); + double freq = m_PaletteFrequencySpin->value(); +#ifdef DO_DOUBLE + Ember ed; + EmberFile efd; + Palette tempPalette; +#else + Ember ed; + EmberFile efd; + Palette tempPalette; +#endif + QModelIndex index = ui.LibraryTree->currentIndex(); + + //First check if a controller has already been created, and if so, save its embers and gracefully shut it down. + if (m_Controller.get()) + { + m_Controller->CopyTempPalette(tempPalette);//Convert float to double or save double verbatim; + m_Controller->CopyEmber(ed); + m_Controller->CopyEmberFile(efd); + m_Controller->Shutdown(); + } + +#ifdef DO_DOUBLE + if (m_Settings->Double()) + m_Controller = auto_ptr(new FractoriumEmberController(this)); + else +#endif + m_Controller = auto_ptr(new FractoriumEmberController(this)); + + //Restore the ember and ember file. + if (m_Controller.get()) + { + m_Controller->SetEmber(ed);//Convert float to double or set double verbatim; + m_Controller->SetEmberFile(efd); + + //Template specific palette table and variations tree setup in controller constructor, but + //must manually setup the library tree here because it's after the embers were assigned. + m_Controller->FillLibraryTree(index.row());//Passing row re-selects the item that was previously selected. + m_Controller->SetTempPalette(tempPalette);//Restore palette. + m_PaletteHueSpin->SetValueStealth(hue); + m_PaletteSaturationSpin->SetValueStealth(sat); + m_PaletteBrightnessSpin->SetValueStealth(bright); + m_PaletteContrastSpin->SetValueStealth(con); + m_PaletteBlurSpin->SetValueStealth(blur); + m_PaletteFrequencySpin->SetValueStealth(freq); + m_Controller->PaletteAdjust();//Fills in the palette. + } + } + + return m_Controller.get() != NULL; +} + +/// +/// Start the render timer. +/// If a renderer has not been created yet, or differs form the options, it will first be created from the options. +/// +void Fractorium::StartRenderTimer() +{ + //Starting the render timer, either for the first time + //or from a paused state, such as resizing or applying new options. + CreateControllerFromOptions(); + + if (m_Controller.get()) + { + //On program startup, the renderer does not get initialized until now. + CreateRendererFromOptions(); + + if (m_Controller->Renderer()) + m_Controller->StartRenderTimer(); + } +} + +/// +/// Idle timer event which calls the controller's Render() function. +/// +void Fractorium::IdleTimer() { m_Controller->Render(); } + +/// +/// Thin wrapper to determine if the controllers have been properly initialized. +/// +/// True if the ember controller and GL controllers are both not NULL, else false. +bool Fractorium::ControllersOk() { return m_Controller.get() && m_Controller->GLController(); } diff --git a/Source/Fractorium/FractoriumSettings.cpp b/Source/Fractorium/FractoriumSettings.cpp new file mode 100644 index 0000000..8f1fe42 --- /dev/null +++ b/Source/Fractorium/FractoriumSettings.cpp @@ -0,0 +1,239 @@ +#include "FractoriumPch.h" +#include "FractoriumSettings.h" + +/// +/// Constructor that passes the parent to the base and sets up reasonable defaults +/// if the settings file was not present or corrupted. +/// +/// The parent widget +FractoriumSettings::FractoriumSettings(QObject* parent) + : QSettings(QSettings::IniFormat, QSettings::UserScope, "Fractorium", "Fractorium", parent) +{ + EnsureDefaults(); +} + +/// +/// Make sure options have reasonable values in them first. +/// +void FractoriumSettings::EnsureDefaults() +{ + if (FinalWidth() == 0) + FinalWidth(1920); + + if (FinalHeight() == 0) + FinalHeight(1080); + + if (FinalQuality() == 0) + FinalQuality(1000); + + if (FinalTemporalSamples() == 0) + FinalTemporalSamples(1000); + + if (FinalSupersample() == 0) + FinalSupersample(2); + + if (XmlWidth() == 0) + XmlWidth(1920); + + if (XmlHeight() == 0) + XmlHeight(1080); + + if (XmlTemporalSamples() == 0) + XmlTemporalSamples(1000); + + if (XmlQuality() == 0) + XmlQuality(1000); + + if (XmlSupersample() == 0) + XmlSupersample(2); + + if (ThreadCount() == 0 || ThreadCount() > Timing::ProcessorCount()) + ThreadCount(max(1, Timing::ProcessorCount() - 1));//Default to one less to keep the UI responsive for first time users. + + if (FinalThreadCount() == 0 || FinalThreadCount() > Timing::ProcessorCount()) + FinalThreadCount(Timing::ProcessorCount()); + + if (CpuSubBatch() < 1) + CpuSubBatch(10); + + if (OpenCLSubBatch() < 1) + OpenCLSubBatch(1); + + //There normally wouldn't be any more than 10 OpenCL platforms and devices + //on the system, so if a value greater than that is read, then the settings file + //was corrupted. + if (PlatformIndex() > 10) + PlatformIndex(0); + + if (DeviceIndex() > 10) + DeviceIndex(0); + + if (FinalScale() > SCALE_HEIGHT) + FinalScale(0); + + if (FinalPlatformIndex() > 10) + FinalPlatformIndex(0); + + if (FinalDeviceIndex() > 10) + FinalDeviceIndex(0); + + if (OpenXmlExt() == "") + OpenXmlExt("Flame (*.flame)"); + + if (SaveXmlExt() == "") + SaveXmlExt("Flame (*.flame)"); + + if (OpenImageExt() == "") + OpenImageExt("Png (*.png)"); + + if (SaveImageExt() == "") + SaveImageExt("Png (*.png)"); + + if (FinalDoAllExt() != "jpg" && FinalDoAllExt() != "png") + FinalDoAllExt("png"); +} + +/// +/// Interactive renderer settings. +/// + +bool FractoriumSettings::EarlyClip() { return value(EARLYCLIP).toBool(); } +void FractoriumSettings::EarlyClip(bool b) { setValue(EARLYCLIP, b); } + +bool FractoriumSettings::Transparency() { return value(TRANSPARENCY).toBool(); } +void FractoriumSettings::Transparency(bool b) { setValue(TRANSPARENCY, b); } + +bool FractoriumSettings::Double() { return value(DOUBLEPRECISION).toBool(); } +void FractoriumSettings::Double(bool b) { setValue(DOUBLEPRECISION, b); } + +bool FractoriumSettings::OpenCL() { return value(OPENCL).toBool(); } +void FractoriumSettings::OpenCL(bool b) { setValue(OPENCL, b); } + +unsigned int FractoriumSettings::PlatformIndex() { return value(PLATFORMINDEX).toUInt(); } +void FractoriumSettings::PlatformIndex(unsigned int i) { setValue(PLATFORMINDEX, i); } + +unsigned int FractoriumSettings::DeviceIndex() { return value(DEVICEINDEX).toUInt(); } +void FractoriumSettings::DeviceIndex(unsigned int i) { setValue(DEVICEINDEX, i); } + +unsigned int FractoriumSettings::ThreadCount() { return value(THREADCOUNT).toUInt(); } +void FractoriumSettings::ThreadCount(unsigned int i) { setValue(THREADCOUNT, i); } + +bool FractoriumSettings::CpuDEFilter() { return value(CPUDEFILTER).toBool(); } +void FractoriumSettings::CpuDEFilter(bool b) { setValue(CPUDEFILTER, b); } + +bool FractoriumSettings::OpenCLDEFilter() { return value(OPENCLDEFILTER).toBool(); } +void FractoriumSettings::OpenCLDEFilter(bool b) { setValue(OPENCLDEFILTER, b); } + +unsigned int FractoriumSettings::CpuSubBatch() { return value(CPUSUBBATCH).toUInt(); } +void FractoriumSettings::CpuSubBatch(unsigned int b) { setValue(CPUSUBBATCH, b); } + +unsigned int FractoriumSettings::OpenCLSubBatch() { return value(OPENCLSUBBATCH).toUInt(); } +void FractoriumSettings::OpenCLSubBatch(unsigned int b) { setValue(OPENCLSUBBATCH, b); } + +/// +/// Final render settings. +/// + +bool FractoriumSettings::FinalEarlyClip() { return value(FINALEARLYCLIP).toBool(); } +void FractoriumSettings::FinalEarlyClip(bool b) { setValue(FINALEARLYCLIP, b); } + +bool FractoriumSettings::FinalTransparency() { return value(FINALTRANSPARENCY).toBool(); } +void FractoriumSettings::FinalTransparency(bool b) { setValue(FINALTRANSPARENCY, b); } + +bool FractoriumSettings::FinalOpenCL() { return value(FINALOPENCL).toBool(); } +void FractoriumSettings::FinalOpenCL(bool b) { setValue(FINALOPENCL, b); } + +bool FractoriumSettings::FinalDouble() { return value(FINALDOUBLEPRECISION).toBool(); } +void FractoriumSettings::FinalDouble(bool b) { setValue(FINALDOUBLEPRECISION, b); } + +bool FractoriumSettings::FinalSaveXml() { return value(FINALSAVEXML).toBool(); } +void FractoriumSettings::FinalSaveXml(bool b) { setValue(FINALSAVEXML, b); } + +bool FractoriumSettings::FinalDoAll() { return value(FINALDOALL).toBool(); } +void FractoriumSettings::FinalDoAll(bool b) { setValue(FINALDOALL, b); } + +bool FractoriumSettings::FinalDoSequence() { return value(FINALDOSEQUENCE).toBool(); } +void FractoriumSettings::FinalDoSequence(bool b) { setValue(FINALDOSEQUENCE, b); } + +bool FractoriumSettings::FinalKeepAspect() { return value(FINALKEEPASPECT).toBool(); } +void FractoriumSettings::FinalKeepAspect(bool b) { setValue(FINALKEEPASPECT, b); } + +unsigned int FractoriumSettings::FinalScale() { return value(FINALSCALE).toUInt(); } +void FractoriumSettings::FinalScale(unsigned int i) { setValue(FINALSCALE, i); } + +QString FractoriumSettings::FinalDoAllExt() { return value(FINALDOALLEXT).toString(); } +void FractoriumSettings::FinalDoAllExt(QString s) { setValue(FINALDOALLEXT, s); } + +unsigned int FractoriumSettings::FinalPlatformIndex() { return value(FINALPLATFORMINDEX).toUInt(); } +void FractoriumSettings::FinalPlatformIndex(unsigned int i) { setValue(FINALPLATFORMINDEX, i); } + +unsigned int FractoriumSettings::FinalDeviceIndex() { return value(FINALDEVICEINDEX).toUInt(); } +void FractoriumSettings::FinalDeviceIndex(unsigned int i) { setValue(FINALDEVICEINDEX, i); } + +unsigned int FractoriumSettings::FinalThreadCount() { return value(FINALTHREADCOUNT).toUInt(); } +void FractoriumSettings::FinalThreadCount(unsigned int i) { setValue(FINALTHREADCOUNT, i); } + +unsigned int FractoriumSettings::FinalWidth() { return value(FINALWIDTH).toUInt(); } +void FractoriumSettings::FinalWidth(unsigned int i) { setValue(FINALWIDTH, i); } + +unsigned int FractoriumSettings::FinalHeight() { return value(FINALHEIGHT).toUInt(); } +void FractoriumSettings::FinalHeight(unsigned int i) { setValue(FINALHEIGHT, i); } + +unsigned int FractoriumSettings::FinalQuality() { return value(FINALQUALITY).toUInt(); } +void FractoriumSettings::FinalQuality(unsigned int i) { setValue(FINALQUALITY, i); } + +unsigned int FractoriumSettings::FinalTemporalSamples() { return value(FINALTEMPORALSAMPLES).toUInt(); } +void FractoriumSettings::FinalTemporalSamples(unsigned int i) { setValue(FINALTEMPORALSAMPLES, i); } + +unsigned int FractoriumSettings::FinalSupersample() { return value(FINALSUPERSAMPLE).toUInt(); } +void FractoriumSettings::FinalSupersample(unsigned int i) { setValue(FINALSUPERSAMPLE, i); } + +/// +/// Xml file saving settings. +/// + +unsigned int FractoriumSettings::XmlWidth() { return value(XMLWIDTH).toUInt(); } +void FractoriumSettings::XmlWidth(unsigned int i) { setValue(XMLWIDTH, i); } + +unsigned int FractoriumSettings::XmlHeight() { return value(XMLHEIGHT).toUInt(); } +void FractoriumSettings::XmlHeight(unsigned int i) { setValue(XMLHEIGHT, i); } + +unsigned int FractoriumSettings::XmlTemporalSamples() { return value(XMLTEMPORALSAMPLES).toUInt(); } +void FractoriumSettings::XmlTemporalSamples(unsigned int i) { setValue(XMLTEMPORALSAMPLES, i); } + +unsigned int FractoriumSettings::XmlQuality() { return value(XMLQUALITY).toUInt(); } +void FractoriumSettings::XmlQuality(unsigned int i) { setValue(XMLQUALITY, i); } + +unsigned int FractoriumSettings::XmlSupersample() { return value(XMLSUPERSAMPLE).toUInt(); } +void FractoriumSettings::XmlSupersample(unsigned int i) { setValue(XMLSUPERSAMPLE, i); } + +QString FractoriumSettings::Id() { return value(IDENTITYID).toString(); } +void FractoriumSettings::Id(QString s) { setValue(IDENTITYID, s); } + +QString FractoriumSettings::Url() { return value(IDENTITYURL).toString(); } +void FractoriumSettings::Url(QString s) { setValue(IDENTITYURL, s); } + +QString FractoriumSettings::Nick() { return value(IDENTITYNICK).toString(); } +void FractoriumSettings::Nick(QString s) { setValue(IDENTITYNICK, s); } + +/// +/// General operations settings. +/// + +QString FractoriumSettings::OpenFolder() { return value(OPENFOLDER).toString(); } +void FractoriumSettings::OpenFolder(QString s) { setValue(OPENFOLDER, s); } + +QString FractoriumSettings::SaveFolder() { return value(SAVEFOLDER).toString(); } +void FractoriumSettings::SaveFolder(QString s) { setValue(SAVEFOLDER, s); } + +QString FractoriumSettings::OpenXmlExt() { return value(OPENXMLEXT).toString(); } +void FractoriumSettings::OpenXmlExt(QString s) { setValue(OPENXMLEXT, s); } + +QString FractoriumSettings::SaveXmlExt() { return value(SAVEXMLEXT).toString(); } +void FractoriumSettings::SaveXmlExt(QString s) { setValue(SAVEXMLEXT, s); } + +QString FractoriumSettings::OpenImageExt() { return value(OPENIMAGEEXT).toString(); } +void FractoriumSettings::OpenImageExt(QString s) { setValue(OPENIMAGEEXT, s); } + +QString FractoriumSettings::SaveImageExt() { return value(SAVEIMAGEEXT).toString(); } +void FractoriumSettings::SaveImageExt(QString s) { setValue(SAVEIMAGEEXT, s); } diff --git a/Source/Fractorium/FractoriumSettings.h b/Source/Fractorium/FractoriumSettings.h new file mode 100644 index 0000000..bc263fc --- /dev/null +++ b/Source/Fractorium/FractoriumSettings.h @@ -0,0 +1,198 @@ +#pragma once + +#include "FractoriumPch.h" + +/// +/// FractoriumSettings class. +/// + +#define EARLYCLIP "render/earlyclip" +#define TRANSPARENCY "render/transparency" +#define OPENCL "render/opencl" +#define DOUBLEPRECISION "render/dp64" +#define PLATFORMINDEX "render/platformindex" +#define DEVICEINDEX "render/deviceindex" +#define THREADCOUNT "render/threadcount" +#define CPUDEFILTER "render/cpudefilter" +#define OPENCLDEFILTER "render/opencldefilter" +#define CPUSUBBATCH "render/cpusubbatch" +#define OPENCLSUBBATCH "render/openclsubbatch" + +#define FINALEARLYCLIP "finalrender/earlyclip" +#define FINALTRANSPARENCY "finalrender/transparency" +#define FINALOPENCL "finalrender/opencl" +#define FINALDOUBLEPRECISION "finalrender/dp64" +#define FINALSAVEXML "finalrender/savexml" +#define FINALDOALL "finalrender/doall" +#define FINALDOSEQUENCE "finalrender/dosequence" +#define FINALKEEPASPECT "finalrender/keepaspect" +#define FINALSCALE "finalrender/scale" +#define FINALDOALLEXT "finalrender/doallext" +#define FINALPLATFORMINDEX "finalrender/platformindex" +#define FINALDEVICEINDEX "finalrender/deviceindex" +#define FINALTHREADCOUNT "finalrender/threadcount" +#define FINALWIDTH "finalrender/width" +#define FINALHEIGHT "finalrender/height" +#define FINALQUALITY "finalrender/quality" +#define FINALTEMPORALSAMPLES "finalrender/temporalsamples" +#define FINALSUPERSAMPLE "finalrender/supersample" + +#define XMLWIDTH "xml/width" +#define XMLHEIGHT "xml/height" +#define XMLTEMPORALSAMPLES "xml/temporalsamples" +#define XMLQUALITY "xml/quality" +#define XMLSUPERSAMPLE "xml/supersample" + +#define OPENFOLDER "path/open" +#define SAVEFOLDER "path/save" + +#define OPENXMLEXT "file/openxmlext" +#define SAVEXMLEXT "file/savexmlext" +#define OPENIMAGEEXT "file/openimageext" +#define SAVEIMAGEEXT "file/saveimageext" + +#define IDENTITYID "identity/id" +#define IDENTITYURL "identity/url" +#define IDENTITYNICK "identity/nick" + +/// +/// Class for preserving various program options between +/// runs of Fractorium. Each of these generally corresponds +/// to items in the options dialog and the final render dialog. +/// +class FractoriumSettings : public QSettings +{ + Q_OBJECT +public: + FractoriumSettings(QObject* parent); + void EnsureDefaults(); + + bool EarlyClip(); + void EarlyClip(bool b); + + bool Transparency(); + void Transparency(bool b); + + bool OpenCL(); + void OpenCL(bool b); + + bool Double(); + void Double(bool b); + + unsigned int PlatformIndex(); + void PlatformIndex(unsigned int b); + + unsigned int DeviceIndex(); + void DeviceIndex(unsigned int b); + + unsigned int ThreadCount(); + void ThreadCount(unsigned int b); + + bool CpuDEFilter(); + void CpuDEFilter(bool b); + + bool OpenCLDEFilter(); + void OpenCLDEFilter(bool b); + + unsigned int CpuSubBatch(); + void CpuSubBatch(unsigned int b); + + unsigned int OpenCLSubBatch(); + void OpenCLSubBatch(unsigned int b); + + bool FinalEarlyClip(); + void FinalEarlyClip(bool b); + + bool FinalTransparency(); + void FinalTransparency(bool b); + + bool FinalOpenCL(); + void FinalOpenCL(bool b); + + bool FinalDouble(); + void FinalDouble(bool b); + + bool FinalSaveXml(); + void FinalSaveXml(bool b); + + bool FinalDoAll(); + void FinalDoAll(bool b); + + bool FinalDoSequence(); + void FinalDoSequence(bool b); + + bool FinalKeepAspect(); + void FinalKeepAspect(bool b); + + unsigned int FinalScale(); + void FinalScale(unsigned int i); + + QString FinalDoAllExt(); + void FinalDoAllExt(QString s); + + unsigned int FinalPlatformIndex(); + void FinalPlatformIndex(unsigned int b); + + unsigned int FinalDeviceIndex(); + void FinalDeviceIndex(unsigned int b); + + unsigned int FinalThreadCount(); + void FinalThreadCount(unsigned int b); + + unsigned int FinalWidth(); + void FinalWidth(unsigned int i); + + unsigned int FinalHeight(); + void FinalHeight(unsigned int i); + + unsigned int FinalQuality(); + void FinalQuality(unsigned int i); + + unsigned int FinalTemporalSamples(); + void FinalTemporalSamples(unsigned int i); + + unsigned int FinalSupersample(); + void FinalSupersample(unsigned int i); + + unsigned int XmlWidth(); + void XmlWidth(unsigned int i); + + unsigned int XmlHeight(); + void XmlHeight(unsigned int i); + + unsigned int XmlTemporalSamples(); + void XmlTemporalSamples(unsigned int i); + + unsigned int XmlQuality(); + void XmlQuality(unsigned int i); + + unsigned int XmlSupersample(); + void XmlSupersample(unsigned int i); + + QString OpenFolder(); + void OpenFolder(QString s); + + QString SaveFolder(); + void SaveFolder(QString s); + + QString OpenXmlExt(); + void OpenXmlExt(QString s); + + QString SaveXmlExt(); + void SaveXmlExt(QString s); + + QString OpenImageExt(); + void OpenImageExt(QString s); + + QString SaveImageExt(); + void SaveImageExt(QString s); + + QString Id(); + void Id(QString s); + + QString Url(); + void Url(QString s); + + QString Nick(); + void Nick(QString s); +}; diff --git a/Source/Fractorium/FractoriumToolbar.cpp b/Source/Fractorium/FractoriumToolbar.cpp new file mode 100644 index 0000000..ad03925 --- /dev/null +++ b/Source/Fractorium/FractoriumToolbar.cpp @@ -0,0 +1,21 @@ +#include "FractoriumPch.h" +#include "Fractorium.h" + +/// +/// Initialize the toolbar UI. +/// +void Fractorium::InitToolbarUI() +{ + //These aren't menus but duplicate menu functionality in a pseudo-toolbar. + connect(ui.SaveCurrentAsXmlButton, SIGNAL(clicked(bool)), this, SLOT(OnSaveCurrentAsXmlButtonClicked(bool)), Qt::QueuedConnection); + connect(ui.SaveEntireFileAsXmlButton, SIGNAL(clicked(bool)), this, SLOT(OnSaveEntireFileAsXmlButtonClicked(bool)), Qt::QueuedConnection); + connect(ui.SaveCurrentToOpenedFileButton, SIGNAL(clicked(bool)), this, SLOT(OnSaveCurrentToOpenedFileButtonClicked(bool)), Qt::QueuedConnection); +} + +/// +/// Wrappers around calls to menu items. +/// + +void Fractorium::OnSaveCurrentAsXmlButtonClicked(bool checked) { OnActionSaveCurrentAsXml(checked); } +void Fractorium::OnSaveEntireFileAsXmlButtonClicked(bool checked) { OnActionSaveEntireFileAsXml(checked); } +void Fractorium::OnSaveCurrentToOpenedFileButtonClicked(bool checked) { OnActionSaveCurrentToOpenedFile(checked); } diff --git a/Source/Fractorium/FractoriumXforms.cpp b/Source/Fractorium/FractoriumXforms.cpp new file mode 100644 index 0000000..689bb95 --- /dev/null +++ b/Source/Fractorium/FractoriumXforms.cpp @@ -0,0 +1,345 @@ +#include "FractoriumPch.h" +#include "Fractorium.h" + +/// +/// Initialize the xforms UI. +/// +void Fractorium::InitXformsUI() +{ + int spinHeight = 20, row = 0; + + connect(ui.AddXformButton, SIGNAL(clicked(bool)), this, SLOT(OnAddXformButtonClicked(bool)), Qt::QueuedConnection); + connect(ui.DuplicateXformButton, SIGNAL(clicked(bool)), this, SLOT(OnDuplicateXformButtonClicked(bool)), Qt::QueuedConnection); + connect(ui.ClearXformButton, SIGNAL(clicked(bool)), this, SLOT(OnClearXformButtonClicked(bool)), Qt::QueuedConnection); + connect(ui.DeleteXformButton, SIGNAL(clicked(bool)), this, SLOT(OnDeleteXformButtonClicked(bool)), Qt::QueuedConnection); + connect(ui.AddFinalXformButton, SIGNAL(clicked(bool)), this, SLOT(OnAddFinalXformButtonClicked(bool)), Qt::QueuedConnection); + connect(ui.CurrentXformCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(OnCurrentXformComboChanged(int)), Qt::QueuedConnection); + + SetFixedTableHeader(ui.XformWeightNameTable->horizontalHeader()); + //Use SetupSpinner() just to create the spinner, but use col of -1 to prevent it from being added to the table. + SetupSpinner(ui.XformWeightNameTable, this, row, -1, m_XformWeightSpin, spinHeight, 0, 1000, 0.05, SIGNAL(valueChanged(double)), SLOT(OnXformWeightChanged(double)), false, 0, 1, 0); + m_XformWeightSpin->setDecimals(3); + m_XformWeightSpin->SmallStep(0.001); + m_XformWeightSpinnerButtonWidget = new SpinnerButtonWidget(m_XformWeightSpin, "=", 20, 19, ui.XformWeightNameTable); + m_XformWeightSpinnerButtonWidget->m_Button->setToolTip("Equalize weights"); + m_XformWeightSpinnerButtonWidget->m_Button->setStyleSheet("text-align: center center"); + connect(m_XformWeightSpinnerButtonWidget->m_Button, SIGNAL(clicked(bool)), this, SLOT(OnEqualWeightButtonClicked(bool)), Qt::QueuedConnection); + + ui.XformWeightNameTable->setCellWidget(0, 0, m_XformWeightSpinnerButtonWidget); + ui.XformWeightNameTable->setItem(0, 1, new QTableWidgetItem()); + connect(ui.XformWeightNameTable, SIGNAL(cellChanged(int, int)), this, SLOT(OnXformNameChanged(int, int)), Qt::QueuedConnection); + + ui.CurrentXformCombo->setProperty("soloxform", -1); +} + +/// +/// Get the current xform. +/// +/// The current xform as specified by the current xform combo box index. NULL if out of range (should never happen). +template +Xform* FractoriumEmberController::CurrentXform() +{ + return m_Ember.GetTotalXform(m_Fractorium->ui.CurrentXformCombo->currentIndex()); +} + +/// +/// Set the current xform to the index passed in. +/// +/// The index to set the current xform to +void Fractorium::CurrentXform(unsigned int i) +{ + if (i < ui.CurrentXformCombo->count()) + ui.CurrentXformCombo->setCurrentIndex(i); +} + +/// +/// Set the current xform and populate all GUI widgets. +/// Called when the current xform combo box index changes. +/// +/// The selected combo box index +template +void FractoriumEmberController::CurrentXformComboChanged(int index) +{ + if (Xform* xform = m_Ember.GetTotalXform(index)) + { + FillWithXform(xform); + m_GLController->SetSelectedXform(xform); + + int solo = m_Fractorium->ui.CurrentXformCombo->property("soloxform").toInt(); + + m_Fractorium->ui.SoloXformCheckBox->blockSignals(true); + m_Fractorium->ui.SoloXformCheckBox->setChecked(solo == index); + m_Fractorium->ui.SoloXformCheckBox->blockSignals(false); + + bool enable = !IsFinal(CurrentXform()); + + m_Fractorium->ui.DuplicateXformButton->setEnabled(enable); + m_Fractorium->m_XformWeightSpin->setEnabled(enable); + m_Fractorium->ui.SoloXformCheckBox->setEnabled(enable); + m_Fractorium->ui.AddFinalXformButton->setEnabled(enable); + } +} + +void Fractorium::OnCurrentXformComboChanged(int index) { m_Controller->CurrentXformComboChanged(index); } + +/// +/// Add an empty xform in the current ember and set it as the current xform. +/// Called when the add xform button is clicked. +/// Resets the rendering process. +/// +/// Ignored +template +void FractoriumEmberController::AddXform() +{ + UpdateCurrentXform([&] (Xform* xform) + { + Xform newXform; + QComboBox* combo = m_Fractorium->ui.CurrentXformCombo; + + newXform.m_Weight = 0.25; + newXform.m_ColorX = m_Rand.Frand01(); + m_Ember.AddXform(newXform); + m_Fractorium->FillXforms(); + combo->setCurrentIndex(combo->count() - (m_Fractorium->HaveFinal() ? 2 : 1));//Set index to the last item before final. + }); +} + +void Fractorium::OnAddXformButtonClicked(bool checked) { m_Controller->AddXform(); } + +/// +/// Duplicate the current xform in the current ember, and set it as the current xform. +/// Called when the duplicate xform button is clicked. +/// Resets the rendering process. +/// +/// Ignored +template +void FractoriumEmberController::DuplicateXform() +{ + UpdateCurrentXform([&] (Xform* xform) + { + QComboBox* combo = m_Fractorium->ui.CurrentXformCombo; + + if (xform && !IsFinal(xform)) + { + m_Ember.AddXform(*xform); + m_Fractorium->FillXforms(); + combo->setCurrentIndex(combo->count() - (m_Fractorium->HaveFinal() ? 2 : 1));//Set index to the last item before final. + } + }); +} + +void Fractorium::OnDuplicateXformButtonClicked(bool checked) { m_Controller->DuplicateXform(); } + +/// +/// Clear all variations from the current xform, affine, palette and xaos are left untouched. +/// Called when the clear xform button is clicked. +/// Resets the rendering process. +/// +/// Ignored +template +void FractoriumEmberController::ClearCurrentXform() +{ + UpdateCurrentXform([&] (Xform* xform) + { + xform->ClearAndDeleteVariations(); + //Note xaos is left alone. + FillVariationTreeWithXform(xform); + }); +} + +void Fractorium::OnClearXformButtonClicked(bool checked) { m_Controller->ClearCurrentXform(); } + +/// +/// Delete the current xform. +/// Will not delete the last remaining non-final xform. +/// Called when the delete xform button is clicked. +/// Resets the rendering process. +/// +/// Ignored +template +void FractoriumEmberController::DeleteCurrentXform() +{ + UpdateCurrentXform([&] (Xform* xform) + { + bool haveFinal = m_Fractorium->HaveFinal(); + QComboBox* combo = m_Fractorium->ui.CurrentXformCombo; + int count = combo->count(); + int index = combo->currentIndex(); + + //Do not allow deleting the only remaining non-final xform. + if (haveFinal && count <= 2 && index == 0) + return; + + if (!haveFinal && count == 1) + return; + + m_Ember.DeleteTotalXform(index); + m_Fractorium->FillXforms(); + combo->setCurrentIndex(combo->count() - (haveFinal ? 2 : 1));//Set index to the last item before final. + }); +} + +void Fractorium::OnDeleteXformButtonClicked(bool checked) { m_Controller->DeleteCurrentXform(); } + +/// +/// Add a final xform to the ember and set it as the current xform. +/// Will only take action if a final xform is not already present. +/// Called when the add final xform button is clicked. +/// Resets the rendering process. +/// +/// Ignored +template +void FractoriumEmberController::AddFinalXform() +{ + QComboBox* combo = m_Fractorium->ui.CurrentXformCombo; + + //Check to see if a final xform is already present. + if (!m_Fractorium->HaveFinal()) + { + Xform xform; + + xform.AddVariation(new LinearVariation());//Just a placeholder so other parts of the code don't see it as being empty. + m_Ember.SetFinalXform(xform); + combo->addItem("Final"); + combo->setCurrentIndex(combo->count() - 1);//Set index to the last item. + UpdateRender(); + } +} + +void Fractorium::OnAddFinalXformButtonClicked(bool checked) { m_Controller->AddFinalXform(); } + +/// +/// Set the weight of the current xform. +/// Called when weight spinner changes. +/// Resets the rendering process. +/// +/// The weight +template +void FractoriumEmberController::XformWeightChanged(double d) +{ + UpdateCurrentXform([&] (Xform* xform) + { + xform->m_Weight = d; + SetNormalizedWeightText(xform); + }); +} + +void Fractorium::OnXformWeightChanged(double d) { m_Controller->XformWeightChanged(d); } + +/// +/// Equalize the weights of all xforms in the ember. +/// +template +void FractoriumEmberController::EqualizeWeights() +{ + UpdateCurrentXform([&] (Xform* xform) + { + m_Ember.EqualizeWeights(); + m_Fractorium->m_XformWeightSpin->setValue(xform->m_Weight);//Will trigger an update, so pass false to updateRender below. + }, false); +} + +void Fractorium::OnEqualWeightButtonClicked(bool checked) { m_Controller->EqualizeWeights(); } + +/// +/// Set the name of the current xform. +/// Called when the user types in the name cell of the table. +/// +/// The row of the cell +/// The col of the cell +template +void FractoriumEmberController::XformNameChanged(int row, int col) +{ + UpdateCurrentXform([&] (Xform* xform) + { + int index = m_Ember.GetXformIndex(xform); + + xform->m_Name = m_Fractorium->ui.XformWeightNameTable->item(row, col)->text().toStdString(); + + if (index != -1) + { + if (QTableWidgetItem* xformNameItem = m_Fractorium->ui.XaosTable->item(index, 0)) + xformNameItem->setText(MakeXaosNameString(index)); + } + }, false); +} + +void Fractorium::OnXformNameChanged(int row, int col) { m_Controller->XformNameChanged(row, col); } + +/// +/// Fill all GUI widgets with values from the passed in xform. +/// +/// The xform whose values will be used to populate the widgets +template +void FractoriumEmberController::FillWithXform(Xform* xform) +{ + m_Fractorium->m_XformWeightSpin->SetValueStealth(xform->m_Weight); + SetNormalizedWeightText(xform); + + if (QTableWidgetItem* item = m_Fractorium->ui.XformWeightNameTable->item(0, 1)) + { + m_Fractorium->ui.XformWeightNameTable->blockSignals(true); + item->setText(QString::fromStdString(xform->m_Name)); + m_Fractorium->ui.XformWeightNameTable->blockSignals(false); + } + + FillVariationTreeWithXform(xform); + FillColorWithXform(xform); + FillAffineWithXform(xform, true); + FillAffineWithXform(xform, false); + FillXaosWithCurrentXform(); +} + +/// +/// Set the normalized weight of the current xform as the suffix text of the weight spinner. +/// +/// The current xform whose normalized weight will be shown +template +void FractoriumEmberController::SetNormalizedWeightText(Xform* xform) +{ + if (xform) + { + int index = m_Ember.GetXformIndex(xform); + + m_Ember.CalcNormalizedWeights(m_NormalizedWeights); + + if (index != -1 && index < m_NormalizedWeights.size()) + m_Fractorium->m_XformWeightSpin->setSuffix(QString(" (") + QString::number((double)m_NormalizedWeights[index], 'g', 3) + ")"); + } +} + +/// +/// Determine whether the specified xform is the final xform in the ember. +/// +/// The xform to examine +/// True if final, else false. +template +bool FractoriumEmberController::IsFinal(Xform* xform) +{ + return (m_Fractorium->HaveFinal() && (xform == m_Ember.FinalXform())); +} + +/// +/// Fill the xforms combo box with the xforms in the current ember. +/// Select the first one and fill all widgets with its values. +/// +void Fractorium::FillXforms() +{ + int spinHeight = 20; + QComboBox* combo = ui.CurrentXformCombo; + + combo->blockSignals(true); + combo->clear(); + + for (int i = 0; i < m_Controller->XformCount(); i++) + combo->addItem(QString::number(i + 1)); + + if (m_Controller->UseFinalXform()) + combo->addItem("Final"); + + combo->blockSignals(false); + combo->setCurrentIndex(0); + FillXaosTable(); + OnSoloXformCheckBoxStateChanged(Qt::Unchecked); + OnCurrentXformComboChanged(0);//Make sure the event gets called, because it won't if the zero index is already selected. +} diff --git a/Source/Fractorium/FractoriumXformsAffine.cpp b/Source/Fractorium/FractoriumXformsAffine.cpp new file mode 100644 index 0000000..75172c4 --- /dev/null +++ b/Source/Fractorium/FractoriumXformsAffine.cpp @@ -0,0 +1,492 @@ +#include "FractoriumPch.h" +#include "Fractorium.h" + +/// +/// Initialize the xforms affine UI. +/// +void Fractorium::InitXformsAffineUI() +{ + int row = 0, affinePrec = 6, spinHeight = 20; + double affineStep = 0.01, affineMin = std::numeric_limits::lowest(), affineMax = std::numeric_limits::max(); + QTableWidget* table = ui.PreAffineTable; + + SetFixedTableHeader(table->horizontalHeader(), QHeaderView::Stretch);//The designer continually clobbers these values, so must manually set them here. + SetFixedTableHeader(table->verticalHeader()); + + //Pre affine spinners. + SetupAffineSpinner(table, this, 0, 0, m_PreX1Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnX1Changed(double))); + SetupAffineSpinner(table, this, 0, 1, m_PreX2Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnX2Changed(double))); + SetupAffineSpinner(table, this, 1, 0, m_PreY1Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnY1Changed(double))); + SetupAffineSpinner(table, this, 1, 1, m_PreY2Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnY2Changed(double))); + SetupAffineSpinner(table, this, 2, 0, m_PreO1Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnO1Changed(double))); + SetupAffineSpinner(table, this, 2, 1, m_PreO2Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnO2Changed(double))); + + table = ui.PostAffineTable; + SetFixedTableHeader(table->horizontalHeader(), QHeaderView::Stretch);//The designer continually clobbers these values, so must manually set them here. + SetFixedTableHeader(table->verticalHeader()); + + //Post affine spinners. + SetupAffineSpinner(table, this, 0, 0, m_PostX1Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnX1Changed(double))); + SetupAffineSpinner(table, this, 0, 1, m_PostX2Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnX2Changed(double))); + SetupAffineSpinner(table, this, 1, 0, m_PostY1Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnY1Changed(double))); + SetupAffineSpinner(table, this, 1, 1, m_PostY2Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnY2Changed(double))); + SetupAffineSpinner(table, this, 2, 0, m_PostO1Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnO1Changed(double))); + SetupAffineSpinner(table, this, 2, 1, m_PostO2Spin, spinHeight, affineMin, affineMax, affineStep, affinePrec, SIGNAL(valueChanged(double)), SLOT(OnO2Changed(double))); + + ui.PreRotateCombo->setValidator(new QDoubleValidator(ui.PreRotateCombo)); + ui.PreMoveCombo->setValidator( new QDoubleValidator(ui.PreMoveCombo)); + ui.PreScaleCombo->setValidator( new QDoubleValidator(ui.PreScaleCombo)); + + ui.PostRotateCombo->setValidator(new QDoubleValidator(ui.PostRotateCombo)); + ui.PostMoveCombo->setValidator( new QDoubleValidator(ui.PostMoveCombo)); + ui.PostScaleCombo->setValidator( new QDoubleValidator(ui.PostScaleCombo)); + + connect(ui.PreFlipHorizontalButton, SIGNAL(clicked(bool)), this, SLOT(OnFlipHorizontalButtonClicked(bool)), Qt::QueuedConnection); + connect(ui.PreFlipVerticalButton, SIGNAL(clicked(bool)), this, SLOT(OnFlipVerticalButtonClicked(bool)), Qt::QueuedConnection); + connect(ui.PreRotate90CButton, SIGNAL(clicked(bool)), this, SLOT(OnRotate90CButtonClicked(bool)), Qt::QueuedConnection); + connect(ui.PreRotate90CcButton, SIGNAL(clicked(bool)), this, SLOT(OnRotate90CcButtonClicked(bool)), Qt::QueuedConnection); + connect(ui.PreRotateCButton, SIGNAL(clicked(bool)), this, SLOT(OnRotateCButtonClicked(bool)), Qt::QueuedConnection); + connect(ui.PreRotateCcButton, SIGNAL(clicked(bool)), this, SLOT(OnRotateCcButtonClicked(bool)), Qt::QueuedConnection); + connect(ui.PreMoveUpButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveUpButtonClicked(bool)), Qt::QueuedConnection); + connect(ui.PreMoveDownButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveDownButtonClicked(bool)), Qt::QueuedConnection); + connect(ui.PreMoveLeftButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveLeftButtonClicked(bool)), Qt::QueuedConnection); + connect(ui.PreMoveRightButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveRightButtonClicked(bool)), Qt::QueuedConnection); + connect(ui.PreScaleDownButton, SIGNAL(clicked(bool)), this, SLOT(OnScaleDownButtonClicked(bool)), Qt::QueuedConnection); + connect(ui.PreScaleUpButton, SIGNAL(clicked(bool)), this, SLOT(OnScaleUpButtonClicked(bool)), Qt::QueuedConnection); + connect(ui.PreResetButton, SIGNAL(clicked(bool)), this, SLOT(OnResetAffineButtonClicked(bool)), Qt::QueuedConnection); + + connect(ui.PostFlipHorizontalButton, SIGNAL(clicked(bool)), this, SLOT(OnFlipHorizontalButtonClicked(bool)), Qt::QueuedConnection); + connect(ui.PostFlipVerticalButton, SIGNAL(clicked(bool)), this, SLOT(OnFlipVerticalButtonClicked(bool)), Qt::QueuedConnection); + connect(ui.PostRotate90CcButton, SIGNAL(clicked(bool)), this, SLOT(OnRotate90CcButtonClicked(bool)), Qt::QueuedConnection); + connect(ui.PostRotateCcButton, SIGNAL(clicked(bool)), this, SLOT(OnRotateCcButtonClicked(bool)), Qt::QueuedConnection); + connect(ui.PostRotateCButton, SIGNAL(clicked(bool)), this, SLOT(OnRotateCButtonClicked(bool)), Qt::QueuedConnection); + connect(ui.PostRotate90CButton, SIGNAL(clicked(bool)), this, SLOT(OnRotate90CButtonClicked(bool)), Qt::QueuedConnection); + connect(ui.PostMoveUpButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveUpButtonClicked(bool)), Qt::QueuedConnection); + connect(ui.PostMoveDownButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveDownButtonClicked(bool)), Qt::QueuedConnection); + connect(ui.PostMoveLeftButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveLeftButtonClicked(bool)), Qt::QueuedConnection); + connect(ui.PostMoveRightButton, SIGNAL(clicked(bool)), this, SLOT(OnMoveRightButtonClicked(bool)), Qt::QueuedConnection); + connect(ui.PostScaleDownButton, SIGNAL(clicked(bool)), this, SLOT(OnScaleDownButtonClicked(bool)), Qt::QueuedConnection); + connect(ui.PostScaleUpButton, SIGNAL(clicked(bool)), this, SLOT(OnScaleUpButtonClicked(bool)), Qt::QueuedConnection); + connect(ui.PostResetButton, SIGNAL(clicked(bool)), this, SLOT(OnResetAffineButtonClicked(bool)), Qt::QueuedConnection); + + connect(ui.PreAffineGroupBox, SIGNAL(toggled(bool)), this, SLOT(OnAffineGroupBoxToggled(bool)), Qt::QueuedConnection); + connect(ui.PostAffineGroupBox, SIGNAL(toggled(bool)), this, SLOT(OnAffineGroupBoxToggled(bool)), Qt::QueuedConnection); + + connect(ui.ShowPreAffineAllRadio, SIGNAL(toggled(bool)), this, SLOT(OnAffineDrawAllCurrentRadioButtonToggled(bool)), Qt::QueuedConnection); + connect(ui.ShowPreAffineCurrentRadio, SIGNAL(toggled(bool)), this, SLOT(OnAffineDrawAllCurrentRadioButtonToggled(bool)), Qt::QueuedConnection); + connect(ui.ShowPostAffineAllRadio, SIGNAL(toggled(bool)), this, SLOT(OnAffineDrawAllCurrentRadioButtonToggled(bool)), Qt::QueuedConnection); + connect(ui.ShowPostAffineCurrentRadio, SIGNAL(toggled(bool)), this, SLOT(OnAffineDrawAllCurrentRadioButtonToggled(bool)), Qt::QueuedConnection); + + ui.PostAffineGroupBox->setChecked(true);//Flip it once to force the disabling of the group box. + ui.PostAffineGroupBox->setChecked(false); +} + +/// +/// Helper for setting the value of a single pre/post affine coefficient. +/// Resets the rendering process. +/// +/// The value to set +/// The index to set, 0-5, corresponding to a, b, c, d, e, f +/// True if pre affine, false if post affine. +template +void FractoriumEmberController::AffineSetHelper(double d, int index, bool pre) +{ + UpdateCurrentXform([&] (Xform* xform) + { + QObject* objSender = m_Fractorium->sender(); + DoubleSpinBox* spinBox = dynamic_cast(objSender); + + if (spinBox) + { + Affine2D* affine = pre ? &xform->m_Affine : &xform->m_Post; + + switch (index) + { + case 0: + affine->A(spinBox->value()); + break; + case 1: + affine->B(spinBox->value()); + break; + case 2: + affine->C(spinBox->value()); + break; + case 3: + affine->D(spinBox->value()); + break; + case 4: + affine->E(spinBox->value()); + break; + case 5: + affine->F(spinBox->value()); + break; + } + } + }); +} + +/// +/// Pre and post affine spinner changed events. +/// Resets the rendering process. +/// +void Fractorium::OnX1Changed(double d) { m_Controller->AffineSetHelper(d, 0, sender() == m_PreX1Spin); } +void Fractorium::OnX2Changed(double d) { m_Controller->AffineSetHelper(d, 3, sender() == m_PreX2Spin); } +void Fractorium::OnY1Changed(double d) { m_Controller->AffineSetHelper(d, 1, sender() == m_PreY1Spin); } +void Fractorium::OnY2Changed(double d) { m_Controller->AffineSetHelper(d, 4, sender() == m_PreY2Spin); } +void Fractorium::OnO1Changed(double d) { m_Controller->AffineSetHelper(d, 2, sender() == m_PreO1Spin); } +void Fractorium::OnO2Changed(double d) { m_Controller->AffineSetHelper(d, 5, sender() == m_PreO2Spin); } + +/// +/// Flip the current pre/post affine vertically and/or horizontally. +/// Resets the rendering process. +/// +/// True to flip horizontally +/// True to flip vertically +/// True if pre affine, else post affine. +template +void FractoriumEmberController::FlipCurrentXform(bool horizontal, bool vertical, bool pre) +{ + UpdateCurrentXform([&] (Xform* xform) + { + Affine2D* affine = pre ? &xform->m_Affine : &xform->m_Post; + + if (horizontal) + { + affine->A(-affine->A()); + affine->B(-affine->B()); + + if (!m_Fractorium->LocalPivot()) + affine->C(-affine->C()); + } + + if (vertical) + { + affine->D(-affine->D()); + affine->E(-affine->E()); + + if (!m_Fractorium->LocalPivot()) + affine->F(-affine->F()); + } + + FillAffineWithXform(xform, pre); + }); +} + +void Fractorium::OnFlipHorizontalButtonClicked(bool checked) { m_Controller->FlipCurrentXform(true, false, sender() == ui.PreFlipHorizontalButton); } +void Fractorium::OnFlipVerticalButtonClicked(bool checked) { m_Controller->FlipCurrentXform(false, true, sender() == ui.PreFlipVerticalButton); } + +/// +/// Rotate the current pre/post affine transform x degrees. +/// Resets the rendering process. +/// +/// The angle to rotate by +/// True if pre affine, else post affine. +template +void FractoriumEmberController::RotateCurrentXformByAngle(double angle, bool pre) +{ + UpdateCurrentXform([&] (Xform* xform) + { + Affine2D* affine = pre ? &xform->m_Affine : &xform->m_Post; + + affine->Rotate(angle); + FillAffineWithXform(xform, pre); + }); +} + +/// +/// Rotate the selected pre/post affine transform 90 degrees clockwise/counter clockwise. +/// Called when the rotate 90 c/cc pre/post buttons are clicked. +/// Resets the rendering process. +/// +/// Ignored +void Fractorium::OnRotate90CButtonClicked(bool checked) { m_Controller->RotateCurrentXformByAngle(90, sender() == ui.PreRotate90CButton); } +void Fractorium::OnRotate90CcButtonClicked(bool checked) { m_Controller->RotateCurrentXformByAngle(-90, sender() == ui.PreRotate90CcButton); } + +/// +/// Rotate the selected pre/post affine transform x degrees clockwise. +/// Called when the rotate x c pre/post buttons are clicked. +/// Resets the rendering process. +/// +/// Ignored +void Fractorium::OnRotateCButtonClicked(bool checked) +{ + bool ok; + bool pre = sender() == ui.PreRotateCButton; + QComboBox* combo = pre ? ui.PreRotateCombo : ui.PostRotateCombo; + double d = combo->currentText().toDouble(&ok); + + if (ok) + m_Controller->RotateCurrentXformByAngle(d, pre); +} + +/// +/// Rotate the selected pre/post affine transform x degrees counter clockwise. +/// Called when the rotate x cc pre/post buttons are clicked. +/// Resets the rendering process. +/// +/// Ignored +void Fractorium::OnRotateCcButtonClicked(bool checked) +{ + bool ok; + bool pre = sender() == ui.PreRotateCcButton; + QComboBox* combo = pre ? ui.PreRotateCombo : ui.PostRotateCombo; + double d = combo->currentText().toDouble(&ok); + + if (ok) + m_Controller->RotateCurrentXformByAngle(-d, pre); +} + +/// +/// Move the current pre/post affine in the x and y directions by the specified amount. +/// Resets the rendering process. +/// +/// The x direction to move +/// The y direction to move +/// True if pre affine, else post affine. +template +void FractoriumEmberController::MoveCurrentXform(double x, double y, bool pre) +{ + UpdateCurrentXform([&] (Xform* xform) + { + Affine2D* affine = pre ? &xform->m_Affine : &xform->m_Post; + + affine->C(affine->C() + x); + affine->F(affine->F() + y); + FillAffineWithXform(xform, pre); + }); +} + +/// +/// Move the selected pre/post affine transform x units up. +/// Called when the move pre/post up buttons are clicked. +/// Resets the rendering process. +/// +/// Ignored +void Fractorium::OnMoveUpButtonClicked(bool checked) +{ + bool ok; + bool pre = sender() == ui.PreMoveUpButton; + QComboBox* combo = pre ? ui.PreMoveCombo : ui.PostMoveCombo; + double d = combo->currentText().toDouble(&ok); + + if (ok) + m_Controller->MoveCurrentXform(0, d, pre); +} + +/// +/// Move the selected pre/post affine transform x units down. +/// Called when the move pre/post down buttons are clicked. +/// Resets the rendering process. +/// +/// Ignored +void Fractorium::OnMoveDownButtonClicked(bool checked) +{ + bool ok; + bool pre = sender() == ui.PreMoveDownButton; + QComboBox* combo = pre ? ui.PreMoveCombo : ui.PostMoveCombo; + double d = combo->currentText().toDouble(&ok); + + if (ok) + m_Controller->MoveCurrentXform(0, -d, pre); +} + +/// +/// Move the selected pre/post affine transform x units left. +/// Called when the move pre/post left buttons are clicked. +/// Resets the rendering process. +/// +/// Ignored +void Fractorium::OnMoveLeftButtonClicked(bool checked) +{ + bool ok; + bool pre = sender() == ui.PreMoveLeftButton; + QComboBox* combo = pre ? ui.PreMoveCombo : ui.PostMoveCombo; + double d = combo->currentText().toDouble(&ok); + + if (ok) + m_Controller->MoveCurrentXform(-d, 0, pre); +} + +/// +/// Move the selected pre/post affine transform x units right. +/// Called when the move pre/post right buttons are clicked. +/// Resets the rendering process. +/// +/// Ignored +void Fractorium::OnMoveRightButtonClicked(bool checked) +{ + bool ok; + bool pre = sender() == ui.PreMoveRightButton; + QComboBox* combo = pre ? ui.PreMoveCombo : ui.PostMoveCombo; + double d = combo->currentText().toDouble(&ok); + + if (ok) + m_Controller->MoveCurrentXform(d, 0, pre); +} + +/// +/// Scale the current pre/post affine by the specified amount. +/// Resets the rendering process. +/// +/// The scale value +/// True if pre affine, else post affine. +template +void FractoriumEmberController::ScaleCurrentXform(double scale, bool pre) +{ + UpdateCurrentXform([&] (Xform* xform) + { + Affine2D* affine = pre ? &xform->m_Affine : &xform->m_Post; + + affine->A(affine->A() * scale); + affine->B(affine->B() * scale); + affine->D(affine->D() * scale); + affine->E(affine->E() * scale); + FillAffineWithXform(xform, pre); + }); +} + +/// +/// Scale the selected pre/post affine transform x units down. +/// Called when the scale pre/post down buttons are clicked. +/// Resets the rendering process. +/// +/// Ignored +void Fractorium::OnScaleDownButtonClicked(bool checked) +{ + bool ok; + bool pre = sender() == ui.PreScaleDownButton; + QComboBox* combo = pre ? ui.PreScaleCombo : ui.PostScaleCombo; + double d = combo->currentText().toDouble(&ok); + + if (ok) + m_Controller->ScaleCurrentXform(1.0 / (d / 100.0), pre); +} + +/// +/// Scale the selected pre/post affine transform x units up. +/// Called when the scale pre/post up buttons are clicked. +/// Resets the rendering process. +/// +/// Ignored +void Fractorium::OnScaleUpButtonClicked(bool checked) +{ + bool ok; + bool pre = sender() == ui.PreScaleUpButton; + QComboBox* combo = pre ? ui.PreScaleCombo : ui.PostScaleCombo; + double d = combo->currentText().toDouble(&ok); + + if (ok) + m_Controller->ScaleCurrentXform(d / 100.0, pre); +} + +/// +/// Reset pre/post affine to the identity matrix. +/// Called when reset pre/post affine buttons are clicked. +/// Resets the rendering process. +/// +template +void FractoriumEmberController::ResetCurrentXformAffine(bool pre) +{ + UpdateCurrentXform([&] (Xform* xform) + { + Affine2D* affine = pre ? &xform->m_Affine : &xform->m_Post; + + affine->MakeID(); + FillAffineWithXform(xform, pre); + }); +} + +/// +/// Reset pre/post affine to the identity matrix. +/// Called when reset pre/post affine buttons are clicked. +/// Resets the rendering process. +/// +void Fractorium::OnResetAffineButtonClicked(bool checked) { m_Controller->ResetCurrentXformAffine(sender() == ui.PreResetButton); } + +/// +/// Fill the GUI with the pre/post affine xform values. +/// +/// The xform to fill with +/// True if pre affine, else post affine. +template +void FractoriumEmberController::FillAffineWithXform(Xform* xform, bool pre) +{ + if (pre) + { + m_Fractorium->m_PreX1Spin->SetValueStealth(xform->m_Affine.A()); + m_Fractorium->m_PreY1Spin->SetValueStealth(xform->m_Affine.B()); + m_Fractorium->m_PreO1Spin->SetValueStealth(xform->m_Affine.C()); + m_Fractorium->m_PreX2Spin->SetValueStealth(xform->m_Affine.D()); + m_Fractorium->m_PreY2Spin->SetValueStealth(xform->m_Affine.E()); + m_Fractorium->m_PreO2Spin->SetValueStealth(xform->m_Affine.F()); + } + else + { + m_Fractorium->m_PostX1Spin->SetValueStealth(xform->m_Post.A()); + m_Fractorium->m_PostY1Spin->SetValueStealth(xform->m_Post.B()); + m_Fractorium->m_PostO1Spin->SetValueStealth(xform->m_Post.C()); + m_Fractorium->m_PostX2Spin->SetValueStealth(xform->m_Post.D()); + m_Fractorium->m_PostY2Spin->SetValueStealth(xform->m_Post.E()); + m_Fractorium->m_PostO2Spin->SetValueStealth(xform->m_Post.F()); + } +} + +/// +/// Trigger a redraw which will show or hide the circle affine transforms +/// based on whether each group box is checked or not. +/// Called when the group box check box for pre or post affine is checked. +/// +/// Ignored +void Fractorium::OnAffineGroupBoxToggled(bool on) +{ + ui.GLDisplay->update(); +} + +/// +/// Trigger a redraw which will show all, or only the current, circle affine transforms +/// based on which radio buttons are selected. +/// Called when and pre/post show all/current radio buttons are checked. +/// +/// Ignored +void Fractorium::OnAffineDrawAllCurrentRadioButtonToggled(bool checked) +{ + OnCurrentXformComboChanged(ui.CurrentXformCombo->currentIndex()); + ui.GLDisplay->update(); +} + +/// +/// Setup a spinner to be placed in a table cell. +/// Special setup function for affine spinners which differs slightly from the regular +/// SetupSpinner() function. +/// +/// The table the spinner belongs to +/// The receiver object +/// The row in the table where this spinner resides +/// The col in the table where this spinner resides +/// Double pointer to spin box which will hold the spinner upon exit +/// The height of the spinner +/// The minimum value of the spinner +/// The maximum value of the spinner +/// The step of the spinner +/// The precision of the spinner +/// The signal the spinner emits +/// The slot to receive the signal +void Fractorium::SetupAffineSpinner(QTableWidget* table, const QObject* receiver, int row, int col, DoubleSpinBox*& spinBox, int height, double min, double max, double step, double prec, const char* signal, const char* slot) +{ + spinBox = new DoubleSpinBox(table, height, step); + spinBox->setRange(min, max); + spinBox->setDecimals(prec); + table->setCellWidget(row, col, spinBox); + connect(spinBox, signal, receiver, slot, Qt::QueuedConnection); + spinBox->DoubleClick(true); + spinBox->DoubleClickNonZero(0); + spinBox->DoubleClickZero(1); +} + +/// +/// GUI wrapper functions, getters only. +/// + +bool Fractorium::DrawAllPre() { return ui.ShowPreAffineAllRadio->isChecked(); } +bool Fractorium::DrawAllPost() { return ui.ShowPostAffineAllRadio->isChecked(); } +bool Fractorium::LocalPivot() { return ui.LocalPivotRadio->isChecked(); } \ No newline at end of file diff --git a/Source/Fractorium/FractoriumXformsColor.cpp b/Source/Fractorium/FractoriumXformsColor.cpp new file mode 100644 index 0000000..5c23b88 --- /dev/null +++ b/Source/Fractorium/FractoriumXformsColor.cpp @@ -0,0 +1,204 @@ +#include "FractoriumPch.h" +#include "Fractorium.h" + +/// +/// Initialize the xforms color UI. +/// +void Fractorium::InitXformsColorUI() +{ + int spinHeight = 20, row = 0; + + m_XformColorValueItem = new QTableWidgetItem(); + ui.XformColorIndexTable->setItem(0, 0, m_XformColorValueItem); + + m_PaletteRefItem = new QTableWidgetItem(); + ui.XformPaletteRefTable->setItem(0, 0, m_PaletteRefItem); + ui.XformPaletteRefTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + connect(ui.XformPaletteRefTable->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), this, SLOT(OnXformRefPaletteResized(int, int, int)), Qt::QueuedConnection); + + SetupSpinner(ui.XformColorIndexTable, this, row, 1, m_XformColorIndexSpin, spinHeight, 0, 1, 0.01, SIGNAL(valueChanged(double)), SLOT(OnXformColorIndexChanged(double)), false, 0, 1, 0); + SetupSpinner(ui.XformColorValuesTable, this, row, 1, m_XformColorSpeedSpin, spinHeight, -1, 1, 0.1, SIGNAL(valueChanged(double)), SLOT(OnXformColorSpeedChanged(double)), true, 0.5, 0.5, 0.5); + SetupSpinner(ui.XformColorValuesTable, this, row, 1, m_XformOpacitySpin, spinHeight, 0, 1, 0.1, SIGNAL(valueChanged(double)), SLOT(OnXformOpacityChanged(double)), true, 1, 1, 0); + SetupSpinner(ui.XformColorValuesTable, this, row, 1, m_XformDirectColorSpin, spinHeight, 0, 1, 0.1, SIGNAL(valueChanged(double)), SLOT(OnXformDirectColorChanged(double)), true, 1, 1, 0); + + m_XformColorIndexSpin->setDecimals(3); + m_XformColorSpeedSpin->setDecimals(3); + m_XformOpacitySpin->setDecimals(3); + m_XformDirectColorSpin->setDecimals(3); + connect(ui.XformColorScroll, SIGNAL(valueChanged(int)), this, SLOT(OnXformScrollColorIndexChanged(int)), Qt::QueuedConnection); + connect(ui.SoloXformCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnSoloXformCheckBoxStateChanged(int)), Qt::QueuedConnection); +} + +/// +/// Set the color index of the current xform. +/// Update the color index scrollbar to match. +/// Called when spinner in the color index cell in the palette ref table is changed. +/// Optionally resets the rendering process. +/// +/// The color index, 0-1/ +/// True to reset the rendering process, else don't. +template +void FractoriumEmberController::XformColorIndexChanged(double d, bool updateRender) +{ + UpdateCurrentXform([&] (Xform* xform) + { + QScrollBar* scroll = m_Fractorium->ui.XformColorScroll; + int scrollVal = d * scroll->maximum(); + + scroll->blockSignals(true); + scroll->setValue(scrollVal); + scroll->blockSignals(false); + + SetCurrentXformColorIndex(d); + }, updateRender); +} + +void Fractorium::OnXformColorIndexChanged(double d) { OnXformColorIndexChanged(d, true); } +void Fractorium::OnXformColorIndexChanged(double d, bool updateRender) { m_Controller->XformColorIndexChanged(d, updateRender); } + +/// +/// Set the color index of the current xform. +/// Update the color index cell in the palette ref table to match. +/// Called when color index scrollbar is changed. +/// Resets the rendering process. +/// +/// The color index, 0-1. +template +void FractoriumEmberController::XformScrollColorIndexChanged(int d) +{ + UpdateCurrentXform([&] (Xform* xform) + { + m_Fractorium->m_XformColorIndexSpin->setValue(d / (double)m_Fractorium->ui.XformColorScroll->maximum());//Will trigger an update. + }, false); +} + +void Fractorium::OnXformScrollColorIndexChanged(int d) { m_Controller->XformScrollColorIndexChanged(d); } + +/// +/// Set the color speed of the current xform. +/// Called when xform color speed spinner is changed. +/// Resets the rendering process. +/// +/// The color speed, -1-1. +template +void FractoriumEmberController::XformColorSpeedChanged(double d) { UpdateCurrentXform([&] (Xform* xform) { xform->m_ColorSpeed = d; }); } +void Fractorium::OnXformColorSpeedChanged(double d) { m_Controller->XformColorSpeedChanged(d); } + +/// +/// Set the opacity of the current xform. +/// Called when xform opacity spinner is changed. +/// Resets the rendering process. +/// +/// The opacity, 0-1. +template +void FractoriumEmberController::XformOpacityChanged(double d) { UpdateCurrentXform([&] (Xform* xform) { xform->m_Opacity = d; }); } +void Fractorium::OnXformOpacityChanged(double d) { m_Controller->XformOpacityChanged(d); } + +/// +/// Set the direct color percentage of the current xform. +/// Called when xform direct color spinner is changed. +/// Note this only affects xforms that include a dc_ variation. +/// Resets the rendering process. +/// +/// The direct color percentage, 0-1. +template +void FractoriumEmberController::XformDirectColorChanged(double d) { UpdateCurrentXform([&] (Xform* xform) { xform->m_DirectColor = d; }); } +void Fractorium::OnXformDirectColorChanged(double d) { m_Controller->XformDirectColorChanged(d); } + +/// +/// Set whether the current xform should be rendered solo. +/// If checked, current is solo, if unchecked, none are solo. +/// Solo means that all other xforms will have their opacity temporarily +/// set to zero while rendering so that only the effect of current xform is visible. +/// This will not permanently alter the ember, as the temporary opacity values will be applied +/// right before rendering and reset right after. +/// Called when solo xform check box is checked. +/// Resets the rendering process. +/// +/// The state of the checkbox +void Fractorium::OnSoloXformCheckBoxStateChanged(int state) +{ + if (state == Qt::Checked) + { + ui.CurrentXformCombo->setProperty("soloxform", ui.CurrentXformCombo->currentIndex()); + ui.SoloXformCheckBox->setText("Solo (" + QString::number(ui.CurrentXformCombo->currentIndex() + 1) + ")"); + } + else if (state == Qt::Unchecked) + { + ui.CurrentXformCombo->setProperty("soloxform", -1); + ui.SoloXformCheckBox->setText("Solo"); + } + + m_Controller->UpdateRender(); +} + +/// +/// Redraw the palette ref table. +/// Called on resize. +/// +/// Ignored +/// Ignored +/// Ignored +void Fractorium::OnXformRefPaletteResized(int logicalIndex, int oldSize, int newSize) +{ + m_Controller->SetPaletteRefTable(NULL); +} + +/// +/// Set the current xform color index spinner to the current xform's color index. +/// Set the color cell in the palette ref table. +/// +/// The index value to set, 0-1. +template +void FractoriumEmberController::SetCurrentXformColorIndex(double d) +{ + UpdateCurrentXform([&] (Xform* xform) + { + xform->m_ColorX = Clamp(d, 0, 1); + + //Grab the current color from the index and assign it to the first cell of the first table. + v4T entry = m_Ember.m_Palette[Clamp(d * COLORMAP_LENGTH_MINUS_1, 0, m_Ember.m_Palette.Size())]; + + entry.r *= 255; + entry.g *= 255; + entry.b *= 255; + + QRgb rgb = (unsigned int)entry.r << 16 | (unsigned int)entry.g << 8 | (unsigned int)entry.b; + m_Fractorium->ui.XformColorIndexTable->item(0, 0)->setBackgroundColor(QColor::fromRgb(rgb)); + }, false); +} + +/// +/// Set the color index, speed and opacity spinners with the values of the current xform. +/// Set the cells of the palette ref table as well. +/// +/// The xform whose values will be copied to the GUI +template +void FractoriumEmberController::FillColorWithXform(Xform* xform) +{ + m_Fractorium->m_XformColorIndexSpin->SetValueStealth(xform->m_ColorX); + m_Fractorium->m_XformColorSpeedSpin->SetValueStealth(xform->m_ColorSpeed); + m_Fractorium->m_XformOpacitySpin->SetValueStealth(xform->m_Opacity); + m_Fractorium->m_XformDirectColorSpin->SetValueStealth(xform->m_DirectColor); + m_Fractorium->OnXformColorIndexChanged(xform->m_ColorX, false);//Had to call stealth before to avoid doing an update, now manually update related controls, still without doing an update. +} + +/// +/// Set the palette reference table to the passed in pixmap +/// +/// The pixmap +void FractoriumEmberControllerBase::SetPaletteRefTable(QPixmap* pixmap) +{ + QSize size(m_Fractorium->ui.XformPaletteRefTable->columnWidth(0), m_Fractorium->ui.XformPaletteRefTable->rowHeight(0) + 1); + + if (pixmap) + { + m_Fractorium->m_PaletteRefItem->setData(Qt::DecorationRole, pixmap->scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + } + else if (!m_FinalPaletteImage.isNull()) + { + QPixmap pixTemp = QPixmap::fromImage(m_FinalPaletteImage); + + m_Fractorium->m_PaletteRefItem->setData(Qt::DecorationRole, pixTemp.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + } +} diff --git a/Source/Fractorium/FractoriumXformsVariations.cpp b/Source/Fractorium/FractoriumXformsVariations.cpp new file mode 100644 index 0000000..e3c571d --- /dev/null +++ b/Source/Fractorium/FractoriumXformsVariations.cpp @@ -0,0 +1,316 @@ +#include "FractoriumPch.h" +#include "Fractorium.h" + +/// +/// Initialize the xforms variations UI. +/// +void Fractorium::InitXformsVariationsUI() +{ + QTreeWidget* tree = ui.VariationsTree; + + tree->clear(); + tree->header()->setSectionsClickable(true); + connect(tree->header(), SIGNAL(sectionClicked(int)), this, SLOT(OnTreeHeaderSectionClicked(int))); + connect(ui.VariationsFilterLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(OnVariationsFilterLineEditTextChanged(const QString&))); + connect(ui.VariationsFilterLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(OnVariationsFilterLineEditTextChanged(const QString&))); + connect(ui.VariationsFilterClearButton, SIGNAL(clicked(bool)), this, SLOT(OnVariationsFilterClearButtonClicked(bool))); + + //Setting dimensions in the designer with a layout is futile, so must hard code here. + tree->setColumnWidth(0, 160); + tree->setColumnWidth(1, 23); +} + +/// +/// Dynamically populate the variation tree widget with VariationTreeWidgetItem and VariationTreeDoubleSpinBox +/// templated with the correct type. +/// This will clear any previous contents. +/// Called upon initialization, or controller type change. +/// +template +void FractoriumEmberController::SetupVariationTree() +{ + T fMin = TLOW; + T fMax = TMAX; + QSize hint0(75, 16); + QSize hint1(30, 16); + QTreeWidget* tree = m_Fractorium->ui.VariationsTree; + + tree->clear(); + tree->blockSignals(true); + + for (size_t i = 0; i < m_VariationList.Size(); i++) + { + Variation* var = m_VariationList.GetVariation(i); + ParametricVariation* parVar = dynamic_cast*>(var); + + //First add the variation, with a spinner for its weight. + VariationTreeWidgetItem* item = new VariationTreeWidgetItem(tree); + VariationTreeDoubleSpinBox* spinBox = new VariationTreeDoubleSpinBox(tree, parVar ? parVar : var, ""); + + item->setText(0, QString::fromStdString(var->Name())); + item->setSizeHint(0, hint0); + item->setSizeHint(1, hint1); + spinBox->setRange(fMin, fMax); + spinBox->DoubleClick(true); + spinBox->DoubleClickZero(1); + spinBox->DoubleClickNonZero(0); + spinBox->SmallStep(0.001); + tree->setItemWidget(item, 1, spinBox); + m_Fractorium->connect(spinBox, SIGNAL(valueChanged(double)), SLOT(OnVariationSpinBoxValueChanged(double)), Qt::QueuedConnection); + + //Check to see if the variation was parametric, and add a tree entry with a spinner for each parameter. + if (parVar) + { + ParamWithName* params = parVar->Params(); + + for (size_t j = 0; j< parVar->ParamCount(); j++) + { + if (!params[j].IsPrecalc()) + { + VariationTreeWidgetItem* paramWidget = new VariationTreeWidgetItem(item); + VariationTreeDoubleSpinBox* varSpinBox = new VariationTreeDoubleSpinBox(tree, parVar, params[j].Name()); + + paramWidget->setText(0, params[j].Name().c_str()); + paramWidget->setSizeHint(0, hint0); + paramWidget->setSizeHint(1, hint1); + varSpinBox->setRange(params[j].Min(), params[j].Max()); + varSpinBox->setValue(params[j].ParamVal()); + varSpinBox->DoubleClick(true); + varSpinBox->DoubleClickZero(1); + varSpinBox->DoubleClickNonZero(0); + + if (params[j].Type() == INTEGER || params[j].Type() == INTEGER_NONZERO) + { + varSpinBox->setSingleStep(1); + varSpinBox->Step(1); + varSpinBox->SmallStep(1); + } + + tree->setItemWidget(paramWidget, 1, varSpinBox); + m_Fractorium->connect(varSpinBox, SIGNAL(valueChanged(double)), SLOT(OnVariationSpinBoxValueChanged(double)), Qt::QueuedConnection); + } + } + } + } + + tree->blockSignals(false); +} + +/// +/// Set every spinner in the variation tree, including params, to zero. +/// +template +void FractoriumEmberController::ClearVariationsTree() +{ + QTreeWidget* tree = m_Fractorium->ui.VariationsTree; + + for (unsigned int i = 0; i < tree->topLevelItemCount(); i++) + { + QTreeWidgetItem* item = tree->topLevelItem(i); + VariationTreeDoubleSpinBox* spinBox = dynamic_cast*>(tree->itemWidget(item, 1)); + + spinBox->SetValueStealth(0); + + for (unsigned int j = 0; j < item->childCount(); j++)//Iterate through all of the children, which will be the params. + { + if (spinBox = dynamic_cast*>(tree->itemWidget(item->child(j), 1)))//Cast the child widget to the VariationTreeDoubleSpinBox type. + spinBox->SetValueStealth(0); + } + } +} + +/// +/// Copy the value of a variation or param spinner to its corresponding value +/// in the currently selected xform. +/// Called when any spinner in the variations tree is changed. +/// Resets the rendering process. +/// +/// The spinner value +template +void FractoriumEmberController::VariationSpinBoxValueChanged(double d) +{ + QObject* objSender = m_Fractorium->sender(); + QTreeWidget* tree = m_Fractorium->ui.VariationsTree; + VariationTreeDoubleSpinBox* sender = dynamic_cast*>(objSender); + Xform* xform = m_Ember.GetTotalXform(m_Fractorium->ui.CurrentXformCombo->currentIndex());//Will retrieve normal xform or final if needed. + + if (sender && xform) + { + Variation* var = sender->GetVariation();//The variation attached to the sender, for reference only. + ParametricVariation* parVar = dynamic_cast*>(var);//The parametric cast of that variation. + Variation* xformVar = xform->GetVariationByName(var->Name());//The corresponding variation in the currently selected xform. + QList items = tree->findItems(QString::fromStdString(var->Name()), Qt::MatchExactly); + bool isParam = parVar && sender->IsParam(); + + if (isParam) + { + //Do not take action if the xform doesn't contain the variation which this param is part of. + if (ParametricVariation* xformParVar = dynamic_cast*>(xformVar))//The parametric cast of the xform's variation. + { + if (xformParVar->SetParamVal(sender->ParamName().c_str(), d)) + { + UpdateRender(); + } + } + } + else + { + //If they spun down to zero, and it wasn't a parameter item, + //and the current xform contained the variation, then remove the variation. + if (IsNearZero(d)) + { + if (xformVar) + xform->DeleteVariationById(var->VariationId()); + + items[0]->setBackgroundColor(0, QColor(255, 255, 255));//Ensure background is always white if weight goes to zero. + } + else + { + if (xformVar)//The xform already contained this variation, which means they just went from a non-zero weight to another non-zero weight (the simple case). + { + xformVar->m_Weight = d; + } + else + { + //If the item wasn't a param and the xform did not contain this variation, + //it means they went from zero to a non-zero weight, so add a new copy of this xform. + Variation* newVar = var->Copy();//Create a new one with default values. + + newVar->m_Weight = d; + xform->AddVariation(newVar); + items[0]->setBackgroundColor(0, QColor(200, 200, 200));//Set background to gray when a variation has non-zero weight in this xform. + + //If they've added a new parametric variation, then grab the values currently in the spinners + //for the child parameters and assign them to the newly added variation. + if (parVar) + { + ParametricVariation* newParVar = dynamic_cast*>(newVar); + + if (!items.empty())//Get the tree widget for the parent variation. + { + for (int i = 0; i < items[0]->childCount(); i++)//Iterate through all of the children, which will be the params. + { + QTreeWidgetItem* childItem = items[0]->child(i);//Get the child. + QWidget* itemWidget = tree->itemWidget(childItem, 1);//Get the widget for the child. + + if (VariationTreeDoubleSpinBox* spinBox = dynamic_cast*>(itemWidget))//Cast the widget to the VariationTreeDoubleSpinBox type. + { + string s = childItem->text(0).toStdString();//Use the name of the child, and the value of the spinner widget to assign the param. + + newParVar->SetParamVal(s.c_str(), spinBox->value()); + } + } + } + } + } + } + + UpdateRender(); + } + } +} + +void Fractorium::OnVariationSpinBoxValueChanged(double d) { m_Controller->VariationSpinBoxValueChanged(d); } + +/// +/// Fill the variation tree values from passed in xform and apply the current sorting mode. +/// Called when the currently selected xform changes. +/// +/// The xform whose variation values will be used to fill the tree +template +void FractoriumEmberController::FillVariationTreeWithXform(Xform* xform) +{ + QTreeWidget* tree = m_Fractorium->ui.VariationsTree; + + for (unsigned int i = 0; i < tree->topLevelItemCount(); i++) + { + QTreeWidgetItem* item = tree->topLevelItem(i); + string varName = item->text(0).toStdString(); + Variation* var = xform->GetVariationByName(varName);//See if this variation in the tree was contained in the xform. + ParametricVariation* parVar = dynamic_cast*>(var);//Attempt cast to parametric variation for later. + ParametricVariation* origParVar = dynamic_cast*>(m_VariationList.GetVariation(varName)); + QWidget* itemWidget = tree->itemWidget(item, 1);//Get the widget for the item. + + if (VariationTreeDoubleSpinBox* spinBox = dynamic_cast*>(itemWidget))//Cast the widget to the VariationTreeDoubleSpinBox type. + { + spinBox->SetValueStealth(var ? var->m_Weight : 0);//If the variation was present, set the spin box to its weight, else zero. + item->setBackgroundColor(0, var ? QColor(200, 200, 200) : QColor(255, 255, 255));//Ensure background is always white if the value goes to zero, else gray if var present. + + for (unsigned int j = 0; j < item->childCount(); j++)//Iterate through all of the children, which will be the params if it was a parametric variation. + { + T* param = NULL; + QTreeWidgetItem* childItem = item->child(j);//Get the child. + QWidget* childItemWidget = tree->itemWidget(childItem, 1);//Get the widget for the child. + + if (VariationTreeDoubleSpinBox* childSpinBox = dynamic_cast*>(childItemWidget))//Cast the widget to the VariationTreeDoubleSpinBox type. + { + string s = childItem->text(0).toStdString();//Get the name of the child. + + if (parVar) + { + if (param = parVar->GetParam(s.c_str()))//Retrieve pointer to the param. + childSpinBox->SetValueStealth(*param); + } + else if (origParVar)//Parametric variation was not present in this xform, so set child values to defaults. + { + if (param = origParVar->GetParam(s.c_str())) + childSpinBox->SetValueStealth(*param); + else + childSpinBox->SetValueStealth(0);//Will most likely never happen, but just to be safe. + } + } + } + } + } + + m_Fractorium->OnTreeHeaderSectionClicked(m_Fractorium->m_VarSortMode); +} + +/// +/// Change the sorting to be either by variation ID, or by weight. +/// If sorting by variation ID, repeated clicks will altername ascending or descending. +/// Called when user clicks the tree headers. +/// +/// Column index of the header clicked. Sort by name if 0, sort by weight if 1. +void Fractorium::OnTreeHeaderSectionClicked(int logicalIndex) +{ + m_VarSortMode = logicalIndex; + ui.VariationsTree->sortItems(m_VarSortMode, m_VarSortMode == 0 ? Qt::AscendingOrder : Qt::DescendingOrder); + + if (logicalIndex == 1) + ui.VariationsTree->scrollToTop(); +} + +/// +/// Apply the text in the variation filter text box to only show variations whose names +/// contain the substring. +/// Called when the user types in the variation filter text box. +/// +/// The text to filter on +void Fractorium::OnVariationsFilterLineEditTextChanged(const QString& text) +{ + QTreeWidget* tree = ui.VariationsTree; + + tree->setUpdatesEnabled(false); + + for (unsigned int i = 0; i < tree->topLevelItemCount(); i++) + { + QTreeWidgetItem* item = tree->topLevelItem(i); + QString varName = item->text(0); + + item->setHidden(!varName.contains(text, Qt::CaseInsensitive)); + } + + OnTreeHeaderSectionClicked(m_VarSortMode);//Must re-sort every time the filter changes. + tree->setUpdatesEnabled(true); +} + +/// +/// Clear the variation name filter, which will display all variations. +/// Called when clear variations filter button is clicked. +/// +/// Ignored +void Fractorium::OnVariationsFilterClearButtonClicked(bool checked) +{ + ui.VariationsFilterLineEdit->clear(); +} diff --git a/Source/Fractorium/FractoriumXformsXaos.cpp b/Source/Fractorium/FractoriumXformsXaos.cpp new file mode 100644 index 0000000..7489471 --- /dev/null +++ b/Source/Fractorium/FractoriumXformsXaos.cpp @@ -0,0 +1,175 @@ +#include "FractoriumPch.h" +#include "Fractorium.h" + +/// +/// Initialize the xforms xaos UI. +/// +void Fractorium::InitXformsXaosUI() +{ + connect(ui.XaosToRadio, SIGNAL(toggled(bool)), this, SLOT(OnXaosFromToToggled(bool)), Qt::QueuedConnection); + connect(ui.ClearXaosButton, SIGNAL(clicked(bool)), this, SLOT(OnClearXaosButtonClicked(bool)), Qt::QueuedConnection); +} + +/// +/// Fill the xaos table with the values from the current xform. +/// +template +void FractoriumEmberController::FillXaosWithCurrentXform() +{ + Xform* currXform = CurrentXform(); + + if (!IsFinal(currXform)) + { + for (int i = 0; i < m_Ember.XformCount(); i++) + { + DoubleSpinBox* spinBox = dynamic_cast(m_Fractorium->ui.XaosTable->cellWidget(i, 1)); + + //Fill in values column. + if (m_Fractorium->ui.XaosToRadio->isChecked())//"To": Single xform, advance index. + spinBox->SetValueStealth(currXform->Xaos(i)); + else//"From": Advance xforms, single index. + if (currXform = m_Ember.GetXform(i)) + spinBox->SetValueStealth(currXform->Xaos(m_Fractorium->ui.CurrentXformCombo->currentIndex())); + + //Fill in name column. + Xform* xform = m_Ember.GetXform(i); + QTableWidgetItem* xformNameItem = m_Fractorium->ui.XaosTable->item(i, 0); + + if (xform && xformNameItem) + xformNameItem->setText(MakeXaosNameString(i)); + } + } + + m_Fractorium->ui.XaosTable->setEnabled(!IsFinal(currXform));//Disable if final, else enable. +} + +/// +/// Create and return a xaos name string. +/// +/// The index of the xform whose xaos will be used +/// The xaos name string +template +QString FractoriumEmberController::MakeXaosNameString(unsigned int i) +{ + Xform* xform = m_Ember.GetXform(i); + QString name; + + if (xform) + { + int i = m_Ember.GetXformIndex(xform) + 1;//GUI is 1 indexed to avoid confusing the user. + int curr = m_Fractorium->ui.CurrentXformCombo->currentIndex() + 1; + + if (i != -1) + { + if (m_Fractorium->ui.XaosToRadio->isChecked()) + name = QString("From ") + QString::number(curr) + QString(" To ") + QString::number(i); + else + name = QString("From ") + QString::number(i) + QString(" To ") + QString::number(curr); + + //if (xform->m_Name != "") + // name = name + " (" + QString::fromStdString(xform->m_Name) + ")"; + } + } + + return name; +} + +/// +/// Set the xaos value. +/// Called when any xaos spinner is changed. +/// Different action taken based on the state of to/from radio button. +/// Resets the rendering process. +/// +/// The DoubleSpinBox that triggered this event +template +void FractoriumEmberController::XaosChanged(DoubleSpinBox* sender) +{ + UpdateCurrentXform([&] (Xform* xform) + { + QTableWidget* xaosTable = m_Fractorium->ui.XaosTable; + + if (!IsFinal(xform))//This should never get called for the final xform because the table will be disabled, but check just to be safe. + { + for (int i = 0; i < xaosTable->rowCount(); i++)//Find the spin box that triggered the event. + { + DoubleSpinBox* spinBox = dynamic_cast(xaosTable->cellWidget(i, 1)); + + if (spinBox == sender) + { + if (m_Fractorium->ui.XaosToRadio->isChecked())//"To": Single xform, advance index. + { + xform->SetXaos(i, spinBox->value()); + } + else//"From": Advance xforms, single index. + { + if (xform = m_Ember.GetXform(i))//Single = is intentional. + xform->SetXaos(m_Fractorium->ui.CurrentXformCombo->currentIndex(), spinBox->value()); + } + + break; + } + } + } + }); +} + +void Fractorium::OnXaosChanged(double d) +{ + if (DoubleSpinBox* senderSpinBox = dynamic_cast(this->sender())) + m_Controller->XaosChanged(senderSpinBox); +} + +/// +/// Update xaos display to use either "to" or "from" logic. +/// Called when xaos to/from radio buttons checked. +/// +/// Ignored +void Fractorium::OnXaosFromToToggled(bool checked) +{ + m_Controller->FillXaosWithCurrentXform(); +} + +/// +/// Clear xaos table, recreate all spinners based on the xaos used by the current xform in the current ember. +/// Called every time the current xform changes. +/// +void Fractorium::FillXaosTable() +{ + int spinHeight = 20; + + ui.XaosTable->setRowCount(m_Controller->XformCount());//This will grow or shrink the number of rows and call the destructor for previous DoubleSpinBoxes. + + for (int i = 0; i < m_Controller->XformCount(); i++) + { + DoubleSpinBox* spinBox = new DoubleSpinBox(ui.XaosTable, spinHeight, 0.1); + QTableWidgetItem* xformNameItem = new QTableWidgetItem(m_Controller->MakeXaosNameString(i)); + + spinBox->DoubleClick(true); + spinBox->DoubleClickZero(1); + spinBox->DoubleClickNonZero(0); + ui.XaosTable->setItem(i, 0, xformNameItem); + ui.XaosTable->setCellWidget(i, 1, spinBox); + connect(spinBox, SIGNAL(valueChanged(double)), this, SLOT(OnXaosChanged(double)), Qt::QueuedConnection); + } +} + +/// +/// Clear all xaos from the current ember. +/// Called when xaos to/from radio buttons checked. +/// +/// Ignored +template +void FractoriumEmberController::ClearXaos() +{ + UpdateCurrentXform([&] (Xform* xform) + { + m_Ember.ClearXaos(); + }); + + //Can't just call FillXaosWithCurrentXform() because the current xform might the final. + for (int i = 0; i < m_Ember.XformCount(); i++) + if (DoubleSpinBox* spinBox = dynamic_cast(m_Fractorium->ui.XaosTable->cellWidget(i, 1))) + spinBox->SetValueStealth(1.0); +} + +void Fractorium::OnClearXaosButtonClicked(bool checked) { m_Controller->ClearXaos(); } \ No newline at end of file diff --git a/Source/Fractorium/GLEmberController.cpp b/Source/Fractorium/GLEmberController.cpp new file mode 100644 index 0000000..b771f01 --- /dev/null +++ b/Source/Fractorium/GLEmberController.cpp @@ -0,0 +1,254 @@ +#include "FractoriumPch.h" +#include "GLEmberController.h" +#include "FractoriumEmberController.h" +#include "Fractorium.h" +#include "GLWidget.h" + +/// +/// Constructor which assigns pointers to the main window and the GLWidget. +/// +/// Pointer to the main window +/// Pointer to the GLWidget +GLEmberControllerBase::GLEmberControllerBase(Fractorium* fractorium, GLWidget* glWidget) +{ + m_Fractorium = fractorium; + m_GL = glWidget; + m_AffineType = AffinePre; + m_HoverType = HoverNone; + m_DragState = DragNone; + m_DragModifier = 0; +} + +/// +/// Empty destructor which does nothing. +/// +GLEmberControllerBase::~GLEmberControllerBase() { } + +/// +/// Constructor which passes the pointers to the main window the GLWidget to the base, +/// then assigns the pointer to the parent controller. +/// +/// Pointer to the main window +/// Pointer to the GLWidget +/// Pointer to the parent controller of the same template type +template +GLEmberController::GLEmberController(Fractorium* fractorium, GLWidget* glWidget, FractoriumEmberController* controller) + : GLEmberControllerBase(fractorium, glWidget) +{ + GridStep = T(1.0 / 8.0); + m_FractoriumEmberController = controller; + + m_HoverXform = NULL; + m_SelectedXform = NULL; + m_CenterDownX = 0; + m_CenterDownY = 0; +} + +/// +/// Empty destructor which does nothing. +/// +template +GLEmberController::~GLEmberController() { } + +/// +/// Check that the final output size of the current ember matches the dimensions passed in. +/// +/// The width to compare to +/// The height to compare to +/// True if any don't match, else false if they are both equal. +template +bool GLEmberController::CheckForSizeMismatch(int w, int h) +{ + return (m_FractoriumEmberController->FinalRasW() != w || m_FractoriumEmberController->FinalRasH() != h); +} + +/// +/// Calculate the scale. +/// Used when dragging the right mouse button. +/// +/// The distance dragged in pixels +template +T GLEmberController::CalcScale() +{ + //Can't operate using world coords here because every time scale changes, the world bounds change. + //So must instead calculate distance traveled based on window coords, which do not change outside of resize events. + v2T windowCenter((T)m_GL->width() / T(2), (T)m_GL->height() / T(2)); + v2T windowMousePosDistanceFromCenter(m_MousePos.x - windowCenter.x, m_MousePos.y - windowCenter.y); + v2T windowMouseDownDistanceFromCenter(m_MouseDownPos.x - windowCenter.x, m_MouseDownPos.y - windowCenter.y); + + T lengthMousePosFromCenterInPixels = glm::length(windowMousePosDistanceFromCenter); + T lengthMouseDownFromCenterInPixels = glm::length(windowMouseDownDistanceFromCenter); + + return lengthMousePosFromCenterInPixels - lengthMouseDownFromCenterInPixels; +} + +/// +/// Calculate the rotation. +/// Used when dragging the right mouse button. +/// +/// The angular distance rotated from -180-180 +template +T GLEmberController::CalcRotation() +{ + T rotStart = NormalizeDeg180(T(90) - (atan2(-m_MouseDownWorldPos.y, m_MouseDownWorldPos.x) * RAD_2_DEG_T)); + T rot = NormalizeDeg180(T(90) - (atan2(-m_MouseWorldPos.y, m_MouseWorldPos.x) * RAD_2_DEG_T)); + + return rotStart - rot; +} + +/// +/// Snap the passed in world cartesian coordinate to the grid for rotation, scale or translation. +/// +/// The world cartesian coordinate to be snapped +/// The snapped world cartesian coordinate +template +typename v3T GLEmberController::SnapToGrid(v3T& vec) +{ + v3T ret; + + ret.x = glm::round(vec.x / GridStep) * GridStep; + ret.y = glm::round(vec.y / GridStep) * GridStep; + + return ret; +} + +/// +/// Snap the passed in world cartesian coordinate to the grid for rotation only. +/// +/// The world cartesian coordinate to be snapped +/// The divisions of a circle to use for snapping +/// The snapped world cartesian coordinate +template +typename v3T GLEmberController::SnapToNormalizedAngle(v3T& vec, unsigned int divisions) +{ + T rsq, theta; + T bestRsq = numeric_limits::max(); + v3T c, best; + + best.x = 1; + best.y = 0; + + for (unsigned int i = 0; i < divisions; i++) + { + theta = 2.0 * M_PI * (T)i / (T)divisions; + c.x = cos(theta); + c.y = sin(theta); + rsq = glm::distance(vec, c); + + if (rsq < bestRsq) + { + best = c; + bestRsq = rsq; + } + } + + return best; +} + +/// +/// Convert raster window coordinates to world cartesian coordinates. +/// +/// The window coordinates to convert +/// True to flip vertically, else don't. +/// The converted world cartesian coordinates +template +typename v3T GLEmberController::WindowToWorld(v3T& v, bool flip) +{ + v3T mouse(v.x, flip ? m_Viewport[3] - v.y : v.y, 0);//Must flip y because in OpenGL, 0,0 is bottom left, but in windows, it's top left. + v3T newCoords = glm::unProject(mouse, m_Modelview, m_Projection, m_Viewport);//Perform the calculation. + + newCoords.z = 0;//For some reason, unProject() always comes back with the z coordinate as something other than 0. It should be 0 at all times. + return newCoords; +} + +/// +/// Template specialization for querying the viewport, modelview and projection +/// matrices as floats. +/// +template <> +void GLEmberController::QueryVMP() +{ + m_GL->glGetIntegerv(GL_VIEWPORT, glm::value_ptr(m_Viewport)); + m_GL->glGetFloatv(GL_MODELVIEW_MATRIX, glm::value_ptr(m_Modelview)); + m_GL->glGetFloatv(GL_PROJECTION_MATRIX, glm::value_ptr(m_Projection)); +} + +#ifdef DO_DOUBLE +/// +/// Template specialization for querying the viewport, modelview and projection +/// matrices as doubles. +/// +template <> +void GLEmberController::QueryVMP() +{ + m_GL->glGetIntegerv(GL_VIEWPORT, glm::value_ptr(m_Viewport)); + m_GL->glGetDoublev(GL_MODELVIEW_MATRIX, glm::value_ptr(m_Modelview)); + m_GL->glGetDoublev(GL_PROJECTION_MATRIX, glm::value_ptr(m_Projection)); +} +#endif + +/// +/// Template specialization for multiplying the current matrix +/// by an m4. +/// +template <> +void GLEmberController::MultMatrix(glm::detail::tmat4x4& mat) +{ + m_GL->glMultMatrixf(glm::value_ptr(mat)); +} + +#ifdef DO_DOUBLE +/// +/// Template specialization for multiplying the current matrix +/// by an m4. +/// +template <> +void GLEmberController::MultMatrix(glm::detail::tmat4x4& mat) +{ + m_GL->glMultMatrixd(glm::value_ptr(mat)); +} +#endif + +/// +/// Query the matrices currently being used. +/// Debugging function, unused. +/// +/// True to print values, else false. +template +void GLEmberController::QueryMatrices(bool print) +{ + RendererBase* renderer = m_FractoriumEmberController->Renderer(); + + if (renderer) + { + double unitX = fabs(renderer->UpperRightX(false) - renderer->LowerLeftX(false)) / 2.0; + double unitY = fabs(renderer->UpperRightY(false) - renderer->LowerLeftY(false)) / 2.0; + + m_GL->glMatrixMode(GL_PROJECTION); + m_GL->glPushMatrix(); + m_GL->glLoadIdentity(); + m_GL->glOrtho(-unitX, unitX, -unitY, unitY, -1, 1); + m_GL->glMatrixMode(GL_MODELVIEW); + m_GL->glPushMatrix(); + m_GL->glLoadIdentity(); + + QueryVMP(); + + m_GL->glMatrixMode(GL_PROJECTION); + m_GL->glPopMatrix(); + m_GL->glMatrixMode(GL_MODELVIEW); + m_GL->glPopMatrix(); + + if (print) + { + for (int i = 0; i < 4; i++) + qDebug() << "Viewport[" << i << "] = " << m_Viewport[i] << endl; + + for (int i = 0; i < 16; i++) + qDebug() << "Modelview[" << i << "] = " << glm::value_ptr(m_Modelview)[i] << endl; + + for (int i = 0; i < 16; i++) + qDebug() << "Projection[" << i << "] = " << glm::value_ptr(m_Projection)[i] << endl; + } + } +} \ No newline at end of file diff --git a/Source/Fractorium/GLEmberController.h b/Source/Fractorium/GLEmberController.h new file mode 100644 index 0000000..beb7ac1 --- /dev/null +++ b/Source/Fractorium/GLEmberController.h @@ -0,0 +1,146 @@ +#pragma once + +#include "FractoriumPch.h" + +/// +/// GLEmberControllerBase and GLEmberController classes. +/// + +/// +/// Use/draw pre or post affine transform. +/// +enum eAffineType { AffinePre, AffinePost }; + +/// +/// Hovering over nothing, the x axis, the y axis or the center. +/// +enum eHoverType { HoverNone, HoverXAxis, HoverYAxis, HoverTranslation }; + +/// +/// Dragging an affine transform or panning, rotating or scaling the image. +/// +enum eDragState { DragNone, DragPanning, DragDragging, DragRotateScale }; + +/// +/// Dragging with no keys pressed, shift, control or alt. +/// +enum eDragModifier { DragModNone = 0x00, DragModShift = 0x01, DragModControl = 0x02, DragModAlt = 0x04 }; + +/// +/// GLController, FractoriumEmberController, GLWidget and Fractorium need each other, but each can't all include the other. +/// So GLWidget includes this file, and GLWidget, FractoriumEmberController and Fractorium are declared as forward declarations here. +/// +class GLWidget; +class Fractorium; +template class FractoriumEmberController; + +/// +/// GLEmberControllerBase serves as a non-templated base class with virtual +/// functions which will be overridden in a derived class that takes a template parameter. +/// The controller serves as a way to access both the GLWidget and the underlying ember +/// objects through an interface that doesn't require template argument, but does allow +/// templated objects to be used underneath. +/// The functions not implemented in this file can be found in GLWidget.cpp near the area of code which uses them. +/// +class GLEmberControllerBase +{ +public: + GLEmberControllerBase(Fractorium* fractorium, GLWidget* glWidget); + virtual ~GLEmberControllerBase(); + + void ClearDrag(); + bool Allocate(bool force = false); + + virtual void DrawImage() { } + virtual void DrawAffines(bool pre, bool post) { } + virtual void ClearWindow() { } + virtual bool KeyPress(QKeyEvent* e); + virtual bool KeyRelease(QKeyEvent* e); + virtual void MousePress(QMouseEvent* e) { } + virtual void MouseRelease(QMouseEvent* e) { } + virtual void MouseMove(QMouseEvent* e) { } + virtual void Wheel(QWheelEvent* e) { } + virtual bool SyncSizes() { return false; } + virtual bool CheckForSizeMismatch(int w, int h) { return true; } + virtual void QueryMatrices(bool print) { } + +protected: + unsigned int m_DragModifier; + glm::ivec2 m_MousePos; + glm::ivec2 m_MouseDownPos; + glm::ivec4 m_Viewport; + eDragState m_DragState; + eHoverType m_HoverType; + eAffineType m_AffineType; + GLWidget* m_GL; + Fractorium* m_Fractorium; +}; + +/// +/// Templated derived class which implements all interaction functionality between the embers +/// of a specific template type and the GLWidget; +/// +template +class GLEmberController : public GLEmberControllerBase +{ +public: + GLEmberController(Fractorium* fractorium, GLWidget* glWidget, FractoriumEmberController* controller); + virtual ~GLEmberController(); + virtual void DrawImage(); + virtual void DrawAffines(bool pre, bool post); + virtual void ClearWindow(); + virtual void MousePress(QMouseEvent* e); + virtual void MouseRelease(QMouseEvent* e); + virtual void MouseMove(QMouseEvent* e); + virtual void Wheel(QWheelEvent* e); + virtual void QueryMatrices(bool print); + virtual bool SyncSizes(); + virtual bool CheckForSizeMismatch(int w, int h); + + T CalcScale(); + T CalcRotation(); + Affine2D CalcDragXAxis(); + Affine2D CalcDragYAxis(); + Affine2D CalcDragTranslation(); + + void SetEmber(Ember* ember); + void SetSelectedXform(Xform* xform); + void DrawAffine(Xform* xform, bool pre, bool selected); + int UpdateHover(v3T& glCoords); + bool CheckXformHover(Xform* xform, v3T& glCoords, T& bestDist, bool pre, bool post); + +private: + v3T SnapToGrid(v3T& vec); + v3T SnapToNormalizedAngle(v3T& vec, unsigned int divisions); + v3T WindowToWorld(v3T& v, bool flip); + void QueryVMP(); + void MultMatrix(m4T& mat); + + T m_CenterDownX; + T m_CenterDownY; + T m_RotationDown; + T m_ScaleDown; + v4T m_BoundsDown; + + v3T m_MouseWorldPos; + v3T m_MouseDownWorldPos; + v3T m_DragHandlePos; + v3T m_DragHandleOffset; + v3T m_HoverHandlePos; + + m4T m_Modelview; + m4T m_Projection; + + Affine2D m_DragSrcTransform; + + Xform* m_HoverXform; + Xform* m_SelectedXform; + FractoriumEmberController* m_FractoriumEmberController; + T GridStep; +}; + +template class GLEmberController; + +#ifdef DO_DOUBLE + template class GLEmberController; +#endif \ No newline at end of file diff --git a/Source/Fractorium/GLWidget.cpp b/Source/Fractorium/GLWidget.cpp new file mode 100644 index 0000000..dc6828b --- /dev/null +++ b/Source/Fractorium/GLWidget.cpp @@ -0,0 +1,1435 @@ +#include "FractoriumPch.h" +#include "GLWidget.h" +#include "Fractorium.h" + +//#define OLDDRAG 1 + +/// +/// Constructor which passes parent widget to the base and initializes OpenGL profile. +/// This will need to change in the future to implement all drawing as shader programs. +/// +/// The parent widget +GLWidget::GLWidget(QWidget* parent) + : QGLWidget(QGLFormat(QGL::SampleBuffers), parent) +{ + QGLFormat qglFormat; + + m_Init = false; + m_Drawing = false; + m_TexWidth = 0; + m_TexHeight = 0; + m_OutputTexID = 0; + m_Fractorium = NULL; + qglFormat.setSwapInterval(1);//Vsync. + qglFormat.setDoubleBuffer(true); + qglFormat.setVersion(2, 0); + //qglFormat.setVersion(3, 2); + qglFormat.setProfile(QGLFormat::CompatibilityProfile); + //qglFormat.setProfile(QGLFormat::CoreProfile); + + setFormat(qglFormat); +} + +/// +/// Empty destructor. +/// +GLWidget::~GLWidget() +{ +} + +/// +/// Draw the final rendered image as a texture on a quad that is the same size as the window. +/// Different action is taken based on whether a CPU or OpenCL renderer is used. +/// For CPU, the output image buffer must be copied to OpenGL every time it's drawn. +/// For OpenCL, the output image and the texture are the same thing, so no copying is necessary +/// and all image memory remains on the card. +/// +void GLWidget::DrawQuad() +{ + GLint texWidth = 0, texHeight = 0; + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + RendererBase* renderer = m_Fractorium->m_Controller->Renderer(); + vector* finalImage = m_Fractorium->m_Controller->FinalImage(); + + //Ensure all allocation has taken place first. + if (m_OutputTexID != 0 && finalImage && !finalImage->empty()) + { + glBindTexture(GL_TEXTURE_2D, m_OutputTexID);//The texture to draw to. + + //Only draw if the dimensions match exactly. + if (m_TexWidth == width() && m_TexHeight == height() && ((m_TexWidth * m_TexHeight * 4) == finalImage->size())) + { + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glOrtho(0, 1, 1, 0, -1, 1); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + + //Copy data from CPU to OpenGL if using a CPU renderer. This is not needed when using OpenCL. + if (renderer->RendererType() == CPU_RENDERER) + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_TexWidth, m_TexHeight, GL_RGBA, GL_UNSIGNED_BYTE, finalImage->data()); + + glBegin(GL_QUADS);//This will need to be converted to a shader at some point in the future. + + glTexCoord2f(0.0, 0.0); glVertex2f(0.0, 0.0); + glTexCoord2f(0.0, 1.0); glVertex2f(0.0, 1.0); + glTexCoord2f(1.0, 1.0); glVertex2f(1.0, 1.0); + glTexCoord2f(1.0, 0.0); glVertex2f(1.0, 0.0); + + glEnd(); + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + } + + glBindTexture(GL_TEXTURE_2D, 0);//Stop using this texture. + } + + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); +} + +/// +/// Set drag and drag modifier states to nothing. +/// +void GLEmberControllerBase::ClearDrag() +{ + m_DragModifier = 0; + m_DragState = DragNone; +} + +/// +/// Wrapper around Allocate() call on the GL widget. +/// +bool GLEmberControllerBase::Allocate(bool force) { return m_GL->Allocate(force); } + +/// +/// Clear the OpenGL output window to be the background color of the current ember. +/// Both buffers must be cleared, else artifacts will show up. +/// +template +void GLEmberController::ClearWindow() +{ + Ember* ember = m_FractoriumEmberController->CurrentEmber(); + + m_GL->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + m_GL->glClearColor(ember->m_Background.r, ember->m_Background.g, ember->m_Background.b, 1.0); + + m_GL->swapBuffers(); + + m_GL->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + m_GL->glClearColor(ember->m_Background.r, ember->m_Background.g, ember->m_Background.b, 1.0); +} + +/// +/// Set the currently selected xform. +/// The currently selected xform is drawn with a circle around it, with all others only showing their axes. +/// +/// The xform. +template +void GLEmberController::SetSelectedXform(Xform* xform) +{ + //By doing this check, it prevents triggering unnecessary events when selecting an xform on this window with the mouse, + //which will set the combo box on the main window, which will trigger this call. However, if the xform has been selected + //here with the mouse, the window has already repainted, so there's no need to do it again. + if (m_SelectedXform != xform || m_HoverXform != xform) + { + m_HoverXform = xform; + m_SelectedXform = xform; + + if (m_GL->m_Init) + m_GL->repaint();//Force immediate redraw with repaint() instead of update(). + } +} + +/// +/// Setters for main window pointers. +/// + +void GLWidget::SetMainWindow(Fractorium* f) { m_Fractorium = f; } + +/// +/// Getters for OpenGL state. +/// + +bool GLWidget::Init() { return m_Init; } +bool GLWidget::Drawing() { return m_Drawing; } +GLuint GLWidget::OutputTexID() { return m_OutputTexID; } + +/// +/// Initialize OpenGL, called once at startup after the main window constructor finishes. +/// Once this is done, the render timer is started after a short delay. +/// Rendering is then clear to begin. +/// +void GLWidget::initializeGL() +{ + if (!m_Init && initializeOpenGLFunctions() && m_Fractorium) + { + glClearColor(0.0, 0.0, 0.0, 1.0); + + //Start with a flock of 10 random embers. Can't do this until now because the window wasn't maximized yet, so the sizes would have been off. + m_Fractorium->OnActionNewFlock(false); + m_Fractorium->m_Controller->DelayedStartRenderTimer(); + m_Init = true; + } +} + +/// +/// The main drawing/update function. +/// First the quad will be drawn, then the remaining affine circles. +/// +void GLWidget::paintGL() +{ + FractoriumEmberControllerBase* controller = m_Fractorium->m_Controller.get(); + + //Ensure there is a renderer and that it's supposed to be drawing, signified by the running timer. + if (controller && controller->Renderer() && controller->RenderTimerRunning()) + { + RendererBase* renderer = controller->Renderer(); + + m_Drawing = true; + //renderer->EnterResize(); + controller->GLController()->DrawImage(); + //renderer->LeaveResize();//Unlock, may not be necessary. + + //Affine drawing. + bool pre = m_Fractorium->ui.PreAffineGroupBox->isChecked(); + bool post = m_Fractorium->ui.PostAffineGroupBox->isChecked(); + float unitX = fabs(renderer->UpperRightX(false) - renderer->LowerLeftX(false)) / 2.0f; + float unitY = fabs(renderer->UpperRightY(false) - renderer->LowerLeftY(false)) / 2.0f; + + glEnable(GL_DEPTH_TEST); + glEnable(GL_BLEND); + glEnable(GL_LINE_SMOOTH); + glEnable(GL_POINT_SMOOTH); + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO); + + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glOrtho(-unitX, unitX, -unitY, unitY, -1, 1);//Projection matrix: OpenGL camera is always centered, just move the ember internally inside the renderer. + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + + glDisable(GL_DEPTH_TEST); + + controller->GLController()->DrawAffines(pre, post); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + glDisable(GL_DEPTH_TEST); + glDisable(GL_BLEND); + glDisable(GL_LINE_SMOOTH); + glDisable(GL_POINT_SMOOTH); + + glFinish(); + m_Drawing = false; + } +} + +/// +/// Draw the image on the quad. +/// +template +void GLEmberController::DrawImage() +{ + RendererBase* renderer = m_FractoriumEmberController->Renderer(); + Ember* ember = m_FractoriumEmberController->CurrentEmber(); + + m_GL->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + m_GL->glClearColor(ember->m_Background.r, ember->m_Background.g, ember->m_Background.b, 1.0); + m_GL->glDisable(GL_DEPTH_TEST); + + renderer->EnterFinalAccum();//Lock, may not be necessary, but just in case. + renderer->EnterResize(); + + if (SyncSizes())//Ensure all sizes are correct. If not, do nothing. + { + vector* finalImage = m_FractoriumEmberController->FinalImage(); + + if (renderer->RendererType() == OPENCL_RENDERER || finalImage)//Final image only matters for CPU renderer. + if (renderer->RendererType() == OPENCL_RENDERER || finalImage->size() == renderer->FinalBufferSize()) + m_GL->DrawQuad();//Output image is drawn here. + } + + renderer->LeaveResize();//Unlock, may not be necessary. + renderer->LeaveFinalAccum(); +} + +/// +/// Draw the affine circles. +/// +/// True to draw pre affines, else don't. +/// True to draw post affines, else don't. +template +void GLEmberController::DrawAffines(bool pre, bool post) +{ + QueryVMP();//Resolves to float or double specialization function depending on T. + Ember* ember = m_FractoriumEmberController->CurrentEmber(); + + //Draw grid if control key is pressed. + if ((m_DragModifier & DragModControl) == DragModControl) + { + m_GL->glLineWidth(1.0f); + m_GL->DrawGrid(); + } + //When dragging, only draw the selected xform's affine and hide all others. + if (m_DragState == DragDragging) + { + if (m_SelectedXform) + DrawAffine(m_SelectedXform, m_AffineType == AffinePre, true); + + m_GL->glPointSize(6.0f);//Draw large yellow dot on select or drag. + m_GL->glBegin(GL_POINTS); + m_GL->glColor4f(1.0f, 1.0f, 0.5f, 1.0f); + m_GL->glVertex2f(m_DragHandlePos.x, m_DragHandlePos.y); + m_GL->glEnd(); + m_GL->glPointSize(1.0f);//Restore point size. + } + else//Not dragging, just hovering/mouse move. + { + if (pre && m_Fractorium->DrawAllPre())//Draw all pre affine if specified. + { + for (unsigned int i = 0; i < ember->TotalXformCount(); i++) + { + Xform* xform = ember->GetTotalXform(i); + bool selected = m_HoverXform == xform; + + DrawAffine(xform, true, selected); + } + } + else if (pre && m_HoverXform)//Only draw current pre affine. + { + DrawAffine(m_HoverXform, true, true); + } + + if (post && m_Fractorium->DrawAllPost())//Draw all post affine if specified. + { + for (unsigned int i = 0; i < ember->TotalXformCount(); i++) + { + Xform* xform = ember->GetTotalXform(i); + bool selected = m_HoverXform == xform; + + DrawAffine(xform, false, selected); + } + } + else if (post && m_HoverXform)//Only draw current post affine. + { + DrawAffine(m_HoverXform, false, true); + } + + //Draw large turquoise dot on hover if they are hovering over the selected xform. + if (m_HoverType != HoverNone && m_HoverXform == m_SelectedXform) + { + m_GL->glPointSize(6.0f); + m_GL->glBegin(GL_POINTS); + m_GL->glColor4f(0.5f, 1.0f, 1.0f, 1.0f); + m_GL->glVertex2f(m_HoverHandlePos.x, m_HoverHandlePos.y); + m_GL->glEnd(); + m_GL->glPointSize(1.0f); + } + } +} + +/// +/// Set drag modifiers based on key press. +/// +/// The event +bool GLEmberControllerBase::KeyPress(QKeyEvent* e) +{ +#ifdef OLDDRAG + if (e->key() == Qt::Key_Shift) + m_DragModifier |= DragModShift; + else if (e->key() == Qt::Key_Control || e->key() == Qt::Key_C) + m_DragModifier |= DragModControl; + else if (e->key() == Qt::Key_Alt || e->key() == Qt::Key_A) + m_DragModifier |= DragModAlt; + else + return false; + + return true; +#else + if (e->key() == Qt::Key_Control) + { + m_DragModifier |= DragModControl; + return true; + } +#endif + + return false; +} + +/// +/// Call controller KeyPress(). +/// +/// The event +void GLWidget::keyPressEvent(QKeyEvent* e) +{ + if (!GLController() || !GLController()->KeyPress(e)) + QGLWidget::keyPressEvent(e); + + update(); +} + +/// +/// Set drag modifiers based on key release. +/// +/// The event +bool GLEmberControllerBase::KeyRelease(QKeyEvent* e) +{ +#ifdef OLDDRAG + if (e->key() == Qt::Key_Shift) + m_DragModifier &= ~DragModShift; + else if (e->key() == Qt::Key_Control || e->key() == Qt::Key_C) + m_DragModifier &= ~DragModControl; + else if (e->key() == Qt::Key_Alt || e->key() == Qt::Key_A) + m_DragModifier &= ~DragModAlt; + else + return false; + + return true; +#else + if (e->key() == Qt::Key_Control) + { + m_DragModifier &= ~DragModControl; + return true; + } +#endif + + return false; +} + +/// +/// Call controller KeyRelease(). +/// +/// The event +void GLWidget::keyReleaseEvent(QKeyEvent* e) +{ + if (!GLController() || !GLController()->KeyRelease(e)) + QGLWidget::keyReleaseEvent(e); + + update(); +} + +/// +/// Determine if the mouse click was over an affine circle +/// and set the appropriate selection information to be used +/// on subsequent mouse move events. +/// If nothing was selected, then reset the selection and drag states. +/// +/// The event +template +void GLEmberController::MousePress(QMouseEvent* e) +{ + v3T mouseFlipped(e->x(), m_Viewport[3] - e->y(), 0);//Must flip y because in OpenGL, 0,0 is bottom left, but in windows, it's top left. + Ember* ember = m_FractoriumEmberController->CurrentEmber(); + RendererBase* renderer = m_FractoriumEmberController->Renderer(); + + //Ensure everything has been initialized. + if (!renderer) + return; + + m_MouseDownPos = glm::ivec2(e->x(), e->y());//Capture the raster coordinates of where the mouse was clicked. + m_MouseWorldPos = WindowToWorld(mouseFlipped, false);//Capture the world cartesian coordinates of where the mouse is. + m_BoundsDown.w = renderer->LowerLeftX(false);//Need to capture these because they'll be changing if scaling. + m_BoundsDown.x = renderer->LowerLeftY(false); + m_BoundsDown.y = renderer->UpperRightX(false); + m_BoundsDown.z = renderer->UpperRightY(false); + +#ifndef OLDDRAG + Qt::KeyboardModifiers mod = e->modifiers(); + + if (mod.testFlag(Qt::ShiftModifier)) + m_DragModifier |= DragModShift; + //if (mod.testFlag(Qt::ControlModifier))// || mod.testFlag(Qt::Key_C)) + // m_DragModifier |= DragModControl; + if (mod.testFlag(Qt::AltModifier))// || mod.testFlag(Qt::Key_A)) + m_DragModifier |= DragModAlt; +#endif + if (m_DragState == DragNone)//Only take action if the user wasn't already dragging. + { + m_MouseDownWorldPos = m_MouseWorldPos;//Set the mouse down position to the current position. + + if (e->button() & Qt::LeftButton) + { + int xformIndex = UpdateHover(mouseFlipped);//Determine if an affine circle was clicked. + + if (m_HoverXform && xformIndex != -1) + { + m_SelectedXform = m_HoverXform; + m_DragSrcTransform = Affine2D(m_AffineType == AffinePre ? m_SelectedXform->m_Affine : m_SelectedXform->m_Post);//Copy the affine of the xform that was selected. + m_DragHandlePos = m_HoverHandlePos; + m_DragHandleOffset = m_DragHandlePos - m_MouseWorldPos; + m_DragState = DragDragging; + + //The user has selected an xform by clicking on it, so update the main GUI by selecting this xform in the combo box. + m_Fractorium->CurrentXform(xformIndex); + } + else//Nothing was selected. + { + //m_SelectedXform = NULL; + m_DragState = DragNone; + } + } + else if (e->button() == Qt::MiddleButton)//Middle button does whole image translation. + { + m_CenterDownX = ember->m_CenterX;//Capture where the center of the image is because this value will change when panning. + m_CenterDownY = ember->m_CenterY; + m_DragState = DragPanning; + } + else if (e->button() == Qt::RightButton)//Right button does whole image rotation and scaling. + { + UpdateHover(mouseFlipped); + m_SelectedXform = m_HoverXform; + m_CenterDownX = ember->m_CenterX;//Capture these because they will change when rotating and scaling. + m_CenterDownY = ember->m_CenterY; + m_RotationDown = ember->m_Rotate; + m_ScaleDown = ember->m_PixelsPerUnit; + m_DragState = DragRotateScale; + } + } +} + +/// +/// Call controller MousePress(). +/// +/// The event +void GLWidget::mousePressEvent(QMouseEvent* e) +{ + setFocus();//Must do this so that this window gets keyboard events. + + if (GLEmberControllerBase* controller = GLController()) + controller->MousePress(e); + + QGLWidget::mousePressEvent(e); +} + +/// +/// Reset the selection and dragging state, but re-calculate the +/// hovering state because the mouse might still be over an affine circle. +/// +/// The event +template +void GLEmberController::MouseRelease(QMouseEvent* e) +{ + v3T mouseFlipped(e->x(), m_Viewport[3] - e->y(), 0);//Must flip y because in OpenGL, 0,0 is bottom left, but in windows, it's top left. + + m_MouseWorldPos = WindowToWorld(mouseFlipped, false); + + if (m_DragState == DragDragging && (e->button() & Qt::LeftButton)) + UpdateHover(mouseFlipped); + + m_DragState = DragNone; + +#ifndef OLDDRAG + m_DragModifier = 0; +#endif + + m_GL->repaint();//Force immediate redraw. +} + +/// +/// Call controller MouseRelease(). +/// +/// The event +void GLWidget::mouseReleaseEvent(QMouseEvent* e) +{ + setFocus();//Must do this so that this window gets keyboard events. + + if (GLEmberControllerBase* controller = GLController()) + controller->MouseRelease(e); + + QGLWidget::mouseReleaseEvent(e); +} + +/// +/// If dragging, update relevant values and reset entire rendering process. +/// If hovering, update display. +/// +/// The event +template +void GLEmberController::MouseMove(QMouseEvent* e) +{ + bool draw = true; + glm::ivec2 mouse(e->x(), e->y()); + v3T mouseFlipped(e->x(), m_Viewport[3] - e->y(), 0);//Must flip y because in OpenGL, 0,0 is bottom left, but in windows, it's top left. + Ember* ember = m_FractoriumEmberController->CurrentEmber(); + + //First check to see if the mouse actually moved. + if (mouse == m_MousePos) + return; + + m_MousePos = mouse; + m_MouseWorldPos = WindowToWorld(mouseFlipped, false); + v3T mouseDelta = m_MouseWorldPos - m_MouseDownWorldPos;//Determine how far the mouse has moved in world cartesian coordinates. + + //Update status bar on main window, regardless of whether anything is being dragged. + if (m_Fractorium->m_Controller->RenderTimerRunning()) + m_Fractorium->SetCoordinateStatus(e->x(), e->y(), m_MouseWorldPos.x, m_MouseWorldPos.y); + + if (m_SelectedXform && m_DragState == DragDragging)//Dragging and affine. + { + bool pre = m_AffineType == AffinePre; + Affine2D* affine = pre ? &m_SelectedXform->m_Affine : &m_SelectedXform->m_Post;//Determine pre or post affine. + + if (m_HoverType == HoverTranslation) + *affine = CalcDragTranslation(); + else if (m_HoverType == HoverXAxis) + *affine = CalcDragXAxis(); + else if (m_HoverType == HoverYAxis) + *affine = CalcDragYAxis(); + + m_FractoriumEmberController->FillAffineWithXform(m_SelectedXform, pre);//Update the spinners in the affine tab of the main window. + m_FractoriumEmberController->UpdateRender();//Restart the rendering process. + } + else if (m_DragState == DragPanning)//Translating the whole image. + { + T x = -(m_MouseWorldPos.x - m_MouseDownWorldPos.x); + T y = (m_MouseWorldPos.y - m_MouseDownWorldPos.y); + Affine2D rotMat; + + rotMat.C(m_CenterDownX); + rotMat.F(m_CenterDownY); + rotMat.Rotate(ember->m_Rotate); + + v2T v1(x, y); + v2T v2 = rotMat.TransformVector(v1); + + ember->m_CenterX = v2.x; + ember->m_CenterY = v2.y; + m_FractoriumEmberController->SetCenter(ember->m_CenterX, ember->m_CenterY);//Will restart the rendering process. + } + else if (m_DragState == DragRotateScale)//Rotating and scaling the whole image. + { + T rot = CalcRotation(); + T scale = CalcScale(); + + ember->m_Rotate = NormalizeDeg180(m_RotationDown + rot); + m_Fractorium->SetRotation(ember->m_Rotate, true); + ember->m_PixelsPerUnit = m_ScaleDown + scale; + m_Fractorium->SetScale(ember->m_PixelsPerUnit);//Will restart the rendering process. + } + else + { + //If the user doesn't already have a key down, and they aren't dragging, clear the keys to be safe. + //This is done because if they do an alt+tab between windows, it thinks the alt key is down. + if (e->modifiers() == Qt::NoModifier) + ClearDrag(); + + //Check if they weren't dragging and weren't hovering over any affine. + //In that case, nothing needs to be done. + if (UpdateHover(mouseFlipped) == -1) + draw = false; + + //Xform* previousHover = m_HoverXform; + // + //if (UpdateHover(mouseFlipped) == -1) + // m_HoverXform = m_SelectedXform; + // + //if (m_HoverXform == previousHover) + // draw = false; + } + + //Only update if the user was dragging or hovered over a point. + //Use repaint() to update immediately for a more responsive feel. + if ((m_DragState != DragNone) || draw) + m_GL->update(); + //m_GL->repaint(); +} + +/// +/// Call controller MouseMove(). +/// +/// The event +void GLWidget::mouseMoveEvent(QMouseEvent* e) +{ + setFocus();//Must do this so that this window gets keyboard events. + + if (GLEmberControllerBase* controller = GLController()) + controller->MouseMove(e); + + QGLWidget::mouseMoveEvent(e); +} + +/// +/// Mouse wheel changes the scale (pixels per unit) which +/// will zoom in the image in our out, while sacrificing quality. +/// If the user needs to preserve quality, they can use the zoom spinner +/// on the main window. +/// +/// The event +template +void GLEmberController::Wheel(QWheelEvent* e) +{ + Ember* ember = m_FractoriumEmberController->CurrentEmber(); + + if (m_Fractorium && !(e->buttons() & Qt::MiddleButton))//Middle button does whole image translation, so ignore the mouse wheel while panning to avoid inadvertent zooming. + m_Fractorium->SetScale(ember->m_PixelsPerUnit + (e->angleDelta().y() >= 0 ? 50 : -50)); +} + +/// +/// Call controller Wheel(). +/// +/// The event +void GLWidget::wheelEvent(QWheelEvent* e) +{ + if (GLEmberControllerBase* controller = GLController()) + controller->Wheel(e); + + QGLWidget::wheelEvent(e); +} + +/// +/// Respond to a resize event which will set the read only +/// main window width and height spinners. +/// The main window will take care of stopping and restarting the +/// render timer. +/// +/// The event +void GLWidget::resizeEvent(QResizeEvent* e) +{ + if (m_Fractorium) + { + m_Fractorium->m_WidthSpin->setValue(width()); + m_Fractorium->m_HeightSpin->setValue(height()); + + if (GLEmberControllerBase* controller = GLController()) + controller->SyncSizes();//For some reason the base resize can't be called here or else it causes a crash. + } +} + +/// +/// Set up texture memory to match the size of the window. +/// If first allocation, generate, bind and set parameters. +/// If subsequent call, only take action if dimensions don't match the window. In such case, +/// first deallocate, then reallocate. +/// +/// True if success, else false. +bool GLWidget::Allocate(bool force) +{ + bool alloc = false; + + if (m_OutputTexID == 0) + { + m_TexWidth = width(); + m_TexHeight = height(); + glEnable(GL_TEXTURE_2D); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glGenTextures(1, &m_OutputTexID); + glBindTexture(GL_TEXTURE_2D, m_OutputTexID); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);//Fractron had this as GL_LINEAR_MIPMAP_LINEAR for OpenCL and Cuda. + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_TexWidth, m_TexHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + alloc = true; + } + else + { + if (force || (m_TexWidth != width() || m_TexHeight != height())) + { + m_TexWidth = width(); + m_TexHeight = height(); + glEnable(GL_TEXTURE_2D); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glBindTexture(GL_TEXTURE_2D, m_OutputTexID); + Deallocate(); + glGenTextures(1, &m_OutputTexID); + glBindTexture(GL_TEXTURE_2D, m_OutputTexID); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);//Fractron had this as GL_LINEAR_MIPMAP_LINEAR for OpenCL and Cuda. + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_TexWidth, m_TexHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + alloc = true; + } + } + + if (alloc) + { + glBindTexture(GL_TEXTURE_2D, 0); + glDisable(GL_TEXTURE_2D); + } + + return m_OutputTexID != 0; +} + +/// +/// Deallocate texture memory. +/// +/// True if anything deleted, else false. +bool GLWidget::Deallocate() +{ + bool deleted = false; + + if (m_OutputTexID != 0) + { + glDeleteTextures(1, &m_OutputTexID); + m_OutputTexID = 0; + deleted = true; + } + + return deleted; +} + +/// +/// Set the viewport to match the window dimensions. +/// If the dimensions already match, no action is taken. +/// +void GLWidget::SetViewport() +{ + if (m_Init && (m_ViewWidth != width() || m_ViewHeight != height())) + { + glViewport(0, 0, (GLint)width(), (GLint)height()); + m_ViewWidth = width(); + m_ViewHeight = height(); + } +} + +/// +/// Synchronize the size of the renderer output image, the texture, and this window. +/// They must all match. If the renderer output size doesn't match, then the render +/// timer is stopped and restarted with a delay. +/// This will get called once or twice when a resize occurs. +/// +/// True if all sizes match, else false. +template +bool GLEmberController::SyncSizes() +{ + if (m_GL->Init()) + { + m_GL->SetViewport(); + + //First make sure the dimensions of the ember match the window size. + if (CheckForSizeMismatch(m_GL->width(), m_GL->height())) + { + if (m_Fractorium)//This will stop the rendering process and start the reset timer. + m_Fractorium->Resize(); + + return false; + } + + //The renderer and window dimensions match, now make sure the dims of the texture match. + if (!m_GL->Allocate()) + return false; + + return true; + } + + return false; +} + +/// +/// Draw the grid in response to the control key being pressed. +/// The frequency of the grid lines will change depending on the zoom. +/// Calculated with the frame always centered, the renderer just moves the camera. +/// +void GLWidget::DrawGrid() +{ + RendererBase* renderer = m_Fractorium->m_Controller->Renderer(); + float unitX = fabs(renderer->UpperRightX(false) - renderer->LowerLeftX(false)) / 2.0f; + float unitY = fabs(renderer->UpperRightY(false) - renderer->LowerLeftY(false)) / 2.0f; + float rad = max(unitX, unitY); + float xLow = floor(-unitX); + float xHigh = ceil(unitX); + float yLow = floor(-unitY); + float yHigh = ceil(unitY); + + glBegin(GL_LINES); + + if (rad <= 8.0f) + { + glColor4f(0.5f, 0.5f, 0.5f, 0.5f); + + for (float x = xLow; x <= xHigh; x += GridStep) + { + glVertex2f(x, yLow); + glVertex2f(x, yHigh); + } + + for (float y = yLow; y < yHigh; y += GridStep) + { + glVertex2f(xLow, y); + glVertex2f(xHigh, y); + } + } + + if (unitX <= 64.0f) + { + glColor4f(0.5f, 0.5f, 0.5f, 1.0f); + + for (float x = xLow; x <= xHigh; x += 1.0f) + { + glVertex2f(x, yLow); + glVertex2f(x, yHigh); + } + + for (float y = yLow; y < yHigh; y += 1.0f) + { + glVertex2f(xLow, y); + glVertex2f(xHigh, y); + } + } + + glColor4f(1.0f, 0.0f, 0.0f, 1.0f); + glVertex2f(0.0f, 0.0f); + glVertex2f(xHigh, 0.0f); + glColor4f(0.5f, 0.0f, 0.0f, 1.0f); + glVertex2f(0.0f, 0.0f); + glVertex2f(xLow, 0.0f); + glColor4f(0.0f, 1.0f, 0.0f, 1.0f); + glVertex2f(0.0f, 0.0f); + glVertex2f(0.0f, yHigh); + glColor4f(0.0f, 0.5f, 0.0f, 1.0f); + glVertex2f(0.0f, 0.0f); + glVertex2f(0.0f, yLow); + glEnd(); +} + +/// +/// Draw the unit square. +/// +void GLWidget::DrawUnitSquare() +{ + glLineWidth(1.0f); + glBegin(GL_LINES); + glColor4f(1.0f, 1.0f, 1.0f, 0.25f); + glVertex2f(-1,-1); + glVertex2f( 1,-1); + glVertex2f(-1, 1); + glVertex2f( 1, 1); + + glVertex2f(-1,-1); + glVertex2f(-1, 1); + glVertex2f( 1,-1); + glVertex2f( 1, 1); + + glColor4f(1.0f, 0.0f, 0.0f, 0.5f); + glVertex2f(-1, 0); + glVertex2f( 1, 0); + glColor4f(0.0f, 1.0f, 0.0f, 0.5f); + glVertex2f( 0,-1); + glVertex2f( 0, 1); + glEnd(); +} + +/// +/// Draw the pre or post affine circle for the passed in xform. +/// For drawing affine transforms, multiply the identity model view matrix by the +/// affine for each xform, so that all points are considered to be "1". +/// +/// A pointer to the xform whose affine will be drawn +/// True for pre affine, else false for post. +/// True if selected (draw enclosing circle), else false (only draw axes). +template +void GLEmberController::DrawAffine(Xform* xform, bool pre, bool selected) +{ + Ember* ember = m_FractoriumEmberController->CurrentEmber(); + bool final = ember->IsFinalXform(xform); + size_t size = ember->m_Palette.m_Entries.size(); + v4T color = ember->m_Palette.m_Entries[Clamp(xform->m_ColorX * size, 0, size - 1)]; + Affine2D* affine = pre ? &xform->m_Affine : &xform->m_Post; + + //For some incredibly strange reason, even though glm and OpenGL use matrices with a column-major + //data layout, nothing will work here unless they are flipped to row major order. This is how it was + //done in Fractron. + m4T mat = affine->ToMat4RowMajor(); + + m_GL->glPushMatrix(); + m_GL->glLoadIdentity(); + MultMatrix(mat); + m_GL->glLineWidth(3.0f);//One 3px wide, colored black, except green on x axis for post affine. + m_GL->DrawAffineHelper(selected, pre, final, true); + + m_GL->glLineWidth(1.0f);//Again 1px wide, colored white, to give a white middle with black outline effect. + m_GL->DrawAffineHelper(selected, pre, final, false); + + m_GL->glPointSize(5.0f);//Three black points, one in the center and two on the circle. Drawn big 5px first to give a black outline. + m_GL->glBegin(GL_POINTS); + m_GL->glColor4f(0.0f, 0.0f, 0.0f, selected ? 1.0f : 0.5f); + m_GL->glVertex2f(0.0f, 0.0f); + m_GL->glVertex2f(1.0f, 0.0f); + m_GL->glVertex2f(0.0f, 1.0f); + m_GL->glEnd(); + + m_GL->glLineWidth(2.0f);//Draw lines again for y axis only, without drawing the circle, using the color of the selected xform. + m_GL->glBegin(GL_LINES); + m_GL->glColor4f(color.r, color.g, color.b, 1.0f); + m_GL->glVertex2f(0.0f, 0.0f); + m_GL->glVertex2f(0.0f, 1.0f); + m_GL->glEnd(); + + m_GL->glPointSize(3.0f);//Draw smaller white points, to give a black outline effect. + m_GL->glBegin(GL_POINTS); + m_GL->glColor4f(1.0f, 1.0f, 1.0f, selected ? 1.0f : 0.5f); + m_GL->glVertex2f(0.0f, 0.0f); + m_GL->glVertex2f(1.0f, 0.0f); + m_GL->glVertex2f(0.0f, 1.0f); + m_GL->glEnd(); + m_GL->glPopMatrix(); +} + +/// +/// Draw the axes, and optionally the surrounding circle +/// of an affine transform. +/// +/// True if selected (draw enclosing circle), else false (only draw axes). +void GLWidget::DrawAffineHelper(bool selected, bool pre, bool final, bool background) +{ + float px = 1.0f; + float py = 0.0f; + + glBegin(GL_LINES); + + //Circle part. + if (!background) + { + if (pre) + glColor4f(1.0f, 1.0f, 1.0f, 1.0f);//Draw pre affine transform with white. + else + glColor4f(0.0f, 0.75f, 0.0f, 1.0f);//Draw post affine transform with green. + } + else + { + glColor4f(0.0f, 0.0f, 0.0f, 1.0f); + } + + if (selected) + { + for (int i = 1; i <= 64; i++)//The circle. + { + float theta = (float)M_PI * 2.0f * (float)(i % 64) / 64.0f; + float x = (float)cos(theta); + float y = (float)sin(theta); + + glVertex2f(px, py); + glVertex2f(x, y); + px = x; + py = y; + } + } + + //Lines from center to circle. + if (!background) + { + if (final) + glColor4f(1.0f, 0.0f, 0.0f, 1.0f);//Draw final xforms with red. + else + glColor4f(1.0f, 1.0f, 1.0f, 1.0f);//Draw normal xforms with white. + } + else + { + if (!pre) + glColor4f(0.0f, 0.75f, 0.0f, 1.0f);//Draw post affine transform with green outline. + } + + //The lines from the center to the circle. + + glVertex2f(0.0f, 0.0f);//X axis. + glVertex2f(1.0f, 0.0f); + + if (background) + glColor4f(0.0f, 0.0f, 0.0f, 1.0f); + + glVertex2f(0.0f, 0.0f);//Y axis. + glVertex2f(0.0f, 1.0f); + + glEnd(); +} + +/// +/// Determine the index of the xform being hovered over if any. +/// Give precedence to the currently selected xform, if any. +/// +/// The mouse raster coordinates to check +/// The index of the xform being hovered over, else -1 if no hover. +template +int GLEmberController::UpdateHover(v3T& glCoords) +{ + bool pre = m_Fractorium->ui.PreAffineGroupBox->isChecked(); + bool post = m_Fractorium->ui.PostAffineGroupBox->isChecked(); + bool preAll = pre && m_Fractorium->DrawAllPre(); + bool postAll = post && m_Fractorium->DrawAllPost(); + unsigned int bestIndex = -1; + T bestDist = 10; + Ember* ember = m_FractoriumEmberController->CurrentEmber(); + + m_HoverType = HoverNone; + + //If there's a selected/current xform, check it first so it gets precedence over the others. + if (m_SelectedXform != NULL) + { + //These checks prevent highlighting the pre/post selected xform circle, when one is set to show all, and the other + //is set to show current, and the user hovers over another xform, but doesn't select it, then moves the mouse + //back over the hidden circle for the pre/post that was set to only show current. + bool checkSelPre = preAll || (pre && m_HoverXform == m_SelectedXform); + bool checkSelPost = postAll || (post && m_HoverXform == m_SelectedXform); + + if (CheckXformHover(m_SelectedXform, glCoords, bestDist, checkSelPre, checkSelPost)) + { + m_HoverXform = m_SelectedXform; + bestIndex = ember->GetTotalXformIndex(m_SelectedXform); + } + } + + //Check all xforms. + for (unsigned int i = 0; i < ember->TotalXformCount(); i++) + { + Xform* xform = ember->GetTotalXform(i); + + if (preAll || (pre && m_HoverXform == xform))//Only check pre affine if they are shown. + { + if (CheckXformHover(xform, glCoords, bestDist, true, false)) + { + m_HoverXform = xform; + bestIndex = i; + } + } + + if (postAll || (post && m_HoverXform == xform))//Only check post affine if they are shown. + { + if (CheckXformHover(xform, glCoords, bestDist, false, true)) + { + m_HoverXform = xform; + bestIndex = i; + } + } + } + + return bestIndex; +} + +/// +/// Determine the passed in xform's pre/post affine transforms are being hovered over. +/// Meant to be called in succession when checking all xforms for hover, and the best +/// hover distance is recorded in the bestDist reference parameter. +/// Mouse coordinates will be converted internally to world cartesian coordinates for checking. +/// +/// A pointer to the xform to check for hover +/// The mouse raster coordinates to check +/// Reference to hold the best distance found so far +/// True to check pre affine, else don't. +/// True to check post affine, else don't. +/// True if hovering and the distance is smaller than the bestDist parameter +template +bool GLEmberController::CheckXformHover(Xform* xform, v3T& glCoords, T& bestDist, bool pre, bool post) +{ + bool preFound = false, postFound = false; + float dist = 0.0f; + v3T pos; + Ember* ember = m_FractoriumEmberController->CurrentEmber(); + + if (pre) + { + v3T translation(xform->m_Affine.C()/* - ember->m_CenterX*/, /*ember->m_CenterY + */xform->m_Affine.F(), 0); + v3T transScreen = glm::project(translation, m_Modelview, m_Projection, m_Viewport); + v3T xAxis(xform->m_Affine.A(), xform->m_Affine.D(), 0); + v3T xAxisScreen = glm::project(translation + xAxis, m_Modelview, m_Projection, m_Viewport); + v3T yAxis(xform->m_Affine.B(), xform->m_Affine.E(), 0); + v3T yAxisScreen = glm::project(translation + yAxis, m_Modelview, m_Projection, m_Viewport); + + pos = translation; + dist = glm::distance(glCoords, transScreen); + + if (dist < bestDist) + { + bestDist = dist; + m_HoverType = HoverTranslation; + m_HoverHandlePos = pos; + preFound = true; + } + + pos = translation + xAxis; + dist = glm::distance(glCoords, xAxisScreen); + + if (dist < bestDist) + { + bestDist = dist; + m_HoverType = HoverXAxis; + m_HoverHandlePos = pos; + preFound = true; + } + + pos = translation + yAxis; + dist = glm::distance(glCoords, yAxisScreen); + + if (dist < bestDist) + { + bestDist = dist; + m_HoverType = HoverYAxis; + m_HoverHandlePos = pos; + preFound = true; + } + + if (preFound) + m_AffineType = AffinePre; + } + + if (post) + { + v3T translation(xform->m_Post.C()/* - ember->m_CenterX*/, /*ember->m_CenterY + */xform->m_Post.F(), 0); + v3T transScreen = glm::project(translation, m_Modelview, m_Projection, m_Viewport); + v3T xAxis(xform->m_Post.A(), xform->m_Post.D(), 0); + v3T xAxisScreen = glm::project(translation + xAxis, m_Modelview, m_Projection, m_Viewport); + v3T yAxis(xform->m_Post.B(), xform->m_Post.E(), 0); + v3T yAxisScreen = glm::project(translation + yAxis, m_Modelview, m_Projection, m_Viewport); + + pos = translation; + dist = glm::distance(glCoords, transScreen); + + if (dist < bestDist) + { + bestDist = dist; + m_HoverType = HoverTranslation; + m_HoverHandlePos = pos; + postFound = true; + } + + pos = translation + xAxis; + dist = glm::distance(glCoords, xAxisScreen); + + if (dist < bestDist) + { + bestDist = dist; + m_HoverType = HoverXAxis; + m_HoverHandlePos = pos; + postFound = true; + } + + pos = translation + yAxis; + dist = glm::distance(glCoords, yAxisScreen); + + if (dist < bestDist) + { + bestDist = dist; + m_HoverType = HoverYAxis; + m_HoverHandlePos = pos; + postFound = true; + } + + if (postFound) + m_AffineType = AffinePost; + } + + return preFound || postFound; +} + +/// +/// Calculate the new affine transform when dragging with the x axis with the left mouse button. +/// The value returned will depend on whether any modifier keys were held down. +/// None: Rotate and scale only. +/// Local Pivot: +/// Shift: Rotate only about affine center. +/// Alt: Free transform. +/// Shift + Alt: Rotate single axis about affine center. +/// Control: Rotate and scale, snapping to grid. +/// Control + Shift: Rotate only, snapping to grid. +/// Control + Alt: Free transform, snapping to grid. +/// Control + Shift + Alt: Rotate single axis about affine center, snapping to grid. +/// World Pivot: +/// Shift + Alt: Rotate single axis about world center. +/// Control + Shift + Alt: Rotate single axis about world center, snapping to grid. +/// All others are the same as local pivot. +/// +/// The new affine transform to be assigned to the selected xform +template +Affine2D GLEmberController::CalcDragXAxis() +{ + v3T t3, newAxis, newPos; + Affine2D result = m_DragSrcTransform; + bool worldPivotShiftAlt = !m_Fractorium->LocalPivot() && + ((m_DragModifier & DragModShift) == DragModShift) && + ((m_DragModifier & DragModAlt) == DragModAlt); + + if (worldPivotShiftAlt) + t3 = v3T(0, 0, 0); + else + t3 = v3T(m_DragSrcTransform.O(), 0); + + if ((m_DragModifier & DragModShift) == DragModShift) + { + v3T targetAxis = m_MouseWorldPos - t3; + v3T norm = glm::normalize(targetAxis); + + if ((m_DragModifier & DragModControl) == DragModControl) + norm = SnapToNormalizedAngle(norm, 24); + + if (worldPivotShiftAlt) + newAxis = norm * glm::length(m_DragSrcTransform.O() + m_DragSrcTransform.X()); + else + newAxis = norm * glm::length(m_DragSrcTransform.X()); + } + else + { + if ((m_DragModifier & DragModControl) == DragModControl) + newPos = SnapToGrid(m_MouseWorldPos); + else + newPos = m_MouseWorldPos + m_DragHandleOffset; + + newAxis = newPos - t3; + } + + if ((m_DragModifier & DragModAlt) == DragModAlt) + { + if (worldPivotShiftAlt) + result.X(v2T(newAxis) - m_DragSrcTransform.O()); + else + result.X(v2T(newAxis)); + } + else + { + result.RotateScaleXTo(v2T(newAxis)); + } + + m_DragHandlePos = v3T(result.O() + result.X(), 0); + + return result; +} + +/// +/// Calculate the new affine transform when dragging with the y axis with the left mouse button. +/// The value returned will depend on whether any modifier keys were held down. +/// None: Rotate and scale only. +/// Local Pivot: +/// Shift: Rotate only about affine center. +/// Alt: Free transform. +/// Shift + Alt: Rotate single axis about affine center. +/// Control: Rotate and scale, snapping to grid. +/// Control + Shift: Rotate only, snapping to grid. +/// Control + Alt: Free transform, snapping to grid. +/// Control + Shift + Alt: Rotate single axis about affine center, snapping to grid. +/// World Pivot: +/// Shift + Alt: Rotate single axis about world center. +/// Control + Shift + Alt: Rotate single axis about world center, snapping to grid. +/// All others are the same as local pivot. +/// +/// The new affine transform to be assigned to the selected xform +template +Affine2D GLEmberController::CalcDragYAxis() +{ + v3T t3, newAxis, newPos; + Affine2D result = m_DragSrcTransform; + bool worldPivotShiftAlt = !m_Fractorium->LocalPivot() && + ((m_DragModifier & DragModShift) == DragModShift) && + ((m_DragModifier & DragModAlt) == DragModAlt); + + if (worldPivotShiftAlt) + t3 = v3T(0, 0, 0); + else + t3 = v3T(m_DragSrcTransform.O(), 0); + + if ((m_DragModifier & DragModShift) == DragModShift) + { + v3T targetAxis = m_MouseWorldPos - t3; + v3T norm = glm::normalize(targetAxis); + + if ((m_DragModifier & DragModControl) == DragModControl) + norm = SnapToNormalizedAngle(norm, 24); + + if (worldPivotShiftAlt) + newAxis = norm * glm::length(m_DragSrcTransform.O() + m_DragSrcTransform.Y()); + else + newAxis = norm * glm::length(m_DragSrcTransform.Y()); + } + else + { + if ((m_DragModifier & DragModControl) == DragModControl) + newPos = SnapToGrid(m_MouseWorldPos); + else + newPos = m_MouseWorldPos + m_DragHandleOffset; + + newAxis = newPos - t3; + } + + if ((m_DragModifier & DragModAlt) == DragModAlt) + { + if (worldPivotShiftAlt) + result.Y(v2T(newAxis) - m_DragSrcTransform.O()); + else + result.Y(v2T(newAxis)); + } + else + { + result.RotateScaleYTo(v2T(newAxis)); + } + + m_DragHandlePos = v3T(result.O() + result.Y(), 0); + + return result; +} + +/// +/// Calculate the new affine transform when dragging the center with the left mouse button. +/// The value returned will depend on whether any modifier keys were held down. +/// None: Free transform. +/// Local Pivot: +/// Shift: Rotate about world center, keeping orientation the same. +/// Control: Free transform, snapping to grid. +/// Control + Shift: Rotate about world center, keeping orientation the same, snapping to grid. +/// World Pivot: +/// Shift: Rotate about world center, rotating orientation. +/// Control + Shift: Rotate about world center, rotating orientation, snapping to grid. +/// All others are the same as local pivot. +/// +/// The new affine transform to be assigned to the selected xform +template +Affine2D GLEmberController::CalcDragTranslation() +{ + v3T newPos, newX, newY; + Affine2D result = m_DragSrcTransform; + bool worldPivotShift = !m_Fractorium->LocalPivot() && ((m_DragModifier & DragModShift) == DragModShift); + + if ((m_DragModifier & DragModShift) == DragModShift) + { + v3T norm = glm::normalize(m_MouseWorldPos); + + if ((m_DragModifier & DragModControl) == DragModControl) + norm = SnapToNormalizedAngle(norm, 12); + + newPos = glm::length(m_DragSrcTransform.O()) * norm; + + if (worldPivotShift) + { + T startAngle = atan2(m_DragSrcTransform.O().y, m_DragSrcTransform.O().x); + T endAngle = atan2(newPos.y, newPos.x); + T angle = startAngle - endAngle; + + result.Rotate(angle * RAD_2_DEG); + } + } + else + { + if ((m_DragModifier & DragModControl) == DragModControl) + newPos = SnapToGrid(m_MouseWorldPos); + else + newPos = m_MouseWorldPos + m_DragHandleOffset; + } + + result.O(v2T(newPos)); + m_DragHandlePos = newPos; + + return result; +} + +/// +/// Thin wrapper to check if all controllers are ok and return a pointer to the GLController. +/// +/// A pointer to the GLController if everything is ok, else false. +GLEmberControllerBase* GLWidget::GLController() +{ + if (m_Fractorium && m_Fractorium->ControllersOk()) + return m_Fractorium->m_Controller->GLController(); + + return NULL; +} diff --git a/Source/Fractorium/GLWidget.h b/Source/Fractorium/GLWidget.h new file mode 100644 index 0000000..a48a694 --- /dev/null +++ b/Source/Fractorium/GLWidget.h @@ -0,0 +1,76 @@ +#pragma once + +#include "FractoriumEmberController.h" + +/// +/// GLWidget class. +/// + +class Fractorium;//Forward declaration since Fractorium uses this widget. + +static const float GridStep = 1.0f / 8.0f; + +/// +/// The main drawing area. +/// This uses the Qt wrapper around OpenGL to draw the output of the render to a texture whose +/// size matches the size of the window. +/// On top of that, the circles that represent the pre and post affine transforms for each xform are drawn. +/// Based on values specified on the GUI, it will either draw the presently selected xform, or all of them. +/// It can show/hide pre/post affine as well. +/// The currently selected xform is drawn with a circle around it, with all others only showing their axes. +/// The current xform is set by either clicking on it, or by changing the index of the xforms combo box on the main window. +/// A problem here is that all drawing is done using the legacy OpenGL fixed function pipeline which is deprecated +/// and even completely disabled on Mac OS. This will need to be replaced with shader programs for every draw operation. +/// Since this window has to know about various states of the renderer and the main window, it retains pointers to +/// the main window and several of its members. +/// This class uses a controller-based design similar to the main window. +/// +class GLWidget : public QGLWidget, protected QOpenGLFunctions_2_0//QOpenGLFunctions_3_2_Compatibility//QOpenGLFunctions_3_2_Core//, protected QOpenGLFunctions +{ + Q_OBJECT + + friend Fractorium; + friend FractoriumEmberController; + friend FractoriumEmberController; + friend GLEmberControllerBase; + friend GLEmberController; + friend GLEmberController; + +public: + GLWidget(QWidget* parent); + ~GLWidget(); + void DrawQuad(); + void SetMainWindow(Fractorium* f); + bool Init(); + bool Drawing(); + GLuint OutputTexID(); + +protected: + virtual void initializeGL(); + virtual void paintGL(); + virtual void keyPressEvent(QKeyEvent* e); + virtual void keyReleaseEvent(QKeyEvent* e); + virtual void mousePressEvent(QMouseEvent* e); + virtual void mouseReleaseEvent(QMouseEvent* e); + virtual void mouseMoveEvent(QMouseEvent* e); + virtual void wheelEvent(QWheelEvent* e); + virtual void resizeEvent(QResizeEvent* e); + +private: + bool Allocate(bool force = false); + bool Deallocate(); + void SetViewport(); + void DrawGrid(); + void DrawUnitSquare(); + void DrawAffineHelper(bool selected, bool pre, bool final, bool background); + GLEmberControllerBase* GLController(); + + bool m_Init; + bool m_Drawing; + GLint m_TexWidth; + GLint m_TexHeight; + GLint m_ViewWidth; + GLint m_ViewHeight; + GLuint m_OutputTexID; + Fractorium* m_Fractorium; +}; diff --git a/Source/Fractorium/Icons/010425-3d-transparent-glass-icon-animals-spiderweb2.png b/Source/Fractorium/Icons/010425-3d-transparent-glass-icon-animals-spiderweb2.png new file mode 100644 index 0000000000000000000000000000000000000000..66f4af87113ad151b638d857a6692af9a70aa2a3 GIT binary patch literal 133174 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 CexGr30_}CTosx$uJA!& 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
0h9No44UBu(cFak*BHiQ<&k?Sn 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 T#%o1)00000NkvXXu0mjfDD0v| literal 0 HcmV?d00001 diff --git a/Source/Fractorium/Icons/Fractorium.ico b/Source/Fractorium/Icons/Fractorium.ico new file mode 100644 index 0000000000000000000000000000000000000000..b632d9debe1afb3dd0ce4a0fd6664ecb9792fbde GIT binary patch literal 60516 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 IM6N<$f?cuIFaQ7m literal 0 HcmV?d00001 diff --git a/Source/Fractorium/Icons/add.png b/Source/Fractorium/Icons/add.png new file mode 100644 index 0000000000000000000000000000000000000000..038e70bed1363ddf63d35569ab8e91949af2fe2e GIT binary patch literal 911 zcmV;A191F_P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGXbpQYZbpg<9qoDu*02*{fSaefwW^{L9 za%BKeVQFr3E>1;MAa*k@H7+qRNAp5A00091NklG>JB7h0o{9d;_o7i-Cawf&Urfn(iAbv_UH<Y_+}zyY z^77KW0cNhm&(6-w8DP=T0I&V(>I$c)r#Lz~5(8W--(L-F&T zFmK@D;Q`0T$H?dN*xTDPXF%1!{r$aUp3CL1ySr<)2D-Ys#K8XkzKCyaZNcSo{q6=h z#W+Zq1rMqO#ocbVJOQts;u{+qa5x-jZ*SLrqu|-ppq9>nY#JFE!SL`fhK7a^2n68w z`_)G>_1fCnBwHHT+1U~COeTZI#zxrfb~v3*baZr7gwMybei)#;(a}-7zrRb5KR-W9 zt5JyX$_f9o1NghWy^VA_jZ`XyVzG$N&rjLI*}&rDT09;{I2^|8>@3E{#>7Nvt^6?X z`ud9N>uWhg^*9gXduOv*$y!}IGhbg{$J*K&5{U#tp%7+fW-vWHjj5?AOioT>VqyZp zU{G$gCIig;;^G1)Cnq>KI1q7GI)$0>=H{jdGdqeidnG~|4HU&8`cUBYHe+m=b4|Mmn$dCoNML#tDy~A z!7O-{EKIf*-ZjpuiA_lGoWf97K>qg zd|Y~*FlM9%lz~Vj@^`=x;|5k&S4DVma8NL2fP;k7jDn?pF8gxZibW?9;ba!ELWdKlNX>N2bPDNB8 zb~7$DE-^7j^FlWO00CY}L_t(IPoB@&7JOs7-VaU9U2(FlXV0R4U+yPmCI#@$Z$B+ z0zt-<0qpmCY&ILLRx2!*ODq-(trv?$h6oRgV&iN!djTAeM;s0Z>~=eBw_B{&Ypv(= zd4>p2n+luYd_LoJI>B|_2Z+k$atsk()jJu$<#JII#AyhkGMNlRB)C&8giUb2-*LO$ zaJ^n1AS#tgF+^IemKNB^`+Wv@p7*c;QSo@3A;OnMg-zi5zS=-I5fzKY7$Wt0T?@}L zfG|a)QHK5tlWMi9FUztllu9KO3I$}dSv{z9I*nv9i9{lyV{_-Ahn^^apKtuO_N2bPDNB8 zb~7$DE-^7j^FlWO00j|AL_t(oN5z$SNaST0$9LV;vOQ+qtz^4(+uC9af+C7#t2KhC z4K`R3QbWZe+67V44on=;^;%KK@xJfd(a8%v|GTU!&WtE&R%5;#M4CMPFr)6&u LsXeqs955HQwId zoA<&m_bdpa5~em34t%FPR3s)QzJt4y;Gny4hXbs=0rg&iL9rXifCS|bSk+hY zj+1nRO1LghBTe(lL&8V#@$pO(yVqO#nZ022%XM~pd3m{4%F4lO_b6%~oX!a@ntpO=?+ z4Kine0mz_K6if$=F*WQ?HVD1sISuGKKzIjaGXgH6SFw_fXv~{*0<$kckKMo>cgjQH zE+BPkqhO?rgfOifJ-IsFjk-e_hb-T52{lqN0>=X}0{{<=rbb=d^#$JFE?cL+twp4PDf?gOigG00000NkvXXu0mjfN2bPDNB8 zb~7$DE-^7j^FlWO00l5fL_t(oN5z(FOqOLB$F-Cs%R^~6triVS4cc;1*37NehgyBf zR-*}HxUs>aF~s6p&@~Nazy)mI`N3W8#XD8t1>b&g05V-GB}q}GGIa1^!D}|vSeCYTRT!x zQUZ03&U@I#j2CYlG>Hjsz3QgrajJlmQGsXlrX5I)40kZhU zoH&tx>C&ZsGW53`AbJZ9m7UaO{g9i_8Bh?QaWA`g7E0gKqLAu_M1)D}3n2d^wLOpCJ zYzEB$Wy{`o=U4BN%aPFi`}gnToJk^A^p>KcqEgo6tj&TPSQ8l;xdY>qt})e&yFR7# z38jyI_)dpH0H}_SbhMBhR6;qqD?2+oQ-;Z?rKF_f4szWJf9YEXAQ+=JWbER_i`F2X z8>R2;*|Vmwu<$lIpTeu4d#y<~>u}K;Xi^4JvX5}!UTJBmj9a)7pRlHLmP=XVa~lSG z>g($bb+d*kgJ*-liJK=+p8P2x7dW)DPQny!!O%)5e7$GLWl8E2XGN zUMedq%XHH1QG6J5Y-x0-)6>)6VxOGn|8jt{+sT4)VSpte_c)+h1;ob2ekfz!ib}T(^L6_z zH8u5n^uB;S@CvMlIZHqjhA<)j$j!|)B0oRhFsQ;=^OQi>M$HmRn*StSHr?unnwpxM zM4ZFgm+(G>fIN47Dk^?f6JlJ3^N`7~ z#;a?19oQKFb6&%kn3(M}>n&!15psaw>RHoy|6W69gK8Cs(XTTzGt+t2IF{kpLA91) zd+lIW!!GD3`t=yA!~x?%^nWELCMx~YgUH#SbgB$NAn$a==55#oT{ZS5`Y1(FA3b{1 z)}Qwp0MMd*eca z5jCQkTQgr@P(^Ob>a90p(?WtFWkF7;A=TvSi$~_hjF+Xrr*QEw%l-l4cb;Deh{b9E O0000%e#b literal 0 HcmV?d00001 diff --git a/Source/Fractorium/Icons/arrow_down.png b/Source/Fractorium/Icons/arrow_down.png new file mode 100644 index 0000000000000000000000000000000000000000..3851ad71051bdf46a6f0e316b0a95c16280fb759 GIT binary patch literal 460 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Ea{HEjtmUzPnffIy#(?(3p^r= z85sBugD~Uq{1qucL8%hgh?3y^w370~qEv=}#LT=BJwMkFg)(D3Q$0gN_s>q|3=E7O zo-U3d7XC{w*>^V;$Q=K;OzyI6vxjCzNx^1KrllDivNAG1eZFIT1jC2*5lfzZwoKbBS#$q*g^k#e_Yq5$UDo_$ zbNp%1!2$~r5MOOF=O&%QKWp6f-xmk*-4+Y_Ef?-Pu3WWuoU?{ z%bV}!wZARn_Pq?^-+udTcIK=bIc5>-uLp#Net6wi#rSb=;pUrrc-ffOglX$c_r7rZ zO4iosr9qzazw@(u)#PuCTOa?DPptds`S&>zY$=idw5EC;eg60G!}6|0H5cB#-o@uR v$!;g_(MNl;w$7S<`f2&Qo3>*A<+$#bHa?mm#!`_640#4mS3j3^P6pF8gxZibW?9;ba!ELWdKlNX>N2bPDNB8 zb~7$DE-^7j^FlWO00EXsL_t(IPn}a)s=`1J%q?bohI+kjRIAl1-)X*{&u5sXiRpCu zoJ=PAXf#qWyQ$S`2}iTifSN*~U}~zeaXOuFzu&=iHk)BE7>M{C&1MtI44PSxYc7|2 zNKjQa4u=CSmkVyU8~XjeqU{N2W`Xs3jq!MlY&QE$r_=gNFdPoWOmsROMcETnDis`$ zN9^}|6pKaBO#Wr&wtKyvnA`2PqU{N2%IEW}Of!vp@M zak*Ru2gJ07CzVP`)_6Rw7JCA}-w&V9r+Yk}Cy$s&B;a&9uM$*Sx?1LMaJ${Qs{C*` c93}wm4?lj+=#(){g8%>k07*qoM6N<$f`@A83IG5A literal 0 HcmV?d00001 diff --git a/Source/Fractorium/Icons/arrow_left.png b/Source/Fractorium/Icons/arrow_left.png new file mode 100644 index 0000000000000000000000000000000000000000..4bbd4331febca321edb2b4754254c83c4c93d70f GIT binary patch literal 398 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Ea{HEjtmUzPnffIy#(?(3p^r= z85sBugD~Uq{1qucL8%hgh?3y^w370~qEv=}#LT=BJwMkFg)(D3Q$0gN_s>q|3=E81 zo-U3d7XC~7_hz*?h_r6Mm41pjA|mqU&B&-*K+F+#?UDW>?SBf3Iu<2N)>^_-IYHNd zonnfpn&G5J%{%3b&)wO{@I&0El5Lp{bMU-G^B?=_Re!CuPBIVpakp^J?`yIjYZrXT z4iwpOH*Z6(^t9B220Slz@%dg}uu4nBwNdBv1QpIj8bNPXHyk=&IWIX`1n4cFo>nH)9om1tv(y}mG0DkQXZYsFoyzT}of zj`N=#z90PjXK_O7wYtLC@1I#Oo7Bft*H-ct%WT(=SZKfh;{5j_b+>-qzricAFKEZz lUCJ{)@jxB^Pd3JmVRGd&hdi|%{J^kd@O1TaS?83{1OT1EpJM<3 literal 0 HcmV?d00001 diff --git a/Source/Fractorium/Icons/arrow_out.png b/Source/Fractorium/Icons/arrow_out.png new file mode 100644 index 0000000000000000000000000000000000000000..059f83bf3e2cc837d3ac8b05607f4b1e770c1925 GIT binary patch literal 531 zcmV+u0_^>XP)pF8gxZibW?9;ba!ELWdKlNX>N2bPDNB8 zb~7$DE-^7j^FlWO00EClL_t(IPo0y=io#G3Mc-fKBc0J8B8c;>QN&qsS~!3YhmCGT z$?s&7FSu5{Cf%*Z20L&GsnoeuN!1gJ#R9DPe2&>{hRI|Cx`dlrtrnWiCJe(styV*& zQh_osO%u1@9=%5{mqR9#f#9~sDi!K ztsW@{yYFN!B6BG{6wz7PxSCWe_EEy z<(COKipS%}SS)5mqfyO?iAJMA_b-^|+wFFFOfQol_(bNQJm|V^hr?kl6beDq>va`m zc)eb$J1vOyY(SMIuP%*StuUD&5C}lz_-M;_;002ovPDHLkV1jG#q|3=E6{ zo-U3d7XC{oZp>;9kZ3jjI`bC;pWn!lg9*g``WYH+1QwyAgu95*~<(6TJ^#{2L3*$0b0TlBs7Eth+H(Yov@shu|GCoa=G z_uP1qhLZrxhg$irMGdJpGj)ID=6*`!Xkzf3lrT#zB{J^Ndha~l3YNad^G@Cp7pmL; v&D{CB!o#k_8T;#%c~{+G0=ZtEeYb!6%f|vow(M~Mh9HBdtDnm{r-UW|Odz20 literal 0 HcmV?d00001 diff --git a/Source/Fractorium/Icons/arrow_rotate_anticlockwise.png b/Source/Fractorium/Icons/arrow_rotate_anticlockwise.png new file mode 100644 index 0000000000000000000000000000000000000000..6c75b04e582e2651bcd6372ed41f2d687ba34243 GIT binary patch literal 584 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 literal 0 HcmV?d00001 diff --git a/Source/Fractorium/Icons/banner.bmp b/Source/Fractorium/Icons/banner.bmp new file mode 100644 index 0000000000000000000000000000000000000000..c8919eec933e034595bedafe560cd58761e471c1 GIT binary patch literal 85894 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 literal 0 HcmV?d00001 diff --git a/Source/Fractorium/Icons/cog.png b/Source/Fractorium/Icons/cog.png new file mode 100644 index 0000000000000000000000000000000000000000..67de2c6ccbeac17742f56cf7391e72b2bf5033ba GIT binary patch literal 512 zcmV+b0{{JqP)CQDsH?WF>AIFt zQuJ}i;w2$ZUU#3SZ6RY0Gw;kZ&ol1~2ky^QZ(fom$=jNJZt!z7w_pH~wdQ;R)Gh%BbQFCx+Nm!4SuS-vkr`vhhrX zM*>w%e+v~?m@q~ImPAgtLkR_3U<2F8LP3W5=LJ*ZN|S5p#sf4YFr$p~Q~Z*0Ngxf2 zjk#J#<7EAlhzlrV53~GF&pIzcCN_lz9@05UeoUXiK%N z#x+4o*i_c|6_Uu1+&TIho?3@y4k-#b8Y_o94zW*B3a1ne2-Y5s0uke$$|@=}OP-i= zNYZQA=>PrZu0MfSL=b8UhD_={W4IY1{b{)U)*gc45xtL%IYLY&hF;d`@GzI&7H&D# zh;z_BX$#hqh@q?AY3sJTod2%*Yd)_>YM0#q&ixGuh+PQsneK)F0000D)XO`;E7-c$%> zEyAGhrp|0diMaeo(UM8ooU@psW=nH(UpA&WXQtC~rb*6esAXk^Qbr8-^lo5Oka=bo z59b{I=lq`kdH>J*vH-B$RcUGIVx!S`WoT$V!$z-F#tHO=*HptA(+|c{7*D*dmj*p){qQ1WVoxi_-9*e~~N?JW{ zgPfe4B%5sj!z05O86AOAseDG@O{8U{1@ktbQfRunyV2kO3jNks=;?X!H99(a)y#VK zjqCuo4Wgo=ywqy-WLsMs1_rF??d?IA^($f};#gUbriHeE}h{a-OdF&#Q=-sWto3BrvI$7c2;jx?GD;TGU zQX}Z>>~vhZdT@^#gm#qwg@$yHTVSIr$}2redL-@d&l zFE4kbq@*Ybww|=mRqJ1}&&Z&rrY7HHYDUrpKK4X&P*PGtb|o^9k6wYX>NvC@Lzl5O||=*K_&%zb+8CvQAZpM1DLXwnrd6JsoroHg9k5&41RJ zkwIf);{ub(RQKS49Px?g5FQZ*KA#V{Tt4RK=eN^Yb0*3A?Li`uc$v*+1I>AyOE`n@ zZDHVYxlkw+pMrvd4ins)Y2W<3p-eBLjJHZ8MFc;Ku<%fDI2>rT+OLt3k>?4%)Sa%M znJr8D`ue&=1tJ$bG;AwELPDU^>AuIr#9SlzN_VF&tFW+e1-1SQ+C8{O4r9yKU<3vR zI(Kf5i;EKze2u$sm%-rRpn;;-nw6E6(bCc~5gZ&$6S3C8Dhm-S)bK_V)>rZqo;Bl((Cm@xw*N{E)Dn}aF+qi_WjiQ mZ%GqrX=yD4W;1D|f9VGpO9jynHk=Xw0000ggd9Fr!P01j^ki78X>%;qup-fXS)f%-%tGq z(QG!~)0Jj#*bDPWM5Q%K#^Ovts{aerpIKS>gkU-=>9+rw6#8)l` zq;I0p=!<+l-z^r4ST2{KJq+V5&S^a`hv*Kv7?GV!&+x}uTm^A&Zx5+dYVC+bA~-%i zrX)L1HErFyBiJ`%z!PjrI-PbW!FkXfjyEPiNmhD&3YYS|TCKiICX)vo#Mx}NN@Nj{ zyciOQeFD+A2BOR_uaLi6E|(8BnfQw;Fy~XqiU^-yB>;8Y&*vUIdva*d^crr0R;z_j zD8#{I{58`w*DrTzl;&~@Me`uU0uW^eO;bRR*rF(6JRU=CG#ZG<<4_buzWKjl|1gmr zka(5E9~T3a$f3t#F-?|bmnFMik%4b36sWD zRn0KQwr!h6#h6Pnt|a^eUNTw6*L2>a3O>+z=dS<*04<+5u=ps%>i_@%07*qoM6N<$ Eg6L{NQ~&?~ literal 0 HcmV?d00001 diff --git a/Source/Fractorium/Icons/database-medium.png b/Source/Fractorium/Icons/database-medium.png new file mode 100644 index 0000000000000000000000000000000000000000..bbe307511f8f1ad5059a3570173280b1b6ba1fb9 GIT binary patch literal 1406 zcmaJ>drT8|96ujChM18pE-;Nd4-Nc5dyl@)R=Dd?C{ofDmNIA1qdn+Ru2=7_w2*9J z5M6W&3^fz71v3#6Wg}#6y2Z>ntOUj~&^y} z{M|ukNx}M*S5p80SZ^<+T=JNvyvd33_qE5*XnDwx^2?=CzDf$y0s|0UzLEj$99_-0 z7}{IEZ=gKw=fiZ~W zw?NOGDt9g5?`kjFUJ9uOru;9=riR^b|L7)7AQ6}h-o zU}%XK%6Psu-V9D3FY%&}4}#WG4d`YAUOp@;b&|bO{A2{Sj`7+Ao&#ek!`WrlLm}BT zVMT3NzEPvWv;<{CQGy_iR-ML#P(~f8BQ?+>(Yq|<7}08?G+L8huO&2Aib7EmF%l$6 zT5UGefT21)1j}x~N`EZtm-v?4gvxZO`{l*b{b2%fx&`@mwAwp%0>G+XJ4KX5#!ns@ zDH(949e8#&sraqWRnf0@G|#0BwoeWwsBg62ZHh+SdoTObu5BHR4h^`GudDX&v!2M? z+1UeZ8&|X>P+@4tv_wE7vb%;w6*;?>&|kQzDx`jUPMy+kFMq3 zR?!8!fAg5XfudxgY4?wlnVXBcuX5^RBVyK{bsM{`B*TZArax-wt$Ey1Gjr>O`$tEn z7Cv})YM{=XeL8hwtS#3(em=YNrgP$-(e(LE*E?Ty>}hUKM4j&p=K;`+<)zgp@-n`! zOuX|bolckcwj^4<-M{NheSz*t_MyY8JEtFAI5(X>|2Hz>KDoh`s=dGY!R&_C(Z2lG zi?O>Bek0*y&%^P8Yv(@wY$OUE=sATrjwNBlkA_3nbVBhyVZp literal 0 HcmV?d00001 diff --git a/Source/Fractorium/Icons/databases.png b/Source/Fractorium/Icons/databases.png new file mode 100644 index 0000000000000000000000000000000000000000..11dcab4b1b3a294a7382643597f0987da644529c GIT binary patch literal 661 zcmV;G0&4wL{0N#3O-*|Id9 zhhB8QJa`gak)0F|Whm2wP&|0?;Gy6_Ja`eLP$=xLC_4+{#Zh(_5!uc{q0-Ch?2j}} zvZkg<yN0suBEn0D1S1cBbIor0;>-ErV zUchmDD9SQ)J&r^ofo%5qYe5jeu9EkCABJJTG)=UO3)mzOkH^851gNVk<9%!qax50x zDV0hX5xA}gPLQC*R}pdnLIl$SS0Qh0T_Wf6Zy|6!oS$j<^s#bGZ6{GLKZ{(e)$seg zh8ktlXuz^9^nDNO+qbZLKaJf7X}o#&evevGlOxPUIQ#}<_OdTtedc-o z$6^mS5NK%)nX(7*6;U0I14?H$%6>_C&yu9a{~!Y$1>Ff#zz8D zM9jL~ZZ!AZDG%BfT22R|%!bHox{jw0AD61t>M5NXMkj}1_?Px>GAcq8MK literal 0 HcmV?d00001 diff --git a/Source/Fractorium/Icons/del.png b/Source/Fractorium/Icons/del.png new file mode 100644 index 0000000000000000000000000000000000000000..58e2c99a3fea99137afa5f931a87cf4398f30c70 GIT binary patch literal 643 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}Ea{HEjtmSN z`?>!lvI6;>1s;*b3=GVrAk26nXYoRypj3%#L`iUdT1k0gQ7S_~VrE{6o}X)oLYc9i zsh**s`)4O}ps}AkT^vIq+>cH=*`?$t;`;jj9D%}l?jlF)g=09l&T05%SSoNRbtrdr zX?Qg@&5r)cG)2#7);r(5Q8FfeDwX*z`^*{kcZC!wO`YSRsvdN*6AN>_{l^NM@2p81 zZ}dA!PFbqqFneJD$Hfehi5@Hu3nJDuA6#(Y{rAJKzaD!0@xa@%!!Jt?JuEnoxBYO| z)-{|QnwpviReK*5?R-#U_qfFBVTH}30*i$G z_mj8ZPTYMrY4gp5y>ZD~qY^hpB$&DNP?MdM;+PJS^a7KB&OA zT*G1c5?(C_{<7V%TcUJLq*Ch$pSQHs zY__Y_TsN($PDV3blqNRBtrs_6rSahN&m&Xszb_ZOmt(e{)!te2jsL2vT2^!U?%lud znyB?r$Km%(pJ&mGbN$rqmM>zcjn4X3VRPaA_ltSkMVBw1Zr43w`J(274144Jrz~Ll zzl(8gnD#S|zy`K%wywOUw%%)c(_forn9cU(k>-`+o$R)_(QE0i{3}^o8``GcTdVQG s^y<#^8~)(H{Sb{a9ze14sd2l%;o}(>&J`c|2uvdkp00i_>zopr0NT|L;{X5v literal 0 HcmV?d00001 diff --git a/Source/Fractorium/Icons/dialog.bmp b/Source/Fractorium/Icons/dialog.bmp new file mode 100644 index 0000000000000000000000000000000000000000..a388f20fe0e201e08e76d93516513679e06fc0a0 GIT binary patch literal 461814 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 nQxZ>V!>L^ap1 literal 0 HcmV?d00001 diff --git a/Source/Fractorium/Icons/document-hf-insert.png b/Source/Fractorium/Icons/document-hf-insert.png new file mode 100644 index 0000000000000000000000000000000000000000..dc98195710b4170e47b9d3c815d64794c591db74 GIT binary patch literal 498 zcmVPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02*{fSaefwW^{L9 za%BKeVQFr3E>1;MAa*k@H7+qRNAp5A0004DNklpb0=G44Z}T@VD!w(W;3%jk4E(cy5Q-EK#l&4$+NHJ3P!sqQ{=Kp2MP`~L8JJ`>iW z0I@8~-~i9_qB@kODdD(SEdC6x0hrI{FC3ujI+><9#4BJe3IM|}UO7NjRW_T=9`ih> z(@D2f`O?wTB+X&Pbyzz{_U28%8SOs7-ittg6S4k83! z2VD+O6a{er;3-51J|nsuAj>l1mSsto%SDLb9J(AZnM{y(0C)-!f`0{F4j7F_h*wor z1Beg+=yHH0NytaNrvDK9D{c1%33rW4bbkS2Kl-of&Lmn{yZ`_I07*qoM6N<$f<%SGO8@`> literal 0 HcmV?d00001 diff --git a/Source/Fractorium/Icons/drive-harddisk-5.png b/Source/Fractorium/Icons/drive-harddisk-5.png new file mode 100644 index 0000000000000000000000000000000000000000..23bde5d91c83fb4b80e34a1cc50ffcc8b5ab289c GIT binary patch literal 1251 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UOiAAEE({E-dXoVTC1JA&la8rYI?Dw7PUYt|)(A zuyM1z+SF6BNqyIL8}I)A|EFEs|0>Vg&zzdSy82m8YxTz+E1l2&Q+)k|^FMw(eNyi8 zzxU|&$2)Fl2i*$3IW^7QKd$C;YEj<&G;6Wkpq0RqguCA7bWwf9zde z)yyQ$v97rA`Kg1GP71IzA59Q&FtCua&|!1TEc}`Ff9|254j(4R?%%pKHGqeWSwqfs z!wrFde|i_K`Oa2ee%rK3S|Zgg~jPSd1`4;OB6`dDGZ#c^uW-5&|RU+qx((f6?T z>|MU7CesWZos~_R9D>rf_ug(_r?TU!)IHg+j?5O{W4t03?p-y(@%N?n?7vs%EO5*( z{=7GJI;#icx1|{k&kHwm9@6m}yiEWBXT$SgK zKI+I6TI@3;u`|>7*30{0+2>N8{z}+;Vqf~H7UL;whPt=5WL{Qr@~}vB5|a_UCKmQ> z7I)r7m%V;-ev3{G^z(53Dbo3+f<^p}m!XAWw^@;Fp!LJ@OU1k;aj)`Qll`tf;xTwU zL38;Mj%C*#yNW(xJoD_tCWptXH2xptGz`p;u{J-UW2o8X{-L2Ls5E(ECU^18>$hT7 zoS(O=_Ca6FrGP-5)g>QOGC1Ul>#o0NZ8YyJoczrsNT*MX?dar5SGqi#&uw`#`OldN zlWy26Pe0WiKVLRqzsfV~Z1B_~$Kt5ll@4Fm-@JJFdFSPq{qe4+`{RY5v(Eg!+bH7s z`H+xJm*>x~{dam{LWh{%7QKum+h#p3{%>CF?GP~emWu7aw-aWz#E5);d|$6V&*M-1 z_4~5~%Gb=i>^c>gCqznIBT5`gOEU6{7`zg*OOq6WOHwoQQj4Db{;m&{R|UzZB$lLF zB^RXvDF!10Ljzp{BV8kd5F=wNBQq-_BV7Y?D+2>x;nX)M8glbfGSe!d8Vq#}4MPkp ztW3?Vj7%XK(uG%D1Zpq>+2E6zoSIjh%8*e~QedU8pPHvvo|&ClkeZU2sFz=qt`Fkr z2b3n|WG2U_tR0fx#%(7GkT?LQCq7(&}%;M}bS7)*U Pl`wd^`njxgN@xNA=p7t^ literal 0 HcmV?d00001 diff --git a/Source/Fractorium/Icons/editraise.png b/Source/Fractorium/Icons/editraise.png new file mode 100644 index 0000000000000000000000000000000000000000..59b7ff2b9ed9cb45b607001c3c2ec0b65fa8f038 GIT binary patch literal 932 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 RuYsBwJYD@<);T3K0RRmm{dWKW literal 0 HcmV?d00001 diff --git a/Source/Fractorium/Icons/infomation.png b/Source/Fractorium/Icons/infomation.png new file mode 100644 index 0000000000000000000000000000000000000000..7418f19419da6003fd0b42e149f6937220b8c936 GIT binary patch literal 2056 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 U&kTcuS^xk507*qoM6N<$f;_kup8x;= literal 0 HcmV?d00001 diff --git a/Source/Fractorium/Icons/monitor.png b/Source/Fractorium/Icons/monitor.png new file mode 100644 index 0000000000000000000000000000000000000000..417a7c1e30c7bbfdef5a28d31cbbdaa6bbf3e010 GIT binary patch literal 647 zcmV;20(kw2P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02*{fSaefwW^{L9 za%BKeVQFr3E>1;MAa*k@H7+qRNAp5A0005@Nkl-Eml>4fL!XJj&&2dPwwzjiPfgx~MyS9+&>KA#6U-7YT@gTVmgkjqpm)z8^%hUs*Q#bSZ=dX4RNi^Ji7s8Iww-;n8RWA(W#%VJ*2#B9Sm* zh(sdFB_@*zR;v|14vk1T+7pJ*eb4cU;|s@Eu~?kp{&80bpF8gxZibW?9;ba!ELWdKlNX>N2bPDNB8 zb~7$DE-^7j^FlWO00H(%L_t(IPkoa~iyBcBg=?9F{7P}_JS?1Di>sik{DSOMP#fH2 zE7&yJpmv$(i1xw}CqhHroO4NSLYac&t)kvNbdF?)Uq@{M+ct!h*N+`E17H zv9WF242MJ0>-Eh2e%I%AyKNGQM8T_>qJe$AUJbA5bgE5*!N9E7Yn|wJyP0yiJn?F# z)PRfT^SO%GXf!gNPDfg;mNXg-Rkd2ZRw|WSsZ@Fcn;h7DRs%3lgVk!KqRC`3G0kRE z9LEt%p#mEL8vz>u8^MPgVDnWC7{N7IE|+FDn;Cu;Yy@lsu>@=cYy@od8Egb>zFFC9 zRwrQQ&1R#OMl^u2Ad!HLHi3=cy-tK4$z(E)g&&){;AOx?h@_$s*hs(Mmu|PKad2ML z!0dLrhX%M;(FANX5+@-GHAp6t18x@&&VIkw%EMHz*END`05$?PIth6YDnL3`EEao; z$K%i8aQIg!6naH{Wd$3ZfSECwRvIl93pL?8Y=Gl=I4DH>;4VBexLhuRkNnp_z~-R= z;&Kf56{o{L_P1v<6$k`=g2ft49Mm)Fnf34eB^V68r&6iYAJ-cw Us75pgegFUf07*qoM6N<$f<>bfNB{r; literal 0 HcmV?d00001 diff --git a/Source/Fractorium/Icons/page_paste.png b/Source/Fractorium/Icons/page_paste.png new file mode 100644 index 0000000000000000000000000000000000000000..5facd443557021c7543a6a10a50381cdbbd7b519 GIT binary patch literal 675 zcmV;U0$lxxP)pF8gxZibW?9;ba!ELWdKlNX>N2bPDNB8 zb~7$DE-^7j^FlWO00JLLL_t(IPj!>W${GO>$8!zjNpj3%0SGF0R)rJtvb%>|{WIMxz1YaF~+)en0qp zJ_4N2XBdq}FdmO7so8A8a=E0AE|&|)ScmN880wrS-p3P#hkdGs9fcbn*=f+NO zAGlPNN_7Y1^Z7@z$z&p-j~(F5tJR7uf@ZTBLGVANQh5htvstQYHk-j{G=jlkAOK$B zJ;3R70);|BfP5qpp|iwdF}eoc8Q;Y1cKa@1x7*IZi6ES*kA=fY)SFj}0O z6L!abe82yDub(9p3criR;-y3)xv`dEEra%-e-epAKc!OX@h{BU4p!mJeWCyW002ov JPDHLkV1jGEB^Cex literal 0 HcmV?d00001 diff --git a/Source/Fractorium/Icons/proxy.png b/Source/Fractorium/Icons/proxy.png new file mode 100644 index 0000000000000000000000000000000000000000..5cef803dde7ccf4f650f476956c27d2a8ebfb449 GIT binary patch literal 2491 zcmV;s2}JgZP)pF8gxZibW?9;ba!ELWdKlNX>N2bPDNB8 zb~7$DE-^7j^FlWO00}oqL_t(oN4-`HP?OmezPv$}!Yu28fQkz8W`;!+QBe^P2!ayj zWrV;+L?vj%2vJBRya^COAPE69$SXX;YYlI77I5osr)8=;h1GUv+FI9Hw^IeAg2>-< z6R@nS*6ECUW=?MY|DN-G=iYnn`47_8c0&ICp=enG10zvl;yJ`*veAhX40w(}c6MG% zv+pmCj^2nwic(=+xS+=J^9x&=MJOU79)4XA^6=Qd)zx*+$4*XO$kNgc+1PmL`1^-% z3Js0%^zk9vn3~$_^LTt@Y`jtJ`5v-*wF}D6=Oa^72R&b3@}`3aV?6!+58G_m;Gtt; zvKD!Il7KhB#KXhi8yz?hRZSv=Cs
6n?hYEh|4hYAY#W7XBNzZDe;ze!5EkO|Iq zfUDu^x=YRQ4)O9L;XwmdZ=0N)#wjiqevPpJ^Hy{;E!@b+QrpRCuhyYMF){xBQ>G1Ih-R$D>_I&X13B%+YGBUE8+uJ)Au~pfzvtrO z83g$y0M0o&{rr8zVzIvff<>`d`iM+E#^G>szU=AgC2rifK@1NMzfzg3Zsz!LdZxL# zt-%uCFT}xNr!H*!h1%Nssg=GM3uB%?fAMZm5V=GmseYhPv=fPm$rFe|Iet+ruKWpt zATlzz-^RzEzFA-2FyGhLNA&jgzS6F)Zi36ryW{O0fREyhLMBsf^Ye?p#DMyT5>2}%t{}O(ca!pq^EPAz?uL3>Q&hb z;9880rQI?&Uv~ieY5Mtv`d+@A^QXqfMuNvHo{5c3{Bu!J=~Q!bGtt)8rc#AML5RhY zIWjql13r3Z&YXkuj?*QPV)cYVA)-?0aOWAc%F60z*ml_uWAJz-vlI&LK|w*$Eapf{ zOaF(nv)gIFF-Ggw*~9&}S#|6fjm75+p2%b}g2}x2fJRH2kVvF2nwpvj?9|ZENaW}9 zrhR+@&f3_xn$T#Bt!#EK8?v1PS9kmF-DHE9m=htz#rz)|8X8nS*jFN{T%b}<{FT8- z{SjkBF#iq+2)UxKZ@2*hP~WPgF5spPhKwvM@(>Xsy zL>wFO^(8Tx%=Dt1oPr6wBUrPt@+R@Sqep3xG}@_VHk9+cD|;j=9M#m&%@UJ3rOejb7f^EArJ_N;^JbJ;{W(vRaG^CZHkKc{}c)< zo-g}jEO=g^P~ut*3_f~iP1k^IrNg#9!%nZOnp^@TA|Ne;Ynd|xJ2DOb7e%59)!Z`S zO1*OB3LJ%KJ}xeS2ONfKswFvjG7V*Cmmr}~3YCnko1M)|gO$u;C#=iF%L>6Q=H(SW zNlZ+-wP%lC&i?(usSF0Qr?9Z-F`ip#yG)K15_+CaPbssuwlfk4#883r;pQYEq)>FB zBS#Xova*V8`FznCs8WA`k3Enj813#*2iamwuQyo-4OpLpoaGLE)ItdMbP2) z^g$h%dXq2cqEINFC@d^ZVYBn>;IprdxlwLzsiR0#&5+6J#~K^u({j07b$wn}{2r3* zuD!i8E*>9)YU=43AYEO3G&(vCk4Vqk+s_Ru+{n8yfMe$B)w-`>$5Sp}E^t9osjRBC zRq+&;VqCGX;!=qfzf+Iu_a!BQueNO2Zfj;{feC*mn>N{_v^1^>fOq05`Ys&&44l(^ zenls$ub1~JmC8j>_+a!9cx-Bt&q5P&Umz&I2Zpowol@BY=PJ7s85#T8*|VujwEqlqpvUK|)0gwm;a z4%clmCMLeh)z$5Yx3^DlN=j-edoZm?4BFs zu;RNE3RU9Z;CxU+LvtVRsjTJ@3XxpQd=ot3Qp9bH0~h;>Is#*wC`CIIWBy?g!8zI{QaU@RIQzVRFgUc#|;B_*X;kB^U|+S(RK zWWk!Ywk|o$Ees3{y?`p%V`;h04X`w|wRKcj7)Kq5w#m+&yFE{zKAV}4kaPvDSz~1e z6Q6==+t@f@qc=)I0)0z;ebd;;$S5&5I6PBPAVE35Sg2M%JidbQblID#(Z=H{V_@@-;nZjCl?cGiW4C9b<$`Dkox48HZ( zC%N2${X${&Zl$vS&e+&3$f5SBsHkK41_p*}@D+WV;9c?X@IqWJE{yLf(9uGr3^au# z6b%i%I*Q?@rq+H~Ic)vl3l=LwX<=cd+IqFu2N3AvJb?HXZZxl7S1w>39UX%zDfw>K zu02P=L|<+BfyCI@R0obC_WJdnA8@aSRiWs38W~B+)zZ>2Uo!h}5*QeYpeb1mNhpR+ zcMk6WG%LN3T)0{Na9jVnK;yBcrlxibD%~V({adsCgNT{gI^^QwrW+rhNDc}LO*S+% zT7y4Izp7MLHsS%V+6eU5+xnH_?(T^;ZnRx``hNtW{{UTFmbOW1*XRHM002ovPDHLk FV1g`w)UE&k literal 0 HcmV?d00001 diff --git a/Source/Fractorium/Icons/shape_flip_horizontal.png b/Source/Fractorium/Icons/shape_flip_horizontal.png new file mode 100644 index 0000000000000000000000000000000000000000..0a0ee75dd76195ec939430a27540cb07a1761688 GIT binary patch literal 459 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Ea{HEjtmUzPnffIy#(?(3p^r= z85sBugD~Uq{1qucL8%hgh?3y^w370~qEv=}#LT=BJwMkFg)(D3Q$0gN_s>q|3=E9! zo-U3d7XC{ooIlm%AaXSQcJddSy}<&hB7qMU2^{{zytPwD*<_7JQ^w*(?(lu>w?E&N z^jMSj(X;#h({nq&8?>Kh_|p=yrWu6x+|BFN=qkE*d)wUTX;**TsZ(*~P6E{hPqp4wDLdvnS-A8lG zKCi0QzbCi+^2*Zv_a~-G^0hO2Cd^vad9P4r#j0DY-W8u}Q!?*6o_wy}|FX#Mzw=Ty zye#SZTGe|stMy~WoPM{(+ncBHy5`x-@h8^V&A;)sOr`na`Ut)0N{xvU5pF8gxZibW?9;ba!ELWdKlNX>N2bPDNB8 zb~7$DE-^7j^FlWO00A;dL_t(IPo3;hLVHec*;tdw!IeVRZF5&{fzZfk}k!6{hrfKw9lLAxZd9F~_C_m#k zW@+e2!7(T=DfgGl1^4?MVHmPJ^ri48MNz1fqpGTKyvyfc*B!bo*W$x zhnILf9w9c{Y&L8UIY}CWYcIKW|Gv(8y=HmnOW`TFw!)J&%Foqm#nRA|g1pF8gxZibW?9;ba!ELWdKlNX>N2bPDNB8 zb~7$DE-^7j^FlWO00JRNL_t(IPo|rSgBMvNcD4& z%3`iarBaB+Vz|D(=Gi!%&KFUD4lS7gOZCHX6-*=&h(@EhxVV7N=fmUUBODILQWUtn zynOHVdbqv4{Spj?LI?x`@Or&CJ3Hf9TCLW1QGgE4JDm>F>GUTs7z}b(kH-U-%Z20P zW1gkSWSWZtkw|3OYPC4@r{f2Ba&iKj&Bj1J&Vz%4-zE6{{{HjxGw(EW`wBcfJcPku z0F970QlruIMS-KEBS|jmMV+={e5V)TF~5igN;Vxcz1Vq`-2k&Sg6(NElPXF#>gf@7mQDYro@{|Fr-rH z&sAnkfQ3q>+S=RO6LdP=g4Q{SvREjUNPx#1ZP1_K>z@;j|==^1poj532;bRa{vHI(EtER(E$bgtCau%02g#cSaefwb#h~6 z07p(wO+_F{V{dLCRA^;wWx@F-X#fBK8gxZibW?9;ba!ELWdKlNX>N2bPDNB8b~7$D zE-^7j^FlWO00ELoL_t(IPh%K-AT&If7l@glToeEl=K*47C>IlCh#WTsDu9+u#DCejnORWf z*a6TGIiOSTf()29fA;_R3+McwHe>St88fH;%gM{ihAP7kK!)`6b-kQ8xex4}S+l4A z2YRCnDoq#wot+Id;9q-Z3((K4|NAC%|F5a5f`SkGQ%yV?-k(5Od}g zz6KKrtZ@Ha0afs;;Sc)Y;tzir&9k+M04eaTXv(0h}hL#>TmM*-Jvh zLvnz85s(-Va{w{FzJZt<807*qoM6N<$f_?V!pa1{> literal 0 HcmV?d00001 diff --git a/Source/Fractorium/Main.cpp b/Source/Fractorium/Main.cpp new file mode 100644 index 0000000..058c023 --- /dev/null +++ b/Source/Fractorium/Main.cpp @@ -0,0 +1,25 @@ +#include "FractoriumPch.h" +#include "Fractorium.h" +#include + +/// +/// Main program entry point for Fractorium.exe. +/// +/// The number of command line arguments passed +/// The command line arguments passed +/// 0 if successful, else 1. +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + +#ifdef TEST_CL + QMessageBox::critical(QApplication::desktop(), "Error", "Fractorium cannot be run in test mode, undefine TEST_CL first."); + return 1; +#endif + //Required for large allocs, else GPU memory usage will be severely limited to small sizes. + //This must be done in the application and not in the EmberCL DLL. + _putenv_s("GPU_MAX_ALLOC_PERCENT", "100"); + Fractorium w; + w.show(); + return a.exec(); +} diff --git a/Source/Fractorium/OptionsDialog.cpp b/Source/Fractorium/OptionsDialog.cpp new file mode 100644 index 0000000..61739b9 --- /dev/null +++ b/Source/Fractorium/OptionsDialog.cpp @@ -0,0 +1,210 @@ +#include "FractoriumPch.h" +#include "OptionsDialog.h" +#include "Fractorium.h" + +/// +/// Constructor that takes a pointer to the settings object and the parent widget. +/// +/// A pointer to the settings object to use +/// The parent widget. Default: NULL. +/// The window flags. Default: 0. +FractoriumOptionsDialog::FractoriumOptionsDialog(FractoriumSettings* settings, QWidget* parent, Qt::WindowFlags f) + : QDialog(parent, f) +{ + int row = 0, spinHeight = 20; + unsigned int i; + + ui.setupUi(this); + m_Settings = settings; + QTableWidget* table = ui.OptionsXmlSavingTable; + ui.ThreadCountSpin->setRange(1, Timing::ProcessorCount()); + connect(ui.OpenCLCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnOpenCLCheckBoxStateChanged(int)), Qt::QueuedConnection); + connect(ui.PlatformCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(OnPlatformComboCurrentIndexChanged(int)), Qt::QueuedConnection); + + SetupSpinner(table, this, row, 1, m_XmlWidthSpin, spinHeight, 10, 100000, 50, "", "", true, 1920); + SetupSpinner(table, this, row, 1, m_XmlHeightSpin, spinHeight, 10, 100000, 50, "", "", true, 1080); + SetupSpinner(table, this, row, 1, m_XmlTemporalSamplesSpin, spinHeight, 1, 1000, 100, "", "", true, 1000); + SetupSpinner(table, this, row, 1, m_XmlQualitySpin, spinHeight, 1, 200000, 50, "", "", true, 1000); + SetupSpinner(table, this, row, 1, m_XmlSupersampleSpin, spinHeight, 1, 4, 1, "", "", true, 2); + + m_IdEdit = new QLineEdit(ui.OptionsIdentityTable); + ui.OptionsIdentityTable->setCellWidget(0, 1, m_IdEdit); + + m_UrlEdit = new QLineEdit(ui.OptionsIdentityTable); + ui.OptionsIdentityTable->setCellWidget(1, 1, m_UrlEdit); + + m_NickEdit = new QLineEdit(ui.OptionsIdentityTable); + ui.OptionsIdentityTable->setCellWidget(2, 1, m_NickEdit); + + //QWidget::setTabOrder(m_IdEdit, m_UrlEdit); + //QWidget::setTabOrder(m_UrlEdit, m_NickEdit); + //QWidget::setTabOrder(m_NickEdit, m_IdEdit); + + m_IdEdit->setText(m_Settings->Id()); + m_UrlEdit->setText(m_Settings->Url()); + m_NickEdit->setText(m_Settings->Nick()); + + if (m_Wrapper.CheckOpenCL()) + { + vector platforms = m_Wrapper.PlatformNames(); + + //Populate combo boxes with available OpenCL platforms and devices. + for (i = 0; i < platforms.size(); i++) + ui.PlatformCombo->addItem(QString::fromStdString(platforms[i])); + + //If init succeeds, set the selected platform and device combos to match what was saved in the settings. + if (m_Wrapper.Init(m_Settings->PlatformIndex(), m_Settings->DeviceIndex())) + { + ui.OpenCLCheckBox->setChecked( m_Settings->OpenCL()); + ui.PlatformCombo->setCurrentIndex(m_Settings->PlatformIndex()); + ui.DeviceCombo->setCurrentIndex( m_Settings->DeviceIndex()); + } + else + { + OnPlatformComboCurrentIndexChanged(0); + ui.OpenCLCheckBox->setChecked(false); + } + } + else + { + ui.OpenCLCheckBox->setChecked(false); + ui.OpenCLCheckBox->setEnabled(false); + } + + ui.EarlyClipCheckBox->setChecked( m_Settings->EarlyClip()); + ui.TransparencyCheckBox->setChecked(m_Settings->Transparency()); + ui.DoublePrecisionCheckBox->setChecked( m_Settings->Double()); + ui.ThreadCountSpin->setValue( m_Settings->ThreadCount()); + + if (m_Settings->CpuDEFilter()) + ui.CpuFilteringDERadioButton->setChecked(true); + else + ui.CpuFilteringLogRadioButton->setChecked(true); + + if (m_Settings->OpenCLDEFilter()) + ui.OpenCLFilteringDERadioButton->setChecked(true); + else + ui.OpenCLFilteringLogRadioButton->setChecked(true); + + ui.CpuSubBatchSpin->setValue(m_Settings->CpuSubBatch()); + ui.OpenCLSubBatchSpin->setValue(m_Settings->OpenCLSubBatch()); + + m_XmlWidthSpin->setValue(m_Settings->XmlWidth()); + m_XmlHeightSpin->setValue(m_Settings->XmlHeight()); + m_XmlTemporalSamplesSpin->setValue(m_Settings->XmlTemporalSamples()); + m_XmlQualitySpin->setValue(m_Settings->XmlQuality()); + m_XmlSupersampleSpin->setValue(m_Settings->XmlSupersample()); + + OnOpenCLCheckBoxStateChanged(ui.OpenCLCheckBox->isChecked()); +} + +/// +/// GUI settings wrapper functions, getters only. +/// + +bool FractoriumOptionsDialog::EarlyClip() { return ui.EarlyClipCheckBox->isChecked(); } +bool FractoriumOptionsDialog::Transparency() { return ui.TransparencyCheckBox->isChecked(); } +bool FractoriumOptionsDialog::OpenCL() { return ui.OpenCLCheckBox->isChecked(); } +bool FractoriumOptionsDialog::Double() { return ui.DoublePrecisionCheckBox->isChecked(); } +unsigned int FractoriumOptionsDialog::PlatformIndex() { return ui.PlatformCombo->currentIndex(); } +unsigned int FractoriumOptionsDialog::DeviceIndex() { return ui.DeviceCombo->currentIndex(); } +unsigned int FractoriumOptionsDialog::ThreadCount() { return ui.ThreadCountSpin->value(); } + +/// +/// Disable or enable the OpenCL related controls based on the state passed in. +/// Called when the state of the OpenCL checkbox is changed. +/// +/// The state of the checkbox +void FractoriumOptionsDialog::OnOpenCLCheckBoxStateChanged(int state) +{ + bool checked = state == Qt::Checked; + + ui.PlatformCombo->setEnabled(checked); + ui.DeviceCombo->setEnabled(checked); + ui.ThreadCountSpin->setEnabled(!checked); +} + +/// +/// Populate the the device combo box with all available +/// OpenCL devices for the selected platform. +/// Called when the platform combo box index changes. +/// +/// The selected index of the combo box +void FractoriumOptionsDialog::OnPlatformComboCurrentIndexChanged(int index) +{ + vector devices = m_Wrapper.DeviceNames(index); + + ui.DeviceCombo->clear(); + + for (size_t i = 0; i < devices.size(); i++) + ui.DeviceCombo->addItem(QString::fromStdString(devices[i])); +} + +/// +/// Save all settings on the GUI to the settings object. +/// Called when the user clicks ok. +/// Not called if cancelled or closed with the X. +/// +void FractoriumOptionsDialog::accept() +{ + //Interactive rendering. + m_Settings->EarlyClip(EarlyClip()); + m_Settings->Transparency(Transparency()); + m_Settings->OpenCL(OpenCL()); + m_Settings->Double(Double()); + m_Settings->PlatformIndex(PlatformIndex()); + m_Settings->DeviceIndex(DeviceIndex()); + m_Settings->ThreadCount(ThreadCount()); + m_Settings->CpuSubBatch(ui.CpuSubBatchSpin->value()); + m_Settings->OpenCLSubBatch(ui.OpenCLSubBatchSpin->value()); + m_Settings->CpuDEFilter(ui.CpuFilteringDERadioButton->isChecked()); + m_Settings->OpenCLDEFilter(ui.OpenCLFilteringDERadioButton->isChecked()); + + //Xml saving. + m_Settings->XmlWidth(m_XmlWidthSpin->value()); + m_Settings->XmlHeight(m_XmlHeightSpin->value()); + m_Settings->XmlTemporalSamples(m_XmlTemporalSamplesSpin->value()); + m_Settings->XmlQuality(m_XmlQualitySpin->value()); + m_Settings->XmlSupersample(m_XmlSupersampleSpin->value()); + + //Identity. + m_Settings->Id(m_IdEdit->text()); + m_Settings->Url(m_UrlEdit->text()); + m_Settings->Nick(m_NickEdit->text()); + + QDialog::accept(); +} + +/// +/// Restore all GUI items to what was originally in the settings object. +/// Called when the user clicks cancel or closes with the X. +/// +void FractoriumOptionsDialog::reject() +{ + //Interactive rendering. + ui.EarlyClipCheckBox->setChecked(m_Settings->EarlyClip()); + ui.TransparencyCheckBox->setChecked(m_Settings->Transparency()); + ui.OpenCLCheckBox->setChecked(m_Settings->OpenCL()); + ui.DoublePrecisionCheckBox->setChecked(m_Settings->Double()); + ui.PlatformCombo->setCurrentIndex(m_Settings->PlatformIndex()); + ui.DeviceCombo->setCurrentIndex(m_Settings->DeviceIndex()); + ui.ThreadCountSpin->setValue(m_Settings->ThreadCount()); + ui.CpuSubBatchSpin->setValue(m_Settings->CpuSubBatch()); + ui.OpenCLSubBatchSpin->setValue(m_Settings->OpenCLSubBatch()); + ui.CpuFilteringDERadioButton->setChecked(m_Settings->CpuDEFilter()); + ui.OpenCLFilteringDERadioButton->setChecked(m_Settings->OpenCLDEFilter()); + + //Xml saving. + m_XmlWidthSpin->setValue(m_Settings->XmlWidth()); + m_XmlHeightSpin->setValue(m_Settings->XmlHeight()); + m_XmlTemporalSamplesSpin->setValue(m_Settings->XmlTemporalSamples()); + m_XmlQualitySpin->setValue(m_Settings->XmlQuality()); + m_XmlSupersampleSpin->setValue(m_Settings->XmlSupersample()); + + //Identity. + m_IdEdit->setText(m_Settings->Id()); + m_UrlEdit->setText(m_Settings->Url()); + m_NickEdit->setText(m_Settings->Nick()); + + QDialog::reject(); +} \ No newline at end of file diff --git a/Source/Fractorium/OptionsDialog.h b/Source/Fractorium/OptionsDialog.h new file mode 100644 index 0000000..405c470 --- /dev/null +++ b/Source/Fractorium/OptionsDialog.h @@ -0,0 +1,56 @@ +#pragma once + +#include "ui_OptionsDialog.h" +#include "FractoriumSettings.h" +#include "SpinBox.h" + +/// +/// FractoriumOptionsDialog class. +/// + +class Fractorium;//Forward declaration since Fractorium uses this dialog. + +/// +/// The options dialog allows the user to save various preferences +/// between program runs. +/// It has a pointer to a FractoriumSettings object which is assigned +/// in the constructor. The main window holds the object as a member and the +/// pointer to it here is just for convenience. +/// +class FractoriumOptionsDialog : public QDialog +{ + Q_OBJECT + + friend Fractorium; + +public: + FractoriumOptionsDialog(FractoriumSettings* settings, QWidget* parent = 0, Qt::WindowFlags f = 0); + +public slots: + void OnOpenCLCheckBoxStateChanged(int state); + void OnPlatformComboCurrentIndexChanged(int index); + virtual void accept(); + virtual void reject(); + +private: + bool EarlyClip(); + bool AlphaChannel(); + bool Transparency(); + bool OpenCL(); + bool Double(); + unsigned int PlatformIndex(); + unsigned int DeviceIndex(); + unsigned int ThreadCount(); + + Ui::OptionsDialog ui; + OpenCLWrapper m_Wrapper; + SpinBox* m_XmlWidthSpin; + SpinBox* m_XmlHeightSpin; + SpinBox* m_XmlTemporalSamplesSpin; + SpinBox* m_XmlQualitySpin; + SpinBox* m_XmlSupersampleSpin; + QLineEdit* m_IdEdit; + QLineEdit* m_UrlEdit; + QLineEdit* m_NickEdit; + FractoriumSettings* m_Settings; +}; diff --git a/Source/Fractorium/OptionsDialog.ui b/Source/Fractorium/OptionsDialog.ui new file mode 100644 index 0000000..5f5563f --- /dev/null +++ b/Source/Fractorium/OptionsDialog.ui @@ -0,0 +1,746 @@ + + + OptionsDialog + + + + 0 + 0 + 286 + 388 + + + + + 0 + 0 + + + + + 286 + 388 + + + + + 286 + 388 + + + + Options + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 0 + 0 + + + + QTabWidget::Triangular + + + 0 + + + + Interactive Rendering + + + + 6 + + + 6 + + + 6 + + + 6 + + + 6 + + + + + <html><head/><body><p>Checked: clip colors and gamma correct after density filtering.</p><p>Unchecked: do it after spatial filtering.</p></body></html> + + + + + + + + + Early Clip + + + + + + + <html><head/><body><p>Use transparency in the final image.</p><p>This will not make a difference in the editor, but will when saving as .png and opening in other programs.</p></body></html> + + + Transparency + + + + + + + <html><head/><body><p>Use OpenCL to render if your video card supports it.</p><p>This is highly recommended as it will give fluid, real-time interactive editing.</p></body></html> + + + Use OpenCL + + + + + + + + + + + + + <html><head/><body><p>The number of threads to use with CPU rendering.</p><p>Decrease for more responsive editing, increase for better performance.</p></body></html> + + + Threads + + + 1 + + + 64 + + + + + + + CPU Filtering + + + + 6 + + + 6 + + + 3 + + + 4 + + + 5 + + + + + Use log scale filtering for interactive editing on the CPU. + + + Log Scale + + + true + + + + + + + <html><head/><body><p>Use full density estimation filtering for interactive editing on the CPU.</p><p>This is slower, but gives better feedback.</p></body></html> + + + Full DE + + + + + + + + + + OpenCL Filtering + + + + 6 + + + 3 + + + 4 + + + 5 + + + + + Use log scale filtering for interactive editing using OpenCL. + + + Log Scale + + + true + + + + + + + <html><head/><body><p>Use full density estimation filtering for interactive editing using OpenCL.</p><p>This is slower, but gives better feedback.</p></body></html> + + + Full DE + + + + + + + + + + The number of 10,000 iteration chunks ran per thread on the CPU +in interactive mode for each mouse movement + + + CPU Sub Batch + + + 1 + + + 100 + + + + + + + The number of ~8M iteration chunks ran using OpenCL +in interactive mode for each mouse movement + + + OpenCL Sub Batch + + + 1 + + + 100 + + + + + + + <html><head/><body><p>Checked: use 64-bit double precision numbers (slower, but better image quality).</p><p>Unchecked: use 32-bit single precision numbers (faster, but worse image quality).</p></body></html> + + + Use Double Precision + + + + + + + + Xml Saving + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 0 + 0 + + + + + 0 + 122 + + + + + 16777215 + 122 + + + + Qt::NoFocus + + + QFrame::Panel + + + QFrame::Plain + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + QAbstractItemView::NoEditTriggers + + + false + + + false + + + QAbstractItemView::NoSelection + + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + true + + + Qt::SolidLine + + + false + + + 2 + + + false + + + false + + + 110 + + + false + + + 35 + + + true + + + false + + + 24 + + + false + + + 24 + + + false + + + + Width + + + + + Height + + + + + Temporal Samples + + + + + Quality + + + + + Supersample + + + + + Field + + + + + + + + + + Width + + + ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled + + + + + 0 + + + + + Height + + + ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled + + + + + 0 + + + + + Temporal Samples + + + ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled + + + + + 0 + + + + + Quality + + + ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled + + + + + 0 + + + + + Supersample + + + ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled + + + + + 0 + + + + + + + + + Identity + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 0 + 0 + + + + + 120 + 74 + + + + + 16777215 + 74 + + + + Qt::NoFocus + + + QFrame::Panel + + + QFrame::Plain + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + QAbstractItemView::NoEditTriggers + + + false + + + false + + + QAbstractItemView::NoSelection + + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + true + + + Qt::SolidLine + + + false + + + 2 + + + false + + + false + + + 110 + + + false + + + 35 + + + true + + + false + + + 24 + + + false + + + 24 + + + false + + + + Id + + + + + Url + + + + + Nick + + + + + Field + + + + + + + + + + Id + + + ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled + + + + + - + + + ItemIsSelectable|ItemIsEditable|ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled + + + + + Url + + + ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled + + + + + - + + + ItemIsSelectable|ItemIsEditable|ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled + + + + + Nick + + + ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled + + + + + - + + + ItemIsSelectable|ItemIsEditable|ItemIsDragEnabled|ItemIsUserCheckable|ItemIsEnabled + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + false + + + + + + + + TableWidget + QTableWidget +
TableWidget.h
+
+
+ + + + OptionsButtonBox + accepted() + OptionsDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + OptionsButtonBox + rejected() + OptionsDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/Source/Fractorium/SpinBox.cpp b/Source/Fractorium/SpinBox.cpp new file mode 100644 index 0000000..761fb87 --- /dev/null +++ b/Source/Fractorium/SpinBox.cpp @@ -0,0 +1,205 @@ +#include "FractoriumPch.h" +#include "SpinBox.h" + +/// +/// Constructor that passes parent to the base and sets up height and step. +/// Specific focus policy is used to allow the user to hover over the control +/// and change its value using the mouse wheel without explicitly having to click +/// inside of it. +/// +/// The parent widget. Default: NULL. +/// The height of the spin box. Default: 16. +/// The step used to increment/decrement the spin box when using the mouse wheel. Default: 1. +SpinBox::SpinBox(QWidget* parent, int height, int step) + : QSpinBox(parent) +{ + m_Select = false; + m_DoubleClick = false; + m_DoubleClickNonZero = 0; + m_DoubleClickZero = 1; + m_Step = step; + m_SmallStep = 1; + setSingleStep(step); + setFrame(false); + setButtonSymbols(QAbstractSpinBox::NoButtons); + setFocusPolicy(Qt::StrongFocus); + setMinimumHeight(height);//setGeometry() has no effect, so set both of these instead. + setMaximumHeight(height); + lineEdit()->installEventFilter(this); + lineEdit()->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); + connect(this, SIGNAL(valueChanged(int)), this, SLOT(onSpinBoxValueChanged(int)), Qt::QueuedConnection); +} + +/// +/// Set the value of the control without triggering signals. +/// +/// The value to set it to +void SpinBox::SetValueStealth(int d) +{ + blockSignals(true); + setValue(d); + blockSignals(false); +} + +/// +/// Set whether to respond to double click events. +/// +/// True if this should respond to double click events, else false. +void SpinBox::DoubleClick(bool b) +{ + m_DoubleClick = b; +} + +/// +/// Set the value to be used when the user double clicks the spinner while +/// it contains zero. +/// +/// The value to be used +void SpinBox::DoubleClickZero(int val) +{ + m_DoubleClickZero = val; +} + +/// +/// Set the value to be used when the user double clicks the spinner while +/// it contains a non-zero value. +/// +/// The value to be used +void SpinBox::DoubleClickNonZero(int val) +{ + m_DoubleClickNonZero = val; +} + +/// +/// Set the small step to be used when the user holds down shift while scrolling. +/// The default is step / 10, so use this if something else is needed. +/// +/// The small step to use for scrolling while the shift key is down +void SpinBox::SmallStep(int step) +{ + m_SmallStep = min(1, step); +} + +/// +/// Expose the underlying QLineEdit control to the caller. +/// +/// A pointer to the QLineEdit +QLineEdit* SpinBox::lineEdit() +{ + return QSpinBox::lineEdit(); +} + +/// +/// Another workaround for the persistent text selection bug in Qt. +/// +void SpinBox::onSpinBoxValueChanged(int i) +{ + lineEdit()->deselect();//Gets rid of nasty "feature" that always has text selected. +} + +/// +/// Event filter for taking special action on double click events. +/// +/// The object +/// The eevent +/// false +bool SpinBox::eventFilter(QObject* o, QEvent* e) +{ + if (e->type() == QMouseEvent::MouseButtonPress && isEnabled()) + { + QPoint pt; + + if (QMouseEvent* me = (QMouseEvent*)e) + pt = me->localPos().toPoint(); + + int pos = lineEdit()->cursorPositionAt(pt); + + if (lineEdit()->selectedText() != "") + { + lineEdit()->deselect(); + lineEdit()->setCursorPosition(pos); + return true; + } + else if (m_Select) + { + lineEdit()->setCursorPosition(pos); + selectAll(); + m_Select = false; + return true; + } + } + else if (m_DoubleClick && e->type() == QMouseEvent::MouseButtonDblClick && isEnabled()) + { + if (value() == 0) + setValue(m_DoubleClickZero); + else + setValue(m_DoubleClickNonZero); + } + else + { + if (e->type() == QEvent::Wheel) + { + //Take special action for shift to reduce the scroll amount. Control already + //increases it automatically. + if (QWheelEvent* wheelEvent = dynamic_cast(e)) + { + Qt::KeyboardModifiers mod = wheelEvent->modifiers(); + + if (mod.testFlag(Qt::ShiftModifier)) + setSingleStep(m_SmallStep); + else + setSingleStep(m_Step); + } + } + } + + return false; +} + +/// +/// Called when focus enters the spinner. +/// +/// The event +void SpinBox::focusInEvent(QFocusEvent* e) +{ + lineEdit()->setReadOnly(false); + QSpinBox::focusInEvent(e); +} + +/// +/// Called when focus leaves the spinner. +/// Qt has a nasty "feature" that leaves the text in a spinner selected +/// and the cursor visible, regardless of whether it has the focus. +/// Manually clear both here. +/// +/// The event +void SpinBox::focusOutEvent(QFocusEvent* e) +{ + lineEdit()->deselect();//Clear selection when leaving. + lineEdit()->setReadOnly(true);//Clever hack to clear the cursor when leaving. + QSpinBox::focusOutEvent(e); +} + +/// +/// Called when focus enters the spinner. +/// Must set the focus to make sure key down messages don't erroneously go to the GLWidget. +/// +/// The event +void SpinBox::enterEvent(QEvent* e) +{ + m_Select = true; + setFocus(); + QSpinBox::enterEvent(e); +} + +/// +/// Called when focus leaves the spinner. +/// Must clear the focus to make sure key down messages don't erroneously go to the GLWidget. +/// +/// The event +void SpinBox::leaveEvent(QEvent* e) +{ + m_Select = false; + clearFocus(); + QSpinBox::leaveEvent(e); +} diff --git a/Source/Fractorium/SpinBox.h b/Source/Fractorium/SpinBox.h new file mode 100644 index 0000000..27087bb --- /dev/null +++ b/Source/Fractorium/SpinBox.h @@ -0,0 +1,45 @@ +#pragma once + +#include "FractoriumPch.h" + +/// +/// SpinBox class. +/// + +/// +/// A derivation to prevent the spin box from selecting its own text +/// when editing. Also to prevent multiple spin boxes from all having +/// selected text at once. +/// +class SpinBox : public QSpinBox +{ + Q_OBJECT + +public: + explicit SpinBox(QWidget* parent = 0, int height = 16, int step = 1); + virtual ~SpinBox() { } + void SetValueStealth(int d); + void DoubleClick(bool b); + void DoubleClickZero(int val); + void DoubleClickNonZero(int val); + void SmallStep(int step); + QLineEdit* lineEdit(); + +public slots: + void onSpinBoxValueChanged(int i); + +protected: + bool eventFilter(QObject* o, QEvent* e); + virtual void focusInEvent(QFocusEvent* e); + virtual void focusOutEvent(QFocusEvent* e); + virtual void enterEvent(QEvent* e); + virtual void leaveEvent(QEvent* e); + +private: + bool m_Select; + bool m_DoubleClick; + int m_DoubleClickNonZero; + int m_DoubleClickZero; + int m_Step; + int m_SmallStep; +}; diff --git a/Source/Fractorium/StealthComboBox.h b/Source/Fractorium/StealthComboBox.h new file mode 100644 index 0000000..1d25178 --- /dev/null +++ b/Source/Fractorium/StealthComboBox.h @@ -0,0 +1,30 @@ +#pragma once + +#include "FractoriumPch.h" + +/// +/// StealthComboBox class. +/// + +/// +/// A thin derivation of QComboBox which allows the user +/// to set the index without triggering signals. +/// +class StealthComboBox : public QComboBox +{ + Q_OBJECT + +public: + explicit StealthComboBox(QWidget* parent = 0) : QComboBox(parent) { } + + /// + /// Set the current index of the combo box without triggering signals. + /// + /// The current index to set + void StealthComboBox::SetCurrentIndexStealth(int index) + { + blockSignals(true); + setCurrentIndex(index); + blockSignals(false); + } +}; diff --git a/Source/Fractorium/TableWidget.h b/Source/Fractorium/TableWidget.h new file mode 100644 index 0000000..112eb4f --- /dev/null +++ b/Source/Fractorium/TableWidget.h @@ -0,0 +1,57 @@ +#pragma once + +#include "FractoriumPch.h" + +/// +/// TableWidget class. +/// + +/// +/// The entire purpose for this subclass is to overcome a glaring flaw +/// in the way Qt handles table drawing. +/// For most of the tables Fractorium uses, it draw the grid lines. Qt draws them +/// in a very naive manner, whereby it draws lines above the first row and below +/// the last row. It also draws to the left of the first column and to the right +/// of the last column. This has the effect of putting an additional border inside +/// of the specified border. This extra border becomes very noticeable when changing +/// the background color of a cell. +/// The workaround is to scrunch the size of the table up by one pixel. However, +/// since the viewable area is then smaller than the size of the table, it will scroll +/// by one pixel if the mouse is hovered over the table and the user moves the mouse wheel. +/// This subclass is done solely to filter out the mouse wheel event. +/// Note that this filtering only applies to the table as a whole, which means +/// mouse wheel events still get properly routed to spinners. +/// +class TableWidget : public QTableWidget +{ + Q_OBJECT +public: + /// + /// Constructor that passes the parent to the base and installs + /// the event filter. + /// + /// The parent widget + explicit TableWidget(QWidget* parent = 0) + : QTableWidget(parent) + { + viewport()->installEventFilter(this); + } + +protected: + /// + /// Event filter to ignore mouse wheel events. + /// + /// The object sending the event + /// The event + /// True if mouse wheel, else return the result of calling the base fucntion. + bool eventFilter(QObject* obj, QEvent* e) + { + if(e->type() == QEvent::Wheel) + { + e->ignore(); + return true; + } + + return QTableWidget::eventFilter(obj, e); + } +}; \ No newline at end of file diff --git a/Source/Fractorium/TwoButtonWidget.cpp b/Source/Fractorium/TwoButtonWidget.cpp new file mode 100644 index 0000000..f981c72 --- /dev/null +++ b/Source/Fractorium/TwoButtonWidget.cpp @@ -0,0 +1,2 @@ +#include "FractoriumPch.h" +#include "TwoButtonWidget.h" diff --git a/Source/Fractorium/TwoButtonWidget.h b/Source/Fractorium/TwoButtonWidget.h new file mode 100644 index 0000000..dd14de3 --- /dev/null +++ b/Source/Fractorium/TwoButtonWidget.h @@ -0,0 +1,113 @@ +#pragma once + +#include "FractoriumPch.h" +#include "DoubleSpinBox.h" + +/// +/// TwoButtonWidget and SpinnerButtonWidget classes. +/// + +/// +/// Thin container that is both a widget and a container of two QPushButtons. +/// Used for when a layout expects a single widget, but two need to go in its place. +/// The buttons are public so the caller can easily use them individually. +/// +class TwoButtonWidget : public QWidget +{ + Q_OBJECT + +public: + /// + /// Constructor that passes the parent to the base, then creates two QPushButtons, + /// and sets up their captions and dimensions. + /// + /// The caption of the first button + /// The caption of the second button + /// The width of the first button + /// The width of the second button + /// The height of both buttons + /// The parent widget + TwoButtonWidget(QString caption1, QString caption2, int w1, int w2, int h, QWidget* parent) + : QWidget(parent) + { + QHBoxLayout* layout = new QHBoxLayout(this); + m_Button1 = new QPushButton(caption1, parent); + m_Button2 = new QPushButton(caption2, parent); + + if (w1 != -1) + { + m_Button1->setMinimumWidth(w1); + m_Button1->setMaximumWidth(w1); + } + + if (w2 != -1) + { + m_Button2->setMinimumWidth(w2); + m_Button2->setMaximumWidth(w2); + } + + m_Button1->setMinimumHeight(h); + m_Button1->setMaximumHeight(h); + m_Button2->setMinimumHeight(h); + m_Button2->setMaximumHeight(h); + + layout->addWidget(m_Button1); + layout->addWidget(m_Button2); + layout->setAlignment(Qt::AlignLeft); + layout->setMargin(0); + layout->setSpacing(2); + + setLayout(layout); + } + + QPushButton* m_Button1; + QPushButton* m_Button2; +}; + +/// +/// Thin container that is both a widget and a container of one DoubleSpinBox and one QPushButton. +/// Used for when a layout expects a single widget, but two need to go in its place. +/// The widgets are public so the caller can easily use them individually. +/// +class SpinnerButtonWidget : public QWidget +{ + Q_OBJECT + +public: + /// + /// Constructor that passes the parent to the base, then creates a QPushButton and + /// sets up its caption and dimensions, then assigns the DoubleSpinBox. + /// + /// The pre-created DoubleSpinBox + /// The caption of the button + /// The width of the button + /// The height of the button + /// The parent widget + SpinnerButtonWidget(DoubleSpinBox* spinBox, QString buttonCaption, int w, int h, QWidget* parent) + : QWidget(parent) + { + QHBoxLayout* layout = new QHBoxLayout(this); + m_Button = new QPushButton(buttonCaption, parent); + m_SpinBox = spinBox; + + if (w != -1) + { + m_Button->setMinimumWidth(w); + m_Button->setMaximumWidth(w); + } + + m_Button->setMinimumHeight(h); + m_Button->setMaximumHeight(h); + + layout->addWidget(spinBox); + layout->addWidget(m_Button); + layout->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); + layout->setMargin(0); + layout->setSpacing(0); + + setLayout(layout); + } + + DoubleSpinBox* m_SpinBox; + QPushButton* m_Button; +}; diff --git a/Source/Fractorium/VariationTreeWidgetItem.h b/Source/Fractorium/VariationTreeWidgetItem.h new file mode 100644 index 0000000..d051872 --- /dev/null +++ b/Source/Fractorium/VariationTreeWidgetItem.h @@ -0,0 +1,89 @@ +#pragma once + +#include "FractoriumPch.h" +#include "DoubleSpinBox.h" + +/// +/// TwoButtonWidget class. +/// + +/// +/// A derivation of QTreeWidgetItem which helps us with sorting. +/// This is used when the user chooses to sort the variations tree +/// by index or by weight. It supports weights less than, equal to, or +/// greater than zero. +/// +template +class VariationTreeWidgetItem : public QTreeWidgetItem +{ +public: + /// + /// Constructor that takes a pointer to a QTreeWidget as the parent + /// and passes it to the base. + /// + /// The parent widget + VariationTreeWidgetItem(QTreeWidget* parent = 0) + : QTreeWidgetItem(parent) + { + } + + /// + /// Constructor that takes a pointer to a QTreeWidgetItem as the parent + /// and passes it to the base. + /// This is used for making sub items for parametric variation parameters. + /// + /// The parent widget + VariationTreeWidgetItem(QTreeWidgetItem* parent = 0) + : QTreeWidgetItem(parent) + { + } + + virtual ~VariationTreeWidgetItem() { } + +private: + /// + /// Less than operator used for sorting. + /// + /// The QTreeWidgetItem to compare against for sorting + /// True if this is less than other, else false. + bool operator < (const QTreeWidgetItem& other) const + { + int column = treeWidget()->sortColumn(); + eVariationId index1, index2; + double weight1 = 0, weight2 = 0; + VariationTreeWidgetItem* varItemWidget; + VariationTreeDoubleSpinBox* spinBox1, *spinBox2; + + QWidget* itemWidget1 = treeWidget()->itemWidget(const_cast*>(this), 1);//Get the widget for the second column. + + if (spinBox1 = dynamic_cast*>(itemWidget1))//Cast the widget to the VariationTreeDoubleSpinBox type. + { + QWidget* itemWidget2 = treeWidget()->itemWidget(const_cast(&other), 1);//Get the widget for the second column of the widget item passed in. + + if (spinBox2 = dynamic_cast*>(itemWidget2))//Cast the widget to the VariationTreeDoubleSpinBox type. + { + if (spinBox1->IsParam() || spinBox2->IsParam())//Do not sort params, their order will always remain the same. + return false; + + weight1 = spinBox1->value(); + weight2 = spinBox2->value(); + index1 = spinBox1->GetVariation()->VariationId(); + index2 = spinBox2->GetVariation()->VariationId(); + + if (column == 0)//First column clicked, sort by variation index. + { + return index1 < index2; + } + else if (column == 1)//Second column clicked, sort by weight. + { + if (IsNearZero(weight1) && IsNearZero(weight2)) + return index1 > index2; + else + return fabs(weight1) < fabs(weight2); + } + } + } + + return false; + } +}; \ No newline at end of file diff --git a/Source/Fractorium/resource.h b/Source/Fractorium/resource.h new file mode 100644 index 0000000..d75cafd --- /dev/null +++ b/Source/Fractorium/resource.h @@ -0,0 +1,17 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Fractorium.rc +// +#define IDI_ICON1 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NO_MFC 1 +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif
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

f_PrhRINYa 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

K<(mLWD;?pfvf7*;0M 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 A{r~^~ literal 0 HcmV?d00001 diff --git a/Builds/MSVC/VS2010/ReadMe.txt b/Builds/MSVC/VS2010/ReadMe.txt new file mode 100644 index 0000000..5f5cd6a --- /dev/null +++ b/Builds/MSVC/VS2010/ReadMe.txt @@ -0,0 +1,277 @@ +License Info: + +This software is licensed under GPL v3. +Copyright Matt Feemster from 2012 on... + +------------------------------------------------------------------------------- +FLAM3: + + FLAM3 - cosmic recursive fractal flames + Copyright (C) 1992-2009 Spotworks LLC + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +------------------------------------------------------------------------------- +Cuburn: + +Copyright 2010, 2011 Steven Robertson and Erik Reckase. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +------------------------------------------------------------------------------- +Fractron 9000: + +Copyright 2010 Mike Thiesen. + +GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + +------------------------------------------------------------------------------- +Apophysis: + +Copyright 2001 - 2012 Mark Townsend, Ronald Hordijk, Peter Sdobnov, Piotr Borys, Georg Kiehne. + +GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + +------------------------------------------------------------------------------- +Qt: + +GNU LESSER GENERAL PUBLIC LICENSE + + The Qt Toolkit is Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). + Contact: http://www.qt-project.org/legal + + You may use, distribute and copy the Qt GUI Toolkit under the terms of + GNU Lesser General Public License version 2.1, which is displayed below. + +------------------------------------------------------------------------------- +glm: + +The MIT License + +Copyright (c) 2005 - 2013 G-Truc Creation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +------------------------------------------------------------------------------- +Threading Building Blocks: + +Copyright Intel Corporation. + +GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + +------------------------------------------------------------------------------- +libjpeg: + +Copyright (C) 1994-2011, Thomas G. Lane, Guido Vollbeding. + +------------------------------------------------------------------------------- +libpng: + +libpng versions 1.2.6, August 15, 2004, through 1.6.0, February 14, 2013, are +Copyright (c) 2004, 2006-2012 Glenn Randers-Pehrson, and are +distributed according to the same disclaimer and license as libpng-1.2.5 +with the following individual added to the list of Contributing Authors + + Cosmin Truta + +libpng versions 1.0.7, July 1, 2000, through 1.2.5 - October 3, 2002, are +Copyright (c) 2000-2002 Glenn Randers-Pehrson, and are +distributed according to the same disclaimer and license as libpng-1.0.6 +with the following individuals added to the list of Contributing Authors + + Simon-Pierre Cadieux + Eric S. Raymond + Gilles Vollant + +and with the following additions to the disclaimer: + + There is no warranty against interference with your enjoyment of the + library or against infringement. There is no warranty that our + efforts or the library will fulfill any of your particular purposes + or needs. This library is provided with all faults, and the entire + risk of satisfactory quality, performance, accuracy, and effort is with + the user. + +libpng versions 0.97, January 1998, through 1.0.6, March 20, 2000, are +Copyright (c) 1998, 1999 Glenn Randers-Pehrson, and are +distributed according to the same disclaimer and license as libpng-0.96, +with the following individuals added to the list of Contributing Authors: + + Tom Lane + Glenn Randers-Pehrson + Willem van Schaik + +libpng versions 0.89, June 1996, through 0.96, May 1997, are +Copyright (c) 1996, 1997 Andreas Dilger +Distributed according to the same disclaimer and license as libpng-0.88, +with the following individuals added to the list of Contributing Authors: + + John Bowler + Kevin Bracey + Sam Bushell + Magnus Holmgren + Greg Roelofs + Tom Tanner + +libpng versions 0.5, May 1995, through 0.88, January 1996, are +Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc. + +For the purposes of this copyright and license, "Contributing Authors" +is defined as the following set of individuals: + + Andreas Dilger + Dave Martindale + Guy Eric Schalnat + Paul Schmidt + Tim Wegner + +------------------------------------------------------------------------------- +libxml2: + +Except where otherwise noted in the source code (e.g. the files hash.c, +list.c and the trio files, which are covered by a similar licence but +with different Copyright notices) all the files are: + + Copyright (C) 1998-2003 Daniel Veillard. All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is fur- +nished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIT- +NESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +DANIEL VEILLARD BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CON- +NECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Daniel Veillard shall not +be used in advertising or otherwise to promote the sale, use or other deal- +ings in this Software without prior written authorization from him. + +------------------------------------------------------------------------------- +zlib: + +(C) 1995-2012 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + +If you use the zlib library in a product, we would appreciate *not* receiving +lengthy legal documents to sign. The sources are provided for free but without +warranty of any kind. The library has been entirely written by Jean-loup +Gailly and Mark Adler; it does not include third-party code. + +If you redistribute modified sources, we would appreciate that you include in +the file ChangeLog history information documenting your changes. Please read +the FAQ for more information on the distribution of modified source versions. + +------------------------------------------------------------------------------- +ISAAC/QTISAAC: + +Robert Jenkins, Quinn Tyler Jackson +Public Domain + +------------------------------------------------------------------------------- +MWC 64: + +Copyright (c) 2011, David Thomas +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Imperial College London nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +------------------------------------------------------------------------------- +SimpleOpt: + +Copyright (c) 2006-2013, Brodie Thiesfield +MIT License + +------------------------------------------------------------------------------- +See About Box for links and licenses for icons. diff --git a/Builds/MSVC/VS2010/zlib.props b/Builds/MSVC/VS2010/zlib.props new file mode 100644 index 0000000..25979fd --- /dev/null +++ b/Builds/MSVC/VS2010/zlib.props @@ -0,0 +1,37 @@ + + + + + + + ..\..\..\..\zlib + + diff --git a/Data/Variations.xlsx b/Data/Variations.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..2a0a30277e22bc7dcfe48982ddd16ca9973ab68c GIT binary patch literal 28988 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

FmeXEg) zfhqNJ+Ove(DgCU+Z_%bu7Dy@}^`9D#nx;!f>yZ)~iObBb`Wq>W|3#1Q#jWzSO%P$|MAGXhU^KP>kHZ;gdqFv_-i0GBShLKG*vaHbts_t~UrKf4& zUm*)Uj8$htEy91=cC-RzdezFgR7w=3j+N%yDhR5TJI(?NHFF(^+b-Qb6L05!zWYL9 z)d54W+G|$9S$ALHREXJc9+*ZIhgi-lwkiKPnKHZxg``vrft!u1XB~H$^z&m5 zHS;PbW3}waIrU7tE?%_SNY1M=%RIQ)&QAa#D%e#0jBF6y!`S@crxFGv_xU4%XrZY( zJ-+uraJdq7{M1$}a_B?cG?fdi$;o&X`7d3 zT<4&ySlM)<(Uj^{DLJ^ZWo?&)oiM-jtR2#ZS57am=0Sxpx){6{oP|3P!fYtlfzZ8S@}db^Yd z5j4UwmSfff9jMyCNlRDmnR=KNg;dY1A=u3m-rAkAD!Vhr7o+U?PODw7r^~U6a4yxj zTr8d5k~p#2-HD4hv$5B{$0vUCJ~4;~AFdp(8d4Dd`-`LMHGl)`_pSokmQOHwy5uA1 z5}U_#W_B%VF7S_lNt_`X^p_fa<0p%uM(G#@oSpjvE8Y<+Gt|6#q%g*q8isP^eD=+fX*U$)3Vm>O!{E*7I zDVIlIL6HEj7SeA?@(QQuw9#Q+sLEDTSwQ6qXfnLq$wc?*V5yo zX2+eHrPOOX`SiZeeo;^zo7Ut#-Sxq@wCo*U@eQ37LK}Jic8fo0NJ9RjMO*aUEw=)v|qmCOK0>k+Gs55eK`2U0MSJ?2cPbx^B21-a*G=-H z=jzs5tRjNFN|yy4dbiAOz@&rilS&3bAWC9Vd|{uds!M^h+=ERF1g)?}jN(zGow(D! z?QM{K*>(N2A#6ri!1h$;a&~(lGi*kU@wfOOTEYRTS(tzf_%%p1G}~MNK;^t{TRc1J zHHR-l4L_5w1s8aWPbU zuQ9f$XdaNU+<5=5sV$E8``=4}J8J=4=)ncN8q@(C*mSAL$$;*WASxW&NAe==JzAGR{%48ybA;z_W&R-WY5cQg{@y0fQJ#nX|DX_R{YLqp7Xi-^{`o-mM;`*B+Z+Po ze;&`CtN-(t_3!F}R)16f&+qJW?dNy1Kl-r1-|j!Vmp#AN{l2#;$-#o>{IADJOo#+9 MUs^O+6$0Y_01cLGY5)KL literal 0 HcmV?d00001 diff --git a/Data/Version History.txt b/Data/Version History.txt new file mode 100644 index 0000000..5a9e101 --- /dev/null +++ b/Data/Version History.txt @@ -0,0 +1,686 @@ +0.4.0.1 Beta 07/06/2014 +--Bug Fixes + Fix name collision of mobius and Mobius. The former is treated as mobius_strip. + +0.4.0.0 Beta 07/06/2014 +--User Changes + Addition of over 200 new variations. + Almost all variations now come with pre and post version. Bringing the total to almost 900. Pre/post are omitted for some direct coloring variations. + Support for 3D fields from Apophysis: cam_zpos, cam_persp, cam_yaw, cam_pitch, cam_dof. + Support for direct coloring dc_ variations. + Allow for saturation spin box to go negative like Apo does. + Select all when first clicking in any of the spin boxes. + Add a Clear Xaos button to easily reset all xaos values to 1. + Default threads to processors - 1 for first time users to keep the UI more responsive. + The following variations have extra precalcs added to them and should run faster: bent2, bipolar, blob, cpow, curve, escher, fan2, flux, modulus, oscilloscope, rings2, separation, split, wedge. + +--Bug Fixes + The wrong visibility value was being used on final xforms. + The options dialog was not being restored to the previous UI state when the user clicked cancel. + Pie variation was not properly using the slices parameter. + Erroneously included gutter when computing bounds and units of OpenGL display window. + Needlessly calling floor() when calculating histogram index during point accumulation. + +--Code Changes +DoubleSpinBox + Change step. + Allow for selecting all on click by making the line edit a custom derived class. + +Ember + Add new fields and functions for 3D projection. + Add new export macro to handle pre, regular and post variations. + Add function to get a list of pointers to the variations present in all xforms with no duplicates and only single entries for reg, pre and post types. Used to construct OpenCL function strings. + Better calling of CacheXforms() in interpolate. + +EmberAnimate, EmberGenome, EmberRender + Print platform and device information when running with OpenCL. + +EmberCLFunctions + Add new global math functions to correspond to the ones added in Utils. + +EmberCLStructs + Add support for 3D fields. + Make m_LastXfUsed be a member of PointCL, rather than a local variable declared in the kernel. + +EmberDefines + Add a define to only instantiate float type. Useful for reducing build times while debugging, and removed on release. + Add defines: M_2PI, M_3PI, CUBE, TLOW, TMAX, m3T, acosh and fma. The last two will be removed once the project moves to C++11 in the future. + +EmberFile + Add UniqueFilename() function to ensure what we save is never overwritten with subsequent saves. + +EmberTester + Add many test functions to test the integrity of the Variations design. This will be made into a formal testing suite in the future, but for now is just a collection of global functions. + +EmberToXml + Write out legacy symmetry field in addition to color speed for each xform. + Write out name field for each xform for Apo compatibility. + +FinalRenderDialog + Add MoveCursorToEnd() signal function to be called from the final render thread. + +FinalRenderEmberController + Call invokeMethod to move cursor to the end rather than directly. This was possibly causing intermittent crashes before. + When saving on render complete, call new UniqueFilename() function for saving both the xml and the image to ensure no overwriting occurs. + +Fractorium + Add functions to support 3D. + +FractoriumColor + Add fields and functions to support direct coloring. + +FractoriumPalette + Allow for saturation to go negative like Apo does. + +FractoriumParams + Add functions to support 3D. + +FractoriumVariations + Add min/max range for params. + Call SetParamVal() on spinner change rather than directly assining to the param to ensure values are in a valid range. + +Interpolate + Use new SetParamVal() function for interpolating all params. This ensures the interpolated values are in the proper range specified by each variation. + Use M_2PI constant. + +Isaac + Make ALPHA template argument 4 instead of 8 for more compactness. + Make functions inline. + Add check for modulo zero in ranged Rand(). + +Iterator + Add support for 3D projections. + During fuse loop, iterate on the same point rather than to the samples buffer to be more cache friendly. Gives a ~1% speedup. + +IterOpenCLKernelCreator + Add support for 3D projections. + Add support for new pre/reg/post variation design. + Directly assign points rather than doing one field at a time. + +OptionsDialog + Add reject() function to restore the UI to the previous state when the user clicks cancel. + +PaletteList + Remove Isaac member and use the new global one instead. + +Point + Add m_Z member for 3D. + +Renderer + Make temporary Isaac seeds buffer match 1 << ISAAC_SIZE, rather than hard code to 256. + +RendererCL + Copy new 3D and DC fields to EmberCL object. + +Utils + Add a global Isaac object for use in places where none exists. Only to be used in single threaded scenarios. + Add many functions for use in new variations: ClampMod(), ClampLteRef(), Sign(), Sqr(), SafeSqrt(), Cube(), Hypot(), Spread(), Powq4(), Powq4c(), Zeps(), Lerp(), Fabsmod(), Fosc(), Foscn(), LogScale(), LogMap(). + Add new int Floor(T val) function that is faster than the system floor() and use it everywhere. Time test proved to be faster. + +Variation + Complete redesign of variations. Split into multiple files to avoid one becoming unweildy. + Each variation will have a type: pre, regular or post, and will have an assignment type that dictates how the results will be assigned to the output points: direct assign or sum, to be used in pre and post variations. Regular ones always sum. + Variation enums are now sorted alphabetically rather than the traditional arbitrary sorting of flam3. + Add Precalc() and PrecalcOpenCLString() functions to be called with pre and post variations. Placed here instead of xform because they depend on the variation type, pre or post. + Add additional functions to help handle pre/post logic and naming. + Add fields to specify valid value ranges for parametric params. Taken from Apo. + Reserve 5 for params vector in parametric variations. + Add randomization function for parametric variations. + Use new range and value checked Set() function in CopyParamVals() instead of directly assigning values. + Add non-null checks in copy macros to delete to prevent a memory leak. + Add new macros to handle creating and copying pre and post variations. + +VariationList + Add new macro for handling the adding of pre, regular, and post variations. + Add three new vectors to hold pointers, but not copies, to pre, regular and post variations. + Add helper function GetVariationCopy(). + +Xform + Add direct color support. + Major rework of Apply() function to use new style of pre/reg/post variations as well as direct coloring. + Add Read() functions and their OpenCL counterparts, to handle reading from the appropriate inputs for pre and post variations. + Move members around to match the order they're accessed to be more cache friendly. + +XmlToEmber + Add support for 3D fields, direct color, and the legacy symmetry field. + Correct poorly named variations and their parameters. + Remove Isaac member and use the new global one instead. + + +0.3.7.3 Beta 05/04/2014 (Intermediate commit without an associated release) +--User Changes + Fix a bug that reset the render when adjusting a parameter of a variation whose weight was zero. Now only update the render if the variation is actually present with a non-zero weight. + Add many new variations, with more to come. Any Z coordinate functionality has been commented out for the time being until 3D support is added. + Preserve xform index during undo/redo. + In EmberRender and EmberAnimate, report iters as ran/requested rather than requested/ran. + +--Code Changes +Solution + Update glm, libjpeg, libpng, libxml2 and tbb to their latest versions. + Change include/link order for every project for all configurations and platforms to prefer AMD over nVidia since AMD's OpenCL support is more current. + +Variations + Split variations into three files, Variation.h, PreVariations.h, and PostVariations.h. + Add many new variations from the Apophysis plugins. + Place Variations in their own project filter folder in Ember. + +Affine2D + Change the name of the template parameter to T instead of Tt. + Make T() function O(). + Remove glm typedefs, they are global now. + +EmberDefines + Add some new constant defines. + Add global glm defines. + +Utils + Add some additional rounding functions needed in the newly added variations. + +Xform + Split variations into three separate lists for regular, pre and post variations. + Add new function to retrieve the total variation count between all three lists. + Add other various functionality to handle the three lists. + +EmberCLFunctions + Add OpenCL equivalent of new rounding functions. + +EmberCLStructs + Add OpenCL equivalent of new constant defines. + Rearrange the members of the structures to match the order they are used in, and remove unused members. + +IterOpenCLKernelCreator + Add support for the three variations lists in each xform. + +OpenCLWrapper + Add #define to use Image2DGL for OpenCL 1.1 on nVidia, or ImageGL for OpenCL 1.2 on AMD. + Fix a glaring bug (device and platform = 0 in the constructor) that was compiling, but was causing an out of bounds memory read under the covers and was only detected in debug mode. + + +0.3.7.2 Beta 04/16/2014 +--User Changes + Fix bug where images with a high number of bad values would crash the rendering process on nVidia cards. + Disable mouse wheel scrolling when it's held down for panning. This makes panning easier on sensitive mice. + +--Code Changes + +Variations + Begin implementing more variations from Apophysis, but leave them disabled for now. + +Ember + Fixed a bug when adding symmetry to an ember that sorted all of the xforms by color speed value, instead of only the newly added symmetry xforms. + +IterOpenCLKernelCreator + Fixed a bug where final xform was considered for the randomly selected xform during each iteration. It was never actually hit, but should have never been there regardless. + Major change in how bad values were being handled. They were failing out on nVidia but running ok on AMD, but giving worse images. Now it's done in a while loop to avoid severe warp divergence. + + +0.3.7.1 Beta 04/07/2014 +--User Changes + Fixed a bug that accidentally disabled double precision support on nVidia cards in the last commit. + + +0.3.7.0 Beta 04/06/2014 +--User Changes + Support for most recent AMD cards. + Final render dialog is now non-modal, allowing the main window to be minimized while doing a lengthy render. + Fixed device selection which was completely broken in the final render dialog. + Save backup Xml before rendering starts just in case it crashes mid way through. The backup file is deleted upon successful render completion. + Set the GPU_MAX_ALLOC_PERCENT env var to 100 to allow full usage of GPU memory. + New --dump_kernel command line option for EmberRender to print the kernel if using OpenCL. + Fix hiding of xform indices in the xforms combo when their count was >= 10 by making the combo slightly wider. + Add ABCDEF to affine xform values table to clarify the meaning of x1, y1, x2, y2, o1, o2. + Variations is now the default shown tab when clicking into the xforms tab. + +--Code Changes + +IterOpenCLKernelCreator, DEOpenCLKernelCreator, OpenCLWrapper + Support for AMD. + +EmberCLFunctions + Support for AMD. + When resizing a buffer, clear the existing one first to avoid having both resident in memory for a brief moment. + +EmberCLStructs + Allow for 21 xforms per ember, was previously 17. + +RendererCL + Support AMD. + Limit variations per xform to MAX_CL_VARS. + +EmberTreeWidgetItem + Derive EmberTreeWidgetItem from EmberTreeWidgetItemBase to make casting possible since a templated argument can't be passed to invokeMethod(). + +Fractorium + New functions to allow updating the preview renders from within a thread better. + +FractoriumLibrary + Better flushing of event queues when starting and stopping preview renderer. + Place a try/catch block around EmberTreeItemChanged() because it seems to get called spurriously for tree items that have already been deleted. + +FractoriumEmberController + Fix bug where Fractorium would crash when opening a new file before the preview renders were finished by doing two things: Updating the thumbnail via invokeMethod() and using BlockingQueuedConnection for that call. + + +0.3.6.0 Beta 03/11/2014 +--User Changes + Fix bug when using early clip. + Fix crash when more than once device is present. + Fix bug where saving the current Xml would continually prompt. + Fix bug where renderer wouldn't reinitialize after changing platform or device. + Add default click values for: brightness, gamma, gamma threshold, vibrancy, highlight power, spatial filter width, min and max DE radius, DE curve, + +--Code Changes + +EmberCL.vcxproj + Add paths for AMD APP SDK. Support will be coming in the following months. + +Renderer + Fix bug in EarlyClip() function signature that prevented it from being overrided, add const in base. + Formatting cleanup. + +EmberCLStructs + Remove typedefs for sbyte, byte and uint. They are not needed. + +IterOpenCLKernelCreator + Change byte to uchar. + +OpenCLWrapper + Fix bug in DeviceAndPlatformNames() that would crash the program when more than one platform was present. + +FractoriumLibrary + Fix bug where saving the current Xml would continually prompt. + +FractoriumRenderer + Fix bug where renderer wouldn't reinitialize after changing platform or device. + +OptionsDialog + Better initialization of the platform/device combo boxes when more than one platform/device is present. + + +0.3.5.0 Beta 02/14/2014 +--User Changes + Add Hemisphere variation because it was needed for some testing. Many more to follow soon. + Integer spin boxes can be scrolled by 1 when shift is held down. + Support for dragging and dropping multiple files. + Support for DnD and pasting Xml over the current file or appending them to it. + Proper behavior when zero variations are present. This differs from flam3, but makes more sense. + Preview and scaling options when doing a final render. + Bug fix in EmberGenome that would crash when creating a sequence that included embers with final Xforms in them. + +--Code Changes + +Ember + Add two members to preserve the original raster width and height of an ember as it was read from file. This is used for aspect ratio calculations later on. + +EmberDefines + Add two new enums for render status and scaling preference. + +EmberToXml + Make some buffers size 128 instead of 100. + Add function AddFilenameWithoutAmpersand() to ensure an ampersand is never added to the edits because Xml can't parse those. + Error in ConvertLinearToPolar() was calling GetXform(). Changed to GetTotalXform(). + +PaletteList + Use a string instead of a vector because it's needed now to deal with ampersands. + +Renderer, RendererCL + Rendering functions now no longer return just true or false. Rather, they return an enum indicating whether it ran ok, with an error, or aborted. + Ok function was inadvertently made not to be an override in OpenCL. Fixed by adding const to match the base. + +SheepTools + Clean up to use our modern design instead of some remnants left over from flam3. + Consolidate variation weight normalization to a function call, NormalizeVariationWeights(). + +Utils + Add some functionality to EmberReport. + Add a function FindAndReplace() to replace all isntances of a value within a collection with a specified value. + +Variation, VariationList + Formatting cleanup. + Add new variation Hemisphere(). + +Xform + Change the behavior for when zero variations are present to just treat them as linear variations with weight 1. + +XmlToEmber + Formatting cleanup. + Preserve original raster width and height in new member variables. + Reserve space in the embers vector based on a rough estimate of how many are present in the file. + Account for strings which contain an ampersand, which Xml can't parse. + +EmberCommon + Generalize CreateRenderer() function to work in all scenarios, which reduced code duplication in the GUI. + +EmberRender, EmberAnimate, EmberGenome + Better error reporting when renderer creation fails. + +IterOpenCLKernelCreator + Update GPU side to use the new Xform behavior when variations are empty. + +DoubleSpinBox + Style cleanup. + +EmberFile + Remove unused CreateRenderer() function. + +EmberTreeWidgetItem + Use RGBA for previews because that's what QPixmap prefers. + +FinalRenderDialog + General cleanup. + Support for new scaling features. + +FinalRenderEmberController + Add support for preview renders. + Support for new scaling features. + Use new CreateRenderer() function. + Use invokeMethod() for all GUI updates inside of the render and preview threads. This was causing an occasional crash before. + Preserve the state of some of the checkboxes that were not being saved before. + +Fractorium + Add support for drag n drop of multiple files at once. + Better drag n drop code. + +FractoriumEmberController + Make the preview renderer keep one core free, instead of maxing out all cores. + +FractoriumInfo + Use invokeMethod() to account for when ErrorReportToQTextEdit() is called from a thread. + +FractoriumLibrary + General cleanup. + Add support for undo/redo list. + +FractoriumMenus + Add support for undo/redo list. + Add support pasting Xml as an append, as well as a replacement. + Preserve original dimension with new m_OrigFinalRasW and m_OrigFinalRasH fields. + Better handling of unicode and html junk in pasted strings. + Stop preview render whenever the open file/list of embers changes in response to user action. + +FractoriumRender + Add support for undo/redo list. + Clear both front and back screen buffers on the main window when showing the final render dialog. + Use new CreateRenderer() function. + +FractoriumSettings + General cleanup and reordering. + Add support for saving scaling options from the final render dialog. + Preserve the state of some of the checkboxes that were not being saved before. + +SpinBox + Add a default small step of 1 to be used when scrolling and holding shift. DoubleSpinBox already had this functionality. + + +0.3.4.0 Beta 01/26/2014 +--User Changes + Fix bug where the last few rows of an image weren't being rendered. + Add commas to various command line numerical outputs to make them easier to read. + Apply sort mode to variations list every time filter is changed or cleared. + Prevent crash when clicking on main window before initialization is complete on program startup. + +--Code Changes + +OpenCLWrapper, RendererCL + Refactor to better separate OpenCL detection from initialization. This should help detect devices better. + +EmberOptions + Fix small formatting errors in command line help text. + +EmberFile + Prevent assign to self. + + +0.3.3.0 Beta 01/23/2014 +--User Changes + More intelligent checking for OpenCL. + Greater zoom amount when scrolling the mouse wheel on the main window. + +0.3.2.0 Beta 01/19/2014 +--User Changes + Add previews to open embers in the Library tab. + Allow for starting and stopping the preview renderer. + Make library tab first and automatically selected. + Fix the ngon and rays variations to use the sum of the squares, instead of their square root. Ngon and rays were completely erroneous before this fix. + Add support for copying and pasting Xml text from and into the GUI. + Holding shift while scrolling xform weight adjusts the value by 0.001 for more precise manipulation. + +--Code Changes + +CarToRas + Prevent assign to self. + Remove padded variables for bounds checking, and check bounds properly. Also discovered that even when points are in bounds, the roundoff error from converting to raster can put them out of bounds. Add a final check in Accum. + +DensityFilter + General cleanup. + Save the filter width (oddly enough, this wasn't being done before). + Add a vector of indices that is used in OpenCL to apply the filter by row, rather than moving from the center outward. This fixes a race condition when applying the DE filter on the GPU. + Remove FinalMinRad, FinalMaxRad and ActualMaxRad members and accessor functions. + +Affine2D, Color, Ember, Palette, Point, Variation, Xform + Prevent assign to self. + +Renderer + Fully setup gutter width the way smoulder does. + In GaussianDensityFilter(), do a more optimized assignment of the bucket hit count if SS=1. + Reorder the assignments for the case (jj == ii) to be cache efficient. + In Accumulate(), add one final check after Car to Ras conversion to make absolutely sure it's within bounds. This was the source of a seldom occurring, but lethal memory error. + +EmberToXml + Clamp supersample to always be greater than 1 just to be safe. + +XmlToEmber + Add a Parse() overload that takes a vector. + More consistent formatting of error messages. + Add, but comment out, an attempt to capture missing variations when parsing. Revisit later. + +FractoriumEmberController, FractoriumMenus + Properly account for scale when opening from file, dragging and dropping, or pasting Xmls into the GUI. + +FractoriumParams + Allow brightness to go to 50 and gamma to go to 9999. + Allow spatial filter width to go down to 0.1, was 0.4 before. + +FractoriumRender + Renderer was not properly being created when returning from options if params other than OpenCL or double precision were changed. Now properly construct for all options changes. + +FractoriumSettings + Default XmlSupersample to 2 if not found in settings file. + +FractoriumXformsAffine + Respect world vs. local pivot when flipping affine to mimic Apophysis behavior. Was previously always using world pivot behavior. + +FractoriumXformsColor + NULL check for empty pixmap on startup. + +GLWidget + Check for m_Init in SetSelectedXform() to avoid recursive paint operation. + +EmberCLStructs + Remove pad members from CarToRas structures to mirrow the CPU side. + +EmberCLFunctions + Remove AddToAccum functions, they are no longer used with new DE code. Add AccumCheck() function to check bounds, but not accumulate. + Change CarToRasInBounds() to check bounds the same way the new CPU code does. + +IterOpenCLKernelCreator + Add histSize parameter to kernel. + Add (histIndex < histSize) check after call to CarToRasConvertPointToSingle() to match the extra check done on the CPU side now. This will guarantee memory errors will never happen while iterating. + +RendererCL + Add support for different block widths and heights for DE. + Add a DE coefficients buffer for applying DE by row, which fixes the race condition problem that occurs when applying from the center outward. + DE blocksize is now 32x32, unless SS>1 or type is double, in which case they run at 32x30. + Add debugging code in GaussianDensityFilter() to fall back to the CPU for testing, leave commented out. + +DEOpenCLKernelCreator + Re-design to apply filter by row, rather than from the center outward. This solves the race condition error that would lead to missing pixels on some images. + Eliminate needless variables and make every effort to get the kernel as compact as possible. This allows running a block size of 32x32. + Rename blockHistCol and blockHistRow to threadHistCol and threadHistRow since that's what they are. + In CreateGaussianDEKernel(), have the last row accumulate since it's slightly faster than having the first row do it. + In CreateGaussianDEKernel(), Change the local buffer to be size 3000. + + +0.3.1.0 Beta 01/06/2014 +--User Changes + Changing size when dragging in an image (orig -> window), resizing (window -> window), or doing a final render (window -> final render) now compensates for scale (pixels per unit). + Spatial filter width now allows values >= 0 and defaults to 0.4. + All spinners now scroll by the normal step / 10 when holding shift. Variation spinners are a special case and scroll by 0.001 with shift held down. + Final render now shows pure render time along with total time. + Fix bug in final render dialog where only the first image in the file would get rendered. + Installer will now uninstall the previous version automatically. + Installer now includes this file. + About box now fully specifies version with 4 digits. + +--Code Changes +-Ember + Add function SetSizeAndAdjustScale() to adjust the scale for a size change. + +-RendererCL + Report error when adding spatial filter params fails, which can happen when GPU memory runs out. + +-DoubleSpinBox + Add m_Step and m_SmallStep members to specify how much to scroll by based on the keyboard modifier. + +-FinalRenderEmberController + Add m_TotalTimer member to differentiate between render time and total time. + + +0.3 Beta 12/27/2013 +--User Changes + Support for single and double precision data types in Fractorium. + Support for single and double precision data types with OpenCL. + Support for --nstrips on the command line with OpenCL. + Support for --lock_accum on the command line. + Fix memory errors. + Fix affine circle selection bugs. + +--Code Changes +Provide templated copy constructors and assignment operators for: + -Affine2D + -CarToRas + -Color + -Ember + -Palette + -Point + -Variation + +-CarToRas + More clearly name raster bounds member variables. + +-DensityFilter + Make a non-templated base with a single virtual function. + Make ToString() and other member functions const. + +-Ember.cpp + Better export defines for single and double template classes. + Only export double template classes with the same type, no more type mixing. + +-Ember.h + Add EqualizeWeights() function. + Replace INTERP macros with templated function that take member variable addresses as template parameters. + Supply proper template type for some mat2x3 objects in Interpolate(). + Make TotalXformCount() const like XformCount(). + Make CompareEmbers() static function conform to new std comparison signature, taking two references and returning a bool. + Make ToString() const. + +EmberDefines.h + Explicitly specify types for all enums. + +Isaac.h + Improve code style. + +Palette.h + Require template argument for newRgb parameter in CalcNewRgb() to support copying to pixels of a different type (float to double, or double to float). + +Renderer.h/cpp + Derive Renderer from a non-templated based named RendererBase. + Add virtual function MakeDmap() which creates the scaled, final palette to use so that RendererCL can override it to specifically use a float palette at all times. + Check results of AccumulatorToFinalImage() instead of blindly assuming it succeeded. + Add virtual function Callback() to set the callback object. + Fix bug in ogScaleDensityFilter() which was causing the last line not to be filtered by starting at the gutter width offset. Filter all pixels now. + Properly template temporary bucket objects in GaussianDensityFilter(), AccumulatorToFinalImage(), Accumulate(), AddToAccum() and GammaCorrection(). + Fix bug in Iterate() where the returned iter count was junk due to overflow. Use unsigned __int64 for everything, including literals with the ULL suffix. + Properly template m_Dmap and m_AccumulatorBuckets members. + Add virtual MemoryAvailable() function to return how much memory is available on the CPU, or on the GPU in derived classes. + +SheepTools.h + Allow it to take two template parameters to pass to the internal renderer. + +SpatialFilter.h + Make ToString(), Filter() and other member functions const. + +TemporalFilter.h + Make ToString() and other member functions const. + +Utils.h + Add global static function CopyVec() to copy a vector of one type to another. + Add global static function RgbaToRgb() to copy an RGBA buffer to an RGB one. + Add global static function ClampGte0Ref() as a thin wrapper. + +Variation.h + Supply templated copy constructors to support creating copies of variations using different types. + Use initializer in Variation constructor for m_Name to avoid unnecessary string constructor call. + Make ToString() const. + Add two new virtual functions to all variations to make a copy of themselves using a different type and placing the new object in a reference to a pointer passed in. + Add ParentXform() function. + Rename SetParentXform() to ParentXform(). + Use real_t instead of float to support double in OpenCL. + +-Xform.h + Rename m_Density member to m_Weight. + Make ToString() and other member functions const. + + +EmberRender, EmberAnimate, EmberGenome: + Take two template arguments to pass to renderer. + Better handling of erroneous arguments for bits per channel and thread count when using OpenCL. + Use proper std::sort() comparison function. + Support saving as 3 channel image, such as bmp or jpg when using 4 channels with OpenCL. + Add --lock_accum option. + Add full double precision support for OpenCL. + Add support for --nstrips when using OpenCL. + Ensure strips divide evenly into the final raster image height. + Move CalcStrips() out of EmberEnder.cpp and into EmberCommon.h. + Dump errors if anything went wrong while rendering. + +EmberCL + Template all kernel creators to support float and double. + +DEOpenCLKernelCreator.h/cpp + Put global barrier on all accum writes. + Only use no-cache version when using double. + +EmberCLFunctions.h + Support double, including when doing atomics using ulong for the int part of the unions. + +EmberCLStructs.h + Template all CL structures. + Remove m_MinRad and m_MaxRad from DensityFilterCL since they are never used. + +FinalAccumOpenCLKernelCreator.h/cpp + Template all. + +IterOpenCLKernelCreator.h/cpp + Template all. + Fix glaring bug in zeroize kernel that was writing past the end of memory. Most likely the source of previous crashes. + +OpenCLWrapper.h/cpp + Add support for double. + +RendererCL.h/cpp + Template to support double. + Use cached color values m_ColorSpeedCache and m_OneMinusColorCache instead of m_Density and m_ColorX. + In RunDensityFilter(), reduce the block size by 2 when running with double, else the GPU runs out of resources. + +Fractorium.h/cpp + Complete rework to a controller architecture to move the processing code out of the GUI and to support templating. + + +0.2 Beta 11/2013 +-Minor fixes. +-Install to Program Data to avoid admin privileges errors. + + +0.1 Beta 11/2013 +-Initial release. \ No newline at end of file diff --git a/Data/Wiki/AboutDialog.htm b/Data/Wiki/AboutDialog.htm new file mode 100644 index 0000000..b8e5c43 --- /dev/null +++ b/Data/Wiki/AboutDialog.htm @@ -0,0 +1,26 @@ +#summary About dialog + + +=About= + +The About dialog shows miscellaneous program information. + +

\ No newline at end of file diff --git a/Data/Wiki/AlgorithmExplanation.htm b/Data/Wiki/AlgorithmExplanation.htm new file mode 100644 index 0000000..d8eb094 --- /dev/null +++ b/Data/Wiki/AlgorithmExplanation.htm @@ -0,0 +1,318 @@ +#summary How the fractal flames algorithm actually works. + + +=Introduction= + +The standard way of getting familiar with the algorithm is reading the paper by Scott Draves and Erik Reckase, titled The Fractal Flame Algorithm. Reading it is highly recommended before proceeding. + +Another paper which gives more detail, and which this project borrows heavily from is the Cuburn paper. The first few sections give a better description of the algorithm. The later sections are more focused on GPU implementations, so they are only recommended for advanced readers. + +While the original paper gives a great introduction, it omits some important details. If one reads the flam3 code, they will notice that quite a bit is left out of the paper. This section of the wiki gives a detailed description of what actually happens when a fractal flame is rendered. + +It's broken down into the data used, and the processing performed on the data in order from start to finish. It dispenses with mathematical terms and notation, and expresses the process in plain English with pseudo code. +

+ +=Details= + +
    +
  • +==Data== + +
      +
    • +===Buffers=== + +-Histogram: Supersampled with gutter, iteration is plotted here. + +-Density filter buffer/accumulator: Same dimensions as histogram, density filter output written here. + +-Final image buffer: Not supersampled, final output written here.

      + +The dimensions of the final image are straightforward. They are just the values specified in the size field of the Xml. + +The dimensions of the histogram are slightly more complex to calculate: + +{{{ +histwidth = (finalwidth * supersample) + (2 * gutter) +histheight = (finalheight * supersample) + (2 * gutter) +}}} + +The gutter is to account for the extra space needed when filtering at edge pixels. It's calculated by: + +{{{ +gutter = max((spatialfilterwidth - supersample) / 2, maxdensityfilterradius * supersample) +}}} + +Computing the histogram bounds (camera) is much more complex. The values are based off of the following Xml fields: size, supersample, scale, zoom, center, spatial and density filter widths. + +Scale is the pixels per unit, meaning the number of raster pixels needed to represent the distance from 0 to 1 in the Cartesian plane. The higher the value, the more zoomed in the camera is. Increasing scale will degrade image quality. + +Zoom is the amount of zoom to apply to the image. It has a similar effect to increasing scale, but does not suffer from quality loss. This will increase the number of iterations done to compensate. + +Supersample is the value to multiply the dimensions of the histogram and accumulator by to accomplish anti-aliasing. + +Center is the camera offset in on each axis. The image will move in the opposite direction of these values. + +
    • +
    • + +===Fractal Flame (Ember)=== + +The main data structure is the fractal flame itself. In this project, it is referred to as an `Ember`. + +An `Ember` contains the following pieces:

      +-Dimensions, filter parameters, and quality settings.

      +-A list of xforms, which each contain a weight, color index, color speed, pre and post affine transforms, and a list of variations.

      +-A color palette. +
    • +
    • + +===Xml=== + +An `Ember` is stored in an Xml file, which can contain one or more `Embers` in them. The first step in rendering is to read the data out of the Xml and into a vector of `Embers`. + +The first parameters encountered in each `Ember` are ones that specify general information about what is to be rendered, such as the dimensions, rotation, quality, supersampling, filter types and sizes, and color information. The colors that are to be used during iteration are specified in a palette, which is a list of 256 colors. + +
    • +
    • + +===Palettes=== + +Palettes can be specified in one of two ways: An index into a palette file, or as an inserted block of data present in the `Ember` Xml. For the first case, the palette file is usually the standard flam3-palettes.xml file which contains 700 palettes and is shipped with all fractal flame editors. The `Ember` has an Xml field named palette whose value is an integer index into the file. A value of -1 means to select a random palette from the file. The palette index field is used in conjunction with the hue field. Hue signifies a hue rotation to be applied to the palette after it's read. The palettes in the file are stored as RGB values in the range of 0-255. Upon reading, the hue rotation is applied to them, and they are converted into normalized values ranging from 0-1. They are stored with the `Ember` in a `Palette` object. + +The other way of specifying a palette is to embed it directly in the Xml as either individual tags for each color entry, or as a hexadecimal representation of the binary data. The latter is preferred because it keeps the Xml files smaller. When embedding the palette, no hue adjustment is applied after reading it. + +
    • +
    • + +===Xforms=== + +The xforms are what contribute most to defining the look of the final output image. Each xform contains several parameters: + +
        +
      • +====Weight==== + +The probability that the xform will be chosen in each iteration. All weights are normalized before running. +
      • +====Color Index==== + +The index in the palette the xform uses. +
      • +====Color Speed==== + +The speed with which the color indices are pulled toward this xform's color index. This value can be negative. +
      • +====Opacity==== + +How visible the xform's contribution to the image is. +
      • +====Pre Affine Transform==== + +The affine transform that is applied to the input coordinates which will be used as inputs to the variations. +
      • +====Post Affine Transform==== + +The affine transform that is applied to the output of the sum of the variations, optionally omitted. +
      • +====Variations==== + +A list of functions which each contain a weight, and optionally more parameters. +
      • +====Xaos==== + +Xaos is an optional advanced feature that adds an element of control to the random selection of xforms during iteration. It adds an adjustment to the probability that a given xform will be selected based on the xform that was selected in the previous iteration. This is usually omitted. +
      • +
      + + +In addition to the list of xforms, an additional one can be specified as the final xform. It contains all of the same parameters, except weight. This is because it is always applied in each iteration. +
    • + +
    • +===Filters=== + +As mentioned earlier, each `Ember` contains filtering parameters. These are values used to specify details about the three filtering stages used to improve the quality of the final output image. They are: +
        +
      • +====Temporal==== + +In addition to creating a still image, the algorithm can be used to create a series of still images where each represents a frame in an animation. This is done by adjusting the affine transforms slightly for each frame. It also involves interpolating (blending) between two different `Embers`. Sometimes, even slight changes in the `Ember` parameters can cause a large change in the final output image. To mitigate this effect, each frame splits its render into a number of temporal samples. This does not increase the number of iterations. Instead, it breaks the total number of iterations into chunks. Each chunk renders an interpolated `Ember` at a specific time between the current frame and the next one to be rendered. The histogram is not cleared between temporal samples, so all iteration values are accumulated to produce a motion blurring effect. A temporal samples value of 1000 is commonly used for animation. When rendering a single frame, the number of temporal samples is always set to 1 since there is nothing to interpolate. +
      • +====Density==== + +Flam3 refers to this as density estimation, or DE. This is a misnaming as there is no estimation taking place. Rather, a variable width Gaussian filter is applied to each log scaled histogram cell. The Xml specifies the minimum and maximum widths that the filter can be, as well as the decay curve for how quickly the filter's values drop off when extending outward from the pixel being filtered. +
      • +====Spatial==== + +After iterating and density filtering are done, final color correction to the output image is computed. Spatial filtering is applied during this step. The Xml parameters specify both the width of the filter as well as the type. This gives very fine adjustment over what the final image looks like. +

        +
      • +
      +
    • +
    +
  • +
  • + +==Processing== + +The process contains 3 main steps: + +-Iterating

    +-Density Filtering

    +-Final Accumulation + +
      +
    • +===Iterating=== +
        +
      • +====Xform Application==== + +Iterating is described in the paper, however it's worth clarifying because it's the most important part of the algorithm. + +Random numbers are obviously a core component of the algorithm, however the paper doesn't touch on exactly how they're implemented and used. Flam3 uses a very fast and high quality RNG named ISAAC because system RNGs are usually of poor quality. Using ISAAC also allows for producing the exact same image on different platforms when supplied with the same seed. + +More interesting though, is how the numbers from ISAAC are used to select random xforms. Before iterating begins, a buffer of 10,000 elements is created. All xform weights are normalized and the elements of the buffer are populated with xform indices with a distribution proportional to each of their weights. For example, given an Ember with 4 xforms, each with a weight of 1, their normalized weights would each become 0.25. The random selection buffer would then be populated like so: + +{{{ +buf[0..2499] = 0 +buf[2500..4999] = 1 +buf[5000..7499] = 2 +buf[7500..9999] = 3 +}}} + +To select a random xform, retrieve the next random unsigned integer from ISAAC and perform a modulo (%) 10,000. The value at that index in the buffer is the index of the next xform to use.

        + +The classic Iterated Function System works like the following pseudo code in flam3 and Ember: + +x and y = random numbers between -1 and 1. + +Pick a random xform from the Ember, with biases specified by their weights. + +Calculate tx and ty by applying the selected xform's pre affine transform to x and y: + +{{{ +tx = Ax * By + C +ty = Dx * Ey + F +}}} + +Pass the transformed point to each of the variations and sum the results. Note that this does not change the transformed points at all, they are only used as inputs. + +{{{ +vx = 0 +vy = 0 + +vx,vy += var1(tx, ty) +vx,vy += var2(tx, ty) +vx,vy += var3(tx, ty) +... +vx,vy += varN(tx, ty) + +ox, oy = vx, vy +}}} + +If a post affine transform is present, apply it to the result calculated from summing the outputs of the variations above. + +{{{ +ox = pAvx * pBvy + pC +oy = pDvx * pEvy + pF +}}} + +Now that the new point has been calculated, compute the new color coordinate. As the paper states, the coordinate is the one specified in the currently chosen xform, blended with the one from the previously chosen xform. It incorrectly states that blending is achieved by adding the current and previous coordinates and dividing by 2. Not only is it calculated differently, but the hard coded value of 2 is actually the user specified color speed parameter of each xform. The real calculation is: + +{{{ +newindex = colorspeed * thisindex + (1.0 - colorspeed) * oldindex +}}} + +It's important to note that the colors themselves are not being blended, only their indices in the palette are. + +At this point, we have the final output point ox,oy to be plotted to the histogram. However, we can't use it just yet. There is a slight possibility that the calculated value was not valid. This is detected by checking for it being very close to infinity, or very close to zero. If either are the case, 5 attempts at the following correction method are tried: + +-Pick a new input point with x and y each being a different random number between -1 and 1.

        +-Pick a new random xform and apply it.

        +-Keep color index from the first xform that was applied, which originally gave us the bad values.

        + +If after 5 attempts, a valid point is not produced, the output point is assigned random numbers between -1 and 1. The number of bad values are saved for statistical use later. + +After computing this point, apply a final xform if one is present. Plot its output next, however do not feed it back into the iteration loop. Rather, only feed the output of the randomly selected xform above to the next iteration of the loop. + +
      • +
      • +====Plotting==== + +Once we have our new point, it's time to plot it. This is one of the most important parts of the algorithm, so it's worth detailing what exactly happens. First, let's review the information the point contains: + +-An x,y coordinate in Cartesian space.

        +-A color index from 0-255.

        + +The first step in plotting is applying any rotation specified in the Xml. Rotation is specified in terms of the camera, so it will actually rotate the image in the opposite direction. + +After applying rotation to the coordinate, bounds checking is done. If the point is outside of the Cartesian space the histogram covers, then it's discarded, otherwise it's plotted if the opacity is non-zero. + +The point can't be plotted directly because it's in a different coordinate system than the one used for indexing the histogram memory. The points are decimal numbers in Cartesian space with 0,0 at the center. The histogram is stored in raster coordinates with 0,0 at the top left and each bucket specified by an integer x,y index. So before plotting, the coordinates must be converted to determine the histogram bucket to write to. + +After computing the raster coordinate, a color must be added to the bucket. This is gotten from the Ember's color palette, at the index specified in the point. A similar coordinate problem occurs in that the computed color index is a decimal number, but the indices in the palette are integers. The algorithm offers two methods for retrieving the color. The first is called "Step" and just rounds the index down to the nearest integer. The other is called "Linear" and does a blending of the values at the integer index and the one next to it. + +Once a color is retrieved, multiply all three RBG values by the opacity and add the result to the RGB values in the histogram bucket at the specified location. The alpha channel is unused for transparency and is instead used as a hit counter to record how many times a given bucket was hit during iteration. Each hit adds one to the alpha channel. + +The Cartesian coordinate calculated from applying the xform in the previous step is fed back into the iteration loop and is used as the starting point for repeating the process all over again. The number of times this is done is referred to as the quality and is equal to: + +{{{ +quality * finalwidth * finalheight +}}} + +Note that while supersampling increases the size of the histogram, it does not increase the number of iterations performed. + +
      • +
      • +====Trajectory==== + +Iterating and plotting don't occur exactly in the order described above or in the paper. The point is not plotted immediately after each xform application. Rather, the points are all stored in a temporary buffer whose size defaults to 10,000, known as a sub batch. Once 10,000 iterations have completed, all of the points are plotted to the histogram. Before the next sub batch begins, the point trajectory is reset by re-enabling the fuse state and assigning the first input coordinate random numbers between -1 and 1. + +This method of using sub batches reveals an interesting characteristic of the algorithm not covered in the paper. That is, the point trajectory need not remain continuous to produce a final image. Even when resetting every 10,000 iterations, the trajectory still converges on the attractor. Thanks to this property, multi-threading does not degrade the image quality by breaking up the trajectory, since each thread will run many sub batches.

        + +
      • +
      +
    • +
    • +===Density Filtering=== + +After the iteration loop is performed many times, most of the buckets in the histogram will have been hit many times. This puts their color values far outside the allowed range for display, 0-255 (or 0-1 for normalized colors). + +To bring these color values into the valid range, log scaling is applied. There are two types, basic log scaling or log scaling with density estimation filtering. As stated above, the term density estimation is misleading, since no estimating of any kind takes place. + +Basic log scaling is triggered by setting the maximum density filtering radius to zero. It is achieved by performing the following step on each histogram bucket: + +{{{ +scale = 2^zoom +scaledquality = quality * scale * scale +area = (finalwidth * finalheight) / (pixelsperunitx * pixelsperunity) +k1 = (brightness * 268) / 256 +k2 = supersample^2 / (area * scaledquality * temporalfilter.sumfilt) +accumulator[index] = (k1 * log(1 + histogram[index].hitcount * k2)) / histogram[index].hitcount +}}} + +This calculation is much more complex than the simplistic log(a)/a mentioned in the flam3 paper. Note the presence of the somewhat mysterious k1 and k2 variables. They are mentioned nowhere in the paper, and are completely undocumented in the flam3 code, yet play a large role in how the final output image appears. k1 is intended to be the brightness multiplied by a magic number. k2 helps adjust the log scaling based on the supersample. + +If the max density filtering radius is greater than zero, a much more advanced algorithm is used for filtering. As stated in the paper, it's a Gaussian blur filter whose width is inversely proportional to the number of hits in a given bucket. This means that buckets which were hit infrequently will have a wide blur applied to the surrounding pixels. A bucket with many hits will have very little blur applied. + +The result of these filtering operations is written to another buffer of identical size called the filtering buffer, or accumulator. +
    • +
    • + +===Final Accumulation=== + +Despite filtering, the image is still not ready for final display. One more step is needed, and that is final accumulation with spatial filtering. + +For each pixel in the filtering buffer at the beginning (top left) of each supersample block (SSxSS), a spatial filter is applied and the resulting value is written as a single pixel to the final output image. + +The spatial filter is of a fixed width and type specified in the original Xml. It multiplies the filter values by the pixel values of all pixels extending forward and down by the length of the filter, and sums them to a final value. This final value will most likely be out of range, so further color correction is necessary. Gamma correction is applied and the final pixels are clamped to the valid range of 0-255 and written to the output image buffer. Note that the color correction process is not documented anywhere and remains mostly a mystery, however it works. + +If early clipping is specified, the color correction is applied before the spatial filter. + +The process is done. +
    • +
    +
  • +
\ No newline at end of file diff --git a/Data/Wiki/Building.htm b/Data/Wiki/Building.htm new file mode 100644 index 0000000..167b1c4 --- /dev/null +++ b/Data/Wiki/Building.htm @@ -0,0 +1,232 @@ +#summary How to build Fractorium from source. + + +=Introduction= + +Step by step instructions for building Fractorium and its associated libraries from source. + +The development environment currently supported is Visual Studio 2010 SP1. In the future, other compilers and operating systems will be supported. For now, this page focuses on VS 2010 SP1. + +First install Visual Studio 2010, and run it once. Then close it and install SP1. + +=Details= + +The release built into the installer on the main page is for x64 systems only. This is because x64 gives a ~30% performance improvement over x86, which is most likely due to additional registers in the x64 standard. Another reason for x64 only, is that such processors have been around for almost a decade, with popular operating system support existing since Windows Vista. There is simply no reason to support x86 systems. This project aims to help move the target platforms of popular applications away from x86 and toward x64. + +Sadly, the free version of Visual Studio does not support building x64 targets. You are welcome to build for x86, but understand that the CPU performance will be significantly lower than what you experience with the executable contained in the installer. + +These steps must be followed exactly as stated, and in the order stated. Skipping any step, or doing any step out of order will break the build. + +==Prerequisites== + +These are the libraries that the project depends on. Download the zip files and extract them into folders organized in the following way: + +/glm (Matrix math)

+/libjpeg (Jpg image support)

+/libpng (Png image support)

+/libxml2 (Xml parsing support)

+/tbb (Intel Threading Building Blocks)

+/zlib (Zip compression) + +===libjpeg=== + +libjpeg does not ship with VS 2010 projects, so its older project files must first be converted before opening the solution. + +Open a Visual Studio command prompt and navigate to: + +/libjpeg + +and run: + +{{{ +NMAKE /f makefile.vc setup-v10 +}}} + +This will create all of the necessary project files for libjpeg. + +===libxml2=== + +The libxml2 project is crippled out of the box so it needs some changes before it will open. Further changes will be required later once it's opened. + +Navigate to: + +libxml2\win32 + +and find the file configure.js. Right click on it and click Properties. Ensure the default program used to open it is Windows Based Script Host, click Ok. Double click configure.js and you will see several message boxes telling you the project files it created. + +===OpenCL=== + +Install the latest drivers for your video card. +
    +
  • +====nVidia==== +Install the latest CUDA development kit, which will contain un-advertized OpenCL libraries. + +Get the file cl.hpp and place it here: + +C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v5.0\include\CL +
  • +
  • +====AMD==== +Install the APP SDK. +
  • +
+===Qt=== + +Qt is what Fractorium uses for its GUI. Sadly, the pre-compiled version of Qt cannot be used. Instead, it must be completely rebuilt from source. The reason being that it does not use the desktop OpenGL library and instead uses OpenGL ES. Since Qt is a very large build, you should only do this step if you plan to build the full Fractorium GUI. If you are only interested in the Ember libraries and command line programs, skip this step. + +Further, Qt will not work with Express versions of Visual Studio. + +These instructions roughly follow what's listed here, however they are a bit more concise. + +Install the Visual Studio Qt Add-on found in the Other Downloads section of the Qt downloads page. + +Install Git, Perl, Python and Jom. + +Assuming Jom was installed to C:\jom and Python was installed to C:\Python27, add both of these to your PATH variable: + +{{{ +C:\jom +C:\Python27\DLLs +}}} + +Open a Visual Studio x64 command prompt and cd to the folder which contains all prerequisites and Fractorium. Enter the following commands: + +{{{ +git clone git://gitorious.org/qt/qt5.git qt5 +cd qt5 +git checkout stable +}}} + +This will take roughly 15 minutes. After it's done, add these paths to your PATH variable: + +{{{ +/your/dev/dir/qt5/qtbase/bin +/your/dev/dir/qt5/gnuwin32/bin +}}} + +Add these environment variables: + +{{{ +QMAKESPEC win32-msvc2010 +QTDIR /your/dev/dir/qt5/qtbase +}}} + +Enter the following commands to configure and build: + +{{{ +Perl init-repository –no-webkit +cd qtbase +configure -developer-build -opensource -shared -opengl desktop -platform win32-msvc2010 -nomake examples -nomake tests +jom.exe +}}} + +The jom.exe command is what actually starts the build. Note that you should replace the number 4 with the number of cores on your system. Configuring and building will take roughly one hour. + +Once finished, open Visual Studio and verify there is a menu item named Qt5. Click on it and click Qt Options. + +Add a new Qt version to the list with the exact name of "5.0.1", and set its path to /your/dev/dir/qt5/qtbase + +The name must match exactly and must be created before any Qt solution is opened. If not, the Qt add-in will completely ruin all solution and project files that use Qt. + +Set the default version to the newly created Qt version and click Ok. + +===Fractorium=== + +Next, checkout Fractorium with cvs so that it's on the same level with the other dependencies like so: + +/fractorium + +/glm + +/libjpeg + +/libpng + +/libxml2 + +/qt5 + +/tbb + +/zlib + +To build the installer, you must have Wix installed. If you are unconcerned with it, you can skip this step and just dismiss the warning that shows when opening the solution later. + +==Opening the Solution== +Navigate to the subfolder: + +fractorium/Builds/MSVC/VS2010 + +and open Fractorium.sln + +If all dependencies were placed in the proper hierarchy, the solution should open with no warnings or errors. However, it's not ready to build yet. + +==Build Configuration== + +Visual Studio merges all build configurations and platforms from every project in a solution. Because Fractorium has dependencies on other libraries, the configuration manager will show all configurations from all projects in the solution. Most of these can safely be ignored. The only configurations that matter are Debug/Release. The only platforms that matter are Win32/x64. + +Building other configurations and platforms is not advised and is unsupported. + +Despite x64 support in Visual Studio for many years, most third party libraries do not ship with an x64 build target in their project files. You must manually create them here before you can build for x64. If you are only building for Win32, you can skip this step. + +Right click on the solution and open the Configuration Manager. + +Set the configuration to Release, and the platform to x64. Do the following for jpeg, libpng, libxml2, pnglibconf, and zlib: + +-Click the combo box in the platform column on the project's row and select New... + +-Set New Platform to x64, and Copy Settings From to Win32. Click Ok. Note, Visual Studio has a bug where you may need to do this twice for each project. After you click Ok, click the combo box again to verify it worked. If x64 is shown as a valid platform in the list, it worked. If not, you must repeat this step. + +===Further libxml2 Changes=== + +Right click on the libxml2 project and open the properties dialog and click Configuration Properties. + +Navigate to General, select all configurations and all platforms and set the configuration type to Dynamic Library (.dll). + +Set Output Directory to: + +{{{ +$(SolutionDir)$(Platform)\$(Configuration)\ +}}} + +Set Intermediate Directory to: + +{{{ +$(Platform)\$(Configuration)\ +}}} + +and click Apply. + +Navigate to C/C++ | General and make sure these paths are present in the Additional Include Directories for Debug/Release configurations and Win32/x64 platforms, and click Apply: + +{{{ +$(ProjectDir); +$(ProjectDir)..\..\include; +}}} + +Navigate to C/C++ | Preprocessor, select All Configurations and All Platforms and add ;WIN32 to the list in Preprocessor Definitions, and click Apply. + +Navigate to Linker | Input, select All Configurations and All Platforms and add ;Ws2_32.lib to the list of Additional Dependencies, and click Ok. + +Find the file /include/libxml/xmlversion.h and comment out line 277: + +{{{ +#define LIBXML_ICONV_ENABLED +}}} + +because Fractorium doesn't need Iconv support. + +===zlib Changes=== + +Open the file zutil.h, and comment out line 33: + +{{{ +typedef long ptrdiff_t; +}}} + +The solution is now ready to build. The output will be placed in: + +{{{ +/your/dev/dir/fractorium/Bin/$(Platform)/$(Configuration) +}}} \ No newline at end of file diff --git a/Data/Wiki/CodingPhilosophy.htm b/Data/Wiki/CodingPhilosophy.htm new file mode 100644 index 0000000..03f2580 --- /dev/null +++ b/Data/Wiki/CodingPhilosophy.htm @@ -0,0 +1,15 @@ +#summary Coding philosophy. + + +=Introduction= + +The philosophy behind the code contained in this project. + + +=Details= + +Code is read many more times than it is written. Therefore, legible code is of high importance. This project makes every effort to write understandable, well designed and well documented code. When a person reads the code for the first time, they should not be blindsided with confusion. Instead, they should be able to navigate and understand the code and project structure relatively quickly. + +Modern language techniques like lambdas, templates and the C++ Standard Template Library can greatly simplify code. These are taken advantage of at every possible opportunity. There is simply no valid reason to use legacy coding styles in a project of this nature. + +This project only supports x64 hardware. Given the prevalence of it and operating systems that support it, as well as the performance boost it gives, there is no reason to continue supporting x86. Much like 16-bit programs have disappeared from the development landscape, 32-bit programs have also had their day. \ No newline at end of file diff --git a/Data/Wiki/CommandLinePrograms.htm b/Data/Wiki/CommandLinePrograms.htm new file mode 100644 index 0000000..ad66519 --- /dev/null +++ b/Data/Wiki/CommandLinePrograms.htm @@ -0,0 +1,29 @@ +#summary User's guide for command line programs. + + +=Introduction= + +Usage of EmberRender, EmberAnimate and EmberGenome command line programs. + +These are advanced tools mostly used in a scripted environment. + + +=Details= + +These programs are replacements for the original flam3-render, flam3-animate and flam3-genome programs. They function the same and produce identical output, however they offer two major advantages over the originals: + +-Option to use OpenCL renderer.
+-Ability to use command line arguments in addition to environment variables. The originals only supported the latter.
+ +Running any with the `--help` argument will provide a list of the options available to that program. + +More information on the what these do and the options to pass them can be found in the original flam3 documentation here: + +EmberRender + +EmberAnimate + +EmberGenome + + +The only option omitted was `field` which was used to render progressive scanned images for animations. Modern LCD/LED displays overcome the need for this. \ No newline at end of file diff --git a/Data/Wiki/Developers.htm b/Data/Wiki/Developers.htm new file mode 100644 index 0000000..7553dab --- /dev/null +++ b/Data/Wiki/Developers.htm @@ -0,0 +1,9 @@ +#summary Information for developers. + +[EmberImplementationDetails Ember] + +[EmberCLImplementationDetails EmberCL] + +[Building Building] + +[CodingPhilosophy Coding Philosophy] \ No newline at end of file diff --git a/Data/Wiki/EmberCLImplementationDetails.htm b/Data/Wiki/EmberCLImplementationDetails.htm new file mode 100644 index 0000000..eba7a06 --- /dev/null +++ b/Data/Wiki/EmberCLImplementationDetails.htm @@ -0,0 +1,266 @@ +#summary EmberCL implementation details. + + += Introduction = + +EmberCL resides in a separate project that links to Ember and uses OpenCL to perform iteration, density filtering and final accumulation. Xml parsing, interpolation, and palette setup are still performed by the base library on the CPU. + +OpenCL was chosen because it provides run-time compilation and cross platform interoperability, both of which nVidia's CUDA platform lack. + + += Details = +
    +
  • +==OpenCLWrapper== + +OpenCL programming requires a large amount of setup code before getting to the point where a kernel can be invoked. To relieve the programmer of having to deal with such details, a class named `OpenCLWrapper` is provided. It holds all buffers, images, kernel source and compiled programs in memory and automatically frees them as necessary. Each object can be accessed via name string or index in a vector. + +
  • +
  • + +==Kernel Creators== + +At the heart of OpenCL is the kernel. It's a small program ran as the body of a number of threads executing in parallel. It takes the form of a text string that is compiled at run-time. The compiled binary output is passed to the device running OpenCL along with various arguments and grid dimensions. The grid is is a 1D, 2D or 3D matrix of blocks, and each block is a 1D, 2D or 3D matrix of threads. A full discussion of OpenCL is beyond the scope of this Wiki. + +EmberCL takes a unique approach to building and running kernels for fractal flame rendering that differs from the other two major OpenCL implementations, flam4 and Fractron. The Single Instruction-Multiple Thread (SIMT) nature of the devices OpenCL runs on suffer from an interesting limitation that CPUs do not suffer from. CPUs are very good at processing conditionals, but are slower at doing calculations. SIMT devices are bad at processing conditionals, but are very fast at doing calculations. Because of this, the EmberCL kernel creators build special versions of kernels for each rendering processing step with the conditionals dynamically stripped out at runtime. This creates highly condensed kernels that only run what is absolutely necessary for the Ember currently being rendered. The drawback of this approach is that if the Ember currently being rendered differs enough from the previous one rendered, an OpenCL recompilation will be triggered. These take from one third to one half of a second on a modern processor. + +
      +
    • + +===Iteration Kernel=== + +The iteration kernel is the most advanced, and most important portion of the EmberCL library. It is here that EmberCL achieves its large performance lead over other OpenCL implementations of the fractal flame algorithm. + +
        +
      • + +====The Naive Implementation=== + +The naive implementation copies the code from the CPU implementation to a kernel and each thread runs it in parallel. This is very inefficient and is an improper use of OpenCL. + +SIMT devices excel at executing the same instruction across multiple threads in a group. On nVidia hardware, this group is known as a warp and is 32 threads wide. On AMD hardware, it's known as a wavefront and is 64 threads wide. They operate at peak efficiency when every thread is executing the same instruction at once. If any threads take a different execution path than the others, then warp divergence occurs. Some threads have to sit idle and wait while others complete their operations until they can all get back in sync. This is a waste of resources and prevents the OpenCL device from achieving its full potential. + +This scenario occurs with the naive implementation. If each thread is choosing random xforms to apply, then they will be diverging from all other threads which picked different xforms. A smarter implementation for randomization is needed when using OpenCL, enter cuburn. + +
      • +
      • + +====Randomization Without Warp Divergence==== + +Cuburn was the senior project for 4 students at Central Florida University in the fall of 2011. It investigated this exact issue and came up with a novel solution using Python and CUDA. Their solution is implemented and improved on here in OpenCL and is what gives EmberCL its performance advantage. It takes the following form. + +Each iteration block is 32 threads wide by 8 threads high, giving 256 threads. Each block gets a buffer of on-chip local shared memory with the same dimensions as the block (32x8) to store point iterations to. + +For each iteration, instead of every thread picking a random xform to apply, each row of threads gets a single random xform and all threads in it execute the same xform. The output of each iteration is accumulated to the histogram and also written to a different thread's location within shared memory. + +After each iteration, the process repeats by re-randomizing each row and having each thread use the point at its location in the shared memory buffer, which was the previous output of a different thread, as the input to the xform it applies. This process is repeated 256 times for each thread, giving a total of 65,536 iterations per block. + +The combination of point shuffling and randomizing the xform each row applies on each iteration achieves the goal of eliminating warp divergence while also producing high quality randomization. + +
      • +
      • + +====MWC vs ISAAC==== + +As mentioned in the Ember description, ISAAC is the RNG used in both Ember and flam3. While performing very well on the CPU, it's a poor choice for OpenCL since it would require a large amount of memory for each thread to keep its own copy. An alternative used in cuburn is the multiply-with-carry RNG. EmberCL uses this as well because it gives good randomization while requiring very little memory. Each block is passed a different seed, and each thread adds its index to the seed to ensure that all threads take a different trajectory when using the RNG. + +
      • +
      • + +====Run-time Compilation==== + +As mentioned above, one of the key strengths of OpenCL is run-time compilation. EmberCL takes heavy advantage of this at every opportunity to achieve maximum performance. The CPU implementation has many conditional checks during iteration. These include the presence/absence of post affine transforms, final xforms, palette indexing mode, pre-blur variations as well as virtual functions (or a case statement in flam3) to execute each variation. Such a large number of conditionals would be detrimental to OpenCL performance. Run-time compilation allows us to eliminate these completely. Once the `Ember` to be rendered is known, the kernel to render it is dynamically generated with only the necessary parts included and is compiled on the fly. + +
      • +
      • + +====Race Conditions==== + +One area where EmberCL differs from cuburn is that it does not account for the case of two threads accumulating to the same bucket in the histogram at the same time by default. Cuburn devoted a large portion of the paper to experimenting with every possible way to avoid such a condition. EmberCL ignores these efforts by default because they are mostly unnecessary. The whole point of using the GPU is to get real-time fractal flame rendering, or to make pre-rendered animations more quickly. With animating flames, a few pixels missing a few iteration values will be unnoticeable to the human eye. The small benefit of a clever implementation of such a mechanism is nowhere near being worth the performance hit and additional code complexity. However, is still interested in comparing the differences between locked and unlocked iteration, they can specify the `--lock_accum` argument on the command line for `EmberRender.exe` and `EmberAnimate.exe`. This will prevent race conditions, but will dramatically slow down the performance because the locking is achieved by using software atomic operations which are very slow. + +
      • +
      • + +====Compatibility With CPU==== + +A concern with GPU implementations of any program originally written for a CPU is that it will not be able to implement every feature from the original. EmberCL addresses these concerns by implementing all other features involved with iteration which were originally implemented on the CPU. These are fusing, opacity, bad value detection, xaos, post affine transforms, final xforms, and step vs linear palette indexing for histogram accumulation. +

        + +
      • +
      +
    • + +
    • +===Density Filter Kernel=== + +As mentioned in the algorithm overview description, density filtering can either be basic log scaling, or a more advanced Gaussian blur filter. EmberCL implements both of these. The former is trivial, the latter is very complex. The extreme difficulty of fully implementing density filtering such that it operates efficiently and also gives identical output to the CPU has prevented it from being done elsewhere. EmberCL overcomes this and is the first full implementation of variable width Gaussian density filtering for fractal flames in OpenCL. Seven different methods were tried, with the fastest being the chosen one. There are two main kernels used for density filtering, one with shared memory and one without. + +
        +
      • + +====Shared Memory Kernel==== + +The shared memory kernel is used for final filter widths of 9 or less with float data types due to the limited space of shared memory. As stated in the algorithm overview, density filtering multiplies the log scaled value at a given histogram bucket by a filter value and adds it to the surrounding pixels in the accumulator. This process repeats for the width of the filter and the scaling values decrease as it moves outward from the pixel being operated on. + +When running on a GPU, these repeated reads and writes to global memory are very slow. A better approach is for each thread to read a pixel from the histogram, and perform filtering to a shared memory buffer. Once all threads in the block have finished, the final result from the shared memory box is written to the accumulator. + +In the EmberCL implementation, each block is 32x32 threads, and the box size of the shared memory is the size of the block plus the width of the filter in each direction. So for the commonly used filter width of 9, the box size would be 32 + 9 x 32 + 9, or 41 x 41. Each block processes a box and exits. No column or row advancements take place. + +The filter is applied in a different manner than on the CPU to avoid race conditions. On the CPU, it's applied from the center pixel outward. In OpenCL, it's applied by row from top to bottom. + +Certain variables were reused because the code is so complex, the card runs out of resources for block sizes greater than 24. + +
      • +
      • + +====Non-shared Memory Kernel==== + +The non-shared memory kernel is used for double precision data types or for final filter widths of 10 or more. This is commonly the case when supersampling is used because the final filter width is the supersample value times the max density filter radius. It takes roughly the same form as the shared memory kernel, but omits shared memory and deals directly with the histogram and accumulator for all reads and writes. Due to the excessive global memory accesses in this method, it offers no real performance improvement over the CPU. + +
      • +
      • + +====Filter Overlapping==== + +Both of these methods present a problem when two kernels are operating on an adjacent block of pixels. Although the pixels themselves don't overlap, the filters extending out from the edges of the blocks do overlap. To overcome this, the kernels are launched in multiple passes that are spaced far enough apart vertically and horizontally on the image so as to not overlap. + +
      • +
      • + +====Special Supersampling Cases==== + +Density filtering performs a few extra calculations depending on the supersample value used. To eliminate conditionals and achieve maximum performance, a separate kernel is built for each of these cases for the shared and non-shared memory cases. This leads to a total of 6 possible kernels being built to cover all scenarios. After being built once, the compiled output is saved for all subsequent renders during a program run. +

        + +
      • +
      +
    • + +
    • + +===Final Accumulation Kernel=== + +The implementation of final accumulation in OpenCL is the simplest of the kernels and is copied almost verbatim from the Ember CPU code. To maintain complete compatibility with the CPU, all advanced features such as transparency, early clipping and highlight power are implemented. Like density filtering, unnecessary calculations and conditionals are eliminated by providing different kernels depending on the parameters of the Ember being rendered. They are: + +-Early clipping with transparency.

      +-Early clipping without transparency.

      +-Late clipping with transparency.

      +-Late clipping without transparency. + +All are assumed to have an alpha channel. Three channel RGB output is implemented, but not supported. +

      + +
    • +
    +
  • + +
  • + +==RendererCL== + +The main rendering class in Ember is `Renderer`. EmberCL contains a class which derives from `Renderer` named `RendererCL` and fully supports both single and double precision data types like the base class does. + +`RendererCL` overrides various virtual functions defined in `Renderer` and implements their processing on the GPU in OpenCL. + +
      +
    • + +===Shared vs. Un-shared=== + +`RendererCL` can operate in two modes, shared and un-shared. + +
        +
      • +====Un-shared==== + +The final output is rendered to an OpenCL 2D image which no other running program is accessing. +
      • +
      • + +====Shared==== + +The final output is rendered to an OpenCL 2D image which another program is also using as an OpenGL 2D texture. This is how interactive rendering is done in the Fractorium GUI. Shared mode benefits from the efficiency of a shared image/texture because no copying is necessary and all outputs remain on the GPU. +For sharing to work, every call to create, access or destroy the output image must be preceded by a call to acquire the object from OpenGL and followed by a call to release it. These calls are handled internally by `OpenCLWrapper`. +
      • + +In either of these modes, the output image can be copied back into main memory as needed for use in writing the final output file. +
      +
    • +
    • + +===Parameter Differences=== + +A few user configurable properties from `Renderer` are hard coded in `RendererCL` due to how processing is implemented. + +
        +
      • +====Thread Count==== + +Always considered to be 1, because threading is managed inside the kernels. +
      • +
      • +====Channels==== + +Always 4 because the type of the output image is `CL_RGBA`. Final output file type can only be PNG. +
      • +
      • +====Bits Per Pixel==== + +Always 8, 16bpp for PNG images is only supported in Ember on the CPU. +
      • +
      • +====Sub Batch Size==== + +Always `iterblocksWide * iterblockshigh * 256 * 256` since that is the number of iterations performed in a single kernel call. +
      • +
      +
    • +
    • + +===Kernel Launching=== + +
        +
      • + +====Iteration Grid Dimensions==== + +The iteration kernel is launched in a grid which is 64 blocks wide by 2 blocks high. Each block has 256 (32x8) threads which each perform 256 iterations. This gives 8,388,608 iterations per kernel launch. The grid dimensions were empirically derived and may change in the future as new hardware is released. + +
      • +
      • + +====Passing Arguments==== + +An `Ember` object cannot be passed directly from the CPU side to an OpenCL kernel. Instead, stripped down versions of the `Ember` object and its filters are created and copied right before each kernel launch and are passed as arguments. However, the palette can be passed verbatim since it's just a 256 element `vector>`. + +
      • +
      • + +====Fusing==== + +Fusing is very important for image quality. Omitting it or choosing the wrong value will lead to strange artifacts in the final output image. Since there are so many threads, setting the fuse value is not as simple as just using the same value from the CPU side. + +Forcing each thread to fuse on each kernel call would be a huge waste of resources since each only performs 256 iterations. On the other hand, not fusing often enough after several kernel calls leads to bad image quality. `RendererCL` uses an empirically derived solution of having every thread fuse 100 times for every 4 kernel calls, which is 1024 iterations. + +
      • +
      • + +====Recompilation==== + +As mentioned above, a custom kernel is created and compiled for every `Ember` that is rendered. However, compilation is not always required if the `Ember` to be rendered does not differ significantly from the previous one rendered. Differences in the following parameters will trigger a recompilation: + +-Xform count.

        +-Presence/absence of post affine transform.

        +-Presence/absence of final xform.

        +-Presence/absence of xaos.

        +-Step/linear palette indexing mode.

        +-Variations present in each xform. + +When requesting iteration to commence, the checks above will be made. If any mismatches occur, a recompilation will be triggered right before the kernel launch. + +
      • +
      +
    • +
    +
  • +
\ No newline at end of file diff --git a/Data/Wiki/EmberImplementationDetails.htm b/Data/Wiki/EmberImplementationDetails.htm new file mode 100644 index 0000000..93be61a --- /dev/null +++ b/Data/Wiki/EmberImplementationDetails.htm @@ -0,0 +1,171 @@ +#summary Ember implementation details for developers. + + +=Introduction= + +This page is intended for developers who wish to get familiar with the Ember code. + +As stated on the main page, the intent of Ember was to re-write the entire flam3 library and the 3 command line programs that use it, in C++. By using modern design and language techniques, a legacy code base was made to be easily understandable to the common programmer. The extensibility of C++ also allows derived projects to implement alternative renderers as they are developed, while still maintaining 100% compatibility with the original. Below are the main design features of Ember and how they compare to the original implementation in flam3. + +Ember takes advantage of a few core language features in C++ that help simplify the coding effort. + +
    +
  • +===Templates=== + +Since the process of rendering a fractal flame from start to finish is lengthy, it's interesting to experiment with how using different data types affects performance and image quality. flam3 implemented just such a capability, but since C doesn't have the concept of templates, it did something else. It used a tricky method of strategically positioning #include statements after #defines for each type. C++ provides a more elegant solution through the use of template arguments. Ember supports not only changing the data types of the histogram, but of every calculation used in the entire algorithm. Supported types are float and double. +
  • + +
  • +===Lambdas=== + +These were added to the standard in C++0x and have been a blessing to those implementing multi-threaded programs ever since. Before that, the traditional threading model required a programmer to butcher their design just to achieve parallelism. Modern C++ offers a vast improvement through the use of lambdas. These allow us to write multi-threaded code while keeping good program design structure in tact. Ember achieves this by using the Intel Threading Building Blocks library for all threading needs. +
  • +
+=Details= + +
    +
  • +==Containers== + +Flam3 contained very verbose code for managing seemingly simple memory operations such as keeping a list of xforms. With the C++ Standard Template Library, such code can be greatly reduced. Containers are used extensively throughout Ember, greatly simplifying the code, reducing its verbosity and enhancing its readability. + +
  • +
  • + +==Variations== + +In flam3, for any action to be taken on a variation, such as calling it or setting parameters, a massive case statement had to be used. The number of cases equaled the number of variations supported by that build. If a new variation was added, all case statements had to be updated. + +In Ember, this cumbersome burden was alleviated by making each variation a class which derived from a base variation class. Each implements a virtual function which does the processing work. Other virtual functions are used for setting random or default states for parametric variations. + +In Apophysis, users can add variations by compiling their own DLLs. Ember could conceivably support such a feature in the future by having DLLs return pointers to base variation objects that have been instantiated as derived variations. However, no such support has been implemented. For the time being, new variation classes will periodically be added to Ember. + +
  • +
  • + +==Iterating== + +Iteration is the portion of the algorithm where the most time is spent. Any possible optimization that can be taken, should be taken within the innermost loops. Flam3 missed a few opportunities to do this, so Ember optimized every last piece to the maximum possible extent. + +
      +
    • +===Fusing=== + +As stated in the paper, the first 20 iterations are not plotted in order to get a more concentrated image with less stray points. This is somewhat misleading, since the number flam3 actually uses is 15. If early clipping is used, the number of fuse iterations is 100. + +Further deviating from the paper, fusing is not just done at the very beginning. Rather, all iteration is broken up into chunks, or sub batches, of 10,000. At the beginning of each sub batch, the point trajectory is reset, and fused again. Assuming a fuse value of 15 and a sub batch size of 10,000, fusing takes place for 0.0015 of the total iterations. + +For each iteration, flam3 checks to see whether fusing is done yet, if so, the point is plotted. This is wasteful to check for something every iteration that occurs so infrequently. In the interest of maximum efficiency, Ember splits all iteration up into two identical loops, one with fusing and one without. This reduces the number of conditional checks needed. + +A further optimization opportunity was that when a final xform was present, flam3 applied it during fusing, even though the computed point was never used. Ember omits the application of the final xform during fusing. + +
    • +
    • + +===Point Assignment=== + +The general structure of the iteration loop in flam3 looks like: + +{{{ +p2 = xform(p1) +save p2 to temp buffer for plotting later +p1 = p2 +}}} + +This was optimized to omit the assignment from p2 back to p1 for the start of the next iteration. Instead, no temporary points are ever created. Rather, the indices of the temporary buffer to read from and write to are incremented. Further, the buffer is read from and written to directly, alleviating the need for temporary assignments. It roughly takes the form of: + +{{{ +i = 0 +while (i < SubBatchSize) +{ + xform(buf[i], buf[i + 1]) + i++ +} +}}} + +
    • +
    • + +===Xaos=== + +When xaos is used, an additional calculation must be performed to look up the next random xform to apply. Ember bypasses this when xaos is not present by having two separate classes for iteration, one with xaos and one without. + +
    • +
    +
  • +
  • + +==Filtering== + +For density and spatial filtering, a box of pixels is processed. Flam3 accessed pixels in row, column order. This is cache inefficient because every pixel access is on a different row. Ember optimizes filtering by processing in a more cache-friendly column, row order. + +While flam3 parallelized the Gaussian density filtering, it did not do so for basic log scale filtering (max radius = 0). While this method is seldom used for a final output image, it is used for interactive renders to give a preview image before full iteration is complete. In an effort to give a more responsive GUI, Ember parallelized this method. + +During an interactive render, the only parameters that are usually changing are the affine transforms. Because this doesn't affect many of the other parameters used to render, intelligent checks are used to skip any unnecessary memory allocations each time the main render function is called. This technique is used with density and spatial filters to only recreate them if the requested filter differs from the one previously used. + +
  • +
  • + +==Incremental Rendering== + +Ember is designed to be run from the command line, or from an interactive GUI. To facilitate the latter, the rendering process keeps state information about its progress. This is done so that it can be aborted in mid-render, and resumed later on. It also serves to ensure the minimum amount of processing is performed in response to a change in the `Ember` being rendered. For example, if a render completes and the user only wants to change the vibrancy, then only final accumulation needs to be ran again. Another feature is that if a render completes and the user increases the quality, all previous iteration information is preserved in the histogram and the new iterations for the quality difference are simply added to them. + +The downside of this design is that it admittedly butchers the structure of the main rendering function with numerous conditionals. The cost is worth it as the state-preserving design greatly facilitates interactive rendering from a GUI. + +
  • +
  • + +==Final Accumulation== + +This stage is where color correction and spatial filtering are done. Flam3 did not multi-thread this step because the percentage of the total time spent here is small. Ember easily parallelized it with the aforementioned use of lambdas and TBB. While inconsequential in a headless render, it's very helpful in providing more responsiveness in an interactive render. + +The process was further optimized by eliminating many of the redundant assignments and bounds checks that flam3 did. + +
  • +
  • + +==Xml Parsing== + +While not much of a speed bottleneck, Xml parsing can take some time if reading in a large file, such as is used for animations. Flam3 implemented this in the least efficient manner possible by reading a single character at a time. Ember does this much faster by reading the entire file at once. The structure of the Xml parsing functions remains mostly the same. + +
  • +
  • + +==Affine Transforms== + +In every iteration, these are applied before, and optionally after, variations are applied. Flam3 treated them as an array of 6 coefficients arranged in column, row order. Ember puts them in a class called Affine2D so they can be accessed more clearly using their coefficient labels, A-F. The layout of the two is like so: + +flam3: 3 columns of 2 rows each. Accessed col, row. + +{{{ +[a(0,0)][b(1,0)][c(2,0)] +[d(0,1)][e(1,1)][f(2,1)] +}}} + +Ember: 2 columns of 3 rows each. Accessed col, row. + +{{{ +[a(0,0)][d(1,0)] +[b(0,1)][e(1,1)] +[c(0,2)][f(1,2)] +}}} + +
  • +
  • + +==Matrices== + +All matrices and vectors are from the glm library and receive the same template argument used to create the classes they're used in. + +
  • +
  • + +==Random Numbers== + +Randomization is at the heart of the fractal flames algorithm. Flam3 used the ISAAC random number generator. Ember does the same, but uses a C++ version with a few additional convenience functions added. + +The flam3 method of using a buffer to hold xform indices to randomly select is also used in Ember. However, flam3 made each element an unsigned short. Since an Ember will have nowhere near 256 xforms, the elements were made to be 1 byte each in an effort to make the buffer fit into the cache better. + +
  • +
\ No newline at end of file diff --git a/Data/Wiki/FinalRenderDialog.htm b/Data/Wiki/FinalRenderDialog.htm new file mode 100644 index 0000000..63fb31a --- /dev/null +++ b/Data/Wiki/FinalRenderDialog.htm @@ -0,0 +1,229 @@ +#summary Final render dialog + + +=Final Render Dialog= + +This dialog allows the user to render flames to an output file. It can render either the current flame, or all +open flames. When rendering all, they can either be treated as individual images, or as frames in an animation. If the +later is chosen, temporal samples are used to achieve motion blur. All values specified here will be saved between program runs. + +Before rendering begins, the current flame will be saved back to the opened file in memory. + +Miscellaneous messages are shown in the bottom text box. + +
    +
  • + ==Early Clip== + + Whether to apply color correction before spatial filtering. It's recommended to only use this if the colors don't look right. + + A more thorough discussion of early clip from the original flam3 documentation is here. +
  • + +
  • + ==Transparency== + + Whether the empty pixels in the image should be transparent, or use the background color. This only applies when saving as PNG. +
  • + +
  • + ==Use OpenCL== + + Whether to use OpenCL in the rendering process. It is highly recommended that you use this if your video card supports it. +
  • + +
  • + ==Use Double Precision== + + Whether to use double precision numbers in the rendering process. This will slow down the render and double the memory usage, but will produce a better looking image in some cases. +
  • + +
  • + ==Save Xml== + + Whether to also save the Xml of every rendered image in the same folder the image is saved. +
  • + +
  • + ==Render All== + + Whether to render all currently opened flames, or just the current one. When checked, image and Xml output names will be auto generated. +
  • + +
  • + ==Render as Animation Sequence== + + When Render All is checked, whether to use the Temporal Samples value specified to create motion blurring. Disabled if Render All is unchecked. +
  • + +
  • + ==Keep Aspect Ratio== + + Whether to keep the aspect ratio of the final output image the same as the original flame. When checked, changing the value of one of the dimensions + will cause the other dimension to change a corresponding amount times the aspect ratio of the original.

    + The original dimensions will be those of the render preview window if the flame originated in Fractorium. + If the flame originated from a file or pasted Xml, the original dimensions will be whatever was specified in those parameters. +
  • + +
  • + ==Scale== + + The scaling method to use. This is used to adjust the zoom specified in the scale parameter based on the difference between the original image dimensions and the final image dimensions. + +
      +
    • + ===None=== + + Do not adjust the scale parameter in response to the difference between the original dimensions and the final dimensions. This is useful for cropping without zooming. +
    • + +
    • + ===Width=== + + Scale the scale parameter by the percentage difference between the original width and the final width. +
    • + +
    • + ===Height=== + + Scale the scale parameter by the percentage difference between the original height and the final height. +
    • +
    +
  • + +
  • + ==Render All Extension== + + The image type to use when Render All is checked. Disabled if Render All is unchecked. +
  • + +
  • + ==OpenCL Platforms== + + The available OpenCL platforms on the system. Disabled if Use OpenCL is unchecked. +
  • + +
  • + ==OpenCL Device== + + The available devices on the currently selected platform. Disabled if Use OpenCL is unchecked. +
  • + +
  • + ==Threads== + + The number of threads to use when using the traditional CPU renderer. Disabled if Use OpenCL is unchecked. + + Range: 1 - number of cores. +
  • + +
  • + ==Width== + + The width of the final output image. + + Range: 10 - 100,000. +
  • + +
  • + ==Height== + + The height of the final output image. + + Range: 10 - 100,000. +
  • + +
  • + ==Quality== + + The quality of the final output image. Values above 500 don't offer noticeable improvement. + + Range: 1 - 200,000. +
  • + +
  • + ==Temporal Samples== + + The temporal samples to use when applying motion blur. A value of 1000 is recommended. Only used when Render as Animation Sequence is checked, otherwise a value of 1 is internally used. + + Range: 1 - 5,000. +
  • + +
  • + ==Supersample== + + The value to multiply the dimensions of the histogram and density filter buffer by to help eliminate jagged lines. + Values greater than one will greatly impact performance and will increase memory usage. See the Memory Usage field for the effect. + + While a value of 2 offers some visual improvement, values greater than 2 don't offer noticeable improvement. + + Range: 1 - 4. +
  • + +
  • + ==Memory Usage== + + The amount of memory required for the the entire render, which is the histogram, density filtering buffer and final image. +
  • + +
  • + ==Output== + + The file to save a single image render to, or the folder to save multiple image renders to. This is set by clicking the ... button. + Clicking Open will open the folder location in an explorer window. +
  • + +
  • + ==Prefix== + + The prefix to prepend to all image and Xml files. +
  • + +
  • + ==Suffix== + + The suffix to append to all image and Xml files. +
  • + +
  • + ==Total Progress== + + The percentage of the entire rendering process which has completed. +
  • + +
  • + ==Iteration== + + The percentage of the iteration step in the current image render which has completed. +
  • + +
  • + ==Density Filtering== + + The percentage of the density filtering step in the current image render which has completed. +
  • + +
  • + ==Final Accumulation== + + The percentage of the final color correction and spatial filtering step in the current image render which has completed. This is almost always instantaneous. +
  • + +
  • + ==Start== + + Begin the rendering process. If a render is already running, it will stop it first. +
  • + +
  • + ==Stop== + + Stop the rendering process. +
  • + +
  • + ==Close== + + Stop the rendering process, close the dialog and return to the main window. +
  • +
\ No newline at end of file diff --git a/Data/Wiki/FlameTab.htm b/Data/Wiki/FlameTab.htm new file mode 100644 index 0000000..33109ba --- /dev/null +++ b/Data/Wiki/FlameTab.htm @@ -0,0 +1,319 @@ +#summary Flame tab + + +=Flame Tab Item Descriptions= + +
    +
  • + ==Color== + + These settings affect color. There is no set combination to make a perfect image. Once you've settled on a design you like, + play with different color combinations to give it the desired final look. + +
      +
    • + ===Brightness=== + + The brightness of the final output image. + + Range: 0.05 - 50. + + Render State: Density filtering. +
    • + +
    • + ===Gamma=== + + The gamma of the final output image. Higher values will give better color, but will reveal more scattered points. + Lower values will reduce scattered points but will wash the colors out to white. + + Range: 1 - 9999. + + Render State: Final accumulation. +
    • + +
    • + ===Gamma Threshold=== + + The gamma threshold of the final output image. Higher values will reduce scattered points, but will also reduce color quality. + Lower values will reveal more scattered points, but give better color. + + Range: 0 - 10. + + Render State: Final accumulation. +
    • + +
    • + ===Vibrancy=== + + The scale factor to apply to the alpha channel log scaling when gamma correcting the final output image. Higher values will + give more saturated colors. Lower values will wash the colors out to white. + + Range: 0 - 1. + + Render State: Final accumulation. +
    • + +
    • + ===Highlight Power=== + + The highlight power of the final image. Set this to a value greater than zero if the colors don't look right. + + A more thorough discussion of highlight power from the original flam3 documentation is here and here. + + Range: -1 - 2. + + Render State: Final accumulation. +
    • + +
    • + ===Background=== + + The background color of the image. Ignored on the final image output if transparency is used. + + Range: 0-255 for RGB. + + Render State: Full render. +
    • + +
    • + ===Palette Mode=== + + The mode used for palette indexing when accumulating to the histogram. + + Step: If the specified palette index is a fraction, round down to the nearest integer. + + Linear: Blend the specified index with the one after it. + + Render State: Full render. +
    • +
    +
  • +
  • + ==Geometry== +
      +
    • + ===Width=== + + The width in pixels of the viewable area. Read only. +
    • + +
    • + ===Height=== + + The height in pixels of the viewable area. Read only. +
    • + +
    • + ===Center X=== + + The center offset of the camera. The image will move in the opposite direction on the X axis. + + Range: -10 - 10. + + Render State: Full render. +
    • + +
    • + ===Center Y=== + + The center offset of the camera. The image will move in the opposite direction on the Y axis. + + Range: -10 - 10. + + Render State: Full render. +
    • + +
    • + ===Scale=== + + The number of pixels in the final image that correspond to the distance from 0 to 1 in the Cartesian rendering plane. + Increasing zooms in, decreasing zooms out. Quality is not scaled when this value is adjusted, so increased values + will degrade the final image quality. + + Range: 10 - 3000. + + Render State: Full render. +
    • + +
    • + ===Zoom=== + + The zoom level of the final image. Quality is scaled when this value is adjusted, so rendering time is greatly increased. + + Range: 0 - 5. + + Render State: Full render. +
    • + +
    • + ===Rotate=== + + The rotation of the final image. + + Range: -180 - 180. + + Render State: Full render. +
    • +
    +
  • +
  • + ==Filter== +
      +
    • + ===Spatial Filter Width=== + + The width of the spatial filter applied to the final image. + + Range: 0.1 - 10. + + Render State: Full render. +
    • + +
    • + ===Spatial Filter Type=== + + The type of the spatial filter applied to the final image. + + Render State: Full render. +
    • + +
    • + ===Temporal Filter Width=== + + The width of the temporal filter used during animation. This value has no effect on the + interactive renderer, however it's stored in the Xml when saved. + + Render State: Unchanged. +
    • + +
    • + ===Temporal Filter Type=== + + The type of the temporal filter used during animation. This value has no effect on the + interactive renderer, however it's stored in the Xml when saved. + + Render State: Unchanged. +
    • + +
    • + ===DE Filter Min Radius=== + + The minimum filter radius to use when performing density filtering. Increasing this value + will add additional blurring even in high density areas, which is generally undesirable. This must + always be less than or equal to the DE max radius. + + A more thorough discussion of density filtering from the original flam3 documentation is here. + + Range: 0 - 25. + + Render State: Full render. +
    • + +
    • + ===DE Filter Max Radius=== + + The maximum filter radius to use when performing density filtering. Increasing this value + will add additional blurring only to low density areas, which is generally desirable. This must + always be greater than or equal to the DE min radius. + + When using OpenCL, if this value multiplied by the supersample is greater than 9, the performance + of density filtering will drop to that of the CPU. This is because a filter size greater than 9 + cannot fit into local shared memory. + + Range: 0 - 25. + + Render State: Full render. +
    • + +
    • + ===DE Curve=== + + The speed with which the density filter values decrease when moving away from the center pixel + being filtered. This value will almost never need to be anything other than the default of 0.40. + + Range: 0.01 - 5. + + Render State: Full render. +
    • +
    +
  • +
  • + ==Iteration== +
      +
    • + ===Passes=== + + The number of steps to break iteration into, applying density filtering each time. + + This value should never be anothing other than one and will most likely be removed in a future release. + + Range: 1 - 3. + + Render State: Full render. +
    • + +
    • + ===Temporal Samples=== + + The number of temporal samples used to blend between frames during animation. This value has no effect on the + interactive renderer, however it's stored in the Xml when saved. + + Range: 1 - 5,000. + + Render State: Unchanged. +
    • + +
    • + ===Quality=== + + The number of iterations per pixel in the final output image. Suggested values: + + CPU, Interactive: 10 + + OpenCL, Interactive: 20 - 60 + + Final Render: 2000+ + + Values greater than 2000 don't offer much noticeable improvement. + + Range: 1 - 200,000. + + Render State: Full render. +
    • + +
    • + ===Supersample=== + + The value to multiply the dimensions of the histogram and density filter buffer by + to help eliminate jagged lines. During interactive editing, it should always be one, + and should only be increased when preparing for a final render. Values greater than one + will greatly impact performance and will increase memory usage. + + While a value of 2 offers some visual improvement, values greater than 2 don't offer noticeable improvement. + + Range: 1 - 4. + + Render State: Full render. +
    • + +
    • + ===Affine Interpolation=== + + The method to use when interpolating affine transforms during animation. This value has no effect on the + interactive renderer, however it's stored in the Xml when saved. + + Render State: Unchanged. +
    • + +
    • + ===Interpolation=== + + The method to use when interpolating flames during animation. This value has no effect on the + interactive renderer, however it's stored in the Xml when saved. + + Render State: Unchanged. +
    • +
    +
  • +
\ No newline at end of file diff --git a/Data/Wiki/FractoriumUserGuide.htm b/Data/Wiki/FractoriumUserGuide.htm new file mode 100644 index 0000000..503e730 --- /dev/null +++ b/Data/Wiki/FractoriumUserGuide.htm @@ -0,0 +1,159 @@ +#summary User's guide for Fractorium. + + +=Introduction= + +Fractorium is a fractal flame editor written in C++ with the Qt library. It uses Ember and EmberCL to perform all rendering. + +The intent of Fractorium is to create an editor which has a cleaner interface, easier usage and better performance than any currently available in order to provide the artist with the best possible experience. +

+ +=Details= + +
    +
  • + ==General Usage== + + Fractorium aims to allow the artist maximum creative freedom with the minimal possible effort. To achieve this, the entire program was designed to be used with a mouse containing two buttons and a wheel. Using Fractorium with a less capable input device will most likely be cumbersome and is not supported. + + In keeping with the idea of minimizing effort, a novel feature is that the user need not click in the spinners or combo boxes they are editing. Rather, just hover over them and scroll the mouse wheel. Almost every control in the program is designed to be altered with the mouse wheel. Scrolling it only applies to the control the mouse is hovering over. Once the mouse moves away from the control, it loses focus and mouse wheel scrolling no longer applies to it. + + In addition to being designed for a mouse, the user is also meant to have one hand on the keyboard with their fingers on the shift, ctrl and alt keys. + + Most controls have a default non-zero value. Fractorium makes it easy for the user to switch back and forth between a default value and a reasonable non-default value. Just double click in any control with a default value and you will see the value change to a reasonable non-default value. Double click again and it will change back to the default. + + If you need to enter a specific value with finer granularity than is provided by double clicking or mouse wheel scrolling, you can enter it by hand. Select the text of the control and type in it while the mouse is still hovering over it. Be careful not to move the mouse away, because it will cause the control to lose focus. + + Note that all rendering is done without locking the histogram. While this is theoretically imperfect, it doesn't seem to have any visual impact. An option to control this may be revisited later. +
  • + +
  • + ==Usage Tips== + + A responsive interface has the highest positive impact on user experience. These tips will help get you running in the best possible configuration for your hardware. + + The first step is to enable OpenCL in the options if your video card supports it. This will give incredibly fluid real-time feedback while editing transforms. + + When running with OpenCL enabled, you may find the movement is a little bit jerky if you have a fast card. This is a case of running "too fast". The jerkiness comes from the fact that your card is so fast that it's completing the render in between every mouse movement and is attempting to perform full Gaussian density filtering which can be slow. To avoid this jerkiness, increase the quality slightly to a value between 20 and 50. This will cause the render to take long enough such that full density filtering will not be performed between rapid mouse movements. + + If OpenCL does not work, and you are forced to use the CPU, keep your quality setting on 10. This will give reasonably responsive feedback. + + Regardless of the renderer type being used, do not increase the quality to a high value until your design is solid enough to be ready for a final render. Keep the quality low until you are sure you've got something you want to keep. + + You can optionally select single or double precision numbers. Single is much faster and is highly recommended. Double will produce better image quality but is so slow that it is only recommended when doing a final render. + + Never use supersampling during interactive rendering. It won't produce any noticeable improvement in quality and will greatly slow down performance. Only use it if you want to determine if it will help a final render look better. After experimenting with values greater than one, always restore supersample to one when finished. As a general rule, values higher than 2 don't add any benefit. + + For each mouse movement, a number of iterations are ran before displaying a preview image. This value is controlled by the sub batch count option. Separate values are allowed for CPU and OpenCL. Increase these to get a better preview, decrease them to get more responsive feedback. + + By default, preview images for mouse movements are scaled using basic log density scaling. Only when iteration has fully completed after the mouse is held still is full Gaussian density filtering performed. If you have an extremely fast processor or video card, try setting the filtering method to Full DE. This will perform full Gaussian density filtering on every mouse movement. The processing cost is high, but the feedback is stunning. + + The circles shown on the main display are the affine transforms for each xform. Dragging them around is how most editing is done. Holding down certain keys can alter the effect dragging has on them. + + To get an existing Xml file open in the editor, you can either select it by clicking the File | Open menu, or by dragging and dropping the file onto the window. + +
      +
    • + ====No Keys==== + + X & Y: Rotate and Scale. + + Center: Move. +
    • + +
    • + ====Shift==== + + X & Y: Rotate only. + + Local Pivot: + + Center: Rotate around 0,0, while keeping local orientation fixed. + + World Pivot: + + Center: Rotate around 0,0, also rotating local orientation. +
    • + +
    • + ====Control==== + + X, Y and Center: Snap current movement to grid. +
    • + +
    • + ====Alt==== + + X & Y: Free movement. + + Center: No effect. +
    • + +
    • + ====Shift + Alt==== + + Local Pivot: + + X & Y: Rotate around transform center. + + Center: No effect. + + World Pivot: + + X & Y: Rotate around 0,0. + + Center: No effect. +
    • +
    + + Dragging the image with the right mouse button rotates and scales. + + Dragging the image with the middle mouse button pans. +
  • + +
  • + ==Parameter Descriptions== + + Behavior and recommended usage of all UI elements. + + [Menus Menus]

    + [Toolbar Toolbar] + +
      +
    • + ===Tabs=== +
        +
      • + [FlameTab Flame]

        +
      • +
      • + [XformsTab Xforms]

        +
      • +
      • + [PaletteTab Palette]

        +
      • +
      • + [LibraryTab Library]

        +
      • +
      • + [InfoTab Info]

        +
      • +
      +
    • +
    • + ===Dialogs=== +
        +
      • + [FinalRenderDialog Final Render]

        +
      • +
      • + [OptionsDialog Options]

        +
      • +
      • + [AboutDialog About]

        +
      • +
      +
    • +
    +
  • +
\ No newline at end of file diff --git a/Data/Wiki/InfoTab.htm b/Data/Wiki/InfoTab.htm new file mode 100644 index 0000000..ed8797d --- /dev/null +++ b/Data/Wiki/InfoTab.htm @@ -0,0 +1,45 @@ +#summary Info tab + + +=Info Tab Item Descriptions= + +
    +
  • + ==Histogram Bounds== + + When the histogram is allocated before starting a render, it does not have the exact dimensions the user requested. Instead, it is slightly larger + to allow for filter padding around the edges. The box helps the user understand the relationship between the Cartesian space the histogram represents and the + dimensions of the memory allocated for it. This is mostly of engineering interest. + + The corners going clockwise from the top left correspond to the bounds of the Cartesian space the histogram represents. They are upper left, + upper right, lower right, lower left. + + The values in the middle of the top and left sides of the box represent the height and width of the histogram in memory. + +
      +
    • + ===Gutter=== + + The amount of padding added to the edges of the histogram to allow for filtering. +
    • + +
    • + ===DE Box Dimensions=== + + The size of the density filtering box used, with the pixel being filtered in the center. This value is used in calculating the gutter. +
    • +
    +
  • + +
  • + ==File Opening== + + If there were any warnings or errors opening a file, the details will be displayed here. +
  • + +
  • + ==Rendering== + + If there were any problems creating a renderer, or finishing the rendering process, the details will be displayed here. +
  • +
diff --git a/Data/Wiki/LibraryTab.htm b/Data/Wiki/LibraryTab.htm new file mode 100644 index 0000000..8e2af9a --- /dev/null +++ b/Data/Wiki/LibraryTab.htm @@ -0,0 +1,34 @@ +#summary Library tab + + +=Library Tab Item Descriptions= +
    +
  • + ==Current Flame File== + + This shows a list of all flames present in the currently opened file, or randomly generated flock. When editing, the latest updates to the + current flame will not be saved back to this list in memory, and the preview will not be updated, unless the user specifically does so. This allows restoration of the original flame + if needed. + + + The entire file will not be saved back to disk unless the user specifically does so. + +
      +
    • + ===Single click=== + + Edit the name of the selected flame. This will be used as the name within the Xml file when the user saves it back to disk. + + Render State: Unchanged. +
    • + +
    • + ===Double click=== + + Set the flame as the current one. This will overwrite any edits currently pending, so be sure to save them first before switching between flames. This will also reset the undo list. + + Render State: Full render. +
    • +
    +
  • +
\ No newline at end of file diff --git a/Data/Wiki/MainPage.htm b/Data/Wiki/MainPage.htm new file mode 100644 index 0000000..3a0363e --- /dev/null +++ b/Data/Wiki/MainPage.htm @@ -0,0 +1,101 @@ +=Description= + +A Qt-based fractal flame editor which uses a C++ re-write of the flam3 algorithm named Ember and a GPU capable version named EmberCL which implements a portion of the cuburn algorithm in OpenCL. + +

+ +==Installer (0.3.7.2 Beta)== + +Windows 7 64-bit Installer + +

+ +==Documentation== + +Get started here. + +

+ +==Requirements and Prerequisites== + +Windows 7, 64-bit + +Install the latest drivers for your video card + +An nVidia or AMD video card to take advantage of OpenCL + +If you have an Intel processor, but do not have a device capable of running OpenCL, and still want to run Fractorium with only CPU support, you must install Intel's CPU-only OpenCL libraries here. + +

+ +==Running== + +Fractorium and its associated command line tools can render fractal flames using the CPU, or OpenCL. In order to use OpenCL, you must have an nVidia card that has the Fermi architecture or later, or a recent AMD card. If you attempt to use an unsupported card, you will receive an error message and the CPU renderer will be used instead. +

+ +==Current Status== + + Initial beta releases supporting the following: + + ===Hardware:=== + -CPU: x64 Intel and AMD CPUs. + + -GPU: Recent AMD and nVidia (Fermi and later) cards. + + ===Variations:=== + -The 98 standard variations included with flam3. + + ===Palettes:=== + -The 700 palettes included in the standard flam3-palettes.xml file. + + ===Data Types:=== + -Single and double precision floating point numbers on both the CPU and the GPU. + + ===Compilers:=== + -Microsoft Visual Studio 2010 SP1 (project files upgrade-able to 2012). + + ===Operating Systems:=== + -Windows 7 x64. + + +==Future Direction== + +Help is needed and welcome for implementing the following features: + +-Support for Intel and AMD APU chips. + +-More variations from Apophysis. + +-Support for other compilers, such as gcc and MingW. + +-Support for other operating systems, Mac and Linux. + +-Conversion of OpenGL calls to shader programs. + +-Implementation of more alternative rendering methods. + +-Standalone animator/music visualizer. + +-Benchmarking suite. +

+ +==Gratitude== + +A sincere thanks to the following people. + +Code and theory questions: + + Scott Draves + + Erik Reckase + + Steve Robertson + + Mike Thiesen + +Testing: + + Richard Vollebregt + + Tai + \ No newline at end of file diff --git a/Data/Wiki/Menus.htm b/Data/Wiki/Menus.htm new file mode 100644 index 0000000..e765dd4 --- /dev/null +++ b/Data/Wiki/Menus.htm @@ -0,0 +1,214 @@ +#summary Menus + + +=Menu Item Descriptions= +
    +
  • + ===Menu=== +
      +
    • + ====File==== +
        + +
      • + =====New Flock===== + + Create a new set of 10 randomly generated flames and set the first one as the current flame. This will clear whatever is currently open. + + Render State: Full render. +
      • + +
      • + =====New Empty Flame===== + + Add a new empty flame to the end of the open flames and set it as the current flame. + + Render State: Full render. +
      • + +
      • + =====New Random Flame===== + + Add a new random flame to the end of the open flames and set it as the current flame. + + Render State: Full render. +
      • + +
      • + =====Copy Flame===== + + Add a copy of the current flame to the end of the open flames and set it as the current flame. + + Render State: Full render. +
      • + +
      • + =====Open===== + + Open a flame Xml file. This will clear whatever is currently open. +
      • + +
      • + =====Save Current as Xml===== + + Save the current flame to an Xml file. If it has not yet been saved, a file save dialog will be shown. On subsequent saves, no dialog will be shown + and it will use the filename specified the first time the dialog was shown. +
      • + +
      • + =====Save Entire File as Xml===== + + Save the all currently open flames to a single Xml file. If it has not yet been saved, a file save dialog will be shown. On subsequent saves, no dialog will be shown + and it will use the filename specified the first time the dialog was shown. +
      • + +
      • + =====Save Current Screen===== + + Save the current screen to an image file. +
      • + +
      • + =====Save Current To Open File===== + + Save the current flame back to the open flame list in memory. This does not save anything to disk. +
      • + +
      • + =====Exit===== + + Exit the program. Save all current work before exiting. +
      • +
      +
    • + +
    • + ====Edit==== +
        +
      • + =====Undo===== + + Revert to the previous edit. The undo list is updated upon completion of the rendering process, when not traversing the undo list. + + If an edit is made while traversing the list, the list is cleared. + + Render State: Full render. +
      • + +
      • + =====Redo===== + + If traversing the undo list, move forward to the next edit. + + Render State: Full render. +
      • + +
      • + =====Copy Xml===== + + Copy the current flame as an Xml to the clipboard. +
      • + +
      • + =====Copy All Xmls===== + + Copy all flames in the currently opened file as Xmls to the clipboard. +
      • + +
      • + =====Paste Xml Append===== + + Paste the current clipboard text as a flame appended to the list of currently opened flames. + + Render State: Full render. +
      • + +
      • + =====Paste Xml Over===== + + Paste the current clipboard text over the list of currently opened flames. This will clear whatever is currently open. + + Render State: Full render. +
      • +
      +
    • + +
    • + ====Tools==== +
        +
      • + =====Add Reflective Symmetry===== + + Add an xform that will reflect the image along the Y axis. This is accomplished by giving the xform a weight of one, color speed of 0 and + a single linear variation with a weight of one. Its affine is centered on 0,0 with X at -1,0 and Y at 0,1. + + Render State: Full render. +
      • + +
      • + =====Add Rotational Symmetry===== + + Add an xform that will duplicate a rotated portion of the image along the Y axis. This is accomplished by giving the xform a weight of one, color speed of 0 and + a single linear variation with a weight of one. Its affine is centered on 0,0 with X at -1,0 and Y at 0,-1. + + Render State: Full render. +
      • + +
      • + =====Add Reflective and Rotational Symmetry===== + + Add two xforms, one for reflective symmetry and another for rotational symmetry. + + Render State: Full render. +
      • + +
      • + =====Clear Flame===== + + Clear the current flame such that it only has one xform with no variations, pre and post affine transforms set to the identity matrix, and no xaos. + + Render State: Full render. +
      • + +
      • + =====Render Previews===== + + Re-render all previews. +
      • + +
      • + =====Stop Rendering Previews===== + + Stop rendering previews. +
      • + +
      • + =====Final Render===== + + Display the final rendering dialog. This will stop the render, and restart it from the beginning when the dialog is closed. + + Render State: Full render. +
      • +
      • + =====Options===== + + Display the options dialog. This will stop the render, and restart it from the beginning when the dialog is closed. + + Render State: Full render. +
      • +
      +
    • + +
    • + ====Help==== +
        +
      • + =====About===== + + Show the about box which gives a description, version, and licensing information about the code this project uses. +
      • +
      +
    • +
    +
  • +
\ No newline at end of file diff --git a/Data/Wiki/OptionsDialog.htm b/Data/Wiki/OptionsDialog.htm new file mode 100644 index 0000000..5da9601 --- /dev/null +++ b/Data/Wiki/OptionsDialog.htm @@ -0,0 +1,178 @@ +#summary Options Dialog + + +=Options= + +Options to use in various parts of Fractorium. + +
    +
  • + ==Interactive Rendering== + + Options used when editing flames and displaying them in the output window. + +
      +
    • + ===Early Clip=== + + Whether to apply color correction before spatial filtering. It's recommended to only use this if the colors don't look right. + + A more thorough discussion of early clip from the original flam3 documentation is here. +
    • + +
    • + ===Transparency=== + + This has no effect since the output window will always have the same color as the flame's specified background color. +
    • + +
    • + ===Use OpenCL=== + + Whether to use OpenCL in the rendering process. It is highly recommended that you use this if your video card supports it. +
    • + +
    • + ===Use Double Precision=== + + Whether to use double precision numbers in the rendering process. This will slow down the render and double the memory usage, but will produce a better looking image in some cases. It is recommended you don't use this for interactive rendering on the GPU unless you have an extremely fast graphics card with double precision support. +
    • + +
    • + ===OpenCL Platforms=== + + The available OpenCL platforms on the system. Disabled if Use OpenCL is unchecked. +
    • + +
    • + ===OpenCL Device=== + + The available devices on the currently selected platform. Disabled if Use OpenCL is unchecked. +
    • + +
    • + ===Threads=== + + The number of threads to use when using the traditional CPU renderer. Disabled if Use OpenCL is unchecked. + + Range: 1 - number of cores. +
    • + +
    • + ===CPU Sub Batch=== + + The number of sub batches of 10,000 iterations to run on each mouse movement. Values between 1 and 10 are recommended. + + Higher values give better preview images, but a less responsive UI. Decrease this value if you have a slower processor. +
    • + +
    • + ===OpenCL Sub Batch=== + + The number of sub batches of ~8 million iterations to run on each mouse movement. Values between 1 and 3 are recommended. + + Higher values give better preview images, but a less responsive UI. Decrease this value if you have a slower video card. + + Note that since the sub batch size for the OpenCL renderer is so large, low quality renders can complete on the first sub batch. In that case, + full density filtering will be performed, which can give a choppy UI. In such cases, increase the quality a bit. +
    • + +
    • + ===CPU Filtering=== + + The type of filtering to perform for preview renders on each mouse movement when using the CPU renderer. Log scaling is recommended for all but the fastest processors. However, if + you have a very fast processor and want to see a more realistic representation of what the final output image will look like on every mouse movement, select + the Full DE option. +
    • + +
    • + ===OpenCL Filtering=== + + The type of filtering to perform for preview renders on each mouse movement when using the OpenCL renderer. Log scaling is recommended for all but the fastest video cards. However, if + you have a very fast processor and want to see a more realistic representation of what the final output image will look like on every mouse movement, select + the Full DE option. + + Note that as stated above under OpenCL Sub Batch, even if this option is set to log scaling, full DE might get performed on each mouse movement for low quality renders. +
    • +
    +
  • + +
  • + ==Xml Saving== + + When editing flames, the user will save the parameters as an Xml file once they are satisfied with the result. Most of the values will be displayed exactly as they + are on the UI. However, there are a few that make sense to override each time. + +
      +
    • + ===Width=== + + The width of the final output image. + + Range: 10 - 100,000. +
    • + +
    • + ===Height=== + + The height of the final output image. + + Range: 10 - 100,000. +
    • + +
    • + ===Quality=== + + The quality of the final output image. Values above 2000 don't offer much noticeable improvement. + + Range: 1 - 200,000. +
    • + +
    • + ===Temporal Samples=== + + The temporal samples to use when applying motion blur. A value of 1000 is recommended. Only used during animation, otherwise the value is overridden with one. + + Range: 1 - 5,000. +
    • + +
    • + ===Supersample=== + + The value to multiply the dimensions of the histogram and density filter buffer by to help eliminate jagged lines. + Values greater than one will greatly impact performance and will increase memory usage. + + While a value of 2 offers some visual improvement, values greater than 2 don't offer noticeable improvement. + + Range: 1 - 4. +
    • +
    +
  • + +
  • + ==Identity== + + When saving flame parameters to an Xml file, there is a field for the identity of the artist who made the flame. Fill these out with your identity so that you + get proper attribution for your work. + +
      +
    • + ===Id=== + + The identity of the user. +
    • + +
    • + ===Url=== + + The website of the user. +
    • + +
    • + ===Nick=== + + The nick name of the user. +
    • +
    +
  • +
\ No newline at end of file diff --git a/Data/Wiki/PaletteTab.htm b/Data/Wiki/PaletteTab.htm new file mode 100644 index 0000000..b9d5b50 --- /dev/null +++ b/Data/Wiki/PaletteTab.htm @@ -0,0 +1,108 @@ +#summary Palette tab + + +=Palette Tab Item Descriptions= + +
    + The list of available palettes as well as optional adjustment values. + +
  • + ==Adjustments== + + Adjustments are applied to the selected palette in the following order: frequency, hue, saturation, brightness, contrast, blur. + + Double clicking a spinner will reset it to its default value. + +
      +
    • + ===Hue=== + + The degrees to rotate the hue of the HSV representation of the RGB color by. + + Range: -180 - 180. + + Render State: Full render. +
    • + +
    • + ===Saturation=== + + The percentage to add to the saturation (intensity) component of the HSV representation of the RGB color. + + Range: 0 - 100. + + Render State: Full render. +
    • + +
    • + ===Brightness=== + + The value to add to each channel. Negative values bring it toward black, positive values toward white. + + Range: -255 - 255. + + Render State: Full render. +
    • + +
    • + ===Contrast=== + + The difference between lightest and darkest colors in the palette. Negative values decrease the difference, and + bring the colors toward gray. Positive values increase the difference and make the colors more saturated. + + Range: -100 - 100. + + Render State: Full render. +
    • + +
    • + ===Blur=== + + The width in pixels of the blurring. + + Range: 0 - 127. + + Render State: Full render. +
    • + +
    • + ===Frequency=== + + The number of times to repeat the palette. + + Range: 1 - 10. + + Render State: Full render. +
    • +
    +
  • + +
  • + ==Palette Preview== + + What the final adjusted palette looks like. This is what's used for iteration. +
  • + +
  • + ==Palette List== + + The full list of palettes and their names in the current palette file, which defaults to flam3-palettes.xml. + +
      +
    • + ===Single click=== + + Set the selected palette as the current one and apply the specified adjustments. + + Render State: Full render. +
    • +
    • + ===Double click=== + + Set the selected palette as the current one and reset all adjustments. + + Render State: Full render. +
    • +
    +
  • +
\ No newline at end of file diff --git a/Data/Wiki/ProjectOverview.htm b/Data/Wiki/ProjectOverview.htm new file mode 100644 index 0000000..7fddcc5 --- /dev/null +++ b/Data/Wiki/ProjectOverview.htm @@ -0,0 +1,88 @@ +=Description= + +A Qt-based fractal flame editor which uses a C++ re-write of the flam3 algorithm named Ember and a GPU capable version named EmberCL which implements a portion of the cuburn algorithm in OpenCL. + +

+ +==Installer== + +Windows 7 64-bit Installer + +

+ +==Requirements and Prerequisites== + +Windows 7, 64-bit + +Install the latest drivers for your video card + +An nVidia video card to take advantage of OpenCL + +If you have an Intel processor, but do not have a device capable of running OpenCL, and still want to run Fractorium with only CPU support, you must install Intel's CPU-only OpenCL libraries here. + +

+ +==Running== + +Fractorium and its associated command line tools can render fractal flames using the CPU, or OpenCL. In order to use OpenCL, you must have an nVidia card that has the Fermi architecture or later. If you attempt to use an unsupported card, you will receive an error message and the CPU renderer will be used instead. + +AMD cards will be supported shortly. + +

+ +==Current Status== + + Initial beta release supporting the following: + + ===Hardware:=== + -CPU: x64 Intel and AMD CPUs. + + -GPU: nVidia Fermi and later cards. + + ===Variations:=== + -The 98 standard variations included with flam3. + + ===Palettes:=== + -The 700 palettes included in the standard flam3-palettes.xml file. + + ===Compilers:=== + -Microsoft Visual Studio 2010 (project files upgrade-able to 2012). + + ===Operating Systems:=== + -Windows 7 x64. + + +==Future Direction== + +Help is needed and welcome for implementing the following features: + +-Support for AMD hardware. + +-More variations from Apophysis. + +-Support for other compilers, such as gcc and MingW. + +-Support for other operating systems, Mac and Linux. + +-Conversion of OpenGL calls to shader programs. + +-Implementation of more alternative rendering methods. + +-Standalone animator/music visualizer. + +-Benchmarking suite. + +

+ +==Gratitude== + +A sincere thanks to the following people for answering all of my questions: + +Scott Draves + +Erik Reckase + +Steve Robertson + +Mike Thiesen + \ No newline at end of file diff --git a/Data/Wiki/SideBar.htm b/Data/Wiki/SideBar.htm new file mode 100644 index 0000000..079f6bb --- /dev/null +++ b/Data/Wiki/SideBar.htm @@ -0,0 +1,21 @@ +#summary Sidebar + * [Users] + * [ProjectOverview Overview] + * [AlgorithmExplanation Algorithm] + * [FractoriumUserGuide Fractorium User's Guide] + * [Menus Menus] + * [Toolbar Toolbar] + * [LibraryTab Library Tab] + * [FlameTab Flame Tab] + * [XformsTab Xforms Tab] + * [PaletteTab Palette Tab] + * [InfoTab Info Tab] + * [FinalRenderDialog Final Render Dialog] + * [OptionsDialog Options Dialog] + * [AboutDialog About Dialog] + * [CommandLinePrograms Command Line Programs] + * [Developers] + * [EmberImplementationDetails Ember] + * [EmberCLImplementationDetails EmberCL] + * [Building Building] + * [CodingPhilosophy Coding Philosophy] \ No newline at end of file diff --git a/Data/Wiki/Toolbar.htm b/Data/Wiki/Toolbar.htm new file mode 100644 index 0000000..89dd7e6 --- /dev/null +++ b/Data/Wiki/Toolbar.htm @@ -0,0 +1,9 @@ +#summary Toolbars + +=Toolbar Item Descriptions= + +
    + ===Toolbar=== + + Buttons perform the same functions as the menu items of the same names. +
\ No newline at end of file diff --git a/Data/Wiki/Users.htm b/Data/Wiki/Users.htm new file mode 100644 index 0000000..cfa104f --- /dev/null +++ b/Data/Wiki/Users.htm @@ -0,0 +1,18 @@ +#summary Information for users. + +[ProjectOverview Overview] + +[AlgorithmExplanation Algorithm] + +[FractoriumUserGuide Fractorium User's Guide] + [Menus Menus]

+ [Toolbar Toolbar]

+ [LibraryTab Library]

+ [FlameTab Flame Tab]

+ [XformsTab Xforms]

+ [PaletteTab Palette]

+ [InfoTab Info]

+ [FinalRenderDialog Final Render Dialog]

+ [OptionsDialog Options Dialog]

+ [AboutDialog About Dialog] +[CommandLinePrograms Command Line Programs] \ No newline at end of file diff --git a/Data/Wiki/XformsTab.htm b/Data/Wiki/XformsTab.htm new file mode 100644 index 0000000..7e2dd5b --- /dev/null +++ b/Data/Wiki/XformsTab.htm @@ -0,0 +1,343 @@ +#summary Xforms tab + + +=Xforms Tab Item Descriptions= + +
    +
  • + ==Current Xform== + + The number shown is the index of the currently selected xform. The values on all controls within the xforms tab + will be from the current xform. +
  • + +
  • + ==Add Xform== + + Add an empty xform to the current flame and set it as the current one. It will have no variations and its affine trasforms will be set to the identity matrix + + Render State: Full render. +
  • + +
  • + ==Duplicate Xform== + + Make a copy of the current xform and add it to the end of the xforms and set it as the current one. + + Render State: Full render. +
  • + +
  • + ==Clear Xform Variations== + + Delete all variations from the current xform. + + Render State: Full render. +
  • + +
  • + ==Delete Xform== + + Delete the current xform from the flame. + + Render State: Full render. +
  • + +
  • + ==Add Final Xform== + + Add a final xform if one is not already present. + + Render State: Full render. +
  • + +
  • + ==Weight== + + The probability that the current xform will be chosen among the others during iteration. Note that all weight values + are normalized before iteration begins. + + Render State: Full render. +
  • + +
  • + ==Equalize Weights== + + Set all xform weights to be 1 / xform count. + + Render State: Full render. +
  • + +
  • + ==Name== + + Optional name for this xform to help more easily identify it. Note this values is only used for display purposes + in Fractorium and is not saved to the Xml file. +
  • +
  • + ==Color== +
      + +
    • + ===Color Index=== + + The index in the palette the current xform uses. This value can be changed by scrolling the mouse wheel in the box displaying the value + or by dragging the scroll bar. + + Range: 0 - 1. + + Render State: Full render. +
    • + +
    • + ===Color Speed=== + + The speed with which the color indices are pulled toward the current xform's color index. This value can be negative. + + Range: -1 - 1. + + Render State: Full render. +
    • + +
    • + ===Opacity=== + + How visible the current xform's contribution to the image is. 0 is invisible, 1 is totally visible. + + Range: 0 - 1. + + Render State: Full render. +
    • + +
    • + ===Solo=== + + When checked, the current xform is the only visible one. The text of the checkbox specifies which xform is + the solo one. If none are selected as solo, no number is displayed. This feature is useful for determining how much + each xform contributes to the final image. + + Note that checking this does not affect the opacity values stored in the Xml file when saved. + + Render State: Full render. +
    • + +
    +
  • +
  • + ==Affine== +
      + +
    • + ===Pre Affine Transform=== + + The affine transform applied to the input points before variations are applied on each iteration. + + The values correspond to the usual affine transform of: + + {{{ + tx = Ax * By + C + ty = Dx * Ey + F + }}} + + like so: + + A: X1, D: X2 + + B: Y1, E: Y2 + + C: O1, F: O2 + +
        + +
      • + ====Enable==== + + Checking/unchecking shows/hides pre affine transforms and enables/disables the controls. +
      • + +
      • + ====Reset==== + + Reset the pre affine transform to the identity matrix. + + Render State: Full render. +
      • + +
      • + ====Adjustments==== + + Change the values of the pre affine transform as the tool tips describe. + + Render State: Full render. +
      • + +
      • + ====Show Current/All==== + + Show current only draws a circle around the current xform's pre affine transform. + + Show all draws a circle around all xforms' pre affine transforms. This can sometimes clutter + the view if the flame contains many xforms, hence the option to only show current. +
      • +
      +
    • + +
    • + ===Post Affine Transform=== + + The affine transform applied to the sum of the applying the variations. + + The values correspond to the usual affine transform of: + + {{{ + tx = Ax * By + C + ty = Dx * Ey + F + }}} + + like so: + + A: X1, D: X2 + + B: Y1, E: Y2 + + C: O1, F: O2 + +
        + +
      • + ====Enable==== + + Checking/unchecking shows/hides post affine transforms and enables/disables the controls. +
      • + +
      • + ====Reset==== + + Reset the post affine transform to the identity matrix. + + Render State: Full render. +
      • + +
      • + ====Adjustments==== + + Change the values of the post affine transform as the tool tips describe. + + Render State: Full render. +
      • + +
      • + ====Show Current/All==== + + Show current only draws a circle around the current xform's post affine transform. + + Show all draws a circle around all xforms' post affine transforms. This can sometimes clutter + the view if the flame contains many xforms, hence the option to only show current. +
      • + +
      +
    • + +
    • + ===Pivot=== + +
        +
      • + ====When dragging the X or Y component of an affine transform and holding Shift+Alt:==== + + Local: Rotates the point around the center of the transform. + + World: Rotates the point around 0, 0. +
      • + +
      • + ====When dragging the center of an affine transform and holding Shift:==== + + Local: Rotates entire transform around the origin, keeping its local orientation fixed. + + World: Rotates entire transform around the origin, also rotating the local orientation. +
      • + +
      • + ====When reflecting an affine transform:==== + + Local: Reflect horizontally and vertically around the center of the transform. + + World: Reflect horizontallly around the Y axis, and vertically around the X axis. +
      • +
      +
    • + +
    + +
  • + +
  • + ==Variations== +
      + Each xform has one or more variations contained in it that get applied during each iteration. The value to the right of the + variation name is its weight. Values below it in sub-tree items are for parametric variations. + +
    • + ===Weight=== + + Add a variation to the current xform by scrolling its weight to a non-zero value. Remove it by scrolling + its weight back to zero. Variations present in the current xform will have a gray background to make them easily + identifiable. + + A quick way to add or remove a variation is to double click the weight spinner, which will flip the weight + between 0 and 1. + + Adding or removing variations will trigger an OpenCL recompile, so you will see a slight pause when doing so. + + Render State: Full render. +
    • + +
    • + ===Search=== + + Typing in this box does a case insensitive search which will only show variations + with matching text. To restore all, click the X button to the right. +
    • + +
    • + ===Sorting=== + + Clicking on the left header column will sort by variation ID (which is hidden from the user). + + Clicking on the right header column will sort by weight, placing all variations in the current xform + with non-zero weights at the top. +
    • +
    +
  • + +
  • + ==Xaos== +
      + Xaos is an advanced feature that adds an element of control to the random selection of xforms during iteration. + It adds an adjustment to the probability that a given xform will be selected based on the xform that was selected in the previous iteration. + + Each of the spinners in the right column show a value to adjust the probability of the scenario described in the left column by. + + Values greater than one make it more likely to happen, values less than one make it less likely. Setting all values equal to one indicate no xaos is used. + + Render State: Full render. + +
    • + ===Direction=== + + Different users understand xaos more easily based on the "direction" the terms are specified in. Switching the direction changes the text description in the left column + and changes the spinner values accordingly. + + To: Adjust the probability of each xform being selected when going from the currently selected xform "to" all of the others. + + From: Adjust the probability of the currently selected xform being selected when coming "from" all of the others. +
    • +
    • + ===Clear Xaos=== + + Set all xaos values in all xforms to 1. +
    • +
    +
  • +
\ No newline at end of file diff --git a/Data/flam3-palettes.xml b/Data/flam3-palettes.xml new file mode 100644 index 0000000..d9a258a --- /dev/null +++ b/Data/flam3-palettes.xml @@ -0,0 +1,23135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Data/gplv3.rtf b/Data/gplv3.rtf new file mode 100644 index 0000000..8c1aed3 --- /dev/null +++ b/Data/gplv3.rtf @@ -0,0 +1,474 @@ +{\rtf1\adeflang1025\ansi\ansicpg1252\uc1\adeff31507\deff0\stshfdbch31506\stshfloch31506\stshfhich31506\stshfbi31507\deflang1033\deflangfe1033\themelang1033\themelangfe0\themelangcs0{\fonttbl{\f0\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f1\fbidi \fswiss\fcharset0\fprq2{\*\panose 020b0604020202020204}Arial;} +{\f3\fbidi \froman\fcharset2\fprq2{\*\panose 05050102010706020507}Symbol;}{\f10\fbidi \fnil\fcharset2\fprq2{\*\panose 05000000000000000000}Wingdings;}{\f10\fbidi \fnil\fcharset2\fprq2{\*\panose 05000000000000000000}Wingdings;} +{\f37\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;}{\flomajor\f31500\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} +{\fdbmajor\f31501\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhimajor\f31502\fbidi \froman\fcharset0\fprq2{\*\panose 02040503050406030204}Cambria;} +{\fbimajor\f31503\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\flominor\f31504\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} +{\fdbminor\f31505\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhiminor\f31506\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;} +{\fbiminor\f31507\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f39\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\f40\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} +{\f42\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f43\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f44\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\f45\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} +{\f46\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f47\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f49\fbidi \fswiss\fcharset238\fprq2 Arial CE;}{\f50\fbidi \fswiss\fcharset204\fprq2 Arial Cyr;} +{\f52\fbidi \fswiss\fcharset161\fprq2 Arial Greek;}{\f53\fbidi \fswiss\fcharset162\fprq2 Arial Tur;}{\f54\fbidi \fswiss\fcharset177\fprq2 Arial (Hebrew);}{\f55\fbidi \fswiss\fcharset178\fprq2 Arial (Arabic);} +{\f56\fbidi \fswiss\fcharset186\fprq2 Arial Baltic;}{\f57\fbidi \fswiss\fcharset163\fprq2 Arial (Vietnamese);}{\f409\fbidi \fswiss\fcharset238\fprq2 Calibri CE;}{\f410\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;} +{\f412\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\f413\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;}{\f416\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;}{\f417\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);} +{\flomajor\f31508\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;} +{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} +{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbmajor\f31518\fbidi \froman\fcharset238\fprq2 Times New Roman CE;} +{\fdbmajor\f31519\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbmajor\f31521\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fdbmajor\f31522\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;} +{\fdbmajor\f31523\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbmajor\f31524\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fdbmajor\f31525\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;} +{\fdbmajor\f31526\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhimajor\f31528\fbidi \froman\fcharset238\fprq2 Cambria CE;}{\fhimajor\f31529\fbidi \froman\fcharset204\fprq2 Cambria Cyr;} +{\fhimajor\f31531\fbidi \froman\fcharset161\fprq2 Cambria Greek;}{\fhimajor\f31532\fbidi \froman\fcharset162\fprq2 Cambria Tur;}{\fhimajor\f31535\fbidi \froman\fcharset186\fprq2 Cambria Baltic;} +{\fhimajor\f31536\fbidi \froman\fcharset163\fprq2 Cambria (Vietnamese);}{\fbimajor\f31538\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbimajor\f31539\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} +{\fbimajor\f31541\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbimajor\f31542\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbimajor\f31543\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} +{\fbimajor\f31544\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbimajor\f31545\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbimajor\f31546\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);} +{\flominor\f31548\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\flominor\f31549\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flominor\f31551\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;} +{\flominor\f31552\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flominor\f31553\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flominor\f31554\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} +{\flominor\f31555\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flominor\f31556\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbminor\f31558\fbidi \froman\fcharset238\fprq2 Times New Roman CE;} +{\fdbminor\f31559\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbminor\f31561\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fdbminor\f31562\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;} +{\fdbminor\f31563\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbminor\f31564\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fdbminor\f31565\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;} +{\fdbminor\f31566\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhiminor\f31568\fbidi \fswiss\fcharset238\fprq2 Calibri CE;}{\fhiminor\f31569\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;} +{\fhiminor\f31571\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\fhiminor\f31572\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;}{\fhiminor\f31575\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;} +{\fhiminor\f31576\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);}{\fbiminor\f31578\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbiminor\f31579\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} +{\fbiminor\f31581\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbiminor\f31582\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbiminor\f31583\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} +{\fbiminor\f31584\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbiminor\f31585\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbiminor\f31586\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}} +{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0; +\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;\red53\green56\blue42;}{\*\defchp \f31506\fs22 }{\*\defpap \ql \li0\ri0\sa200\sl276\slmult1 +\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 }\noqfpromote {\stylesheet{\ql \li0\ri0\sa200\sl276\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 +\ltrch\fcs0 \f31506\fs22\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \snext0 \sqformat \spriority0 Normal;}{\s3\ql \li0\ri0\sb100\sa100\sbauto1\saauto1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel2\adjustright\rin0\lin0\itap0 +\rtlch\fcs1 \ab\af0\afs27\alang1025 \ltrch\fcs0 \b\fs27\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext3 \slink15 \sqformat \spriority9 \styrsid8467792 heading 3;}{ +\s4\ql \li0\ri0\sb100\sa100\sbauto1\saauto1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel3\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \ab\af0\afs24\alang1025 \ltrch\fcs0 \b\fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 +\sbasedon0 \snext4 \slink16 \sqformat \spriority9 \styrsid8467792 heading 4;}{\*\cs10 \additive \ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\* +\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv \ql \li0\ri0\sa200\sl276\slmult1 +\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \snext11 \ssemihidden \sunhideused Normal Table;}{\*\cs15 \additive +\rtlch\fcs1 \ab\af0\afs27 \ltrch\fcs0 \b\f0\fs27 \sbasedon10 \slink3 \slocked \spriority9 \styrsid8467792 Heading 3 Char;}{\*\cs16 \additive \rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24 \sbasedon10 \slink4 \slocked \spriority9 \styrsid8467792 +Heading 4 Char;}{\s17\ql \li0\ri0\sb100\sa100\sbauto1\saauto1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs24\alang1025 \ltrch\fcs0 \fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 +\sbasedon0 \snext17 \ssemihidden \sunhideused \styrsid8467792 Normal (Web);}{\*\cs18 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf2 \sbasedon10 \ssemihidden \sunhideused \styrsid8467792 Hyperlink;}}{\*\listtable{\list\listtemplateid1944733200{\listlevel +\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fs20\fbias0 \fi-360\li720\jclisttab\tx720\lin720 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0 +\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fs20\fbias0 \fi-360\li1440\jclisttab\tx1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1 +\lvltentative\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fs20\fbias0 \fi-360\li2160\jclisttab\tx2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0 +\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fs20\fbias0 \fi-360\li2880\jclisttab\tx2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext +\'01\u-3929 ?;}{\levelnumbers;}\f10\fs20\fbias0 \fi-360\li3600\jclisttab\tx3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;} +\f10\fs20\fbias0 \fi-360\li4320\jclisttab\tx4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fs20\fbias0 \fi-360\li5040 +\jclisttab\tx5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fs20\fbias0 \fi-360\li5760\jclisttab\tx5760\lin5760 } +{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fs20\fbias0 \fi-360\li6480\jclisttab\tx6480\lin6480 }{\listname ;}\listid341057278} +{\list\listtemplateid1071024108{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fs20\fbias0 \fi-360\li720\jclisttab\tx720\lin720 }{\listlevel\levelnfc23 +\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fs20\fbias0 \fi-360\li1440\jclisttab\tx1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0 +\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fs20\fbias0 \fi-360\li2160\jclisttab\tx2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1 +\lvltentative\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fs20\fbias0 \fi-360\li2880\jclisttab\tx2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0 +\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fs20\fbias0 \fi-360\li3600\jclisttab\tx3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext +\'01\u-3929 ?;}{\levelnumbers;}\f10\fs20\fbias0 \fi-360\li4320\jclisttab\tx4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;} +\f10\fs20\fbias0 \fi-360\li5040\jclisttab\tx5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fs20\fbias0 \fi-360\li5760 +\jclisttab\tx5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fs20\fbias0 \fi-360\li6480\jclisttab\tx6480\lin6480 } +{\listname ;}\listid598561567}{\list\listtemplateid1482595662{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fs20\fbias0 \fi-360\li720 +\jclisttab\tx720\lin720 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fs20\fbias0 \fi-360\li1440\jclisttab\tx1440\lin1440 }{\listlevel +\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fs20\fbias0 \fi-360\li2160\jclisttab\tx2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0 +\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fs20\fbias0 \fi-360\li2880\jclisttab\tx2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1 +\lvltentative\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fs20\fbias0 \fi-360\li3600\jclisttab\tx3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0 +\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fs20\fbias0 \fi-360\li4320\jclisttab\tx4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext +\'01\u-3929 ?;}{\levelnumbers;}\f10\fs20\fbias0 \fi-360\li5040\jclisttab\tx5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;} +\f10\fs20\fbias0 \fi-360\li5760\jclisttab\tx5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fs20\fbias0 \fi-360\li6480 +\jclisttab\tx6480\lin6480 }{\listname ;}\listid1629435584}}{\*\listoverridetable{\listoverride\listid1629435584\listoverridecount1{\lfolevel\listoverrideformat{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat0\levelspace0 +\levelindent0{\leveltext\'01\u-3913 ?;}{\levelnumbers;}\f3\fs20\fbias0 \fi-360\li720\jclisttab\tx720\lin720 }}\ls1}{\listoverride\listid341057278\listoverridecount1{\lfolevel\listoverrideformat{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0 +\levelfollow0\levelstartat0\levelspace0\levelindent0{\leveltext\'01\u-3913 ?;}{\levelnumbers;}\f3\fs20\fbias0 \fi-360\li720\jclisttab\tx720\lin720 }}\ls2}{\listoverride\listid598561567\listoverridecount1{\lfolevel\listoverrideformat{\listlevel\levelnfc23 +\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat0\levelspace0\levelindent0{\leveltext\'01\u-3913 ?;}{\levelnumbers;}\f3\fs20\fbias0 \fi-360\li720\jclisttab\tx720\lin720 }}\ls3}}{\*\pgptbl {\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}} +{\*\rsidtbl \rsid6316632\rsid8003581\rsid8467792}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}{\info{\author Person}{\operator Person}{\creatim\yr2013\mo11\dy24\hr12\min3} +{\revtim\yr2013\mo11\dy24\hr12\min3}{\version2}{\edmins0}{\nofpages12}{\nofwords4738}{\nofchars27009}{\nofcharsws31684}{\vern49167}}{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/wordml}} +\paperw12240\paperh15840\margl1440\margr1440\margt1440\margb1440\gutter0\ltrsect +\widowctrl\ftnbj\aenddoc\trackmoves0\trackformatting1\donotembedsysfont1\relyonvml0\donotembedlingdata0\grfdocevents0\validatexml1\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors1\noxlattoyen +\expshrtn\noultrlspc\dntblnsbdb\nospaceforul\formshade\horzdoc\dgmargin\dghspace180\dgvspace180\dghorigin1440\dgvorigin1440\dghshow1\dgvshow1 +\jexpand\viewkind1\viewscale100\pgbrdrhead\pgbrdrfoot\splytwnine\ftnlytwnine\htmautsp\nolnhtadjtbl\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\allowfieldendsel\wrppunct +\asianbrkrule\rsidroot6316632\newtblstyruls\nogrowautofit\usenormstyforlist\noindnmbrts\felnbrelev\nocxsptable\indrlsweleven\noafcnsttbl\afelev\utinl\hwelev\spltpgpar\notcvasp\notbrkcnstfrctbl\notvatxbx\krnprsnet\cachedcolbal \nouicompat \fet0 +{\*\wgrffmtfilter 2450}\nofeaturethrottle1\ilfomacatclnup0\ltrpar \sectd \ltrsect\linex0\endnhere\sectlinegrid360\sectdefaultcl\sftnbj {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang +{\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang +{\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}} +\pard\plain \ltrpar\qc \li0\ri0\sb240\sa240\sl360\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel2\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 +\f31506\fs22\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \ab\af1\afs38 \ltrch\fcs0 \b\f1\fs38\cf17\insrsid8467792\charrsid8467792 GNU GENERAL PUBLIC LICENSE +\par }\pard \ltrpar\qc \li0\ri0\sa240\sl312\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f1\fs21\cf17\insrsid8467792\charrsid8467792 Version 3, 29 June 2007 +\par }\pard \ltrpar\ql \li0\ri0\sa240\sl312\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f1\fs21\cf17\insrsid8467792\charrsid8467792 Copyright \'a9 + 2007 Free Software Foundation, Inc. <}{\field\fldedit{\*\fldinst {\rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f1\fs21\cf17\insrsid8467792\charrsid8467792 HYPERLINK "http://fsf.org/" }}{\fldrslt {\rtlch\fcs1 \af1\afs21 \ltrch\fcs0 +\f1\fs21\ul\cf12\insrsid8467792\charrsid8467792 http://fsf.org/}}}\sectd \ltrsect\linex0\endnhere\sectlinegrid360\sectdefaultcl\sftnbj {\rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f1\fs21\cf17\insrsid8467792\charrsid8467792 > +\par Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. +\par }\pard \ltrpar\ql \li0\ri0\sb240\sa240\sl360\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel2\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \ab\af1\afs38 \ltrch\fcs0 \b\f1\fs38\cf17\insrsid8467792\charrsid8467792 +{\*\bkmkstart preamble}{\*\bkmkend preamble}Preamble +\par }\pard \ltrpar\ql \li0\ri0\sa240\sl312\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f1\fs21\cf17\insrsid8467792\charrsid8467792 +The GNU General Public License is a free, copyleft license for software and other kinds of works. +\par The licenses for most software + and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. +\par When we speak of free soft +ware, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want i +t, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. +\par To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you ha +ve certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. +\par For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the reci +pients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. +\par Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. +\par For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both use +rs' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. +\par Some devices are designed to deny users access to install or run modified versio +ns of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals t +o + use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to thos +e domains in future versions of the GPL, as needed to protect the freedom of users. +\par Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers +, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. +\par The precise terms and conditions for copying, distribution and modification follow. +\par }\pard \ltrpar\ql \li0\ri0\sb240\sa240\sl360\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel2\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \ab\af1\afs38 \ltrch\fcs0 \b\f1\fs38\cf17\insrsid8467792\charrsid8467792 +{\*\bkmkstart terms}{\*\bkmkend terms}TERMS AND CONDITIONS +\par }\pard \ltrpar\ql \li0\ri0\sl360\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel3\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \ab\af1\afs31 \ltrch\fcs0 \b\f1\fs31\cf17\insrsid8467792\charrsid8467792 +{\*\bkmkstart section0}{\*\bkmkend section0}0. Definitions. +\par }\pard \ltrpar\ql \li0\ri0\sa240\sl312\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f1\fs21\cf17\insrsid8467792\charrsid8467792 \'93This License\'94 + refers to version 3 of the GNU General Public License. +\par \'93Copyright\'94 also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. +\par \'93The Program\'94 refers to any copyrightable work licensed under this License. Each licensee is addressed as \'93you\'94. \'93Licensees\'94 and \'93recipients\'94 may be individuals or organizations. +\par To \'93modify\'94 a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a \'93modified version\'94 of the earlier work or a work \'93 +based on\'94 the earlier work. +\par A \'93covered work\'94 means either the unmodified Program or a work based on the Program. +\par To \'93propagate\'94 a work means to do anything with it that, without permission, would make you directly or secondarily liable for infr +ingement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as we +ll. +\par To \'93convey\'94 a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. +\par An interactive user interface displays \'93Appropriate Legal Notices\'94 + to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provi +ded), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. +\par }\pard \ltrpar\ql \li0\ri0\sl360\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel3\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \ab\af1\afs31 \ltrch\fcs0 \b\f1\fs31\cf17\insrsid8467792\charrsid8467792 +{\*\bkmkstart section1}{\*\bkmkend section1}1. Source Code. +\par }\pard \ltrpar\ql \li0\ri0\sa240\sl312\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f1\fs21\cf17\insrsid8467792\charrsid8467792 The \'93source code\'94 + for a work means the preferred form of the work for making modifications to it. \'93Object code\'94 means any non-source form of a work. +\par A \'93Standard Interface\'94 means an interface that either is an official standard defined by a recognized standards body, + or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. +\par The \'93System Libraries\'94 of an executable work include anything, other than the work as a whole, that (a) is in +cluded in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available + to the public in source code form. A \'93Major Component\'94 +, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. + +\par The \'93Corresponding Source\'94 for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Cor +r +esponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communi +cation or control flow between those subprograms and other parts of the work. +\par The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. +\par The Corresponding Source for a work in source code form is that same work. +\par }\pard \ltrpar\ql \li0\ri0\sl360\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel3\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \ab\af1\afs31 \ltrch\fcs0 \b\f1\fs31\cf17\insrsid8467792\charrsid8467792 +{\*\bkmkstart section2}{\*\bkmkend section2}2. Basic Permissions. +\par }\pard \ltrpar\ql \li0\ri0\sa240\sl312\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f1\fs21\cf17\insrsid8467792\charrsid8467792 +All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited pe +rmission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided + by copyright law. +\par You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exc +lusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must + do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. +\par Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. +\par }\pard \ltrpar\ql \li0\ri0\sl360\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel3\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \ab\af1\afs31 \ltrch\fcs0 \b\f1\fs31\cf17\insrsid8467792\charrsid8467792 +{\*\bkmkstart section3}{\*\bkmkend section3}3. Protecting Users' Legal Rights From Anti-Circumvention Law. +\par }\pard \ltrpar\ql \li0\ri0\sa240\sl312\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f1\fs21\cf17\insrsid8467792\charrsid8467792 +No covered work shall be deemed part of an effective technological measure under any applicable law fulfill +ing obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. +\par When you convey a covered work, you waive any legal power to forbid circumvention of techn +ological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the wor +k's users, your or third parties' legal rights to forbid circumvention of technological measures. +\par }\pard \ltrpar\ql \li0\ri0\sl360\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel3\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \ab\af1\afs31 \ltrch\fcs0 \b\f1\fs31\cf17\insrsid8467792\charrsid8467792 +{\*\bkmkstart section4}{\*\bkmkend section4}4. Conveying Verbatim Copies. +\par }\pard \ltrpar\ql \li0\ri0\sa240\sl312\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f1\fs21\cf17\insrsid8467792\charrsid8467792 +You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and + appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; a +nd give all recipients a copy of this License along with the Program. +\par You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. +\par }\pard \ltrpar\ql \li0\ri0\sl360\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel3\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \ab\af1\afs31 \ltrch\fcs0 \b\f1\fs31\cf17\insrsid8467792\charrsid8467792 +{\*\bkmkstart section5}{\*\bkmkend section5}5. Conveying Modified Source Versions. +\par }\pard \ltrpar\ql \li0\ri0\sa240\sl312\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f1\fs21\cf17\insrsid8467792\charrsid8467792 +You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: +\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f3\fs20\cf17\insrsid8467792\charrsid8467792 \loch\af3\dbch\af0\hich\f3 \'b7\tab}}\pard \ltrpar\ql \fi-360\li336\ri240\sl312\slmult0\widctlpar +\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls1\adjustright\rin240\lin336\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f1\fs21\cf17\insrsid8467792\charrsid8467792 a) The work m +ust carry prominent notices stating that you modified it, and giving a relevant date. +\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f3\fs20\cf17\insrsid8467792\charrsid8467792 \loch\af3\dbch\af0\hich\f3 \'b7\tab} +b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to \'93keep intact all notices\'94. +\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f3\fs20\cf17\insrsid8467792\charrsid8467792 \loch\af3\dbch\af0\hich\f3 \'b7\tab} +c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the +whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. +\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f3\fs20\cf17\insrsid8467792\charrsid8467792 \loch\af3\dbch\af0\hich\f3 \'b7\tab}d) If the work has interactive +user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. +\par }\pard \ltrpar\ql \li0\ri0\sa240\sl312\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f1\fs21\cf17\insrsid8467792\charrsid8467792 +A compilation of a covered work with other separate and inde +pendent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an \'93aggregate\'94 + if the compilation and its res +ulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. + +\par }\pard \ltrpar\ql \li0\ri0\sl360\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel3\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \ab\af1\afs31 \ltrch\fcs0 \b\f1\fs31\cf17\insrsid8467792\charrsid8467792 +{\*\bkmkstart section6}{\*\bkmkend section6}6. Conveying Non-Source Forms. +\par }\pard \ltrpar\ql \li0\ri0\sa240\sl312\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f1\fs21\cf17\insrsid8467792\charrsid8467792 +You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: +\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f3\fs20\cf17\insrsid8467792\charrsid8467792 \loch\af3\dbch\af0\hich\f3 \'b7\tab}}\pard \ltrpar\ql \fi-360\li336\ri240\sl312\slmult0\widctlpar +\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls2\adjustright\rin240\lin336\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f1\fs21\cf17\insrsid8467792\charrsid8467792 a) Convey the o +bject code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. +\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f3\fs20\cf17\insrsid8467792\charrsid8467792 \loch\af3\dbch\af0\hich\f3 \'b7\tab}b) Convey the object code in, or embodied in +, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the obj +e +ct code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically +performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. +\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f3\fs20\cf17\insrsid8467792\charrsid8467792 \loch\af3\dbch\af0\hich\f3 \'b7\tab} +c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is +allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. +\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f3\fs20\cf17\insrsid8467792\charrsid8467792 \loch\af3\dbch\af0\hich\f3 \'b7\tab} +d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent ac +cess to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corres +p +onding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what ser +ver hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. +\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f3\fs20\cf17\insrsid8467792\charrsid8467792 \loch\af3\dbch\af0\hich\f3 \'b7\tab} +e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. +\par }\pard \ltrpar\ql \li0\ri0\sa240\sl312\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f1\fs21\cf17\insrsid8467792\charrsid8467792 +A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. +\par A \'93User Product\'94 is either (1) a \'93consumer product\'94, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwell +ing. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, \'93normally used\'94 + refers to a typical or common use of that class of product, regardle +ss of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or no +n-consumer uses, unless such uses represent the only significant mode of use of the product. +\par \'93Installation Information\'94 for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versi +ons of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because mod +ification has been made. +\par If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transfer +red to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if n +either you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). +\par The requirement to provide Installation Information does not include a requirement to continue to provi +de support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adv +ersely affects the operation of the network or violates the rules and protocols for communication across the network. +\par Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly docu +mented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. +\par }\pard \ltrpar\ql \li0\ri0\sl360\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel3\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \ab\af1\afs31 \ltrch\fcs0 \b\f1\fs31\cf17\insrsid8467792\charrsid8467792 +{\*\bkmkstart section7}{\*\bkmkend section7}7. Additional Terms. +\par }\pard \ltrpar\ql \li0\ri0\sa240\sl312\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f1\fs21\cf17\insrsid8467792\charrsid8467792 \'93Additional permissions\'94 + are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this Lic +ense, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to +the additional permissions. +\par When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when +you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. +\par Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: +\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f3\fs20\cf17\insrsid8467792\charrsid8467792 \loch\af3\dbch\af0\hich\f3 \'b7\tab}}\pard \ltrpar\ql \fi-360\li336\ri240\sl312\slmult0\widctlpar +\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls3\adjustright\rin240\lin336\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f1\fs21\cf17\insrsid8467792\charrsid8467792 +a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or +\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f3\fs20\cf17\insrsid8467792\charrsid8467792 \loch\af3\dbch\af0\hich\f3 \'b7\tab}b) Requiring preserv +ation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or +\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f3\fs20\cf17\insrsid8467792\charrsid8467792 \loch\af3\dbch\af0\hich\f3 \'b7\tab}c) Prohibiting misrepresentation of the origin of that material, or requiring that modified version +s of such material be marked in reasonable ways as different from the original version; or +\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f3\fs20\cf17\insrsid8467792\charrsid8467792 \loch\af3\dbch\af0\hich\f3 \'b7\tab}d) Limiting the use for publicity purposes of names of licensors or authors of the material; or +\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f3\fs20\cf17\insrsid8467792\charrsid8467792 \loch\af3\dbch\af0\hich\f3 \'b7\tab}e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or + +\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f3\fs20\cf17\insrsid8467792\charrsid8467792 \loch\af3\dbch\af0\hich\f3 \'b7\tab} +f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability t +hat these contractual assumptions directly impose on those licensors and authors. +\par }\pard \ltrpar\ql \li0\ri0\sa240\sl312\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f1\fs21\cf17\insrsid8467792\charrsid8467792 +All other non-permissive additional terms are considered \'93further restrictions\'94 within the meaning of section 10. If the Program as you received it, or any part of it, contai +ns a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to + a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. +\par If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. +\par Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. +\par }\pard \ltrpar\ql \li0\ri0\sl360\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel3\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \ab\af1\afs31 \ltrch\fcs0 \b\f1\fs31\cf17\insrsid8467792\charrsid8467792 +{\*\bkmkstart section8}{\*\bkmkend section8}8. Termination. +\par }\pard \ltrpar\ql \li0\ri0\sa240\sl312\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f1\fs21\cf17\insrsid8467792\charrsid8467792 +You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate you +r rights under this License (including any patent licenses granted under the third paragraph of section 11). +\par However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless a +nd until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. +\par Moreover, your license from a par +ticular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and +you cure the violation prior to 30 days after your receipt of the notice. +\par Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been termi +nated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. +\par }\pard \ltrpar\ql \li0\ri0\sl360\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel3\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \ab\af1\afs31 \ltrch\fcs0 \b\f1\fs31\cf17\insrsid8467792\charrsid8467792 +{\*\bkmkstart section9}{\*\bkmkend section9}9. Acceptance Not Required for Having Copies. +\par }\pard \ltrpar\ql \li0\ri0\sa240\sl312\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f1\fs21\cf17\insrsid8467792\charrsid8467792 +You are not required to accept this License in order to receive or run a copy of the Prog +ram. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify + any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. +\par }\pard \ltrpar\ql \li0\ri0\sl360\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel3\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \ab\af1\afs31 \ltrch\fcs0 \b\f1\fs31\cf17\insrsid8467792\charrsid8467792 +{\*\bkmkstart section10}{\*\bkmkend section10}10. Automatic Licensing of Downstream Recipients. +\par }\pard \ltrpar\ql \li0\ri0\sa240\sl312\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f1\fs21\cf17\insrsid8467792\charrsid8467792 Each time +you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. + +\par An \'93entity transaction\'94 + is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party +to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the + predecessor in interest, if the predecessor has it or can get it with reasonable efforts. +\par You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royal +ty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or +importing the Program or any portion of it. +\par }\pard \ltrpar\ql \li0\ri0\sl360\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel3\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \ab\af1\afs31 \ltrch\fcs0 \b\f1\fs31\cf17\insrsid8467792\charrsid8467792 +{\*\bkmkstart section11}{\*\bkmkend section11}11. Patents. +\par }\pard \ltrpar\ql \li0\ri0\sa240\sl312\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f1\fs21\cf17\insrsid8467792\charrsid8467792 A \'93contributor\'94 + is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's \'93contributor version\'94. +\par A contributor's \'93essential patent claims\'94 + are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling it +s contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, \'93control\'94 + includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. +\par Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propaga +te the contents of its contributor version. +\par In the following three paragraphs, a \'93patent license\'94 is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To \'93 +grant\'94 such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. +\par If you convey a covered work, knowingly relying on a patent license, and the Corresponding Sour +ce of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so availab +le, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. \'93 +Knowingly relying\'94 me +ans you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to + believe are valid. +\par If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them + to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. +\par A patent license is \'93discriminatory\'94 if it does not include w +ithin the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a +t +hird party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the co +v +ered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covere +d work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. +\par Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. +\par }\pard \ltrpar\ql \li0\ri0\sl360\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel3\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \ab\af1\afs31 \ltrch\fcs0 \b\f1\fs31\cf17\insrsid8467792\charrsid8467792 +{\*\bkmkstart section12}{\*\bkmkend section12}12. No Surrender of Others' Freedom. +\par }\pard \ltrpar\ql \li0\ri0\sa240\sl312\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f1\fs21\cf17\insrsid8467792\charrsid8467792 +If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of +this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate + you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. +\par }\pard \ltrpar\ql \li0\ri0\sl360\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel3\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \ab\af1\afs31 \ltrch\fcs0 \b\f1\fs31\cf17\insrsid8467792\charrsid8467792 +{\*\bkmkstart section13}{\*\bkmkend section13}13. Use with the GNU Affero General Public License. +\par }\pard \ltrpar\ql \li0\ri0\sa240\sl312\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f1\fs21\cf17\insrsid8467792\charrsid8467792 +Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work +. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. + +\par }\pard \ltrpar\ql \li0\ri0\sl360\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel3\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \ab\af1\afs31 \ltrch\fcs0 \b\f1\fs31\cf17\insrsid8467792\charrsid8467792 +{\*\bkmkstart section14}{\*\bkmkend section14}14. Revised Versions of this License. +\par }\pard \ltrpar\ql \li0\ri0\sa240\sl312\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f1\fs21\cf17\insrsid8467792\charrsid8467792 +The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +\par Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License \'93or any later version\'94 applies to it, you have the option of following the term +s and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free S +oftware Foundation. +\par If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. +\par Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. +\par }\pard \ltrpar\ql \li0\ri0\sl360\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel3\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \ab\af1\afs31 \ltrch\fcs0 \b\f1\fs31\cf17\insrsid8467792\charrsid8467792 +{\*\bkmkstart section15}{\*\bkmkend section15}15. Disclaimer of Warranty. +\par }\pard \ltrpar\ql \li0\ri0\sa240\sl312\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f1\fs21\cf17\insrsid8467792\charrsid8467792 THERE IS NO W +ARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \'93AS IS\'94 + WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. +\par }\pard \ltrpar\ql \li0\ri0\sl360\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel3\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \ab\af1\afs31 \ltrch\fcs0 \b\f1\fs31\cf17\insrsid8467792\charrsid8467792 +{\*\bkmkstart section16}{\*\bkmkend section16}16. Limitation of Liability. +\par }\pard \ltrpar\ql \li0\ri0\sa240\sl312\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f1\fs21\cf17\insrsid8467792\charrsid8467792 +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILUR +E OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +\par }\pard \ltrpar\ql \li0\ri0\sl360\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel3\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \ab\af1\afs31 \ltrch\fcs0 \b\f1\fs31\cf17\insrsid8467792\charrsid8467792 +{\*\bkmkstart section17}{\*\bkmkend section17}17. Interpretation of Sections 15 and 16. +\par }\pard \ltrpar\ql \li0\ri0\sa240\sl312\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid8467792 \cbpat8 {\rtlch\fcs1 \af1\afs21 \ltrch\fcs0 \f1\fs21\cf17\insrsid8467792\charrsid8467792 +If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connec +tion with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. +\par END OF TERMS AND CONDITIONS +\par }\pard \ltrpar\ql \li0\ri0\sa200\sl276\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 {\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid8003581 +\par }{\*\themedata 504b030414000600080000002100e9de0fbfff0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cb4ec3301045f748fc83e52d4a +9cb2400825e982c78ec7a27cc0c8992416c9d8b2a755fbf74cd25442a820166c2cd933f79e3be372bd1f07b5c3989ca74aaff2422b24eb1b475da5df374fd9ad +5689811a183c61a50f98f4babebc2837878049899a52a57be670674cb23d8e90721f90a4d2fa3802cb35762680fd800ecd7551dc18eb899138e3c943d7e503b6 +b01d583deee5f99824e290b4ba3f364eac4a430883b3c092d4eca8f946c916422ecab927f52ea42b89a1cd59c254f919b0e85e6535d135a8de20f20b8c12c3b0 +0c895fcf6720192de6bf3b9e89ecdbd6596cbcdd8eb28e7c365ecc4ec1ff1460f53fe813d3cc7f5b7f020000ffff0300504b030414000600080000002100a5d6 +a7e7c0000000360100000b0000005f72656c732f2e72656c73848fcf6ac3300c87ef85bd83d17d51d2c31825762fa590432fa37d00e1287f68221bdb1bebdb4f +c7060abb0884a4eff7a93dfeae8bf9e194e720169aaa06c3e2433fcb68e1763dbf7f82c985a4a725085b787086a37bdbb55fbc50d1a33ccd311ba548b6309512 +0f88d94fbc52ae4264d1c910d24a45db3462247fa791715fd71f989e19e0364cd3f51652d73760ae8fa8c9ffb3c330cc9e4fc17faf2ce545046e37944c69e462 +a1a82fe353bd90a865aad41ed0b5b8f9d6fd010000ffff0300504b0304140006000800000021006b799616830000008a0000001c0000007468656d652f746865 +6d652f7468656d654d616e616765722e786d6c0ccc4d0ac3201040e17da17790d93763bb284562b2cbaebbf600439c1a41c7a0d29fdbd7e5e38337cedf14d59b +4b0d592c9c070d8a65cd2e88b7f07c2ca71ba8da481cc52c6ce1c715e6e97818c9b48d13df49c873517d23d59085adb5dd20d6b52bd521ef2cdd5eb9246a3d8b +4757e8d3f729e245eb2b260a0238fd010000ffff0300504b03041400060008000000210030dd4329a8060000a41b0000160000007468656d652f7468656d652f +7468656d65312e786d6cec594f6fdb3614bf0fd87720746f6327761a07758ad8b19b2d4d1bc46e871e698996d850a240d2497d1bdae38001c3ba618715d86d87 +615b8116d8a5fb34d93a6c1dd0afb0475292c5585e9236d88aad3e2412f9e3fbff1e1fa9abd7eec70c1d1221294fda5efd72cd4324f1794093b0eddd1ef62fad +79482a9c0498f184b4bd2991deb58df7dfbb8ad755446282607d22d771db8b944ad79796a40fc3585ee62949606ecc458c15bc8a702910f808e8c66c69b9565b +5d8a314d3c94e018c8de1a8fa94fd05093f43672e23d06af89927ac06762a049136785c10607758d9053d965021d62d6f6804fc08f86e4bef210c352c144dbab +999fb7b4717509af678b985ab0b6b4ae6f7ed9ba6c4170b06c788a705430adf71bad2b5b057d03606a1ed7ebf5babd7a41cf00b0ef83a6569632cd467faddec9 +699640f6719e76b7d6ac355c7c89feca9cccad4ea7d36c65b258a206641f1b73f8b5da6a6373d9c11b90c537e7f08dce66b7bbeae00dc8e257e7f0fd2badd586 +8b37a088d1e4600ead1ddaef67d40bc898b3ed4af81ac0d76a197c86826828a24bb318f3442d8ab518dfe3a20f000d6458d104a9694ac6d88728eee2782428d6 +0cf03ac1a5193be4cbb921cd0b495fd054b5bd0f530c1931a3f7eaf9f7af9e3f45c70f9e1d3ff8e9f8e1c3e3073f5a42ceaa6d9c84e5552fbffdeccfc71fa33f +9e7ef3f2d117d57859c6fffac327bffcfc793510d26726ce8b2f9ffcf6ecc98baf3efdfdbb4715f04d814765f890c644a29be408edf3181433567125272371be +15c308d3f28acd249438c19a4b05fd9e8a1cf4cd296699771c393ac4b5e01d01e5a30a787d72cf1178108989a2159c77a2d801ee72ce3a5c545a6147f32a9979 +3849c26ae66252c6ed637c58c5bb8b13c7bfbd490a75330f4b47f16e441c31f7184e140e494214d273fc80900aedee52ead87597fa824b3e56e82e451d4c2b4d +32a423279a668bb6690c7e9956e90cfe766cb37b077538abd27a8b1cba48c80acc2a841f12e698f13a9e281c57911ce298950d7e03aba84ac8c154f8655c4f2a +f074481847bd804859b5e696007d4b4edfc150b12addbecba6b18b148a1e54d1bc81392f23b7f84137c2715a851dd0242a633f900710a218ed715505dfe56e86 +e877f0034e16bafb0e258ebb4faf06b769e888340b103d331115bebc4eb813bf83291b63624a0d1475a756c734f9bbc2cd28546ecbe1e20a3794ca175f3fae90 +fb6d2dd99bb07b55e5ccf68942bd0877b23c77b908e8db5f9db7f024d9239010f35bd4bbe2fcae387bfff9e2bc289f2fbe24cfaa301468dd8bd846dbb4ddf1c2 +ae7b4c191ba8292337a469bc25ec3d411f06f53a73e224c5292c8de0516732307070a1c0660d125c7d44553488700a4d7bddd3444299910e254ab984c3a219ae +a4adf1d0f82b7bd46cea4388ad1c12ab5d1ed8e1153d9c9f350a3246aad01c6873462b9ac05999ad5cc988826eafc3acae853a33b7ba11cd1445875ba1b236b1 +399483c90bd560b0b0263435085a21b0f22a9cf9356b38ec6046026d77eba3dc2dc60b17e92219e180643ed27acffba86e9c94c7ca9c225a0f1b0cfae0788ad5 +4adc5a9aec1b703b8b93caec1a0bd8e5de7b132fe5113cf312503b998e2c2927274bd051db6b35979b1ef271daf6c6704e86c73805af4bdd476216c26593af84 +0dfb5393d964f9cc9bad5c313709ea70f561ed3ea7b053075221d51696910d0d339585004b34272bff7213cc7a510a5454a3b349b1b206c1f0af490176745d4b +c663e2abb2b34b23da76f6352ba57ca2881844c1111ab189d8c7e07e1daaa04f40255c77988aa05fe06e4e5bdb4cb9c5394bbaf28d98c1d971ccd20867e556a7 +689ec9166e0a522183792b8907ba55ca6e943bbf2a26e52f48957218ffcf54d1fb09dc3eac04da033e5c0d0b8c74a6b43d2e54c4a10aa511f5fb021a07533b20 +5ae07e17a621a8e082dafc17e450ffb739676998b48643a4daa7211214f623150942f6a02c99e83b85583ddbbb2c4996113211551257a656ec1139246ca86be0 +aadedb3d1441a89b6a929501833b197fee7b9641a3503739e57c732a59b1f7da1cf8a73b1f9bcca0945b874d4393dbbf10b1680f66bbaa5d6f96e77b6f59113d +316bb31a795600b3d256d0cad2fe354538e7566b2bd69cc6cbcd5c38f0e2bcc63058344429dc2121fd07f63f2a7c66bf76e80d75c8f7a1b622f878a18941d840 +545fb28d07d205d20e8ea071b283369834296bdaac75d256cb37eb0bee740bbe278cad253b8bbfcf69eca23973d939b97891c6ce2cecd8da8e2d343578f6648a +c2d0383fc818c798cf64e52f597c740f1cbd05df0c264c49134cf09d4a60e8a107260f20f92d47b374e32f000000ffff0300504b030414000600080000002100 +0dd1909fb60000001b010000270000007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73848f4d0ac2301484f7 +8277086f6fd3ba109126dd88d0add40384e4350d363f2451eced0dae2c082e8761be9969bb979dc9136332de3168aa1a083ae995719ac16db8ec8e4052164e89 +d93b64b060828e6f37ed1567914b284d262452282e3198720e274a939cd08a54f980ae38a38f56e422a3a641c8bbd048f7757da0f19b017cc524bd62107bd500 +1996509affb3fd381a89672f1f165dfe514173d9850528a2c6cce0239baa4c04ca5bbabac4df000000ffff0300504b01022d0014000600080000002100e9de0f +bfff0000001c0200001300000000000000000000000000000000005b436f6e74656e745f54797065735d2e786d6c504b01022d0014000600080000002100a5d6 +a7e7c0000000360100000b00000000000000000000000000300100005f72656c732f2e72656c73504b01022d00140006000800000021006b799616830000008a +0000001c00000000000000000000000000190200007468656d652f7468656d652f7468656d654d616e616765722e786d6c504b01022d00140006000800000021 +0030dd4329a8060000a41b00001600000000000000000000000000d60200007468656d652f7468656d652f7468656d65312e786d6c504b01022d001400060008 +00000021000dd1909fb60000001b0100002700000000000000000000000000b20900007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73504b050600000000050005005d010000ad0a00000000} +{\*\colorschememapping 3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d3822207374616e64616c6f6e653d22796573223f3e0d0a3c613a636c724d +617020786d6c6e733a613d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f64726177696e676d6c2f323030362f6d6169 +6e22206267313d226c743122207478313d22646b3122206267323d226c743222207478323d22646b322220616363656e74313d22616363656e74312220616363 +656e74323d22616363656e74322220616363656e74333d22616363656e74332220616363656e74343d22616363656e74342220616363656e74353d22616363656e74352220616363656e74363d22616363656e74362220686c696e6b3d22686c696e6b2220666f6c486c696e6b3d22666f6c486c696e6b222f3e} +{\*\latentstyles\lsdstimax267\lsdlockeddef0\lsdsemihiddendef1\lsdunhideuseddef1\lsdqformatdef0\lsdprioritydef99{\lsdlockedexcept \lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority0 \lsdlocked0 Normal; +\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 1;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 2;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 3;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 4; +\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 5;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 6;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 7;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 8;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 9; +\lsdpriority39 \lsdlocked0 toc 1;\lsdpriority39 \lsdlocked0 toc 2;\lsdpriority39 \lsdlocked0 toc 3;\lsdpriority39 \lsdlocked0 toc 4;\lsdpriority39 \lsdlocked0 toc 5;\lsdpriority39 \lsdlocked0 toc 6;\lsdpriority39 \lsdlocked0 toc 7; +\lsdpriority39 \lsdlocked0 toc 8;\lsdpriority39 \lsdlocked0 toc 9;\lsdqformat1 \lsdpriority35 \lsdlocked0 caption;\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority10 \lsdlocked0 Title;\lsdpriority1 \lsdlocked0 Default Paragraph Font; +\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority11 \lsdlocked0 Subtitle;\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority22 \lsdlocked0 Strong;\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority20 \lsdlocked0 Emphasis; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority59 \lsdlocked0 Table Grid;\lsdunhideused0 \lsdlocked0 Placeholder Text;\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority1 \lsdlocked0 No Spacing; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority60 \lsdlocked0 Light Shading;\lsdsemihidden0 \lsdunhideused0 \lsdpriority61 \lsdlocked0 Light List;\lsdsemihidden0 \lsdunhideused0 \lsdpriority62 \lsdlocked0 Light Grid; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority63 \lsdlocked0 Medium Shading 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority64 \lsdlocked0 Medium Shading 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority65 \lsdlocked0 Medium List 1; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority66 \lsdlocked0 Medium List 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority67 \lsdlocked0 Medium Grid 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority68 \lsdlocked0 Medium Grid 2; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority69 \lsdlocked0 Medium Grid 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority70 \lsdlocked0 Dark List;\lsdsemihidden0 \lsdunhideused0 \lsdpriority71 \lsdlocked0 Colorful Shading; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority72 \lsdlocked0 Colorful List;\lsdsemihidden0 \lsdunhideused0 \lsdpriority73 \lsdlocked0 Colorful Grid;\lsdsemihidden0 \lsdunhideused0 \lsdpriority60 \lsdlocked0 Light Shading Accent 1; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority61 \lsdlocked0 Light List Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority62 \lsdlocked0 Light Grid Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 1; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority65 \lsdlocked0 Medium List 1 Accent 1;\lsdunhideused0 \lsdlocked0 Revision; +\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority34 \lsdlocked0 List Paragraph;\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority29 \lsdlocked0 Quote;\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority30 \lsdlocked0 Intense Quote; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority66 \lsdlocked0 Medium List 2 Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 1; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority70 \lsdlocked0 Dark List Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority71 \lsdlocked0 Colorful Shading Accent 1; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority72 \lsdlocked0 Colorful List Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority73 \lsdlocked0 Colorful Grid Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority60 \lsdlocked0 Light Shading Accent 2; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority61 \lsdlocked0 Light List Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority62 \lsdlocked0 Light Grid Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 2; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority65 \lsdlocked0 Medium List 1 Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority66 \lsdlocked0 Medium List 2 Accent 2; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 2; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority70 \lsdlocked0 Dark List Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority71 \lsdlocked0 Colorful Shading Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority72 \lsdlocked0 Colorful List Accent 2; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority73 \lsdlocked0 Colorful Grid Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority60 \lsdlocked0 Light Shading Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority61 \lsdlocked0 Light List Accent 3; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority62 \lsdlocked0 Light Grid Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 3; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority65 \lsdlocked0 Medium List 1 Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority66 \lsdlocked0 Medium List 2 Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 3; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority70 \lsdlocked0 Dark List Accent 3; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority71 \lsdlocked0 Colorful Shading Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority72 \lsdlocked0 Colorful List Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority73 \lsdlocked0 Colorful Grid Accent 3; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority60 \lsdlocked0 Light Shading Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority61 \lsdlocked0 Light List Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority62 \lsdlocked0 Light Grid Accent 4; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority65 \lsdlocked0 Medium List 1 Accent 4; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority66 \lsdlocked0 Medium List 2 Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 4; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority70 \lsdlocked0 Dark List Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority71 \lsdlocked0 Colorful Shading Accent 4; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority72 \lsdlocked0 Colorful List Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority73 \lsdlocked0 Colorful Grid Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority60 \lsdlocked0 Light Shading Accent 5; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority61 \lsdlocked0 Light List Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority62 \lsdlocked0 Light Grid Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 5; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority65 \lsdlocked0 Medium List 1 Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority66 \lsdlocked0 Medium List 2 Accent 5; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 5; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority70 \lsdlocked0 Dark List Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority71 \lsdlocked0 Colorful Shading Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority72 \lsdlocked0 Colorful List Accent 5; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority73 \lsdlocked0 Colorful Grid Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority60 \lsdlocked0 Light Shading Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority61 \lsdlocked0 Light List Accent 6; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority62 \lsdlocked0 Light Grid Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 6; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority65 \lsdlocked0 Medium List 1 Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority66 \lsdlocked0 Medium List 2 Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 6; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority70 \lsdlocked0 Dark List Accent 6; +\lsdsemihidden0 \lsdunhideused0 \lsdpriority71 \lsdlocked0 Colorful Shading Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority72 \lsdlocked0 Colorful List Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority73 \lsdlocked0 Colorful Grid Accent 6; +\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority19 \lsdlocked0 Subtle Emphasis;\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority21 \lsdlocked0 Intense Emphasis; +\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority31 \lsdlocked0 Subtle Reference;\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority32 \lsdlocked0 Intense Reference; +\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority33 \lsdlocked0 Book Title;\lsdpriority37 \lsdlocked0 Bibliography;\lsdqformat1 \lsdpriority39 \lsdlocked0 TOC Heading;}}{\*\datastore 010500000200000018000000 +4d73786d6c322e534158584d4c5265616465722e362e3000000000000000000000060000 +d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff090006000000000000000000000001000000010000000000000000100000feffffff00000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +fffffffffffffffffdfffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffff0c6ad98892f1d411a65f0040963251e5000000000000000000000000401f +3d3b50e9ce01feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000105000000000000}} \ No newline at end of file diff --git a/Source/Ember/Affine2D.cpp b/Source/Ember/Affine2D.cpp new file mode 100644 index 0000000..3023065 --- /dev/null +++ b/Source/Ember/Affine2D.cpp @@ -0,0 +1,352 @@ +#include "EmberPch.h" +#include "Affine2D.h" + +namespace EmberNs +{ +/// +/// Default constructor which sets the matrix to the identity. +/// +template +Affine2D::Affine2D() +{ + MakeID(); +} + +/// +/// Constructor which takes each column of the affine as a separate parameter. +/// +/// A and D +/// B and E +/// C and F +template +Affine2D::Affine2D(v2T& x, v2T& y, v2T& t) +{ + X(x); + Y(y); + O(t); +} + +/// +/// Constructor which takes all six of the affine values as parameters. +/// +/// A +/// D +/// B +/// E +/// C +/// F +template +Affine2D::Affine2D(T xx, T xy, T yx, T yy, T tx, T ty) +{ + A(xx); + D(xy); + B(yx); + E(yy); + C(tx); + F(ty); +} + +/// +/// Constructor which takes a 4x4 matrix and assigns the +/// corresponding values in the 2x3 affine matrix. +/// +/// The 4x4 affine matrix to read from +template +Affine2D::Affine2D(m4T& mat) +{ + A(mat[0][0]); + B(mat[0][1]); + C(mat[0][3]); + D(mat[1][0]); + E(mat[1][1]); + F(mat[1][3]); +} + +/// +/// Make this affine transform the identity matrix. +/// A and E = 1, all else 0. +/// +template +void Affine2D::MakeID() +{ + A(1); + B(0); + C(0); + D(0); + E(1); + F(0); +} + +/// +/// Determine whether this affine transform is the identity matrix. +/// +/// True if A and E are equal to 1 and all others are 0, else false. +template +bool Affine2D::IsID() const +{ + return (IsClose(A(), 1)) && + (IsClose(B(), 0)) && + (IsClose(C(), 0)) && + (IsClose(D(), 0)) && + (IsClose(E(), 1)) && + (IsClose(F(), 0)); +} + +/// +/// Determine whether this affine transform is all zeroes. +/// +/// True if all 6 elements equal zero, else false. +template +bool Affine2D::IsZero() const +{ + return (IsClose(A(), 0)) && + (IsClose(B(), 0)) && + (IsClose(C(), 0)) && + (IsClose(D(), 0)) && + (IsClose(E(), 0)) && + (IsClose(F(), 0)); +} + +/// +/// Rotate this affine transform around its origin by the specified angle in degrees. +/// +/// The angle to rotate by +template +void Affine2D::Rotate(T angle) +{ + m4T origMat4 = ToMat4ColMajor(true);//Must center and use column major for glm to work. + m4T newMat4 = glm::rotate(origMat4, angle * DEG_2_RAD_T, v3T(0, 0, 1));//Assuming only rotating around z. + + A(newMat4[0][0]);//Use direct assignments instead of constructor to skip assigning C and F. + B(newMat4[0][1]); + D(newMat4[1][0]); + E(newMat4[1][1]); +} + +/// +/// Move by v. +/// +/// The vec2 describing how far to move in the x and y directions +template +void Affine2D::Translate(v2T& v) +{ + O(O() + v); +} + +/// +/// Rotate and scale the X and Y components by a certain amount based on X. +/// +/// The vec2 describing how much to rotate and scale the X and Y components +template +void Affine2D::RotateScaleXTo(v2T& v) +{ + Affine2D rs = CalcRotateScale(X(), v); + + X(rs.TransformNormal(X())); + Y(rs.TransformNormal(Y())); +} + +/// +/// Rotate and scale the X and Y components by a certain amount based on Y. +/// +/// The vec2 describing how much to rotate and scale the X and Y components +template +void Affine2D::RotateScaleYTo(v2T& v) +{ + Affine2D rs = CalcRotateScale(Y(), v); + + X(rs.TransformNormal(X())); + Y(rs.TransformNormal(Y())); +} + +/// +/// Return the inverse of the 2x3 affine matrix. +/// +/// The inverse of this affine transform +template +Affine2D Affine2D::Inverse() const +{ + T det = A() * E() - D() * B(); + + return Affine2D(E() / det, -D() / det, + -B() / det, A() / det, + (F() * B() - C() * E()) / det, (C() * D() - F() * A()) / det); +} + +/// +/// Return a vec2 gotten from transforming this affine transform +/// by the vec2 passed in, but with a T component of 0, 0. +/// +/// The vec2 describing how much to transform by +/// The centered, transformed vec2 +template +typename v2T Affine2D::TransformNormal(const v2T& v) const +{ + return v2T(A() * v.x + B() * v.y, D() * v.x + E() * v.y); +} + +/// +/// Return a vec2 gotten from transforming this affine transform +/// by the vec2 passed in, and applying T translation. +/// +/// The vec2 describing how much to transform by +/// The translated, transformed vec2 +template +typename v2T Affine2D::TransformVector(const v2T& v) const +{ + return v2T(A() * v.x + B() * v.y + C(), D() * v.x + E() * v.y + F()); +} + +/// +/// Return the X and Y components as a 2x2 matrix in column major order. +/// +/// The 2x2 matrix +template +typename m2T Affine2D::ToMat2ColMajor() const +{ + return m2T(A(), B(),//Col0... + D(), E());//1 +} + +/// +/// Return the X and Y components as a 2x2 matrix in row major order. +/// +/// The 2x2 matrix +template +typename m2T Affine2D::ToMat2RowMajor() const +{ + return m2T(A(), D(),//Col0... + B(), E());//1 +} + +/// +/// Return the 2x3 affine transform matrix as a 4x4 matrix in column major order. +/// +/// Whether to use T translation value or just 0 for center +/// The 4x4 matrix +template +typename m4T Affine2D::ToMat4ColMajor(bool center) const +{ + m4T mat(A(), B(), 0, center ? 0 : C(),//Col0... + D(), E(), 0, center ? 0 : F(),//1 + 0, 0, 1, 0,//2 + 0, 0, 0, 1);//3 + + return mat; +} + +/// +/// Return the 2x3 affine transform matrix as a 4x4 matrix in row major order. +/// +/// Whether to use T translation value or just 0 for center +/// The 4x4 matrix +template +typename m4T Affine2D::ToMat4RowMajor(bool center) const +{ + m4T mat(A(), D(), 0, 0, + B(), E(), 0, 0, + 0, 0, 1, 0, + center ? 0 : C(), center ? 0 : F(), 0, 1); + + return mat; +} + +/// +/// == operator which tests if all fields are equal with another Affine2D. +/// +/// The Affine2D to compare to +/// True if all fields are equal, else false +template +bool Affine2D::operator == (const Affine2D& affine) +{ + return IsClose(A(), affine.A()) && + IsClose(B(), affine.B()) && + IsClose(C(), affine.C()) && + IsClose(D(), affine.D()) && + IsClose(E(), affine.E()) && + IsClose(F(), affine.F()); +} + +/// +/// * operator to multiply this affine transform by another and return the result. +/// +/// The Affine2D to multiply by +/// A new Affine2D which is the product of the multiplication +template +Affine2D& Affine2D::operator * (const Affine2D& affine) +{ + X(TransformNormal(affine.X())); + Y(TransformNormal(affine.Y())); + O(TransformVector(affine.O())); + + return *this; +} + +/// +/// * operator to multiply this affine transform by a vec2 and return the result as a vec2. +/// +/// The vec2 to multiply by +/// A new vec2 which is the product of the multiplication +template +typename v2T Affine2D::operator * (const v2T& v) +{ + return TransformVector(v); +} + +/// +/// Accessors. +/// +template T Affine2D::A() const { return m_Mat[0][0]; }//[0][0]//flam3 +template T Affine2D::B() const { return m_Mat[0][1]; }//[1][0] +template T Affine2D::C() const { return m_Mat[0][2]; }//[2][0] +template T Affine2D::D() const { return m_Mat[1][0]; }//[0][1] +template T Affine2D::E() const { return m_Mat[1][1]; }//[1][1] +template T Affine2D::F() const { return m_Mat[1][2]; }//[2][1] + +template void Affine2D::A(T a) { m_Mat[0][0] = a; } +template void Affine2D::B(T b) { m_Mat[0][1] = b; } +template void Affine2D::C(T c) { m_Mat[0][2] = c; } +template void Affine2D::D(T d) { m_Mat[1][0] = d; } +template void Affine2D::E(T e) { m_Mat[1][1] = e; } +template void Affine2D::F(T f) { m_Mat[1][2] = f; } + +template typename v2T Affine2D::X() const { return v2T(A(), D()); }//X Axis. +template typename v2T Affine2D::Y() const { return v2T(B(), E()); }//Y Axis. +template typename v2T Affine2D::O() const { return v2T(C(), F()); }//Translation. + +template void Affine2D::X(v2T& x) { A(x.x); D(x.y); }//X Axis. +template void Affine2D::Y(v2T& y) { B(y.x); E(y.y); }//Y Axis. +template void Affine2D::O(v2T& t) { C(t.x); F(t.y); }//Translation. + +/// +/// Rotate and scale this affine transform and return as a copy. Orginal is unchanged. +/// +/// The starting point to rotate and scale from +/// The ending point to rotate and scale to +/// The newly rotated and scalled Affine2D +template +Affine2D Affine2D::CalcRotateScale(v2T& from, v2T& to) +{ + T a, c; + + CalcRSAC(from, to, a, c); + return Affine2D(a, c, -c, a, 0, 0); +} + +/// +/// Never fully understood what this did or why it's named what it is. +/// But it seems to handle some rotating and scaling. +/// +/// The starting point to rotate and scale from +/// The ending point to rotate and scale to +/// a +/// c +template +void Affine2D::CalcRSAC(v2T& from, v2T& to, T& a, T& c) +{ + T lsq = from.x * from.x + from.y * from.y; + + a = (from.y * to.y + from.x * to.x) / lsq; + c = (from.x * to.y - from.y * to.x) / lsq; +} +} \ No newline at end of file diff --git a/Source/Ember/Affine2D.h b/Source/Ember/Affine2D.h new file mode 100644 index 0000000..8889762 --- /dev/null +++ b/Source/Ember/Affine2D.h @@ -0,0 +1,142 @@ +#pragma once + +#include "Utils.h" + +/// +/// Affine2D class. +/// + +namespace EmberNs +{ +/// +/// Uses matrix composition to handle the +/// affine matrix. Taken almost entirely from +/// Fractron, but using glm, and in C++. +/// Note that the matrix layout differs from flam3 so it's best to use +/// the A, B, C, D, E, F wrappers around the underlying matrix indices. But if the matrix must +/// be accessed directly, the two are laid out as such: +/// flam3: 3 columns of 2 rows each. Accessed col, row. +/// [a(0,0)][b(1,0)][c(2,0)] +/// [d(0,1)][e(1,1)][f(2,1)] +/// Ember: 2 columns of 3 rows each. Accessed col, row. +/// [a(0,0)][d(1,0)] +/// [b(0,1)][e(1,1)] +/// [c(0,2)][f(1,2)] +/// Template argument expected to be float or double. +/// +template +class EMBER_API Affine2D +{ +public: + Affine2D(); + + /// + /// Default copy constructor. + /// + /// The Affine2D object to copy + Affine2D(const Affine2D& affine) + { + Affine2D::operator=(affine); + } + + /// + /// Copy constructor to copy an Affine2D object of type U. + /// + /// The Affine2D object to copy + template + Affine2D(const Affine2D& affine) + { + Affine2D::operator=(affine); + } + + Affine2D(v2T& x, v2T& y, v2T& t); + Affine2D(T xx, T xy, T yx, T yy, T tx, T ty); + Affine2D(m4T& mat); + + /// + /// Default assignment operator. + /// + /// The Affine2D object to copy + Affine2D& operator = (const Affine2D& affine) + { + if (this != &affine) + Affine2D::operator=(affine); + + return *this; + } + + /// + /// Assignment operator to assign an Affine2D object of type U. + /// + /// The Affine2D object to copy. + /// Reference to updated self + template + Affine2D& operator = (const Affine2D& affine) + { + A(T(affine.A())); + B(T(affine.B())); + C(T(affine.C())); + D(T(affine.D())); + E(T(affine.E())); + F(T(affine.F())); + + return *this; + } + + inline void MakeID(); + inline bool IsID() const; + inline bool IsZero() const; + inline void Rotate(T angle); + inline void Translate(v2T& v); + inline void RotateScaleXTo(v2T& v); + inline void RotateScaleYTo(v2T& v); + inline Affine2D Inverse() const; + inline v2T TransformNormal(const v2T& v) const; + inline v2T TransformVector(const v2T& v) const; + inline m2T ToMat2ColMajor() const; + inline m2T ToMat2RowMajor() const; + inline m4T ToMat4ColMajor(bool center = false) const; + inline m4T ToMat4RowMajor(bool center = false) const; + + bool operator == (const Affine2D& affine); + Affine2D& operator * (const Affine2D& affine); + v2T operator * (const v2T& v); + + inline T A() const; + inline T B() const; + inline T C() const; + inline T D() const; + inline T E() const; + inline T F() const; + + inline void A(T a); + inline void B(T b); + inline void C(T c); + inline void D(T d); + inline void E(T e); + inline void F(T f); + + inline v2T X() const; + inline v2T Y() const; + inline v2T O() const; + + inline void X(v2T& x); + inline void Y(v2T& y); + inline void O(v2T& t); + + //static Affine2D Identity();//Complains about inline. + static inline Affine2D CalcRotateScale(v2T& from, v2T& to); + static inline void CalcRSAC(v2T& from, v2T& to, T& a, T& c); + + m23T m_Mat; +}; + +//This class had to be implemented in a cpp file because the compiler was breaking. +//So the explicit instantiation must be declared here rather than in Ember.cpp where +//all of the other classes are done. +template EMBER_API class Affine2D; + +#ifdef DO_DOUBLE + template EMBER_API class Affine2D; +#endif +} \ No newline at end of file diff --git a/Source/Ember/CarToRas.h b/Source/Ember/CarToRas.h new file mode 100644 index 0000000..3d0e649 --- /dev/null +++ b/Source/Ember/CarToRas.h @@ -0,0 +1,252 @@ +#pragma once + +#include "Point.h" + +/// +/// CarToRas class. +/// + +namespace EmberNs +{ +/// +/// When iterating, everything is positioned in terms of a carteseian plane with 0,0 in the center like so: +/// [-1,1] [1,1] +/// [-1,-1] [1,-1] +/// However, when accumulating to the histogram, the data is stored in the traditional raster coordinate system +/// of 0,0 at the top left and x,y at the bottom right. This class provides functionality to convert from one +/// to the other and is used when accumulating a sub batch of iteration results to the histogram. +/// Note the functions use reference arguments for the converted values because they are slightly faster than returning a value. +/// Template argument expected to be float or double. +/// +template +class EMBER_API CarToRas +{ +public: + /// + /// Empty constructor. This class should never be used unless it's been properly constructed with the constructor that takes arguments. + /// + CarToRas() + { + } + + /// + /// Constructor that takes arguments to set up the bounds and passes them to Init(). + /// + /// The lower left x of the cartesian plane + /// The lower left y of the cartesian plane + /// The upper right x of the cartesian plane + /// The upper right y of the cartesian plane + /// The width in pixels of the raster image/histogram + /// The height in pixels of the raster image/histogram + /// The aspect ratio, generally 1 + CarToRas(T carLlX, T carLlY, T carUrX, T carUrY, unsigned int rasW, unsigned int rasH, T aspectRatio) + { + Init(carLlX, carLlY, carUrX, carUrY, rasW, rasH, aspectRatio); + } + + /// + /// Default copy constructor. + /// + /// The CarToRas object to copy + CarToRas(const CarToRas& carToRas) + { + CarToRas::operator=(carToRas); + } + + /// + /// Copy constructor to copy a CarToRas object of type U. + /// + /// The CarToRas object to copy + template + CarToRas(const CarToRas& carToRas) + { + CarToRas::operator=(carToRas); + } + + /// + /// Default assignment operator. + /// + /// The CarToRas object to copy + CarToRas& operator = (const CarToRas& carToRas) + { + if (this != &carToRas) + CarToRas::operator=(carToRas); + + return *this; + } + + /// + /// Assignment operator to assign a CarToRas object of type U. + /// + /// The CarToRas object to copy. + /// Reference to updated self + template + CarToRas& operator = (const CarToRas& carToRas) + { + m_RasWidth = carToRas.RasWidth(); + m_RasHeight = carToRas.RasHeight(); + m_OneRow = T(carToRas.OneRow()); + m_OneCol = T(carToRas.OneCol()); + m_PixPerImageUnitW = T(carToRas.PixPerImageUnitW()); + m_RasLlX = T(carToRas.RasLlX()); + m_PixPerImageUnitH = T(carToRas.PixPerImageUnitH()); + m_RasLlY = T(carToRas.RasLlY()); + m_CarLlX = T(carToRas.CarLlX()); + m_CarLlY = T(carToRas.CarLlY()); + m_CarUrX = T(carToRas.CarUrX()); + m_CarUrY = T(carToRas.CarUrY()); + m_PadCarLlX = T(carToRas.PadCarLlX()); + m_PadCarLlY = T(carToRas.PadCarLlY()); + m_PadCarUrX = T(carToRas.PadCarUrX()); + m_PadCarUrY = T(carToRas.PadCarUrY()); + + return *this; + } + + /// + /// Initialize the dimensions with the specified bounds. + /// + /// The lower left x of the cartesian plane + /// The lower left y of the cartesian plane + /// The upper right x of the cartesian plane + /// The upper right y of the cartesian plane + /// The width in pixels of the raster image/histogram + /// The height in pixels of the raster image/histogram + /// The aspect ratio, generally 1 + void Init(T carLlX, T carLlY, T carUrX, T carUrY, unsigned int rasW, unsigned int rasH, T aspectRatio) + { + m_RasWidth = rasW; + m_RasHeight = rasH; + + m_CarLlX = carLlX; + m_CarLlY = carLlY; + m_CarUrX = carUrX; + m_CarUrY = carUrY; + + T carW = m_CarUrX - m_CarLlX;//Right minus left. + T carH = m_CarUrY - m_CarLlY;//Top minus bottom. + T invSizeW = T(1.0) / carW; + T invSizeH = T(1.0) / carH; + + m_PixPerImageUnitW = (T)rasW * invSizeW; + m_RasLlX = m_PixPerImageUnitW * carLlX; + + m_PixPerImageUnitH = (T)rasH * invSizeH; + m_RasLlY = m_PixPerImageUnitH * carLlY; + + T m_OneRow = abs(m_CarUrY - m_CarLlY) / m_RasHeight; + T m_OneCol = abs(m_CarUrX - m_CarLlX) / m_RasWidth; + + m_PadCarLlX = m_CarLlX + m_OneCol; + m_PadCarUrX = m_CarUrX - m_OneCol; + + m_PadCarLlY = m_CarLlY + m_OneRow; + m_PadCarUrY = m_CarUrY - m_OneRow; + } + + /// + /// Convert a cartesian x, y coordinate to a raster x, y coordinate. + /// This will flip the Y coordinate, so points that hit the bottom of the cartesian plane will + /// be mapped to the top of the histogram and vice versa. + /// There is a very slim chance that a point will be right on the border and will technically be in bounds, passing the InBounds() test, + /// but ends up being mapped to a histogram bucket that is out of bounds due to roundoff error. Perform an additional check after this call to make sure the + /// mapped point is in bounds. + /// + /// The cartesian x + /// The cartesian y + /// The converted raster x + /// The converted raster y + inline void Convert(T cartX, T cartY, unsigned int& rasX, unsigned int& rasY) + { + rasX = (unsigned int)(m_PixPerImageUnitW * cartX - m_RasLlX); + rasY = (unsigned int)(m_RasLlY - (m_PixPerImageUnitH * cartY)); + } + + /// + /// Convert a cartesian x, y coordinate to a single raster buffer index. + /// This will flip the Y coordinate, so points that hit the bottom of the cartesian plane will + /// be mapped to the top of the histogram and vice versa. + /// There is a very slim chance that a point will be right on the border and will technically be in bounds, passing the InBounds() test, + /// but ends up being mapped to a histogram bucket that is out of bounds due to roundoff error. Perform an additional check after this call to make sure the + /// mapped point is in bounds. + /// + /// The cartesian x + /// The cartesian y + /// The converted single raster buffer index + inline void Convert(T cartX, T cartY, unsigned int& singleBufferIndex) + { + singleBufferIndex = (unsigned int)(m_PixPerImageUnitW * cartX - m_RasLlX) + (m_RasWidth * (unsigned int)(m_PixPerImageUnitH * cartY - m_RasLlY)); + } + + /// + /// Convert a cartesian x, y point to a single raster buffer index. + /// This will flip the Y coordinate, so points that hit the bottom of the cartesian plane will + /// be mapped to the top of the histogram and vice versa. + /// This is the most efficient possible way of converting, consisting of only + /// a multiply and subtract per coordinate element. + /// There is a very slim chance that a point will be right on the border and will technically be in bounds, passing the InBounds() test, + /// but ends up being mapped to a histogram bucket that is out of bounds due to roundoff error. Perform an additional check after this call to make sure the + /// mapped point is in bounds. + /// + /// The cartesian y + /// The converted single raster buffer index + inline void Convert(Point& point, unsigned int& singleBufferIndex) + { + singleBufferIndex = (unsigned int)(m_PixPerImageUnitW * point.m_X - m_RasLlX) + (m_RasWidth * (unsigned int)(m_PixPerImageUnitH * point.m_Y - m_RasLlY)); + } + + /// + /// Determine if a point in the cartesian plane can be converted to a point within the raster plane. + /// There is a very slim chance that a point will be right on the border and will technically be in bounds, passing the InBounds() test, + /// but ends up being mapped to a histogram bucket that is out of bounds due to roundoff error. Perform an additional check after this call to make sure the + /// mapped point is in bounds. + /// + /// The point to test + /// True if within bounds, else false + inline bool InBounds(Point& point) + { + //Debug check for hitting the very first pixel in the image. + //if (point.m_Y > m_CarLlY && point.m_Y <= m_PadCarLlY && //Mapped to top row... + // point.m_X > m_CarLlX && point.m_X <= m_PadCarLlX)//...first col. + //{ + // cout << "First pixel hit." << endl; + //} + + return point.m_X >= m_CarLlX && + point.m_X < m_CarUrX && + point.m_Y < m_CarUrY && + point.m_Y >= m_CarLlY; + } + + /// + /// Accessors. + /// + inline unsigned int RasWidth() const { return m_RasWidth; } + inline unsigned int RasHeight() const { return m_RasHeight; } + inline T OneRow() const { return m_OneRow; } + inline T OneCol() const { return m_OneCol; } + inline T PixPerImageUnitW() const { return m_PixPerImageUnitW; } + inline T RasLlX() const { return m_RasLlX; } + inline T PixPerImageUnitH() const { return m_PixPerImageUnitH; } + inline T RasLlY() const { return m_RasLlY; } + inline T CarLlX() const { return m_CarLlX; } + inline T CarLlY() const { return m_CarLlY; } + inline T CarUrX() const { return m_CarUrX; } + inline T CarUrY() const { return m_CarUrY; } + inline T PadCarLlX() const { return m_PadCarLlX; } + inline T PadCarLlY() const { return m_PadCarLlY; } + inline T PadCarUrX() const { return m_PadCarUrX; } + inline T PadCarUrY() const { return m_PadCarUrY; } + +private: + unsigned int m_RasWidth, m_RasHeight;//The width and height of the raster image. + T m_OneRow;//The distance that one raster row represents in the cartesian plane. + T m_OneCol;//The distance that one raster column represents in the cartesian plane. + T m_PixPerImageUnitW;//The number of columns in the raster plane that a horizontal distance of 1 in the cartesian plane represents. The higher the number, the more zoomed in. + T m_RasLlX;//The lower left x of the raster image plane. + T m_PixPerImageUnitH;//The number of rows in the raster plane that a vertical distance of 1 in the cartesian plane represents. The higher the number, the more zoomed in. + T m_RasLlY;//The lower left y of the raster image plane. + T m_CarLlX, m_CarLlY, m_CarUrX, m_CarUrY;//The bounds of the cartesian plane. + T m_PadCarLlX, m_PadCarLlY, m_PadCarUrX, m_PadCarUrY;//The bounds of the cartesian plane padded by one raster row and column on each side. +}; +} \ No newline at end of file diff --git a/Source/Ember/DensityFilter.h b/Source/Ember/DensityFilter.h new file mode 100644 index 0000000..5f45c16 --- /dev/null +++ b/Source/Ember/DensityFilter.h @@ -0,0 +1,340 @@ +#pragma once + +#include "SpatialFilter.h" + +/// +/// DensityFilter class. +/// + +namespace EmberNs +{ +/// +/// A base class with virtual functions to allow both templating and polymorphism to work together. +/// Derived classes will implement all of these functions. +/// +class EMBER_API DensityFilterBase +{ +public: + DensityFilterBase() { } + virtual ~DensityFilterBase() { } + + virtual int FilterWidth() { return 0; } +}; + +/// +/// The density estimation filter is used after iterating, but before final accumulation. +/// It's a variable width Gaussian filter, whose width is inversely proportional +/// to the number of hits a given histogram cell has received. +/// That means the fewer hits in a cell, the more blur is applied. The greater the hits, +/// the less blur. +/// Template argument expected to be float or double. +/// +template +class EMBER_API DensityFilter : public DensityFilterBase +{ +public: + /// + /// Constructor that assigns various fields but does not create the actual filter vector. + /// This is done because filter creation could fail, so the user must manually call it + /// after construction. + /// + /// The minimum filter radius + /// The maximum filter radius + /// The curve of the filter + /// The supersample of the ember this filter will be used with + DensityFilter(T minRad, T maxRad, T curve, unsigned int supersample) + { + m_MinRad = minRad; + m_MaxRad = maxRad; + m_Curve = curve; + m_Supersample = supersample; + m_MaxFilterIndex = 0; + + //Make sure the values make sense. + if (m_Curve <= 0.0) + m_Curve = T(0.5); + + if (m_MaxRad < m_MinRad) + m_MaxRad = m_MinRad + 1; + } + + /// + /// Copy constructor. + /// + /// The DensityFilter object to copy + DensityFilter(const DensityFilter& filter) + { + *this = filter; + } + + /// + /// Assignment operator. + /// + /// The DensityFilter object to copy. + /// Reference to updated self + DensityFilter& operator = (const DensityFilter& filter) + { + if (this != &filter) + { + m_MinRad = filter.m_MinRad; + m_MaxRad = filter.m_MaxRad; + m_Curve = filter.m_Curve; + m_Supersample = filter.m_Supersample; + m_KernelSize = filter.m_KernelSize; + m_MaxFilterIndex = filter.m_MaxFilterIndex; + m_MaxFilteredCounts = filter.m_MaxFilteredCounts; + m_FilterWidth = filter.m_FilterWidth; + m_Coefs = filter.m_Coefs; + m_Widths = filter.m_Widths; + } + + return *this; + } + + /// + /// Create the filter vector of up to 10M entries. + /// If more than that are requested, it isn't created and + /// false is returned. + /// + /// True if success, else false. + bool Create() + { + int i, j, w; + int intFilterCount, maxIndex; + int rowSize; + int filterLoop; + int keepThresh = 100; + unsigned int filterCoefIndex = 0; + T decFilterCount; + T finalMinRad = m_MinRad * m_Supersample + 1;//Should scale the filter width by the oversample. + T finalMaxRad = m_MaxRad * m_Supersample + 1;//The '+1' comes from the assumed distance to the first pixel. + GaussianFilter gaussianFilter(m_MaxRad, m_Supersample); + + m_KernelSize = 0; + m_MaxFilterIndex = 0; + + //Calculate how many filter kernels are needed based on the decay function + // + // num filters = (de_max_width / de_min_width)^(1 / estimator_curve) + // + decFilterCount = pow(finalMaxRad / finalMinRad, T(1.0) / m_Curve); + + if (decFilterCount > 1e7)//Too many filters. + return false; + + intFilterCount = (int)ceil(decFilterCount); + + //Condense the smaller kernels to save space. + if (intFilterCount > keepThresh) + { + maxIndex = (int)ceil(DE_THRESH + pow(T(intFilterCount - DE_THRESH), m_Curve)) + 1; + m_MaxFilteredCounts = (int)pow(T(maxIndex - DE_THRESH), T(1.0) / m_Curve) + DE_THRESH; + } + else + { + maxIndex = intFilterCount; + m_MaxFilteredCounts = maxIndex; + } + + //Allocate the memory for these filters and the hit/width lookup array. + rowSize = (int)(2 * ceil(finalMaxRad) - 1); + m_FilterWidth = (rowSize - 1) / 2; + m_KernelSize = (m_FilterWidth + 1) * (2 + m_FilterWidth) / 2; + + m_Coefs.resize(maxIndex * m_KernelSize); + m_Widths.resize(maxIndex); + + //Generate the filter coefficients. + for (filterLoop = 0; filterLoop < maxIndex; filterLoop++) + { + int dej, dek; + int coefIndex; + T filterSum = 0.0; + T filterVal; + T filterHeight; + T loopAdjust; + + //Calculate the filter width for this number of hits in a bin. + if (filterLoop < keepThresh) + { + filterHeight = (finalMaxRad / pow(T(filterLoop + 1), m_Curve)); + } + else + { + loopAdjust = pow(T(filterLoop - keepThresh), (T(1.0) / m_Curve)) + keepThresh; + filterHeight = (finalMaxRad / pow(loopAdjust + 1, m_Curve)); + } + + //Once we've reached the min radius, don't populate any more. + if (filterHeight <= finalMinRad) + { + filterHeight = finalMinRad; + m_MaxFilterIndex = filterLoop; + } + + m_Widths[filterLoop] = filterHeight; + + //Calculate norm of kernel separately (easier). + for (dej = -m_FilterWidth; dej <= m_FilterWidth; dej++) + { + for (dek = -m_FilterWidth; dek <= m_FilterWidth; dek++) + { + filterVal = sqrt((T)(dej * dej + dek * dek)) / filterHeight; + + //Only populate the coefs within this radius. + if (filterVal <= 1.0) + filterSum += gaussianFilter.Filter(gaussianFilter.Support() * filterVal); + } + } + + coefIndex = filterLoop * m_KernelSize; + + //Calculate the unique entries of the kernel. + for (dej = 0; dej <= m_FilterWidth; dej++) + { + for (dek = 0; dek <= dej; dek++) + { + filterVal = sqrt(T(dej * dej + dek * dek)) / filterHeight; + + //Only populate the coefs within this radius. + if (filterVal > 1.0) + m_Coefs[coefIndex] = 0.0; + else + m_Coefs[coefIndex] = gaussianFilter.Filter(gaussianFilter.Support() * filterVal) / filterSum; + + coefIndex++; + } + } + + if (m_MaxFilterIndex > 0) + break; + } + + if (m_MaxFilterIndex == 0) + m_MaxFilterIndex = maxIndex - 1; + + w = m_FilterWidth + 1; + m_CoefIndices.resize(w * w); + + //This will populate one quadrant of filter indices. + //Really only need 1/8th, but that would require a sparse matrix. + for (j = 0; j <= m_FilterWidth; j++) + { + for (i = 0; i <= j; i++, filterCoefIndex++) + { + if (j == 0 && i == 0) + { + m_CoefIndices[(j * w) + i] = filterCoefIndex; + } + else if (i == 0) + { + m_CoefIndices[(0 * w) + j] = filterCoefIndex; + m_CoefIndices[(j * w) + 0] = filterCoefIndex; + } + else if (j == i) + { + m_CoefIndices[(j * w) + i] = filterCoefIndex; + } + else + { + m_CoefIndices[(i * w) + j] = filterCoefIndex; + m_CoefIndices[(j * w) + i] = filterCoefIndex; + } + } + } + + return true; + } + + /// + /// Return whether the requested dimensions are valid. + /// Meaning, is the requested filter size less than or equal to 10M? + /// + /// True if requested filter size is less than or equal to 10M, else false. + inline bool Valid() const + { + T finalMaxRad = m_MaxRad * m_Supersample + 1; + T finalMinRad = m_MinRad * m_Supersample + 1; + + return pow(finalMaxRad / finalMinRad, T(1.0) / m_Curve) <= 1e7; + } + + /// + /// Return a string representation of this density estimation filter. + /// + /// The string representation of this density estimation filter + string ToString() const + { + unsigned int i, j, coefIndex = 0, w = m_FilterWidth + 1; + stringstream ss; + + ss + << "Density Filter:" << endl + << " Min radius: " << MinRad() << endl + << " Max radius: " << MaxRad() << endl + << " Curve: " << Curve() << endl + << " Kernel size: " << KernelSize() << endl + << " Max filter index: " << MaxFilterIndex() << endl + << "Max Filtered counts: " << MaxFilteredCounts() << endl + << " Filter width: " << FilterWidth() << endl; + + ss << "Coefficients: " << endl; + + for (i = 0; i < m_Widths.size(); i++) + { + for (coefIndex = 0; coefIndex < m_KernelSize; coefIndex++) + ss << "Kernel[" << i << "].Coefs[" << coefIndex << "]: " << m_Coefs[(i * m_KernelSize) + coefIndex] << endl; + } + + ss << endl << "Widths: " << endl; + + for (i = 0; i < m_Widths.size(); i++) + { + ss << "Widths[" << i << "]: " << m_Widths[i] << endl; + } + + for (i = 0; i < w; i++) + { + for (j = 0; j < w; j++) + { + cout << std::setw(2) << std::setfill('0') << m_CoefIndices[i * w + j] << "\t"; + } + + cout << endl; + } + + return ss.str(); + } + + /// + /// Accessors. + /// + inline T MinRad() const { return m_MinRad; } + inline T MaxRad() const { return m_MaxRad; } + inline T Curve() const { return m_Curve; } + inline unsigned int KernelSize() const { return m_KernelSize; } + inline unsigned int MaxFilterIndex() const { return m_MaxFilterIndex; } + inline unsigned int MaxFilteredCounts() const { return m_MaxFilteredCounts; } + virtual int FilterWidth() const { return m_FilterWidth; } + inline unsigned int BufferSize() const { return (unsigned int)m_Widths.size(); } + inline unsigned int CoefsSizeBytes() const { return BufferSize() * m_KernelSize * sizeof(T); } + inline unsigned int WidthsSizeBytes() const { return BufferSize() * sizeof(T); } + inline unsigned int CoefsIndicesSizeBytes() const { return unsigned int(m_CoefIndices.size() * sizeof(unsigned int)); } + inline const T* Coefs() const { return m_Coefs.data(); } + inline const T* Widths() const { return m_Widths.data(); } + inline const unsigned int* CoefIndices() const { return m_CoefIndices.data(); } + +private: + T m_MinRad; + T m_MaxRad;//The original specified filter radius. + T m_Curve; + unsigned int m_Supersample; + unsigned int m_KernelSize; + unsigned int m_MaxFilterIndex; + unsigned int m_MaxFilteredCounts; + int m_FilterWidth;//The new radius after scaling for super sample and rounding. This is what's actually used. + vector m_Coefs; + vector m_Widths; + vector m_CoefIndices; +}; +} \ No newline at end of file diff --git a/Source/Ember/DllMain.cpp b/Source/Ember/DllMain.cpp new file mode 100644 index 0000000..7503d02 --- /dev/null +++ b/Source/Ember/DllMain.cpp @@ -0,0 +1,20 @@ +#include "EmberPch.h" + +/// +/// Generated by Visual Studio to make the DLL run properly. +/// +BOOL APIENTRY DllMain( HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} diff --git a/Source/Ember/Ember.cpp b/Source/Ember/Ember.cpp new file mode 100644 index 0000000..61f43af --- /dev/null +++ b/Source/Ember/Ember.cpp @@ -0,0 +1,410 @@ +#include "EmberPch.h" +#include "EmberDefines.h" +#include "Isaac.h" +#include "Ember.h" +#include "Utils.h" +#include "Iterator.h" +#include "Palette.h" +#include "PaletteList.h" +#include "Point.h" +#include "Variation.h" +#include "Variations01.h" +#include "Variations02.h" +#include "Variations03.h" +#include "Variations04.h" +#include "Variations05.h" +#include "VariationsDC.h" +#include "VariationList.h" +#include "Affine2D.h" +#include "Xform.h" +#include "EmberToXml.h" +#include "XmlToEmber.h" +#include "SpatialFilter.h" +#include "DensityFilter.h" +#include "TemporalFilter.h" +#include "Interpolate.h" +#include "Renderer.h" +#include "Timing.h" +#include "SheepTools.h" + +/// +/// Explicit instantiation of all templated classes which aren't implemented in cpp files. +/// All new templated classes, such as new variations, must be added here. +/// Additional instances of static class member variables. +/// + +namespace EmberNs +{ +bool Timing::m_TimingInit = false; +int Timing::m_ProcessorCount; +LARGE_INTEGER Timing::m_Freq; +auto_ptr> QTIsaac::GlobalRand = auto_ptr>(new QTIsaac()); + +#define EXPORTPREPOSTREGVAR(varName, T) \ + template EMBER_API class varName##Variation; \ + template EMBER_API class Pre##varName##Variation; \ + template EMBER_API class Post##varName##Variation; + +#define EXPORT_SINGLE_TYPE_EMBER(T) \ + template EMBER_API class Point; \ + template EMBER_API class Color; \ + template EMBER_API class Affine2D; \ + template EMBER_API class Palette; \ + template EMBER_API class PaletteList; \ + template EMBER_API class Iterator; \ + template EMBER_API class StandardIterator; \ + template EMBER_API class XaosIterator; \ + template EMBER_API class Xform; \ + template EMBER_API class IteratorHelper; \ + template EMBER_API class Variation; \ + template EMBER_API class ParamWithName; \ + template EMBER_API class ParametricVariation; \ + EXPORTPREPOSTREGVAR(Linear, T) \ + EXPORTPREPOSTREGVAR(Sinusoidal, T) \ + EXPORTPREPOSTREGVAR(Spherical, T) \ + EXPORTPREPOSTREGVAR(Swirl, T) \ + EXPORTPREPOSTREGVAR(Horseshoe, T) \ + EXPORTPREPOSTREGVAR(Polar, T) \ + EXPORTPREPOSTREGVAR(Handkerchief, T) \ + EXPORTPREPOSTREGVAR(Heart, T) \ + EXPORTPREPOSTREGVAR(Disc, T) \ + EXPORTPREPOSTREGVAR(Spiral, T) \ + EXPORTPREPOSTREGVAR(Hyperbolic, T) \ + EXPORTPREPOSTREGVAR(Diamond, T) \ + EXPORTPREPOSTREGVAR(Ex, T) \ + EXPORTPREPOSTREGVAR(Julia, T) \ + EXPORTPREPOSTREGVAR(Bent, T) \ + EXPORTPREPOSTREGVAR(Waves, T) \ + EXPORTPREPOSTREGVAR(Fisheye, T) \ + EXPORTPREPOSTREGVAR(Popcorn, T) \ + EXPORTPREPOSTREGVAR(Exponential, T) \ + EXPORTPREPOSTREGVAR(Power, T) \ + EXPORTPREPOSTREGVAR(Cosine, T) \ + EXPORTPREPOSTREGVAR(Rings, T) \ + EXPORTPREPOSTREGVAR(Fan, T) \ + EXPORTPREPOSTREGVAR(Blob, T) \ + EXPORTPREPOSTREGVAR(Pdj, T) \ + EXPORTPREPOSTREGVAR(Fan2, T) \ + EXPORTPREPOSTREGVAR(Rings2, T) \ + EXPORTPREPOSTREGVAR(Eyefish, T) \ + EXPORTPREPOSTREGVAR(Bubble, T) \ + EXPORTPREPOSTREGVAR(Cylinder, T) \ + EXPORTPREPOSTREGVAR(Perspective, T) \ + EXPORTPREPOSTREGVAR(Noise, T) \ + EXPORTPREPOSTREGVAR(JuliaNGeneric, T) \ + EXPORTPREPOSTREGVAR(JuliaScope, T) \ + EXPORTPREPOSTREGVAR(Blur, T) \ + EXPORTPREPOSTREGVAR(GaussianBlur, T) \ + EXPORTPREPOSTREGVAR(RadialBlur, T) \ + EXPORTPREPOSTREGVAR(Pie, T) \ + EXPORTPREPOSTREGVAR(Ngon, T) \ + EXPORTPREPOSTREGVAR(Curl, T) \ + EXPORTPREPOSTREGVAR(Rectangles, T) \ + EXPORTPREPOSTREGVAR(Arch, T) \ + EXPORTPREPOSTREGVAR(Tangent, T) \ + EXPORTPREPOSTREGVAR(Square, T) \ + EXPORTPREPOSTREGVAR(Rays, T) \ + EXPORTPREPOSTREGVAR(Blade, T) \ + EXPORTPREPOSTREGVAR(Secant2, T) \ + EXPORTPREPOSTREGVAR(TwinTrian, T) \ + EXPORTPREPOSTREGVAR(Cross, T) \ + EXPORTPREPOSTREGVAR(Disc2, T) \ + EXPORTPREPOSTREGVAR(SuperShape, T) \ + EXPORTPREPOSTREGVAR(Flower, T) \ + EXPORTPREPOSTREGVAR(Conic, T) \ + EXPORTPREPOSTREGVAR(Parabola, T) \ + EXPORTPREPOSTREGVAR(Bent2, T) \ + EXPORTPREPOSTREGVAR(Bipolar, T) \ + EXPORTPREPOSTREGVAR(Boarders, T) \ + EXPORTPREPOSTREGVAR(Butterfly, T) \ + EXPORTPREPOSTREGVAR(Cell, T) \ + EXPORTPREPOSTREGVAR(Cpow, T) \ + EXPORTPREPOSTREGVAR(Curve, T) \ + EXPORTPREPOSTREGVAR(Edisc, T) \ + EXPORTPREPOSTREGVAR(Elliptic, T) \ + EXPORTPREPOSTREGVAR(Escher, T) \ + EXPORTPREPOSTREGVAR(Foci, T) \ + EXPORTPREPOSTREGVAR(LazySusan, T) \ + EXPORTPREPOSTREGVAR(Loonie, T) \ + EXPORTPREPOSTREGVAR(Modulus, T) \ + EXPORTPREPOSTREGVAR(Oscilloscope, T) \ + EXPORTPREPOSTREGVAR(Polar2, T) \ + EXPORTPREPOSTREGVAR(Popcorn2, T) \ + EXPORTPREPOSTREGVAR(Scry, T) \ + EXPORTPREPOSTREGVAR(Separation, T) \ + EXPORTPREPOSTREGVAR(Split, T) \ + EXPORTPREPOSTREGVAR(Splits, T) \ + EXPORTPREPOSTREGVAR(Stripes, T) \ + EXPORTPREPOSTREGVAR(Wedge, T) \ + EXPORTPREPOSTREGVAR(WedgeJulia, T) \ + EXPORTPREPOSTREGVAR(WedgeSph, T) \ + EXPORTPREPOSTREGVAR(Whorl, T) \ + EXPORTPREPOSTREGVAR(Waves2, T) \ + EXPORTPREPOSTREGVAR(Exp, T) \ + EXPORTPREPOSTREGVAR(Log, T) \ + EXPORTPREPOSTREGVAR(Sin, T) \ + EXPORTPREPOSTREGVAR(Cos, T) \ + EXPORTPREPOSTREGVAR(Tan, T) \ + EXPORTPREPOSTREGVAR(Sec, T) \ + EXPORTPREPOSTREGVAR(Csc, T) \ + EXPORTPREPOSTREGVAR(Cot, T) \ + EXPORTPREPOSTREGVAR(Sinh, T) \ + EXPORTPREPOSTREGVAR(Cosh, T) \ + EXPORTPREPOSTREGVAR(Tanh, T) \ + EXPORTPREPOSTREGVAR(Sech, T) \ + EXPORTPREPOSTREGVAR(Csch, T) \ + EXPORTPREPOSTREGVAR(Coth, T) \ + EXPORTPREPOSTREGVAR(Auger, T) \ + EXPORTPREPOSTREGVAR(Flux, T) \ + EXPORTPREPOSTREGVAR(Hemisphere, T) \ + EXPORTPREPOSTREGVAR(Epispiral, T) \ + EXPORTPREPOSTREGVAR(Bwraps, T) \ + EXPORTPREPOSTREGVAR(Extrude, T) \ + EXPORTPREPOSTREGVAR(BlurCircle, T) \ + EXPORTPREPOSTREGVAR(BlurZoom, T) \ + EXPORTPREPOSTREGVAR(BlurPixelize, T) \ + EXPORTPREPOSTREGVAR(Crop, T) \ + EXPORTPREPOSTREGVAR(BCircle, T) \ + EXPORTPREPOSTREGVAR(BlurLinear, T) \ + EXPORTPREPOSTREGVAR(BlurSquare, T) \ + EXPORTPREPOSTREGVAR(Boarders2, T) \ + EXPORTPREPOSTREGVAR(Cardioid, T) \ + EXPORTPREPOSTREGVAR(Checks, T) \ + EXPORTPREPOSTREGVAR(Circlize, T) \ + EXPORTPREPOSTREGVAR(Circlize2, T) \ + EXPORTPREPOSTREGVAR(CosWrap, T) \ + EXPORTPREPOSTREGVAR(DeltaA, T) \ + EXPORTPREPOSTREGVAR(Expo, T) \ + EXPORTPREPOSTREGVAR(FDisc, T) \ + EXPORTPREPOSTREGVAR(Fibonacci, T) \ + EXPORTPREPOSTREGVAR(Fibonacci2, T) \ + EXPORTPREPOSTREGVAR(Glynnia, T) \ + EXPORTPREPOSTREGVAR(GridOut, T) \ + EXPORTPREPOSTREGVAR(Hole, T) \ + EXPORTPREPOSTREGVAR(Hypertile, T) \ + EXPORTPREPOSTREGVAR(Hypertile1, T) \ + EXPORTPREPOSTREGVAR(Hypertile2, T) \ + EXPORTPREPOSTREGVAR(Hypertile3D, T) \ + EXPORTPREPOSTREGVAR(Hypertile3D1, T) \ + EXPORTPREPOSTREGVAR(Hypertile3D2, T) \ + EXPORTPREPOSTREGVAR(IDisc, T) \ + EXPORTPREPOSTREGVAR(Julian2, T) \ + EXPORTPREPOSTREGVAR(JuliaQ, T) \ + EXPORTPREPOSTREGVAR(Murl, T) \ + EXPORTPREPOSTREGVAR(Murl2, T) \ + EXPORTPREPOSTREGVAR(NPolar, T) \ + EXPORTPREPOSTREGVAR(Ortho, T) \ + EXPORTPREPOSTREGVAR(Poincare, T) \ + EXPORTPREPOSTREGVAR(Poincare3D, T) \ + EXPORTPREPOSTREGVAR(Polynomial, T) \ + EXPORTPREPOSTREGVAR(PSphere, T) \ + EXPORTPREPOSTREGVAR(Rational3, T) \ + EXPORTPREPOSTREGVAR(Ripple, T) \ + EXPORTPREPOSTREGVAR(Sigmoid, T) \ + EXPORTPREPOSTREGVAR(SinusGrid, T) \ + EXPORTPREPOSTREGVAR(Stwin, T) \ + EXPORTPREPOSTREGVAR(TwoFace, T) \ + EXPORTPREPOSTREGVAR(Unpolar, T) \ + EXPORTPREPOSTREGVAR(WavesN, T) \ + EXPORTPREPOSTREGVAR(XHeart, T) \ + EXPORTPREPOSTREGVAR(Barycentroid, T) \ + EXPORTPREPOSTREGVAR(BiSplit, T) \ + EXPORTPREPOSTREGVAR(Crescents, T) \ + EXPORTPREPOSTREGVAR(Mask, T) \ + EXPORTPREPOSTREGVAR(Cpow2, T) \ + EXPORTPREPOSTREGVAR(Curl3D, T) \ + EXPORTPREPOSTREGVAR(Disc3D, T) \ + EXPORTPREPOSTREGVAR(Funnel, T) \ + EXPORTPREPOSTREGVAR(Linear3D, T) \ + EXPORTPREPOSTREGVAR(PowBlock, T) \ + EXPORTPREPOSTREGVAR(Squirrel, T) \ + EXPORTPREPOSTREGVAR(Ennepers, T) \ + EXPORTPREPOSTREGVAR(SphericalN, T) \ + EXPORTPREPOSTREGVAR(Kaleidoscope, T) \ + EXPORTPREPOSTREGVAR(GlynnSim1, T) \ + EXPORTPREPOSTREGVAR(GlynnSim2, T) \ + EXPORTPREPOSTREGVAR(GlynnSim3, T) \ + EXPORTPREPOSTREGVAR(Starblur, T) \ + EXPORTPREPOSTREGVAR(Sineblur, T) \ + EXPORTPREPOSTREGVAR(Circleblur, T) \ + EXPORTPREPOSTREGVAR(CropN, T) \ + EXPORTPREPOSTREGVAR(ShredRad, T) \ + EXPORTPREPOSTREGVAR(Blob2, T) \ + EXPORTPREPOSTREGVAR(Julia3D, T) \ + EXPORTPREPOSTREGVAR(Julia3Dz, T) \ + EXPORTPREPOSTREGVAR(LinearT, T) \ + EXPORTPREPOSTREGVAR(LinearT3D, T) \ + EXPORTPREPOSTREGVAR(Ovoid, T) \ + EXPORTPREPOSTREGVAR(Ovoid3D, T) \ + EXPORTPREPOSTREGVAR(Spirograph, T) \ + EXPORTPREPOSTREGVAR(Petal, T) \ + EXPORTPREPOSTREGVAR(RoundSpher, T) \ + EXPORTPREPOSTREGVAR(RoundSpher3D, T) \ + EXPORTPREPOSTREGVAR(SpiralWing, T) \ + EXPORTPREPOSTREGVAR(Squarize, T) \ + EXPORTPREPOSTREGVAR(Sschecks, T) \ + EXPORTPREPOSTREGVAR(PhoenixJulia, T) \ + EXPORTPREPOSTREGVAR(Mobius, T) \ + EXPORTPREPOSTREGVAR(MobiusN, T) \ + EXPORTPREPOSTREGVAR(MobiusStrip, T) \ + EXPORTPREPOSTREGVAR(Lissajous, T) \ + EXPORTPREPOSTREGVAR(Svf, T) \ + EXPORTPREPOSTREGVAR(Target, T) \ + EXPORTPREPOSTREGVAR(Taurus, T) \ + EXPORTPREPOSTREGVAR(Collideoscope, T) \ + EXPORTPREPOSTREGVAR(BMod, T) \ + EXPORTPREPOSTREGVAR(BSwirl, T) \ + EXPORTPREPOSTREGVAR(BTransform, T) \ + EXPORTPREPOSTREGVAR(BCollide, T) \ + EXPORTPREPOSTREGVAR(Eclipse, T) \ + EXPORTPREPOSTREGVAR(FlipCircle, T) \ + EXPORTPREPOSTREGVAR(FlipY, T) \ + EXPORTPREPOSTREGVAR(ECollide, T) \ + EXPORTPREPOSTREGVAR(EJulia, T) \ + EXPORTPREPOSTREGVAR(EMod, T) \ + EXPORTPREPOSTREGVAR(EMotion, T) \ + EXPORTPREPOSTREGVAR(EPush, T) \ + EXPORTPREPOSTREGVAR(ERotate, T) \ + EXPORTPREPOSTREGVAR(EScale, T) \ + EXPORTPREPOSTREGVAR(ESwirl, T) \ + EXPORTPREPOSTREGVAR(LazyTravis, T) \ + EXPORTPREPOSTREGVAR(Squish, T) \ + EXPORTPREPOSTREGVAR(Circus, T) \ + EXPORTPREPOSTREGVAR(Tancos, T) \ + EXPORTPREPOSTREGVAR(Rippled, T) \ + EXPORTPREPOSTREGVAR(Flatten, T) \ + EXPORTPREPOSTREGVAR(Zblur, T) \ + EXPORTPREPOSTREGVAR(Blur3D, T) \ + EXPORTPREPOSTREGVAR(ZScale, T) \ + EXPORTPREPOSTREGVAR(ZTranslate, T) \ + EXPORTPREPOSTREGVAR(ZCone, T) \ + EXPORTPREPOSTREGVAR(Boarders2, T) \ + EXPORTPREPOSTREGVAR(RotateX, T) \ + EXPORTPREPOSTREGVAR(RotateY, T) \ + EXPORTPREPOSTREGVAR(RotateZ, T) \ + EXPORTPREPOSTREGVAR(MirrorX, T) \ + EXPORTPREPOSTREGVAR(MirrorY, T) \ + EXPORTPREPOSTREGVAR(MirrorZ, T) \ + EXPORTPREPOSTREGVAR(Depth, T) \ + EXPORTPREPOSTREGVAR(RBlur, T) \ + EXPORTPREPOSTREGVAR(JuliaNab, T) \ + EXPORTPREPOSTREGVAR(Sintrange, T) \ + EXPORTPREPOSTREGVAR(Voron, T) \ + EXPORTPREPOSTREGVAR(Waffle, T) \ + EXPORTPREPOSTREGVAR(Square3D, T) \ + EXPORTPREPOSTREGVAR(SuperShape3D, T) \ + EXPORTPREPOSTREGVAR(Sphyp3D, T) \ + EXPORTPREPOSTREGVAR(Circlecrop, T) \ + EXPORTPREPOSTREGVAR(Julian3Dx, T) \ + EXPORTPREPOSTREGVAR(Fourth, T) \ + EXPORTPREPOSTREGVAR(Mobiq, T) \ + EXPORTPREPOSTREGVAR(Spherivoid, T) \ + EXPORTPREPOSTREGVAR(Farblur, T) \ + EXPORTPREPOSTREGVAR(CurlSP, T) \ + EXPORTPREPOSTREGVAR(Heat, T) \ + EXPORTPREPOSTREGVAR(Interference2, T) \ + EXPORTPREPOSTREGVAR(Sinq, T) \ + EXPORTPREPOSTREGVAR(Sinhq, T) \ + EXPORTPREPOSTREGVAR(Secq, T) \ + EXPORTPREPOSTREGVAR(Sechq, T) \ + EXPORTPREPOSTREGVAR(Tanq, T) \ + EXPORTPREPOSTREGVAR(Tanhq, T) \ + EXPORTPREPOSTREGVAR(Cosq, T) \ + EXPORTPREPOSTREGVAR(Coshq, T) \ + EXPORTPREPOSTREGVAR(Cotq, T) \ + EXPORTPREPOSTREGVAR(Cothq, T) \ + EXPORTPREPOSTREGVAR(Cscq, T) \ + EXPORTPREPOSTREGVAR(Cschq, T) \ + EXPORTPREPOSTREGVAR(Estiq, T) \ + EXPORTPREPOSTREGVAR(Loq, T) \ + EXPORTPREPOSTREGVAR(Curvature, T) \ + EXPORTPREPOSTREGVAR(Qode, T) \ + EXPORTPREPOSTREGVAR(BlurHeart, T) \ + EXPORTPREPOSTREGVAR(Truchet, T) \ + EXPORTPREPOSTREGVAR(Gdoffs, T) \ + EXPORTPREPOSTREGVAR(Octagon, T) \ + EXPORTPREPOSTREGVAR(Trade, T) \ + EXPORTPREPOSTREGVAR(Juliac, T) \ + EXPORTPREPOSTREGVAR(Blade3D, T) \ + EXPORTPREPOSTREGVAR(Blob3D, T) \ + EXPORTPREPOSTREGVAR(Blocky, T) \ + EXPORTPREPOSTREGVAR(Bubble2, T) \ + EXPORTPREPOSTREGVAR(CircleLinear, T) \ + EXPORTPREPOSTREGVAR(CircleRand, T) \ + EXPORTPREPOSTREGVAR(CircleTrans1, T) \ + EXPORTPREPOSTREGVAR(Cubic3D, T) \ + EXPORTPREPOSTREGVAR(CubicLattice3D, T) \ + EXPORTPREPOSTREGVAR(Foci3D, T) \ + EXPORTPREPOSTREGVAR(Ho, T) \ + EXPORTPREPOSTREGVAR(Julia3Dq, T) \ + EXPORTPREPOSTREGVAR(Line, T) \ + EXPORTPREPOSTREGVAR(Loonie3D, T) \ + EXPORTPREPOSTREGVAR(Mcarpet, T) \ + EXPORTPREPOSTREGVAR(Waves23D, T) \ + EXPORTPREPOSTREGVAR(Pie3D, T) \ + EXPORTPREPOSTREGVAR(Popcorn23D, T) \ + EXPORTPREPOSTREGVAR(Sinusoidal3D, T) \ + EXPORTPREPOSTREGVAR(Scry3D, T) \ + EXPORTPREPOSTREGVAR(Shredlin, T) \ + EXPORTPREPOSTREGVAR(SplitBrdr, T) \ + EXPORTPREPOSTREGVAR(Wdisc, T) \ + EXPORTPREPOSTREGVAR(Falloff, T) \ + EXPORTPREPOSTREGVAR(Falloff2, T) \ + EXPORTPREPOSTREGVAR(Falloff3, T) \ + EXPORTPREPOSTREGVAR(Xtrb, T) \ + template EMBER_API class DCBubbleVariation; \ + EXPORTPREPOSTREGVAR(DCCarpet, T) \ + EXPORTPREPOSTREGVAR(DCCube, T) \ + template EMBER_API class DCCylinderVariation; \ + EXPORTPREPOSTREGVAR(DCGridOut, T) \ + template EMBER_API class DCLinearVariation; \ + EXPORTPREPOSTREGVAR(DCZTransl, T) \ + EXPORTPREPOSTREGVAR(DCTriangle, T) \ + template EMBER_API class VariationList; \ + template EMBER_API class SpatialFilter; \ + template EMBER_API class GaussianFilter; \ + template EMBER_API class HermiteFilter; \ + template EMBER_API class BoxFilter; \ + template EMBER_API class TriangleFilter; \ + template EMBER_API class BellFilter; \ + template EMBER_API class BsplineFilter; \ + template EMBER_API class MitchellFilter; \ + template EMBER_API class BlackmanFilter; \ + template EMBER_API class CatromFilter; \ + template EMBER_API class HanningFilter; \ + template EMBER_API class HammingFilter; \ + template EMBER_API class Lanczos3Filter; \ + template EMBER_API class Lanczos2Filter; \ + template EMBER_API class QuadraticFilter; \ + template EMBER_API class DensityFilter; \ + template EMBER_API class TemporalFilter; \ + template EMBER_API class ExpTemporalFilter; \ + template EMBER_API class GaussianTemporalFilter; \ + template EMBER_API class BoxTemporalFilter; \ + template EMBER_API class SpatialFilterCreator; \ + template EMBER_API class TemporalFilterCreator; \ + template EMBER_API class Interpolater; \ + template EMBER_API class Ember; \ + /*template EMBER_API class RenderCallback;*/ \ + template EMBER_API class CarToRas; \ + template EMBER_API class XmlToEmber; \ + template EMBER_API class EmberToXml; \ + bool PaletteList::m_Init = false; \ + vector> PaletteList::m_Palettes = vector>(); \ + bool XmlToEmber::m_Init = false; \ + vector> XmlToEmber::m_BadParamNames = vector>(); \ + vector> XmlToEmber::m_BadVariationNames = vector>(); + +EXPORT_SINGLE_TYPE_EMBER(float) + +#define EXPORT_TWO_TYPE_EMBER(T, bucketT) \ + template EMBER_API class Renderer; \ + template EMBER_API class SheepTools; + +EXPORT_TWO_TYPE_EMBER(float, float) + +#ifdef DO_DOUBLE + EXPORT_SINGLE_TYPE_EMBER(double) + EXPORT_TWO_TYPE_EMBER(double, double) +#endif +} \ No newline at end of file diff --git a/Source/Ember/Ember.h b/Source/Ember/Ember.h new file mode 100644 index 0000000..d0b2d7e --- /dev/null +++ b/Source/Ember/Ember.h @@ -0,0 +1,1607 @@ +#pragma once + +#include "Xform.h" +#include "PaletteList.h" +#include "SpatialFilter.h" +#include "TemporalFilter.h" + +/// +/// Ember class. +/// + +namespace EmberNs +{ +/// +/// Bit position specifying the presence of each type of 3D parameter. +/// One, none, some or all of these can be present. +/// +enum eProjBits +{ + PROJBITS_ZPOS = 1, + PROJBITS_PERSP = 2, + PROJBITS_PITCH = 4, + PROJBITS_YAW = 8, + PROJBITS_BLUR = 16, +}; + +/// +/// Ember is the main class for holding all of the information required to render a fractal flame. +/// This includes a vector of xforms, a final xform, size and color information as well as an Xml edit +/// document that users can use to keep track of changes. +/// Operations will often desire operating on just the regular xforms, or the regular xforms plus the final one. +/// The word "total" is used to signify when the final xform is included in the operation. +/// Template argument expected to be float or double. +/// +template +class EMBER_API Ember +{ +public: + /// + /// Default constructor which calls Init() to set default values. + /// + Ember() + { + Init(); + } + + Ember(const Ember& ember) + : m_Edits(NULL) + { + Ember::operator=(ember); + } + + /// + /// Copy constructor to copy an Ember object of type U, where T is usually the same type as U. + /// + /// The Ember object to copy + template + Ember(const Ember& ember) + : m_Edits(NULL) + { + Ember::operator=(ember); + } + + /// + /// Destructor which clears the Xml edits. + /// + ~Ember() + { + ClearEdit(); + } + + //For some reason the compiler requires Xform to define two assignment operators, + //however it gets confused when Ember has two. + Ember& operator = (const Ember& ember) + { + if (this != &ember) + Ember::operator=(ember); + + return *this; + } + + /// + /// Assignment operator to assign an Ember object of type U, where T is usually the same type as U. + /// + /// The Ember object to copy. + /// Reference to updated self + template + Ember& operator = (const Ember& ember) + { + m_FinalRasW = ember.m_FinalRasW; + m_FinalRasH = ember.m_FinalRasH; + m_OrigFinalRasW = ember.m_OrigFinalRasW; + m_OrigFinalRasH = ember.m_OrigFinalRasH; + m_Supersample = ember.m_Supersample; + m_Passes = ember.m_Passes; + m_TemporalSamples = ember.m_TemporalSamples; + m_Symmetry = ember.m_Symmetry; + + m_Quality = T(ember.m_Quality); + m_PixelsPerUnit = T(ember.m_PixelsPerUnit); + m_Zoom = T(ember.m_Zoom); + m_CamZPos = T(ember.m_CamZPos); + m_CamPerspective = T(ember.m_CamPerspective); + m_CamYaw = T(ember.m_CamYaw); + m_CamPitch = T(ember.m_CamPitch); + m_CamDepthBlur = T(ember.m_CamDepthBlur); + m_CamMat = ember.m_CamMat; + m_CenterX = T(ember.m_CenterX); + m_CenterY = T(ember.m_CenterY); + m_Rotate = T(ember.m_Rotate); + m_Hue = T(ember.m_Hue); + m_Brightness = T(ember.m_Brightness); + m_Gamma = T(ember.m_Gamma); + m_Vibrancy = T(ember.m_Vibrancy); + m_GammaThresh = T(ember.m_GammaThresh); + m_HighlightPower = T(ember.m_HighlightPower); + m_Time = T(ember.m_Time); + m_Background = ember.m_Background; + m_Interp = ember.m_Interp; + m_AffineInterp = ember.m_AffineInterp; + + m_MinRadDE = T(ember.m_MinRadDE); + m_MaxRadDE = T(ember.m_MaxRadDE); + m_CurveDE = T(ember.m_CurveDE); + + m_SpatialFilterType = ember.m_SpatialFilterType; + m_SpatialFilterRadius = T(ember.m_SpatialFilterRadius); + + m_TemporalFilterType = ember.m_TemporalFilterType; + m_TemporalFilterExp = T(ember.m_TemporalFilterExp); + m_TemporalFilterWidth = T(ember.m_TemporalFilterWidth); + + m_PaletteMode = ember.m_PaletteMode; + m_PaletteInterp = ember.m_PaletteInterp; + + m_Name = ember.m_Name; + m_ParentFilename = ember.m_ParentFilename; + + m_Index = ember.m_Index; + m_Palette = ember.m_Palette; + + m_Xforms.clear(); + + for (unsigned int i = 0; i < ember.XformCount(); i++) + { + Xform xform = *ember.GetXform(i);//Will call assignment operator to convert between types T and U. + + AddXform(xform); + } + + Xform finalXform = *ember.FinalXform();//Will call assignment operator to convert between types T and U. + + SetFinalXform(finalXform); + + //Interpolated-against final xforms need animate & color speed set to 0. + if (!ember.UseFinalXform()) + { + m_FinalXform.m_Motion.clear(); + m_FinalXform.m_Animate = 0; + m_FinalXform.m_ColorSpeed = 0; + } + + SetProjFunc(); + ClearEdit(); + + if (ember.m_Edits != NULL) + m_Edits = xmlCopyDoc(ember.m_Edits, 1); + + return *this; + } + + /// + /// Set common default values. + /// + void Init() + { + m_FinalRasW = 1920; + m_FinalRasH = 1080; + m_OrigFinalRasW = 1920; + m_OrigFinalRasH = 1080; + m_Supersample = 1; + m_Passes = 1; + m_TemporalSamples = 1000; + m_Symmetry = 0; + m_Quality = 100; + m_PixelsPerUnit = 240; + m_Zoom = 0; + m_ProjFunc = &EmberNs::Ember::ProjectNone; + m_CamZPos = 0; + m_CamPerspective = 0; + m_CamYaw = 0; + m_CamPitch = 0; + m_CamDepthBlur = 0; + m_BlurCoef = 0; + m_CamMat = m3T(0); + m_CenterX = 0; + m_CenterY = 0; + m_Rotate = 0; + m_Hue = 0; + m_Brightness = 4; + m_Gamma = 4; + m_Vibrancy = 1; + m_GammaThresh = T(0.01); + m_HighlightPower = -1; + m_Time = 0; + m_Background.Reset(); + m_Interp = EMBER_INTERP_LINEAR; + m_AffineInterp = INTERP_LOG; + + //DE filter. + m_MinRadDE = 0; + m_MaxRadDE = 9; + m_CurveDE = T(0.4); + + //Spatial filter. + m_SpatialFilterType = GAUSSIAN_SPATIAL_FILTER; + m_SpatialFilterRadius = T(0.5); + + //Temporal filter. + m_TemporalFilterType = BOX_TEMPORAL_FILTER; + m_TemporalFilterExp = 0; + m_TemporalFilterWidth = 1; + + //Palette. + m_PaletteMode = PALETTE_STEP; + m_PaletteInterp = INTERP_HSV; + + m_Name = "No name"; + m_ParentFilename = "No parent"; + + //Internal values. + m_Index = 0; + m_Xforms.reserve(12); + + m_Edits = NULL; + } + + /// + /// Add a copy of a new xform to the xforms vector. + /// + /// The xform to copy and add + void AddXform(const Xform& xform) + { + m_Xforms.push_back(xform); + m_Xforms[m_Xforms.size() - 1].CacheColorVals(); + m_Xforms[m_Xforms.size() - 1].ParentEmber(this); + } + + /// + /// Add the specified number of empty xforms. + /// + /// The number of xforms to add + void AddXforms(unsigned int count) + { + for (unsigned int i = 0; i < count; i++) + { + Xform xform; + + AddXform(xform); + } + } + + /// + /// Add empty padding xforms until the total xform count is xformPad. + /// + /// The total number of xforms to finish with + void PadXforms(unsigned int xformPad) + { + if (xformPad > XformCount()) + AddXforms(xformPad - XformCount()); + } + + /// + /// Copy this ember with optional padding xforms added. + /// + /// The total number of xforms if additional padding xforms are desired. Default: 0. + /// Whether to copy the final xform. Default: false. + /// The newly constructed ember + Ember Copy(unsigned int xformPad = 0, bool doFinal = false) + { + Ember ember(*this); + + ember.PadXforms(xformPad); + + if (doFinal) + { + if (UseFinalXform())//Caller wanted one and this ember has one. + { + ember.m_FinalXform = m_FinalXform; + } + else//Caller wanted one and this ember doesn't have one. + { + //Interpolated-against final xforms need animate & color speed set to 0 and motion elements cleared. + ember.m_FinalXform.m_Animate = 0; + ember.m_FinalXform.m_ColorSpeed = 0; + ember.m_FinalXform.m_Motion.clear(); + ember.m_FinalXform.ClearAndDeleteVariations(); + ember.m_FinalXform.AddVariation(new LinearVariation(0));//Do this so it doesn't appear empty. + } + } + + return ember; + } + + /// + /// Delete an xform at the specified index. + /// Shuffle xaos if present to reflect the deleted xform. + /// + /// The index to delete + /// True if success, else false. + bool DeleteXform(unsigned int i) + { + Xform* xform; + + if (i < XformCount()) + { + m_Xforms.erase(m_Xforms.begin() + i); + + //Now shuffle xaos values from i on back by 1 for every xform. + for (unsigned int x1 = 0; x1 < XformCount(); x1++) + { + if (xform = GetXform(x1)) + { + for (unsigned int x2 = i + 1; x2 <= XformCount(); x2++)//Iterate from the position after the deletion index up to the old count. + xform->SetXaos(x2 - 1, xform->Xaos(x2)); + + xform->TruncateXaos();//Make sure no old values are hanging around in case more xforms are added to this ember later. + } + } + + return true; + } + + return false; + } + + /// + /// Delete the xform at the specified index, including the final one. + /// + /// The index to delete + /// True if success, else false. + bool DeleteTotalXform(unsigned int i) + { + if (DeleteXform(i)) + { } + else if (i == XformCount() && UseFinalXform()) + m_FinalXform.Clear(); + else + return false; + + return true; + } + + /// + /// Get a pointer to the xform at the specified index, excluding the final one. + /// + /// The index to get + /// A pointer to the xform at the index if successful, else NULL. + Xform* GetXform(unsigned int i) const + { + if (i < XformCount()) + return (Xform*)&m_Xforms[i]; + else + return NULL; + } + + /// + /// Get a pointer to the xform at the specified index, including the final one. + /// + /// The index to get + /// If true, return the final xform when its index is requested even if one is not present + /// A pointer to the xform at the index if successful, else NULL. + Xform* GetTotalXform(unsigned int i, bool forceFinal = false) const + { + if (i < XformCount()) + return (Xform*)&m_Xforms[i]; + else if (i == XformCount() || forceFinal) + return (Xform*)&m_FinalXform; + else + return NULL; + } + + /// + /// Search the xforms, excluding final, to find which one's address matches the address of the specified xform. + /// + /// A pointer to the xform to find + /// The index of the matched xform if found, else -1. + int GetXformIndex(Xform* xform) const + { + int index = -1; + + for (unsigned int i = 0; i < m_Xforms.size(); i++) + if (GetXform(i) == xform) + return (int)i; + + return index; + } + + /// + /// Search the xforms, including final, to find which one's address matches the address of the specified xform. + /// + /// A pointer to the xform to find + /// The index of the matched xform if found, else -1. + int GetTotalXformIndex(Xform* xform) const + { + unsigned int totalXformCount = TotalXformCount(); + + for (unsigned int i = 0; i < totalXformCount; i++) + if (GetTotalXform(i) == xform) + return (int)i; + + return -1; + } + + /// + /// Assign the final xform. + /// + /// The xform to copy and assign to the final xform + void SetFinalXform(const Xform& xform) + { + m_FinalXform = xform; + m_FinalXform.CacheColorVals(); + m_FinalXform.ParentEmber(this); + } + + /// + /// Delete the final xform. + /// + void DeleteFinalXform() + { + m_FinalXform.ClearAndDeleteVariations(); + } + + /// + /// Determine whether the specified xform has the same address as the final xform. + /// + /// A pointer to the xform to test + /// True if matched, else false. + bool IsFinalXform(Xform* xform) + { + return &m_FinalXform == xform; + } + + /// + /// Delete all motion elements from all xforms including final. + /// + void DeleteMotionElements() + { + for (unsigned int i = 0; i < TotalXformCount(); i++) + GetTotalXform(i)->DeleteMotionElements(); + } + + /// + /// Call CacheColorVals() and SetPrecalcFlags() on all xforms including final. + /// + void CacheXforms() + { + for (unsigned int i = 0; i < TotalXformCount(); i++) + { + Xform* xform = GetTotalXform(i); + + xform->CacheColorVals(); + xform->SetPrecalcFlags(); + } + } + + void SetProjFunc() + { + unsigned int projBits = ProjBits(); + + if (!projBits)//No 3D at all, then do nothing. + { + m_ProjFunc = &EmberNs::Ember::ProjectNone; + } + else + { + m_CamMat[0][0] = cos(-m_CamYaw); + m_CamMat[1][0] = -sin(-m_CamYaw); + m_CamMat[2][0] = 0; + m_CamMat[0][1] = cos(m_CamPitch) * sin(-m_CamYaw); + m_CamMat[1][1] = cos(m_CamPitch) * cos(-m_CamYaw); + m_CamMat[2][1] = -sin(m_CamPitch); + m_CamMat[0][2] = sin(m_CamPitch) * sin(-m_CamYaw); + m_CamMat[1][2] = sin(m_CamPitch) * cos(-m_CamYaw); + m_CamMat[2][2] = cos(m_CamPitch); + + if (projBits & PROJBITS_BLUR) + { + if (projBits & PROJBITS_YAW) + m_ProjFunc = &EmberNs::Ember::ProjectPitchYawDepthBlur; + else + m_ProjFunc = &EmberNs::Ember::ProjectPitchDepthBlur; + } + else if ((projBits & PROJBITS_PITCH) || (projBits & PROJBITS_YAW)) + { + if (m_CamYaw != 0) + m_ProjFunc = &EmberNs::Ember::ProjectPitchYaw; + else + m_ProjFunc = &EmberNs::Ember::ProjectPitch; + } + else + { + m_ProjFunc = &EmberNs::Ember::ProjectZPerspective; + } + } + + m_BlurCoef = T(0.1) * m_CamDepthBlur; + } + + /// + /// Determine whether xaos is used in any xform, excluding final. + /// + /// True if any xaos found, else false. + bool XaosPresent() + { + bool b = false; + + std::for_each(m_Xforms.begin(), m_Xforms.end(), [&](Xform& xform) { b |= xform.XaosPresent(); });//If at least one entry is not equal to 1, then xaos is present. + + return b; + } + + /// + /// Remove all xaos from this ember. + /// + void ClearXaos() + { + std::for_each(m_Xforms.begin(), m_Xforms.end(), [&](Xform& xform) { xform.ClearXaos(); }); + } + + /// + /// Change the size of the final output image and adjust the pixels per unit + /// so that the orientation of the image remains the same in the new size. + /// + /// New width + /// New height + /// True to only scale if the new dimensions are smaller than the original, else always scale. + void SetSizeAndAdjustScale(unsigned int width, unsigned int height, bool onlyScaleIfNewIsSmaller, eScaleType scaleType) + { + if ((onlyScaleIfNewIsSmaller && (width < m_FinalRasW || height < m_FinalRasH)) || !onlyScaleIfNewIsSmaller) + { + if (scaleType == SCALE_WIDTH) + m_PixelsPerUnit *= T(width) / T(m_FinalRasW); + else if (scaleType == SCALE_HEIGHT) + m_PixelsPerUnit *= T(height) / T(m_FinalRasH); + } + + m_FinalRasW = width; + m_FinalRasH = height; + } + + /// + /// Set all xform weights to 1 / xform count. + /// + void EqualizeWeights() + { + T weight = T(1) / m_Xforms.size(); + + std::for_each(m_Xforms.begin(), m_Xforms.end(), [&](Xform& xform) { xform.m_Weight = weight; }); + } + + /// + /// Calculates the normalized weights of the xforms and places them in the passed in vector. + /// A vector to hold the normalized weights + /// + void CalcNormalizedWeights(vector& normalizedWeights) + { + T norm = 0; + size_t i = 0; + + if (normalizedWeights.size() != m_Xforms.size()) + normalizedWeights.resize(m_Xforms.size()); + + std::for_each(m_Xforms.begin(), m_Xforms.end(), [&](Xform& xform) { norm += xform.m_Weight; }); + std::for_each(normalizedWeights.begin(), normalizedWeights.end(), [&](T& weight) { weight = m_Xforms[i].m_Weight / norm; i++; }); + } + + /// + /// Get a vector of pointers to all variations present in all Xforms, with no duplicates. + /// Meaning, that if a variation is present in more than one Xform, only the first one encountered + /// will be added. + /// A vector to hold pointers to the variations present. This will be cleared first. + /// + void GetPresentVariations(vector*>& variations, bool baseOnly = true) const + { + unsigned int i = 0, xformIndex = 0, totalVarCount = m_FinalXform.TotalVariationCount(); + + variations.clear(); + std::for_each(m_Xforms.begin(), m_Xforms.end(), [&](const Xform& xform) { totalVarCount += xform.TotalVariationCount(); }); + variations.reserve(totalVarCount); + + while (Xform* xform = GetTotalXform(xformIndex++)) + { + i = 0; + totalVarCount = xform->TotalVariationCount(); + + while (Variation* tempVar = xform->GetVariation(i++)) + { + if (baseOnly) + { + string tempVarBaseName = tempVar->BaseName(); + + if ((std::find_if(variations.begin(), variations.end(), [&] (const Variation* var) -> bool { return tempVar->VariationId() == var->VariationId(); }) == variations.end()) && + (std::find_if(variations.begin(), variations.end(), [&] (const Variation* var) -> bool { return tempVarBaseName == var->BaseName(); }) == variations.end())) + variations.push_back(tempVar); + } + else + { + if (std::find_if(variations.begin(), variations.end(), [&] (const Variation* var) -> bool { return tempVar->VariationId() == var->VariationId(); }) == variations.end()) + variations.push_back(tempVar); + } + } + } + } + + /// + /// Thin wrapper around Interpolate() which takes a vector of embers rather than a pointer and size. + /// All embers are expected to be aligned, including the final xform. If not the behavior is undefined. + /// + /// Vector of embers + /// Coefficients vector which must be the same length as the vector of embers + /// Stagger if greater than 0 + void Interpolate(vector>& embers, vector& coefs, T stagger) + { + Interpolate(embers.data(), embers.size(), coefs, stagger); + } + + /// + /// Interpolate the specified embers using the coefficients supplied. + /// All embers are expected to be aligned, including the final xform. If not the behavior is undefined. + /// + /// Pointer to buffer of embers + /// Number of elements in the buffer of embers + /// Coefficients vector which must be the same length as the vector of embers + /// Stagger if greater than 0 + void Interpolate(Ember* embers, size_t size, vector& coefs, T stagger) + { + if (size != coefs.size() || size < 2) + return; + + bool allID, final; + unsigned int i, j, k, l, maxXformCount, totalXformCount; + T bgAlphaSave = m_Background.a; + T coefSave[2]; + vector*> xformVec; + + //Palette and others + if (embers[0].m_PaletteInterp == INTERP_HSV) + { + for (i = 0; i < 256; i++) + { + T t[3], s[4] = { 0, 0, 0, 0 }; + + for (k = 0; k < size; k++) + { + Palette::RgbToHsv(glm::value_ptr(embers[k].m_Palette[i]), t); + + for (j = 0; j < 3; j++) + s[j] += coefs[k] * t[j]; + + s[3] += coefs[k] * embers[k].m_Palette[i][3]; + } + + Palette::HsvToRgb(s, glm::value_ptr(m_Palette[i])); + m_Palette[i][3] = s[3]; + + for (j = 0; j < 4; j++) + Clamp(m_Palette[i][j], 0, 1); + } + } + else if (embers[0].m_PaletteInterp == INTERP_SWEEP) + { + //Sweep - not the best option for float indices. + for (i = 0; i < 256; i++) + { + j = (i < (256 * coefs[0])) ? 0 : 1; + m_Palette[i] = embers[j].m_Palette[i]; + } + } + + m_Palette.m_Index = -1;//Random. + m_Symmetry = 0; + m_SpatialFilterType = embers[0].m_SpatialFilterType; + m_TemporalFilterType = embers[0].m_TemporalFilterType; + m_PaletteMode = embers[0].m_PaletteMode; + m_AffineInterp = embers[0].m_AffineInterp; + + //Interpolate ember parameters. + InterpT<&Ember::m_Brightness>(embers, coefs, size); + InterpT<&Ember::m_HighlightPower>(embers, coefs, size); + InterpT<&Ember::m_Gamma>(embers, coefs, size); + InterpT<&Ember::m_Vibrancy>(embers, coefs, size); + InterpT<&Ember::m_Hue>(embers, coefs, size); + InterpI<&Ember::m_FinalRasW>(embers, coefs, size); + InterpI<&Ember::m_FinalRasH>(embers, coefs, size); + InterpI<&Ember::m_Supersample>(embers, coefs, size); + InterpT<&Ember::m_CenterX>(embers, coefs, size); + InterpT<&Ember::m_CenterY>(embers, coefs, size); + InterpX, &Ember::m_Background>(embers, coefs, size); m_Background.a = bgAlphaSave;//Don't interp alpha. + InterpT<&Ember::m_PixelsPerUnit>(embers, coefs, size); + InterpT<&Ember::m_SpatialFilterRadius>(embers, coefs, size); + InterpT<&Ember::m_TemporalFilterExp>(embers, coefs, size); + InterpT<&Ember::m_TemporalFilterWidth>(embers, coefs, size); + InterpT<&Ember::m_Quality>(embers, coefs, size); + InterpT<&Ember::m_Zoom>(embers, coefs, size); + InterpT<&Ember::m_CamZPos>(embers, coefs, size); + InterpT<&Ember::m_CamPerspective>(embers, coefs, size); + InterpT<&Ember::m_CamYaw>(embers, coefs, size); + InterpT<&Ember::m_CamPitch>(embers, coefs, size); + InterpT<&Ember::m_CamDepthBlur>(embers, coefs, size); + InterpX::m_CamMat>(embers, coefs, size); + InterpT<&Ember::m_Rotate>(embers, coefs, size); + InterpI<&Ember::m_Passes>(embers, coefs, size); + InterpI<&Ember::m_TemporalSamples>(embers, coefs, size); + InterpT<&Ember::m_MaxRadDE>(embers, coefs, size); + InterpT<&Ember::m_MinRadDE>(embers, coefs, size); + InterpT<&Ember::m_CurveDE>(embers, coefs, size); + InterpT<&Ember::m_GammaThresh>(embers, coefs, size); + + //An extra step needed here due to the OOD that was not needed in the original. + //A small price to pay for the conveniences it affords us elsewhere. + //First clear the xforms, and find out the max number of xforms in all of the embers in the list. + m_Xforms.clear(); + maxXformCount = Interpolater::MaxXformCount(embers, size);//Max number of standard transforms in embers, excluding final. + //m_Xforms.reserve(maxXformCount); + final = Interpolater::AnyFinalPresent(embers, size);//Did any embers have a final xform? + totalXformCount = maxXformCount + (final ? 1 : 0); + xformVec.reserve(size); + + //Populate the xform list member such that each element is a merge of all of the xforms at that position in all of the embers. + for (i = 0; i < totalXformCount; i++)//For each xform to populate. + { + for (j = 0; j < size; j++)//For each ember in the list. + { + if (i < embers[j].TotalXformCount())//Xform in this position in this ember. + { + xformVec.push_back(embers[j].GetTotalXform(i));//Temporary list to pass to MergeXforms(). + } + } + + if (i < maxXformCount)//Working with standard xforms? + AddXform(Interpolater::MergeXforms(xformVec, true));//Merge, set weights to zero, and add to the xform list. + else if (final)//Or is it the final xform (i will be == maxXformCount)? + m_FinalXform = Interpolater::MergeXforms(xformVec, true); + + xformVec.clear(); + } + + //Now have a merged list, so interpolate the weight values. + //This includes all xforms plus final. + for (i = 0; i < totalXformCount; i++) + { + Xform* thisXform = GetTotalXform(i); + + if (size == 2 && stagger > 0 && thisXform != &m_FinalXform) + { + coefSave[0] = coefs[0]; + coefSave[1] = coefs[1]; + coefs[0] = Interpolater::GetStaggerCoef(coefSave[0], stagger, XformCount(), i);//Standard xform count without final. + coefs[1] = 1 - coefs[0]; + } + + for (j = 0; j < thisXform->TotalVariationCount(); j++)//For each variation in this xform. + { + Variation* var = thisXform->GetVariation(j); + ParametricVariation* parVar = dynamic_cast*>(var);//Will use below if it's parametric. + + var->m_Weight = 0; + + if (parVar != NULL) + parVar->Clear(); + + for (k = 0; k < size; k++)//For each ember in the list. + { + Xform* tempXform = embers[k].GetTotalXform(i);//Xform in this position in this ember, including final. + + if (tempXform) + { + Variation* tempVar = tempXform->GetVariationById(var->VariationId());//See if the variation at this xform index exists in that ember at this xform index. + + if (tempVar != NULL) + { + //Interp weight. + var->m_Weight += tempVar->m_Weight * coefs[k]; + + //If it was a parametric variation, interp params. + if (parVar != NULL) + { + ParametricVariation* tempParVar = dynamic_cast*>(tempVar); + + if (tempParVar != NULL && (parVar->ParamCount() == tempParVar->ParamCount()))//This check will should always be true, but just check to be absolutely sure to avoid clobbering memory. + { + ParamWithName* params = parVar->Params(); + ParamWithName* tempParams = tempParVar->Params(); + + for (l = 0; l < parVar->ParamCount(); l++) + { + if (!tempParams[l].IsPrecalc()) + *(params[l].Param()) += tempParams[l].ParamVal() * coefs[k]; + } + } + } + } + } + } + } + + InterpXform<&Xform::m_Weight> (thisXform, i, embers, coefs, size); + InterpXform<&Xform::m_ColorX> (thisXform, i, embers, coefs, size); + InterpXform<&Xform::m_ColorSpeed>(thisXform, i, embers, coefs, size); + InterpXform<&Xform::m_Opacity> (thisXform, i, embers, coefs, size); + InterpXform<&Xform::m_Animate> (thisXform, i, embers, coefs, size); + + ClampGte0Ref(thisXform->m_Weight); + ClampRef(thisXform->m_ColorX, 0, 1); + ClampRef(thisXform->m_ColorSpeed, -1, 1); + + //Interp affine and post. + if (m_AffineInterp == INTERP_LOG) + { + vector cxMag(size); + vector cxAng(size); + vector cxTrn(size); + + thisXform->m_Affine.m_Mat = m23T(0); + + //Affine part. + Interpolater::ConvertLinearToPolar(embers, size, i, 0, cxAng, cxMag, cxTrn); + Interpolater::InterpAndConvertBack(coefs, cxAng, cxMag, cxTrn, thisXform->m_Affine); + + //Post part. + allID = true; + + for (k = 0; k < size; k++)//For each ember in the list. + { + if (i < embers[k].TotalXformCount())//Xform in this position in this ember. + { + Xform* tempXform = embers[k].GetTotalXform(i); + allID &= tempXform->m_Post.IsID(); + } + } + + thisXform->m_Post.m_Mat = m23T(0); + + if (allID) + { + thisXform->m_Post.A(1); + thisXform->m_Post.E(1); + } + else + { + Interpolater::ConvertLinearToPolar(embers, size, i, 1, cxAng, cxMag, cxTrn); + Interpolater::InterpAndConvertBack(coefs, cxAng, cxMag, cxTrn, thisXform->m_Post); + } + } + else if (m_AffineInterp == INTERP_LINEAR) + { + //Interpolate pre and post affine using coefs. + allID = true; + thisXform->m_Affine.m_Mat = m23T(0); + thisXform->m_Post.m_Mat = m23T(0); + + for (k = 0; k < size; k++) + { + Xform* tempXform = embers[k].GetTotalXform(i);//Xform in this position in this ember. + + if (tempXform) + { + allID &= tempXform->m_Post.IsID(); + thisXform->m_Affine.m_Mat += (coefs[k] * tempXform->m_Affine.m_Mat); + thisXform->m_Post.m_Mat += (coefs[k] * tempXform->m_Post.m_Mat); + } + } + + if (allID) + thisXform->m_Post.m_Mat = glm::mat2x3(1); + } + + //Final stagger cleanup goes here. + if (size == 2 && stagger > 0 && thisXform != &m_FinalXform) + { + coefs[0] = coefSave[0]; + coefs[1] = coefSave[1]; + } + } + + //Normally these functions are called automatically when + //adding variations to a xform, or when setting a parametric + //variation value via name lookup. However, since the values + //were directly written to, must manually call them here. + CacheXforms(); + + //Need to merge chaos. Original does chaos all the time, but really only need to do it if at least one xform in at least one ember uses it, else skip. + //Omit final xform from chaos processing. + if (Interpolater::AnyXaosPresent(embers, size)) + { + for (i = 0; i < XformCount(); i++) + { + m_Xforms[i].SetXaos(i, 0);//First make each xform xaos array be maxXformCount elements long and set them to zero. + + //Now fill them with interpolated values. + for (j = 0; j < size; j++)//For each ember in the list. + { + Xform* tempXform = embers[j].GetXform(i); + + for (k = 0; k < XformCount(); k++)//For each xaos entry in this xform's xaos array, sum it with the same entry in all of the embers multiplied by the coef for that ember. + { + m_Xforms[i].SetXaos(k, m_Xforms[i].Xaos(k) + tempXform->Xaos(k) * coefs[j]); + } + } + + //Make sure no xaos entries for this xform were less than zero. + for (k = 0; k < XformCount(); k++) + if (m_Xforms[i].Xaos(k) < 0) + m_Xforms[i].SetXaos(k, 0); + } + } + } + + /// + /// Thin wrapper around InterpolateCatmullRom(). + /// The ember vector is expected to be a length of 4 with all xforms aligned, including final. + /// + /// Vector of embers + /// Used in calculating Catmull-Rom coefficients + void InterpolateCatmullRom(vector>& embers, T t) + { + InterpolateCatmullRom(embers.data(), embers.size(), t); + } + + /// + /// Use Catmull-Rom coefficients and pass to Interpolate(). + /// The ember array is expected to be a length of 4 with all xforms aligned, including final. + /// + /// Pointer to buffer of embers + /// Number of elements in the buffer of embers + /// Used in calculating Catmull-Rom coefficients + void InterpolateCatmullRom(Ember* embers, size_t size, T t) + { + T t2 = t * t; + T t3 = t2 * t; + vector cmc(4); + + cmc[0] = (2 * t2 - t - t3) / 2; + cmc[1] = (3 * t3 - 5 * t2 + 2) / 2; + cmc[2] = (4 * t2 - 3 * t3 + t) / 2; + cmc[3] = (t3 - t2) / 2; + + Interpolate(embers, size, cmc, 0); + } + + /// + /// Rotate all pre-affine transforms in non-final xforms whose animate value is non-zero by + /// the specified angle in a counter-clockwise direction. + /// Omit padding xforms. + /// Do not rotate post affine transforms. + /// + /// The angle to rotate by + void RotateAffines(T angle) + { + for (unsigned int i = 0; i < XformCount(); i++)//Only look at normal xforms, exclude final. + { + //Don't rotate xforms with animate set to 0. + if (m_Xforms[i].m_Animate == 0) + continue; + + //Assume that if there are no variations, then it's a padding xform. + if (m_Xforms[i].Empty() && m_AffineInterp != INTERP_LOG) + continue; + + m_Xforms[i].m_Affine.Rotate(angle); + //Don't rotate post. + } + } + + /// + /// Adds symmetry to this ember by adding additional xforms. + /// sym = 2 or more means rotational. + /// sym = 1 means identity, ie no symmetry. + /// sym = 0 means pick a random symmetry (maybe none). + /// sym = -1 means bilateral (reflection). + /// sym = -2 or less means rotational and reflective. + /// + /// The type of symmetry to add + /// The random context to use for generating random symmetry + void AddSymmetry(int sym, QTIsaac& rand) + { + int i, k, result = 0; + T a; + + if (sym == 0) + { + static int symDistrib[] = { + -4, -3, + -2, -2, -2, + -1, -1, -1, + 2, 2, 2, + 3, 3, + 4, 4, + }; + + if (rand.Rand() & 1) + sym = symDistrib[rand.Rand() % Vlen(symDistrib)]; + else if (rand.Rand() & 31) + sym = (rand.Rand() % 13) - 6; + else + sym = (rand.Rand() % 51) - 25; + } + + if (sym == 1 || sym == 0) + return; + + m_Symmetry = sym; + + if (sym < 0) + { + i = XformCount();//Don't apply sym to final. + Xform xform; + AddXform(xform); + + m_Xforms[i].m_Weight = 1; + m_Xforms[i].m_ColorSpeed = 0; + m_Xforms[i].m_Animate = 0.0; + m_Xforms[i].m_ColorX = 1; + m_Xforms[i].m_ColorY = 1;//Added in case 2D palette support is ever added. + m_Xforms[i].m_Affine.A(-1); + m_Xforms[i].m_Affine.B(0); + m_Xforms[i].m_Affine.C(0); + m_Xforms[i].m_Affine.D(0); + m_Xforms[i].m_Affine.E(1); + m_Xforms[i].m_Affine.F(0); + m_Xforms[i].AddVariation(new LinearVariation()); + + result++; + sym = -sym; + } + + a = T(2 * M_PI / sym); + + for (k = 1; k < sym; k++) + { + i = XformCount(); + Xform xform; + AddXform(xform); + + m_Xforms[i].m_Weight = 1.0; + m_Xforms[i].m_ColorSpeed = 0.0; + m_Xforms[i].m_Animate = 0.0; + m_Xforms[i].m_ColorX = m_Xforms[i].m_ColorY = (sym < 3) ? 0 : (T(k - 1) / T(sym - 2));//Added Y. + m_Xforms[i].m_Affine.A(Round6(cos(k * a))); + m_Xforms[i].m_Affine.D(Round6(sin(k * a))); + m_Xforms[i].m_Affine.B(Round6(-m_Xforms[i].m_Affine.D())); + m_Xforms[i].m_Affine.E(m_Xforms[i].m_Affine.A()); + m_Xforms[i].m_Affine.C(0); + m_Xforms[i].m_Affine.F(0); + m_Xforms[i].AddVariation(new LinearVariation()); + + result++; + } + + //Sort the newly added xforms, do not touch the original ones. + std::sort(m_Xforms.end() - result, m_Xforms.end(), &Interpolater::CompareXforms); + } + + unsigned int ProjBits() + { + unsigned int val = 0; + + if (m_CamZPos != 0) val |= PROJBITS_ZPOS; + if (m_CamPerspective != 0) val |= PROJBITS_PERSP; + if (m_CamPitch != 0) val |= PROJBITS_PITCH; + if (m_CamYaw != 0) val |= PROJBITS_YAW; + if (m_CamDepthBlur != 0) val |= PROJBITS_BLUR; + + return val; + } + + inline void Proj(Point& point, QTIsaac& rand) + { + (this->*m_ProjFunc)(point, rand); + } + + void ProjectNone(Point& point, QTIsaac& rand) + { + } + + void ProjectZPerspective(Point& point, QTIsaac& rand) + { + T zr = 1 - m_CamPerspective * (point.m_Z - m_CamZPos); + + point.m_X /= zr; + point.m_Y /= zr; + point.m_Z -= m_CamZPos; + } + + void ProjectPitch(Point& point, QTIsaac& rand) + { + T z = point.m_Z - m_CamZPos; + T y = m_CamMat[1][1] * point.m_Y + m_CamMat[2][1] * z; + T zr = 1 - m_CamPerspective * (m_CamMat[1][2] * point.m_Y + m_CamMat[2][2] * z); + + point.m_X /= zr; + point.m_Y = y / zr; + point.m_Z -= m_CamZPos; + } + + void ProjectPitchDepthBlur(Point& point, QTIsaac& rand) + { + T y, z, zr; + T dsin, dcos; + T t = rand.Frand01() * M_2PI; + + z = point.m_Z - m_CamZPos; + y = m_CamMat[1][1] * point.m_Y + m_CamMat[2][1] * z; + z = m_CamMat[1][2] * point.m_Y + m_CamMat[2][2] * z; + zr = 1 - m_CamPerspective * z; + + sincos(t, &dsin, &dcos); + + T dr = rand.Frand01() * m_BlurCoef * z; + + point.m_X = (point.m_X + dr * dcos) / zr; + point.m_Y = (y + dr * dsin) / zr; + point.m_Z -= m_CamZPos; + } + + void ProjectPitchYawDepthBlur(Point& point, QTIsaac& rand) + { + T dsin, dcos; + T t = rand.Frand01() * M_2PI; + T z = point.m_Z - m_CamZPos; + T x = m_CamMat[0][0] * point.m_X + m_CamMat[1][0] * point.m_Y; + T y = m_CamMat[0][1] * point.m_X + m_CamMat[1][1] * point.m_Y + m_CamMat[2][1] * z; + + z = m_CamMat[0][2] * point.m_X + m_CamMat[1][2] * point.m_Y + m_CamMat[2][2] * z; + + T zr = 1 - m_CamPerspective * z; + T dr = rand.Frand01() * m_BlurCoef * z; + + sincos(t, &dsin, &dcos); + + point.m_X = (x + dr * dcos) / zr; + point.m_Y = (y + dr * dsin) / zr; + point.m_Z -= m_CamZPos; + } + + void ProjectPitchYaw(Point& point, QTIsaac& rand) + { + T z = point.m_Z - m_CamZPos; + T x = m_CamMat[0][0] * point.m_X + m_CamMat[1][0] * point.m_Y; + T y = m_CamMat[0][1] * point.m_X + m_CamMat[1][1] * point.m_Y + m_CamMat[2][1] * z; + T zr = 1 - m_CamPerspective * (m_CamMat[0][2] * point.m_X + m_CamMat[1][2] * point.m_Y + m_CamMat[2][2] * z); + + point.m_X = x / zr; + point.m_Y = y / zr; + point.m_Z -= m_CamZPos; + } + + /// + /// Clear this ember and set to either reasonable or unreasonable default values. + /// + /// Use reasonable default if true, else use out of bounds values. + void Clear(bool useDefaults = true) + { + m_Palette.m_Index = -1; + m_CenterX = 0; + m_CenterY = 0; + m_Gamma = 4; + m_Vibrancy = 1; + m_Brightness = 4; + m_Symmetry = 0; + m_Hue = 0; + m_Rotate = 0; + m_PixelsPerUnit = 50; + m_Interp = EMBER_INTERP_LINEAR; + m_PaletteInterp = INTERP_HSV; + m_Index = 0; + m_ParentFilename = ""; + + if (useDefaults) + { + //If defaults are on, set to reasonable values. + m_HighlightPower = -1; + m_Background.Reset(); + m_FinalRasW = 100; + m_FinalRasH = 100; + m_Supersample = 1; + m_SpatialFilterRadius = T(0.5); + m_Zoom = 0; + m_ProjFunc = &EmberNs::Ember::ProjectNone; + m_CamZPos = 0; + m_CamPerspective = 0; + m_CamYaw = 0; + m_CamPitch = 0; + m_CamDepthBlur = 0; + m_BlurCoef = 0; + m_CamMat = m3T(0); + m_Quality = 1; + m_MaxRadDE = T(9.0); + m_MinRadDE = 0; + m_CurveDE = T(0.4); + m_GammaThresh = T(0.01); + m_Passes = 1; + m_TemporalSamples = 1000; + m_SpatialFilterType = GAUSSIAN_SPATIAL_FILTER; + m_AffineInterp = INTERP_LOG; + m_TemporalFilterType = BOX_TEMPORAL_FILTER; + m_TemporalFilterWidth = 1; + m_TemporalFilterExp = 0; + m_PaletteMode = PALETTE_STEP; + } + else + { + //Defaults are off, so set to UN-reasonable values. + m_HighlightPower = -1; + m_Background = Color(-1, -1, -1, 1); + m_FinalRasW = 0; + m_FinalRasH = 0; + m_Supersample = 0; + m_SpatialFilterRadius = -1; + m_Zoom = 999999; + m_ProjFunc = NULL; + m_CamZPos = 999999; + m_CamPerspective = 999999; + m_CamYaw = 999999; + m_CamPitch = 999999; + m_CamDepthBlur = 999999; + m_BlurCoef = 999999; + m_CamMat = m3T(999999); + m_Quality = -1; + m_MaxRadDE = -1; + m_MinRadDE = -1; + m_CurveDE = -1; + m_GammaThresh = -1; + m_Passes = 0; + m_TemporalSamples = 0; + m_SpatialFilterType = GAUSSIAN_SPATIAL_FILTER; + m_AffineInterp = INTERP_LOG; + m_TemporalFilterType = BOX_TEMPORAL_FILTER; + m_TemporalFilterWidth = -1; + m_TemporalFilterExp = -999; + m_PaletteMode = PALETTE_STEP; + } + + m_Xforms.clear(); + m_FinalXform.Clear(); + ClearEdit(); + } + + /// + /// Thin wrapper to clear edit doc if not NULL and set to NULL. + /// + void ClearEdit() + { + if (m_Edits != NULL) + xmlFreeDoc(m_Edits); + + m_Edits = NULL; + } + + /// + /// Return a string representation of this ember. + /// + /// The string representation of this ember + string ToString() const + { + unsigned int i; + ostringstream ss; + + ss << "FinalRasW: " << m_FinalRasW << endl + << "FinalRasH: " << m_FinalRasH << endl + << "OrigRasW: " << m_OrigFinalRasW << endl + << "OrigRasH: " << m_OrigFinalRasH << endl + << "Supersample: " << m_Supersample << endl + << "Passes: " << m_Passes << endl + << "TemporalSamples: " << m_TemporalSamples << endl + << "Symmetry: " << m_Symmetry << endl + + << "Quality: " << m_Quality << endl + << "PixelsPerUnit: " << m_PixelsPerUnit << endl + << "Zoom: " << m_Zoom << endl + << "ZPos: " << m_CamZPos << endl + << "Perspective: " << m_CamPerspective << endl + << "Yaw: " << m_CamYaw << endl + << "Pitch: " << m_CamPitch << endl + << "Depth Blur: " << m_CamDepthBlur << endl + << "CenterX: " << m_CenterX << endl + << "CenterY: " << m_CenterY << endl + << "Rotate: " << m_Rotate << endl + << "Hue: " << m_Hue << endl + << "Brightness: " << m_Brightness << endl + << "Gamma: " << m_Gamma << endl + << "Vibrancy: " << m_Vibrancy << endl + << "GammaThresh: " << m_GammaThresh << endl + << "HighlightPower: " << m_HighlightPower << endl + << "Time: " << m_Time << endl + << "Background: " << m_Background.r << ", " << m_Background.g << ", " << m_Background.b << ", " << m_Background.a << endl + + << "Interp: " << m_Interp << endl + << "Affine Interp Type: " << m_AffineInterp << endl + + << "Minimum DE Radius: " << m_MinRadDE << endl + << "Maximum DE Radius: " << m_MaxRadDE << endl + << "DE Curve: " << m_CurveDE << endl + + << "Spatial Filter Type: " << m_SpatialFilterType << endl + << "Spatial Filter Radius: " << m_SpatialFilterRadius << endl + + << "Temporal Filter Type: " << m_TemporalFilterType << endl + << "Temporal Filter Exp: " << m_TemporalFilterExp << endl + << "Temporal Filter Width: " << m_TemporalFilterWidth << endl + + << "Palette Mode: " << m_PaletteMode << endl + << "Palette Interp: " << m_PaletteInterp << endl + << "Palette Index: " << m_Palette.m_Index << endl + //Add palette info here if needed. + + << "Name: " << m_Name << endl + << "Parent Filename: " << m_ParentFilename << endl + << endl; + + for (i = 0; i < XformCount(); i++) + { + ss << "Xform " << i << ":" << endl << m_Xforms[i].ToString() << endl; + } + + if (UseFinalXform()) + ss << "Final Xform: " << m_FinalXform.ToString() << endl; + + return ss.str(); + } + + /// + /// Accessors. + /// + inline const Xform* Xforms() const { return &m_Xforms[0]; } + inline Xform* NonConstXforms() { return &m_Xforms[0]; } + inline unsigned int XformCount() const { return (unsigned int)m_Xforms.size(); } + inline const Xform* FinalXform() const { return &m_FinalXform; } + inline Xform* NonConstFinalXform() { return &m_FinalXform; } + inline bool UseFinalXform() const { return !m_FinalXform.Empty(); } + inline unsigned int TotalXformCount() const { return XformCount() + (UseFinalXform() ? 1 : 0); } + inline int PaletteIndex() const { return m_Palette.m_Index; } + inline T BlurCoef() { return m_BlurCoef; } + + //The width and height in pixels of the final output image. The size of the histogram and DE filtering buffers will differ from this. + //Xml fields: "size". + unsigned int m_FinalRasW; + unsigned int m_FinalRasH; + unsigned int m_OrigFinalRasW;//Keep track of the originals read from the Xml, because... + unsigned int m_OrigFinalRasH;//the dimension may change in an editor and the originals are needed for the aspect ratio. + + //The multiplier in size of the histogram and DE filtering buffers. Must be at least one, preferrably never larger than 4, only useful at 2. + //Xml field: "supersample" or "overample (deprecated)". + unsigned int m_Supersample; + + //Times to run the algorithm while clearing the histogram, but not the filter. Almost always set to 1 and may even be deprecated. + //Xml field: "passes". + unsigned int m_Passes; + + //When animating, split each pass into this many pieces, each doing a fraction of the total iterations. Each temporal sample + //will render an interpolated instance of the ember that is a fraction of the current ember and the next one. + //When rendering a single image, this field is always set to 1. + //Xml field: "temporal_samples". + unsigned int m_TemporalSamples; + + //Whether or not any symmetry was added. This field is in a bit of a state of conflict right now as flam3 has a severe bug. + //Xml field: "symmetry". + int m_Symmetry; + + //The number of iterations per pixel of the final output image. Note this is not affected by the increase in pixels in the + //histogram and DE filtering buffer due to supersampling. It can be affected by a non-zero zoom value though. + //10 is a good value for interactive/real-time rendering, 100-200 is good for previewing, 1000 is good for final output. Above that is mostly a waste of energy. + //Xml field: "quality". + T m_Quality; + + //The number of pixels in the final output image that corresponds to the distance from 0-1 in the cartesian plane used for iterating. + //A larger value produces a more zoomed in imgage. A value of 240 is commonly used, but in practice it varies widely depending on the image. + //Note that increasing this value does not adjust the quality by a proportional amount, so an increased value may produce a degraded image. + //Xml field: "scale". + T m_PixelsPerUnit; + + //A value greater than 0 will zoom in the field of view, however it will also increase the quality by a proportional amount. This is used to + //overcome the shortcoming of scale by also adjusting the quality. + //Xml field: "zoom". + T m_Zoom; + + //3D fields. +private: + typedef void (Ember::*ProjFuncPtr)(Point&, QTIsaac&); + ProjFuncPtr m_ProjFunc; + +public: + //Xml field: "cam_zpos". + T m_CamZPos; + + //Xml field: "cam_persp". + T m_CamPerspective; + + //Xml field: "cam_yaw". + T m_CamYaw; + + //Xml field: "cam_pitch". + T m_CamPitch; + + //Xml field: "cam_dof". + T m_CamDepthBlur; + +private: + T m_BlurCoef; + +public: + m3T m_CamMat; + + //The camera offset from the center of the cartesian plane. Since this is the camera offset, the final output image will be moved in the opposite + //direction of the values specified. + //Xml field: "center". + T m_CenterX; + T m_CenterY; + + //Rotate the camera by this many degrees. Since this is a camera rotation, the final output image will be rotated counter-clockwise. + //Xml field: "rotate". + T m_Rotate; + + //When specifying the palette as an index in the palette file, rather than inserted in the Xml, it can optionally have its hue + //rotated by this amount. + //Xml field: "hue". + T m_Hue; + + //Determine how bright to make the image during final accumulation. + //Xml field: "brightness". + T m_Brightness; + + //Gamma level used in gamma correction during final accumulation. + //Xml field: "gamma". + T m_Gamma; + + //Used in color correction during final accumulation. + //Xml field: "vibrancy". + T m_Vibrancy; + + //Gamma threshold used in gamma correction during final accumulation. + //Xml field: "gamma_threshold". + T m_GammaThresh; + + //Value to control saturation of some pixels in gamma correction during final accumulation. + //Xml field: "highlight_power". + T m_HighlightPower; + + //When animating a file full of many embers, this value is used to specify the time in the animation + //that this ember should be rendered. They must all be sequential and increase by a default value of 1. + //Xml field: "time". + T m_Time; + + //The background color of the image used in final accumulation, ranged 0-1. + //Xml field: "background". + Color m_Background; + + //Animation/interpolation. + + //The type of interpolation to use when interpolating between embers for animation. + //Xml field: "interpolation". + eInterp m_Interp; + + //The type of interpolation to use on affine transforms when interpolating embers for animation. + //Xml field: "interpolation_type" or "interpolation_space (deprecated)". + eAffineInterp m_AffineInterp; + + //The type of interpolation to use for the palette when interpolating embers for animation. + //Xml field: "palette_interpolation". + ePaletteInterp m_PaletteInterp; + + //Temporal Filter. + + //Only used if temporal filter type is exp, else unused. + //Xml field: "temporal_filter_exp". + T m_TemporalFilterExp; + + //The width of the temporal filter. + //Xml field: "temporal_filter_width". + T m_TemporalFilterWidth; + + //The type of the temporal filter: Gaussian, Box or Exp. + //Xml field: "temporal_filter_type". + eTemporalFilterType m_TemporalFilterType; + + //Density Estimation Filter. + + //The minimum radius of the DE filter. + //Xml field: "estimator_minimum". + T m_MinRadDE; + + //The maximum radius of the DE filter. + //Xml field: "estimator_radius". + T m_MaxRadDE; + + //The shape of the curve that governs how quickly or slowly the filter drops off as it moves away from the center point. + //Xml field: "estimator_curve". + T m_CurveDE; + + //Spatial Filter. + + //The radius of the spatial filter used in final accumulation. + //Xml field: "filter". + T m_SpatialFilterRadius; + + //The type of spatial filter used in final accumulation: + //Gaussian, Hermite, Box, Triangle, Bell, Bspline, Lanczos3 + //Lanczos2, Mitchell, Blackman, Catrom, Hamming, Hanning, Quadratic. + //Xml field: "filter_shape". + eSpatialFilterType m_SpatialFilterType; + + //Palette. + + //The method used for retrieving a color from the palette when accumulating to the histogram: step, linear. + //Xml field: "palette_mode". + ePaletteMode m_PaletteMode; + + //The color palette to use. Can be specified inline as Xml color fields, or as a hex buffer. Can also be specified + //as an index into the palette file with an optional hue rotation applied. Inserting as a hex buffer is the preferred method. + //Xml field: "color" or "colors" or "palette" . + Palette m_Palette; + + //Strings. + + //The name of this ember. + //Xml field: "name". + string m_Name; + + //The name of the file that this ember was contained in. + //Xml field: "". + string m_ParentFilename; + + //An Xml edit document describing information about the author as well as an edit history of the ember. + //Xml field: "edit". + xmlDocPtr m_Edits; + + //The 0-based position of this ember in the file it was contained in. + int m_Index; + +private: + /// + /// Interps the t. + /// + /// The embers. + /// The coefs. + /// The size. + template ::*m> + void InterpT(Ember* embers, vector& coefs, size_t size) + { + this->*m = 0; + + for (size_t k = 0; k < size; k++) + this->*m += coefs[k] * embers[k].*m; + } + + template ::*m> + void InterpX(Ember* embers, vector& coefs, size_t size) + { + this->*m = M(); + + for (size_t k = 0; k < size; k++) + this->*m += coefs[k] * embers[k].*m; + } + + template ::*m> + void InterpI(Ember* embers, vector& coefs, size_t size) + { + T t = 0; + + for (size_t k = 0; k < size; k++) + t += coefs[k] * embers[k].*m; + + this->*m = (int)Rint(t); + } + + template ::*m> + void InterpXform(Xform* xform, unsigned int i, Ember* embers, vector& coefs, size_t size) + { + xform->*m = T(0); + + for (size_t k = 0; k < size; k++) + xform->*m += coefs[k] * embers[k].GetTotalXform(i)->*m; + } + + //The vector containing all of the xforms. + //Xml field: "xform". + vector> m_Xforms; + + //Optional final xform. Default is empty. + //Discussed in section 3.2 of the paper, page 6. + //Xml field: "finalxform". + Xform m_FinalXform; +}; + +/// +/// Comparer for sorting embers based on time. +/// +/// Pointer to the first ember to compare +/// Pointer to the second ember to compare +/// True if av's time is less than bv's time, else false. +template +static inline bool CompareEmbers(const Ember& a, const Ember& b) +{ + return a.m_Time < b.m_Time; +} +} \ No newline at end of file diff --git a/Source/Ember/EmberDefines.h b/Source/Ember/EmberDefines.h new file mode 100644 index 0000000..eb819af --- /dev/null +++ b/Source/Ember/EmberDefines.h @@ -0,0 +1,83 @@ +#pragma once + +#include "EmberPch.h" + +/// +/// Basic #defines used throughout the library. +/// + +//MSVC specific? +#if defined(BUILDING_EMBER) +#define EMBER_API __declspec(dllexport) +#else +#define EMBER_API __declspec(dllimport) +#endif + +#define RESTRICT __restrict//This might make things faster, unsure if it really does though. +//#define RESTRICT + +namespace EmberNs +{ +//Wrap the sincos function for Macs and PC. +#if defined(__APPLE__) || defined(_MSC_VER) + #define sincos(x, s, c) *(s)=sin(x); *(c)=cos(x); +#else + extern void sincos(double x, double *s, double *c); +#endif + +#define EMBER_VERSION "0.4.0.2" +#define EPS6 T(1e-6) +#define EPS T(1e-10)//Apoplugin.h uses -20, but -10 seems to work fine. +#define ISAAC_SIZE 4 +#define MEMALIGN 32 +#define DE_THRESH 100 +#define MAX_VARS_PER_XFORM 8 +#define DEG_2_RAD (M_PI / 180) +#define RAD_2_DEG (180 / M_PI) +#define DEG_2_RAD_T (T(M_PI) / T(180)) +#define RAD_2_DEG_T (T(180) / T(M_PI)) +#define M_2PI (T(M_PI * 2)) +#define M_3PI (T(M_PI * 3)) +#define SQRT5 T(2.2360679774997896964091736687313) +#define M_PHI T(1.61803398874989484820458683436563) +#define COLORMAP_LENGTH 256//These will need to change if 2D palette support is ever added, or variable sized palettes. +#define COLORMAP_LENGTH_MINUS_1 255 +#define WHITE 255 +#define XC (const xmlChar*) +#define BadVal(x) (((x) != (x)) || ((x) > 1e10) || ((x) < -1e10)) +#define Rint(A) floor((A) + (((A) < 0) ? T(-0.5) : T(0.5))) +#define Vlen(x) (sizeof(x) / sizeof(*x)) +#define SQR(x) ((x) * (x)) +#define CUBE(x) ((x) * (x) * (x)) +#define TLOW std::numeric_limits::lowest() +#define TMAX std::numeric_limits::max() + +#ifndef acosh +#define acosh(x) (log(x + sqrt(SQR(x) - 1)))//Remove this once you upgrade compilers to VS 2013 or later.//TODO +#endif + +#ifndef fma +#define fma(x, y, z) ((x * y) + z) +#endif + +#define DO_DOUBLE 1//Comment this out for shorter build times during development. Always uncomment for release. + +#define v2T glm::detail::tvec2 +#define v3T glm::detail::tvec3 +#define v4T glm::detail::tvec4 +#define m2T glm::detail::tmat2x2 +#define m3T glm::detail::tmat3x3 +#define m4T glm::detail::tmat4x4 +#define m23T glm::detail::tmat2x3 + +enum eInterp : unsigned int { EMBER_INTERP_LINEAR = 0, EMBER_INTERP_SMOOTH = 1 }; +enum eAffineInterp : unsigned int { INTERP_LINEAR = 0, INTERP_LOG = 1, INTERP_COMPAT = 2, INTERP_OLDER = 3 }; +enum ePaletteMode : unsigned int { PALETTE_STEP = 0, PALETTE_LINEAR = 1 }; +enum ePaletteInterp : unsigned int { INTERP_HSV = 0, INTERP_SWEEP = 1 }; +enum eMotion : unsigned int { MOTION_SIN = 1, MOTION_TRIANGLE = 2, MOTION_HILL = 3 }; +enum eProcessAction : unsigned int { NOTHING = 0, ACCUM_ONLY = 1, FILTER_AND_ACCUM = 2, KEEP_ITERATING = 3, FULL_RENDER = 4 }; +enum eProcessState : unsigned int { NONE = 0, ITER_STARTED = 1, ITER_DONE = 2, FILTER_DONE = 3, ACCUM_DONE = 4 }; +enum eInteractiveFilter : unsigned int { FILTER_LOG = 0, FILTER_DE = 1 }; +enum eScaleType : unsigned int { SCALE_NONE = 0, SCALE_WIDTH = 1, SCALE_HEIGHT = 2 }; +enum eRenderStatus : unsigned int { RENDER_OK = 0, RENDER_ERROR = 1, RENDER_ABORT = 2 }; +} diff --git a/Source/Ember/EmberPch.cpp b/Source/Ember/EmberPch.cpp new file mode 100644 index 0000000..4efc096 --- /dev/null +++ b/Source/Ember/EmberPch.cpp @@ -0,0 +1 @@ +#include "EmberPch.h" diff --git a/Source/Ember/EmberPch.h b/Source/Ember/EmberPch.h new file mode 100644 index 0000000..c66ffda --- /dev/null +++ b/Source/Ember/EmberPch.h @@ -0,0 +1,62 @@ +#pragma once + +/// +/// Precompiled header file. Place all system includes here with appropriate #defines for different operating systems and compilers. +/// + +#define NOMINMAX +#define _USE_MATH_DEFINES + +#ifdef _WIN32 + #define basename(x) _strdup(x) + #define snprintf _snprintf + #define snprintf_s _snprintf_s + #define WIN32_LEAN_AND_MEAN + #define EMBER_OS "WIN" + + #include + #include +#elif __APPLE__ + #define EMBER_OS "OSX" +#else + #include + #include + #define EMBER_OS "LNX" +#endif + +//Standard headers. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//Third party headers. +#include + +//Intel's Threading Building Blocks is what's used for all threading. +#include "tbb/task_group.h" +#include "tbb/parallel_for.h" +#include "tbb/task_scheduler_init.h" + +#define GLM_FORCE_RADIANS + +//glm is what's used for matrix math. +#include "glm/glm.hpp" +#include "glm/gtc/matrix_transform.hpp" +#include "glm/gtc/type_ptr.hpp" +#include "glm/gtx/string_cast.hpp" + +using namespace tbb; +using namespace std; diff --git a/Source/Ember/EmberToXml.h b/Source/Ember/EmberToXml.h new file mode 100644 index 0000000..1365328 --- /dev/null +++ b/Source/Ember/EmberToXml.h @@ -0,0 +1,686 @@ +#pragma once + +#include "Utils.h" +#include "PaletteList.h" +#include "VariationList.h" +#include "Ember.h" + +/// +/// EmberToXml class. +/// + +namespace EmberNs +{ +/// +/// Class for converting ember objects to Xml documents. +/// Support for saving one or more to a single file. +/// Template argument expected to be float or double. +/// +template +class EMBER_API EmberToXml : public EmberReport +{ +public: + /// + /// Empty constructor. + /// + EmberToXml() + { + } + + /// + /// Save the ember to the specified file. + /// + /// Full path and filename + /// The ember to save + /// How deep the edit depth goes + /// If true included edit tags, else don't. + /// If true use integers instead of floating point numbers when embedding a non-hex formatted palette, else use floating point numbers. + /// If true, embed a hexadecimal palette instead of Xml Color tags, else use Xml color tags. + /// If true, append to the file if it already exists, else create a new file. + /// Whether a new file is to be started + /// Whether an existing file is to be ended + /// True if successful, else false + bool Save(string filename, Ember& ember, unsigned int printEditDepth, bool doEdits, bool intPalette, bool hexPalette, bool append = false, bool start = false, bool finish = false) + { + vector> vec; + + vec.push_back(ember); + return Save(filename, vec, printEditDepth, doEdits, intPalette, hexPalette, append, start, finish); + } + + /// + /// Save a vector of embers to the specified file. + /// + /// Full path and filename + /// The vector of embers to save + /// How deep the edit depth goes + /// If true included edit tags, else don't. + /// If true use integers instead of floating point numbers when embedding a non-hex formatted palette, else use floating point numbers. + /// If true, embed a hexadecimal palette instead of Xml Color tags, else use Xml color tags. + /// If true, append to the file if it already exists, else create a new file. + /// Whether a new file is to be started + /// Whether an existing file is to be ended + /// True if successful, else false + bool Save(string filename, vector>& embers, unsigned int printEditDepth, bool doEdits, bool intPalette, bool hexPalette, bool append = false, bool start = false, bool finish = false) + { + bool b = false; + string temp; + ofstream f; + + try + { + if (append) + f.open(filename, std::ofstream::out | std::ofstream::app);//Appending allows us to write multiple embers to a single file. + else + f.open(filename); + + if (f.is_open()) + { + if ((append && start) || !append) + { + temp = "\n"; + f.write(temp.c_str(), temp.size()); + } + + for (size_t i = 0; i < embers.size(); i++) + { + string s = ToString(embers[i], "", printEditDepth, doEdits, intPalette, hexPalette); + f.write(s.c_str(), s.size()); + } + + if ((append && finish) || !append) + { + temp = "\n"; + f.write(temp.c_str(), temp.size()); + } + + f.close(); + b = true; + } + else + { + cout << "Error: Writing flame " << filename << " failed." << endl; + b = false; + } + } + catch (...) + { + if (f.is_open()) + f.close(); + + b = false; + } + + return b; + } + + /// + /// Return the Xml string representation of an ember. + /// + /// The ember to create the Xml with + /// If true, add extra attributes, else don't + /// How deep the edit depth goes + /// If true included edit tags, else don't. + /// If true use integers instead of floating point numbers when embedding a non-hex formatted palette, else use floating point numbers. + /// If true, embed a hexadecimal palette instead of Xml Color tags, else use Xml color tags. + /// The Xml string representation of the passed in ember + string ToString(Ember& ember, string extraAttributes, unsigned int printEditDepth, bool doEdits, bool intPalette, bool hexPalette = true) + { + unsigned int i, j; + ostringstream os; + + os << "::ToString(ember.m_SpatialFilterType)) << "\""; + os << " temporal_filter_type=\"" << ToLower(TemporalFilterCreator::ToString(ember.m_TemporalFilterType)) << "\""; + + if (ember.m_TemporalFilterType == EXP_TEMPORAL_FILTER) + os << " temporal_filter_exp=\"" << ember.m_TemporalFilterExp << "\""; + + os << " temporal_filter_width=\"" << ember.m_TemporalFilterWidth << "\""; + os << " quality=\"" << ember.m_Quality << "\""; + os << " passes=\"" << ember.m_Passes << "\""; + os << " temporal_samples=\"" << ember.m_TemporalSamples << "\""; + os << " background=\"" << ember.m_Background.r << " " << ember.m_Background.g << " " << ember.m_Background.b << "\""; + os << " brightness=\"" << ember.m_Brightness << "\""; + os << " gamma=\"" << ember.m_Gamma << "\""; + os << " highlight_power=\"" << ember.m_HighlightPower << "\""; + os << " vibrancy=\"" << ember.m_Vibrancy << "\""; + //os << " hue=\"" << ember.m_Hue << "\"";//Oddly enough, flam3 never wrote this value out.//ORIG + os << " estimator_radius=\"" << ember.m_MaxRadDE << "\""; + os << " estimator_minimum=\"" << ember.m_MinRadDE << "\""; + os << " estimator_curve=\"" << ember.m_CurveDE << "\""; + os << " gamma_threshold=\"" << ember.m_GammaThresh << "\""; + os << " cam_zpos=\"" << ember.m_CamZPos << "\""; + os << " cam_persp=\"" << ember.m_CamPerspective << "\""; + os << " cam_yaw=\"" << ember.m_CamYaw << "\""; + os << " cam_pitch=\"" << ember.m_CamPitch << "\""; + os << " cam_dof=\"" << ember.m_CamDepthBlur << "\""; + + if (ember.m_PaletteMode == PALETTE_STEP) + os << " palette_mode=\"step\""; + else if (ember.m_PaletteMode == PALETTE_LINEAR) + os << " palette_mode=\"linear\""; + + if (ember.m_Interp == EMBER_INTERP_SMOOTH) + os << " interpolation=\"smooth\""; + + if (ember.m_AffineInterp == INTERP_LINEAR) + os << " interpolation_type=\"linear\""; + else if (ember.m_AffineInterp == INTERP_LOG) + os << " interpolation_type=\"log\""; + else if (ember.m_AffineInterp == INTERP_COMPAT) + os << " interpolation_type=\"old\""; + else if (ember.m_AffineInterp == INTERP_OLDER) + os << " interpolation_type=\"older\""; + + if (ember.m_PaletteInterp == INTERP_SWEEP) + os << " palette_interpolation=\"sweep\""; + + if (!extraAttributes.empty()) + os << " " << extraAttributes; + + os << ">\n"; + + //This is a grey area, what to do about symmetry to avoid duplicating the symmetry xforms when reading back?//TODO//BUG. + //if (ember.m_Symmetry) + // os << " \n"; + + for (i = 0; i < ember.XformCount(); i++) + os << ToString(*ember.GetXform(i), ember.XformCount(), false, false);//Not final, don't do motion. + + if (ember.UseFinalXform()) + os << ToString(*ember.NonConstFinalXform(), ember.XformCount(), true, false);//Final, don't do motion. + + if (hexPalette) + { + os << " \n"; + + for (i = 0; i < 32; i++) + { + os << " "; + + for (j = 0; j < 8; j++) + { + int idx = 8 * i + j; + + os << hex << setw(2) << setfill('0') << (int)Rint(ember.m_Palette[idx][0] * 255); + os << hex << setw(2) << setfill('0') << (int)Rint(ember.m_Palette[idx][1] * 255); + os << hex << setw(2) << setfill('0') << (int)Rint(ember.m_Palette[idx][2] * 255); + } + + os << endl; + } + + os << " \n"; + } + else + { + for (i = 0; i < 256; i++) + { + double r = ember.m_Palette[i][0] * 255; + double g = ember.m_Palette[i][1] * 255; + double b = ember.m_Palette[i][2] * 255; + double a = ember.m_Palette[i][3] * 255; + + os << " "; + //The original used a precision of 6 which is totally unnecessary, use 2. + if (IsClose(a, 255.0)) + { + if (intPalette) + os << ""; + else + os << ""; + } + else + { + if (intPalette) + os << " "; + else + os << " "; + } + + os << "\n"; + } + } + + if (doEdits && ember.m_Edits != NULL) + os << ToString(xmlDocGetRootElement(ember.m_Edits), 1, true, printEditDepth); + + os << "\n"; + + return os.str(); + } + + /// + /// Create a new editdoc optionally based on parents passed in. + /// This is used when an ember is made out of some mutation or edit from one or two existing embers and + /// the user wants to capture the genetic lineage history information in the edit doc of the new ember. + /// + /// The first parent, optionally NULL. + /// The second parent, optionally NULL. + /// The action that was taken to create the new ember + /// The nickname of the author + /// The Url of the author + /// The id of the author + /// The comment to include + /// The sheep generation used if > 0. Default: 0. + /// The sheep id used if > 0. Default: 0. + /// + xmlDocPtr CreateNewEditdoc(Ember* parent0, Ember* parent1, string action, string nick, string url, string id, string comment, int sheepGen = 0, int sheepId = 0) + { + char timeString[128]; + char buffer[128]; + char commentString[128]; + tm localt; + time_t myTime; + xmlDocPtr commentDoc = NULL; + xmlDocPtr doc = xmlNewDoc(XC "1.0"); + xmlNodePtr rootNode = NULL, node = NULL, nodeCopy = NULL; + xmlNodePtr rootComment = NULL; + + //Create the root node, called "edit". + rootNode = xmlNewNode(NULL, XC "edit"); + xmlDocSetRootElement(doc, rootNode); + + //Add the edit attributes. + //Date. + myTime = time(NULL); + localtime_s(&localt, &myTime); + strftime(timeString, 128, "%a %b %d %H:%M:%S %z %Y", &localt);//XXX use standard time format including timezone. + xmlNewProp(rootNode, XC "date", XC timeString); + + //Nick. + if (nick != "") + xmlNewProp(rootNode, XC "nick", XC nick.c_str()); + + //Url. + if (url != "") + xmlNewProp(rootNode, XC "url", XC url.c_str()); + + if (id != "") + xmlNewProp(rootNode, XC "id", XC id.c_str()); + + //Action. + xmlNewProp(rootNode, XC "action", XC action.c_str()); + + //Sheep info. + if (sheepGen > 0 && sheepId > 0) + { + //Create a child node of the root node called sheep. + node = xmlNewChild(rootNode, NULL, XC "sheep", NULL); + + //Create the sheep attributes. + sprintf_s(buffer, 128, "%d", sheepGen); + xmlNewProp(node, XC "generation", XC buffer); + + sprintf_s(buffer, 128, "%d", sheepId); + xmlNewProp(node, XC "id", XC buffer); + } + + //Check for the parents. + //If parent 0 not specified, this is a randomly generated genome. + if (parent0) + { + if (parent0->m_Edits) + { + //Copy the node from the parent. + node = xmlDocGetRootElement(parent0->m_Edits); + nodeCopy = xmlCopyNode(node, 1); + AddFilenameWithoutAmpersand(nodeCopy, parent0->m_ParentFilename); + sprintf_s(buffer, 128, "%d", parent0->m_Index); + xmlNewProp(nodeCopy, XC "index", XC buffer); + xmlAddChild(rootNode, nodeCopy); + } + else + { + //Insert a (parent has no edit) message. + nodeCopy = xmlNewChild(rootNode, NULL, XC "edit", NULL); + AddFilenameWithoutAmpersand(nodeCopy, parent0->m_ParentFilename); + sprintf_s(buffer, 128, "%d", parent0->m_Index); + xmlNewProp(nodeCopy, XC "index", XC buffer); + } + } + + if (parent1) + { + if (parent1->m_Edits) + { + //Copy the node from the parent. + node = xmlDocGetRootElement(parent1->m_Edits); + nodeCopy = xmlCopyNode(node, 1); + AddFilenameWithoutAmpersand(nodeCopy, parent1->m_ParentFilename); + sprintf_s(buffer, 128, "%d", parent1->m_Index); + xmlNewProp(nodeCopy, XC "index", XC buffer); + xmlAddChild(rootNode, nodeCopy); + } + else + { + //Insert a (parent has no edit) message. + nodeCopy = xmlNewChild(rootNode, NULL, XC "edit",NULL); + AddFilenameWithoutAmpersand(nodeCopy, parent1->m_ParentFilename); + sprintf_s(buffer, 128, "%d", parent1->m_Index); + xmlNewProp(nodeCopy, XC "index", XC buffer); + } + } + + //Comment string: + //This one's hard, since the comment string must be treated as + //a valid XML document. Create a new document using the comment + //string as the in-memory document, and then copy all children of + //the root node into the edit structure + //Parsing the comment string should be done once and then copied + //for each new edit doc, but that's for later. + if (comment != "") + { + sprintf_s(commentString, 128, "%s", comment.c_str()); + commentDoc = xmlReadMemory(commentString, (int)strlen(commentString), "comment.env", NULL, XML_PARSE_NONET); + + //Check for errors. + if (commentDoc != NULL) + { + + //Loop through the children of the new document and copy them into the rootNode. + rootComment = xmlDocGetRootElement(commentDoc); + + for (node = rootComment->children; node; node = node->next) + { + nodeCopy = xmlCopyNode(node, 1); + xmlAddChild(rootNode, nodeCopy); + } + + //Free the created document. + xmlFreeDoc(commentDoc); + } + else + { + cout << "Failed to parse comment into Xml." << endl; + } + } + + //Return the xml doc. + return doc; + } + +private: + /// + /// Return the Xml string representation of an xform. + /// + /// The xform to create the Xml with + /// The number of non-final xforms in the ember to which this xform belongs. Used for xaos. + /// True if the xform is the final xform in the ember, else false. + /// If true, include motion elements in the Xml string, else omit. + /// The Xml string representation of the passed in xform + string ToString(Xform& xform, unsigned int xformCount, bool isFinal, bool doMotion) + { + unsigned int i, j; + ostringstream os; + + if (doMotion) + { + os << " * var = xform.GetVariation(i); + ParametricVariation* parVar = dynamic_cast*>(var); + + if (var->m_Weight != 0) + { + os << var->Name() << "=\"" << var->m_Weight << "\" "; + + if (parVar) + { + ParamWithName* params = parVar->Params(); + + for (j = 0; j < parVar->ParamCount(); j++) + { + if ((!doMotion || (doMotion && (params[j].ParamVal() != 0))) && !params[j].IsPrecalc()) + os << params[j].Name() << "=\"" << params[j].ParamVal() << "\" "; + } + } + } + } + + if (!doMotion || (doMotion && !xform.m_Affine.IsZero())) + { + os << "coefs=\"" << xform.m_Affine.A() << " " << xform.m_Affine.D() << " " << xform.m_Affine.B() << " " + << xform.m_Affine.E() << " " << xform.m_Affine.C() << " " << xform.m_Affine.F() << "\""; + } + + if ((!doMotion && !xform.m_Post.IsID()) || (doMotion && !xform.m_Post.IsZero())) + { + os << " post=\"" << xform.m_Post.A() << " " << xform.m_Post.D() << " " << xform.m_Post.B() << " " + << xform.m_Post.E() << " " << xform.m_Post.C() << " " << xform.m_Post.F() << "\""; + } + + //Original only printed xaos values that were not 1. Here, print them all out if any are present. + if (!isFinal && !doMotion && xform.XaosPresent()) + { + os << " chaos=\""; + + for (i = 0; i < xformCount; i++) + os << xform.Xaos(i) << " "; + + os << "\""; + } + + if (!doMotion) + os << " opacity=\"" << xform.m_Opacity << "\""; + + if (!doMotion && !xform.m_Motion.empty()) + { + os << ">\n"; + + for (i = 0; i < xform.m_Motion.size(); i++) + os << ToString(xform.m_Motion[i], 0, false, true); + + if (isFinal)//Fixed to properly close final.//SMOULDER + os << " \n"; + else + os << " \n"; + } + else + os << "/>\n"; + + return os.str(); + } + + /// + /// Return an edit node Xml string. + /// + /// The edit node to get the string for + /// How many tabs to use + /// If true, include newlines and tabs, else don't. + /// How deep the edit depth goes + /// The edit node Xml string + string ToString(xmlNodePtr editNode, unsigned int tabs, bool formatting, unsigned int printEditDepth) + { + bool indentPrinted = false; + char* tabString = " ", *attStr; + unsigned int ti, editOrSheep = 0; + xmlAttrPtr attPtr = NULL, curAtt = NULL; + xmlNodePtr childPtr = NULL, curChild = NULL; + ostringstream os; + + if (printEditDepth > 0 && tabs > printEditDepth) + return ""; + + //If this node is an XML_ELEMENT_NODE, print it and its attributes. + if (editNode->type == XML_ELEMENT_NODE) + { + //Print the node at the tab specified. + if (formatting) + for (ti = 0; ti < tabs; ti++) + os << tabString; + + os << "<" << editNode->name; + + //This can either be an edit node or a sheep node. + //If it's an edit node, add one to the tab. + if (!Compare(editNode->name, "edit")) + { + editOrSheep = 1; + tabs++; + } + else if (!Compare(editNode->name, "sheep")) + editOrSheep = 2; + else + editOrSheep = 0; + + //Print the attributes. + attPtr = editNode->properties; + + for (curAtt = attPtr; curAtt; curAtt = curAtt->next) + { + attStr = (char*)xmlGetProp(editNode, curAtt->name); + os << " " << curAtt->name << "=\"" << attStr << "\""; + xmlFree(attStr); + } + + //Does this node have children? + if (!editNode->children || (printEditDepth > 0 && tabs > printEditDepth)) + { + //Close the tag and subtract the tab. + os << "/>"; + + if (formatting) + os << "\n"; + + tabs--; + } + else + { + //Close the tag. + os << ">"; + + if (formatting) + os << "\n"; + + //Loop through the children and print them. + childPtr = editNode->children; + indentPrinted = false; + + for (curChild = childPtr; curChild; curChild = curChild->next) + { + //If child is an element, indent first and then print it. + if (curChild->type == XML_ELEMENT_NODE && + (!Compare(curChild->name, "edit") || !Compare(curChild->name, "sheep"))) + { + if (indentPrinted) + { + indentPrinted = false; + os << "\n"; + } + + os << ToString(curChild, tabs, true, printEditDepth); + } + else + { + //Child is a text node, don't want to indent more than once. + if (xmlIsBlankNode(curChild)) + continue; + + if (!indentPrinted && formatting) + { + for (ti = 0; ti < tabs; ti++) + os << tabString; + + indentPrinted = true; + } + + //Print nodes without formatting. + os << ToString(curChild, tabs, false, printEditDepth); + } + } + + if (indentPrinted && formatting) + os << "\n"; + + tabs--;//Tab out. + + if (formatting) + for (ti = 0; ti < tabs; ti++) + os << tabString; + + os << "name << ">";//Close the tag. + + if (formatting) + os << "\n"; + } + } + else if (editNode->type == XML_TEXT_NODE) + { + string s((char*)xmlNodeGetContent(editNode)); + os << Trim(s); + } + + return os.str(); + } + + void AddFilenameWithoutAmpersand(xmlNodePtr node, string& filename) + { + if (filename.find_first_of('&') != std::string::npos) + { + string filenameWithoutAmpersands = filename; + + FindAndReplace(filenameWithoutAmpersands, "&", "&"); + xmlNewProp(node, XC "filename", XC filenameWithoutAmpersands.c_str()); + } + else + { + xmlNewProp(node, XC "filename", XC filename.c_str()); + } + } +}; +} \ No newline at end of file diff --git a/Source/Ember/Interpolate.h b/Source/Ember/Interpolate.h new file mode 100644 index 0000000..9ed0d92 --- /dev/null +++ b/Source/Ember/Interpolate.h @@ -0,0 +1,1018 @@ +#pragma once + +#include "Variation.h" + +/// +/// Interpolater class. +/// + +namespace EmberNs +{ +/// +/// Contains many static functions for handling interpolation and other miscellaneous operations on +/// embers and vectors of embers. This class is similar to, and used in conjunction with SheepTools. +/// Template argument expected to be float or double. +/// +template +class EMBER_API Interpolater +{ +public: + /// + /// Aligns the specified array of embers and stores in the output array. + /// This is used to prepare embers before interpolating them. + /// Alignment means that every ember in a list will have the same number of xforms. + /// Each xform at a given position will have mostly the same variations as the xform + /// in the same position in the rest of the embers. However some + /// intelligence is applied to add or remove variations that wouldn't look good with + /// the others present. + /// After this function completes, sourceEmbers will remain unchanged and destEmbers + /// will contain the aligned list of embers from sourceEmbers. + /// + /// The array of embers to align + /// The array which will contain the aligned embers + /// The number of elements in sourceEmbers + static void Align(Ember* sourceEmbers, Ember* destEmbers, unsigned int count) + { + bool aligned = true; + bool currentFinal, final = sourceEmbers[0].UseFinalXform(); + unsigned int i, xf, currentCount, maxCount = sourceEmbers[0].XformCount(); + Xform* destXform; + Xform* destOtherXform; + + //Determine the max number of xforms present in sourceEmbers. + //Also check if final xforms are used in any of them. + for (i = 1; i < count; i++) + { + currentCount = sourceEmbers[i].XformCount(); + + if (currentCount != maxCount)//Any difference, less or more, means unaligned. + { + aligned = false; + + if (currentCount > maxCount) + maxCount = currentCount; + } + + currentFinal = sourceEmbers[i].UseFinalXform(); + + if (final != currentFinal)//Check if any used final. + { + aligned = false; + final |= currentFinal; + } + } + + //Copy them using the max xform count, and do final if any had final. + for (i = 0; i < count; i++) + destEmbers[i] = sourceEmbers[i].Copy(maxCount, final); + + if (final) + maxCount++; + + //Check to see if there's a parametric variation present in one xform + //but not in an aligned xform. If this is the case, use the parameters + //from the xform with the variation as the defaults for the blank one. + //All embers will have the same number of xforms at this point. + for (i = 0; i < count; i++) + { + unsigned int ii; + + for (xf = 0; xf < maxCount; xf++)//This will include both normal xforms and the final. + { + destXform = destEmbers[i].GetTotalXform(xf, final); + + //Ensure every parametric variation contained in every xform at either position i - 1 or i + 1 is also contained in the dest xform. + if (i > 0) + destOtherXform = destEmbers[i - 1].GetTotalXform(xf); + else if (i < count - 1) + destOtherXform = destEmbers[i + 1].GetTotalXform(xf); + else + destOtherXform = NULL;//Should never happen + + if (destOtherXform) + MergeXformVariations1Way(destOtherXform, destXform, true, true); + + //This is a new xform. Let's see if it's possible to choose a better 'identity' xform. + //Check the neighbors to see if any of these variations are used: + //rings2, fan2, blob, perspective, julian, juliascope, ngon, curl, super_shape, split + //If so, can use a better starting point for these. + //If the current xform index is greater than what the original xform count was for this ember, then it's a padding xform. + if (xf >= sourceEmbers[i].TotalXformCount() && !aligned) + { + unsigned int found = 0; + + //Remove linear. + destXform->DeleteVariationById(VAR_LINEAR); + + //Only do the next substitution for log interpolation. + if ((i == 0 && destEmbers[i].m_AffineInterp == INTERP_LOG) || + (i > 0 && destEmbers[i - 1].m_AffineInterp == INTERP_LOG)) + { + for (ii = -1; ii <= 1; ii += 2) + { + //Skip if out of bounds. + if (i + ii < 0 || i + ii >= count) + continue; + + //Skip if this is also padding. + if (xf >= sourceEmbers[i + ii].TotalXformCount()) + continue; + + destOtherXform = destEmbers[i + ii].GetTotalXform(xf); + + //Spherical / Ngon (trumps all others due to holes) + //Interpolate these against a 180 degree rotated identity + //with weight -1. + //Added JULIAN/JULIASCOPE to get rid of black wedges. + if (destOtherXform->GetVariationById(VAR_SPHERICAL) || + destOtherXform->GetVariationById(VAR_NGON) || + destOtherXform->GetVariationById(VAR_JULIAN) || + destOtherXform->GetVariationById(VAR_JULIASCOPE) || + destOtherXform->GetVariationById(VAR_POLAR) || + destOtherXform->GetVariationById(VAR_WEDGE_SPH) || + destOtherXform->GetVariationById(VAR_WEDGE_JULIA)) + { + destXform->AddVariation(new LinearVariation(-1)); + + //Set the coefs appropriately. + destXform->m_Affine.A(-1); + destXform->m_Affine.D(0); + destXform->m_Affine.B(0); + destXform->m_Affine.E(-1); + destXform->m_Affine.C(0); + destXform->m_Affine.F(0); + found = -1; + } + } + } + + if (found == 0) + { + for (ii = -1; ii <= 1; ii += 2) + { + //Skip if out of bounds. + if (i + ii < 0 || i + ii >= count) + continue; + + //Skip if this is also padding. + if (xf >= sourceEmbers[i + ii].TotalXformCount()) + continue; + + destOtherXform = destEmbers[i + ii].GetTotalXform(xf); + + if (destOtherXform->GetVariationById(VAR_RECTANGLES)) + { + RectanglesVariation* var = new RectanglesVariation(); + + var->SetParamVal("rectangles_x", 0); + var->SetParamVal("rectangles_y", 0); + destXform->AddVariation(var); + found++; + } + + if (destOtherXform->GetVariationById(VAR_RINGS2)) + { + Rings2Variation* var = new Rings2Variation(); + + var->SetParamVal("rings2_val", 0); + destXform->AddVariation(var); + found++; + } + + if (destOtherXform->GetVariationById(VAR_FAN2)) + { + Fan2Variation* var = new Fan2Variation(); + + destXform->AddVariation(var); + found++; + } + + if (destOtherXform->GetVariationById(VAR_BLOB)) + { + BlobVariation* var = new BlobVariation(); + + var->SetParamVal("blob_low", 1); + destXform->AddVariation(var); + found++; + } + + if (destOtherXform->GetVariationById(VAR_PERSPECTIVE)) + { + PerspectiveVariation* var = new PerspectiveVariation(); + + destXform->AddVariation(var); + found++; + } + + if (destOtherXform->GetVariationById(VAR_CURL)) + { + CurlVariation* var = new CurlVariation(); + + var->SetParamVal("curl_c1", 0); + destXform->AddVariation(var); + found++; + } + + if (destOtherXform->GetVariationById(VAR_SUPER_SHAPE)) + { + SuperShapeVariation* var = new SuperShapeVariation(); + + var->SetParamVal("super_shape_n1", 2); + var->SetParamVal("super_shape_n2", 2); + var->SetParamVal("super_shape_n3", 2); + destXform->AddVariation(var); + found++; + } + } + } + + //If none matched those, try the affine ones, fan and rings. + if (found == 0) + { + for (ii = -1; ii <= 1; ii += 2) + { + //Skip if out of bounds. + if (i + ii < 0 || i + ii >= count) + continue; + + //Skip if this is also padding. + if (xf >= sourceEmbers[i + ii].TotalXformCount()) + continue; + + Xform* destOtherXform = destEmbers[i + ii].GetTotalXform(xf); + + if (destOtherXform->GetVariationById(VAR_FAN)) + { + destXform->AddVariation(new FanVariation()); + found++; + } + + if (destOtherXform->GetVariationById(VAR_RINGS)) + { + destXform->AddVariation(new RingsVariation()); + found++; + } + } + + if (found > 0) + { + //Set the coefs appropriately. + destXform->m_Affine.A(0); + destXform->m_Affine.B(1);//This will be swapping x and y, seems strange, but it's what the original did. + destXform->m_Affine.C(0); + destXform->m_Affine.D(1); + destXform->m_Affine.E(0); + destXform->m_Affine.F(0); + } + } + + //If there still are no matches, switch back to linear. + if (found == 0) + { + destXform->AddVariation(new LinearVariation()); + } + else if (found > 0) + { + //Otherwise, normalize the weights. + destXform->NormalizeVariationWeights(); + } + } + }//Xforms. + }//Embers. + } + + /// + /// Thin wrapper around AnyXaosPresent(). + /// + /// The vector of embers to inspect for xaos + /// True if at least one ember contained xaos, else false. + static bool AnyXaosPresent(vector>& embers) + { + return AnyXaosPresent(embers.data(), embers.size()); + } + + /// + /// Determine whether at least one ember in the array contained xaos. + /// + /// The array of embers to inspect + /// The size of the embers array + /// True if at least one ember contained xaos, else false. + static bool AnyXaosPresent(Ember* embers, size_t size) + { + for (unsigned int i = 0; i < size; i++) + if (embers[i].XaosPresent()) + return true; + + return false; + } + + /// + /// Thin wrapper around MaxXformCount(). + /// + /// The vector of embers to inspect for the greatest xform count + /// The greatest non-final xform count in any of the embers + static unsigned int MaxXformCount(vector>& embers) + { + return MaxXformCount(embers.data(), embers.size()); + } + + /// + /// Find the maximum number of non-final xforms present in the array of embers. + /// + /// The array of embers to inspect + /// The size of the embers array + /// The greatest non-final xform count in any of the embers + static unsigned int MaxXformCount(Ember* embers, size_t size) + { + unsigned int i, maxCount = 0; + + for (i = 0; i < size; i++) + if (embers[i].XformCount() > maxCount) + maxCount = embers[i].XformCount(); + + return maxCount; + } + + /// + /// Thin wrapper around AnyFinalPresent(). + /// + /// The vector of embers to inspect the presence of a final xform + /// True if any contained a non-empty final xform, else false. + static bool AnyFinalPresent(vector>& embers) + { + return AnyFinalPresent(embers.data(), embers.size()); + } + + /// + /// Determine whether at least one ember in the array contained a non-empty final xform. + /// + /// The array of embers to inspect the presence of a final xform + /// The size of the embers array + /// True if any contained a final xform, else false. + static bool AnyFinalPresent(Ember* embers, size_t size) + { + for (size_t i = 0; i < size; i++) + if (embers[i].UseFinalXform()) + return true; + + return false; + } + + /// + /// Thin wrapper around Interpolate(). + /// + /// The vector of embers to interpolate + /// The time position in the vector specifying the point of interpolation + /// Stagger if > 0 + /// The interpolated result + static void Interpolate(vector>& embers, T time, T stagger, Ember& result) + { + Interpolate(embers.data(), embers.size(), time, stagger, result); + } + + /// + /// Interpolates the array of embers at a specified time and stores the result. + /// + /// The embers array + /// The size of the embers array + /// The time position in the vector specifying the point of interpolation + /// Stagger if > 0 + /// The interpolated result + static void Interpolate(Ember* embers, size_t size, T time, T stagger, Ember& result) + { + if (size == 1) + { + result = embers[0];//Deep copy. + return; + } + + size_t i1, i2; + vector c(2); + Ember localEmbers[4]; + bool smoothFlag = false; + c.resize(2); + + if (embers[0].m_Time >= time) + { + i1 = 0; + i2 = 1; + } + else if (embers[size - 1].m_Time <= time) + { + i1 = size - 2; + i2 = size - 1; + } + else + { + i1 = 0; + + while (embers[i1].m_Time < time) + i1++; + + i1--; + i2 = i1 + 1; + } + + c[0] = (embers[i2].m_Time - time) / (embers[i2].m_Time - embers[i1].m_Time); + c[1] = 1 - c[0]; + + //To interpolate the xforms, make copies of the source embers + //and ensure that they both have the same number of xforms before progressing. + if (embers[i1].m_Interp == INTERP_LINEAR) + { + Align(&embers[i1], &localEmbers[0], 2); + smoothFlag = false; + } + else + { + if (i1 == 0) + { + //fprintf(stderr, "error: cannot use smooth interpolation on first segment.\n"); + //fprintf(stderr, "reverting to linear interpolation.\n"); + Align(&embers[i1], &localEmbers[0], 2); + smoothFlag = false; + } + + if (i2 == size - 1) + { + //fprintf(stderr, "error: cannot use smooth interpolation on last segment.\n"); + //fprintf(stderr, "reverting to linear interpolation.\n"); + Align(&embers[i1], &localEmbers[0], 2); + smoothFlag = false; + } + + Align(&embers[i1 - 1], &localEmbers[0], 4);//Should really be doing some sort of checking here to ensure the ember vectors have 4 elements. + smoothFlag = true; + } + + result.m_Time = time; + result.m_Interp = EMBER_INTERP_LINEAR; + result.m_AffineInterp = embers[0].m_AffineInterp; + result.m_PaletteInterp = INTERP_HSV; + + if (!smoothFlag) + result.Interpolate(&localEmbers[0], 2, c, stagger); + else + result.InterpolateCatmullRom(&localEmbers[0], 4, c[1]); + } + + /// + /// Merge the variations in a vector of xforms into a single xform so that + /// it contains one variation for each variation type that was present in the + /// vector of xforms. + /// + /// The xforms to merge + /// Clear weights if true, else copy weights + /// The xform whose variations are a result of the merge + static Xform MergeXforms(vector*>& xforms, bool clearWeights = false) + { + Xform xform; + + for (unsigned int xf = 0; xf < xforms.size(); xf++) + MergeXformVariations1Way(xforms[xf], &xform, false, clearWeights); + + return xform; + } + + /// + /// Merges the xform variations from one xform to another, but not back. + /// + /// The source xform to merge from + /// The destination xform to merge to + /// If true, only merge parametric variations, else merge all + /// If true, set variation weights in dest to 0, else copy weights + static void MergeXformVariations1Way(Xform* source, Xform* dest, bool parVarsOnly, bool clearWeights) + { + for (unsigned int i = 0; i < source->TotalVariationCount(); i++)//Iterate through the first xform's variations. + { + Variation* var = source->GetVariation(i);//Grab the variation at index in in the first xform. + Variation* var2 = dest->GetVariationById(var->VariationId());//See if the same variation exists in the second xform. + ParametricVariation* parVar = dynamic_cast*>(var);//Parametric cast of the first var for later. + + if (!var2)//Only take action if the second xform did not contain this variation. + { + if (parVarsOnly)//Only add if parametric. + { + if (parVar) + { + Variation* parVarCopy = parVar->Copy(); + + if (clearWeights) + parVarCopy->m_Weight = 0; + + dest->AddVariation(parVarCopy); + } + } + else//Add regardless of type. + { + Variation* varCopy = var->Copy(); + + if (clearWeights) + varCopy->m_Weight = 0; + + dest->AddVariation(varCopy); + } + } + } + } + + /// + /// Merges the xform variations from one xform to another, and back. + /// After this function completes, both xforms will have the same variations. + /// + /// The source xform to merge from, and to + /// The destination xform to merge to, and from + /// If true, only merge parametric variations, else merge all + /// If true, set variation weights in dest to 0, else copy weights + static void MergeXformVariations2Way(Xform* source, Xform* dest, bool parVarsOnly, bool clearWeights) + { + MergeXformVariations1Way(source, dest, parVarsOnly, clearWeights); + MergeXformVariations1Way(dest, source, parVarsOnly, clearWeights); + } + + /// + /// Interpolate a vector of parametric variations by a vector of coefficients and store the ouput in a new parametric variation. + /// Elements in first which are not the same variation type as second will be ignored. + /// + /// The vector of parametric variations to interpolate + /// The parametric variation to store the output. This must be initialized first to the desired type. + /// The vector of coefficients used to interpolate + static void InterpParametricVar(vector*>& first, ParametricVariation* second, vector& c) + { + //First, make sure the variation vector is the same size as the coefficient vector. + if (second != NULL && first.size() == c.size()) + { + second->Clear(); + ParamWithName* secondParams = second->Params(); + + //Iterate through each of the source variations. + for (unsigned int i = 0; i < first.size(); i++) + { + ParametricVariation* firstVar = first[i]; + + //Make sure the source variation at this index is the same type as the variation being written to. + if (firstVar->VariationId() == second->VariationId()) + { + unsigned int size = firstVar->ParamCount(); + ParamWithName* firstParams = firstVar->Params(); + + //Multiply each parameter of the variation at this index by the coefficient at this index, and add + //the result to the corresponding parameter in second. + for (unsigned int j = 0; j < size; j++) + { + if (!firstParams[j].IsPrecalc()) + *(secondParams[j].Param()) += c[i] * firstParams[j].ParamVal(); + } + } + } + + second->Precalc(); + } + } + + /// + /// Thin wrapper around ConvertLinearToPolar(). + /// + /// The vector of embers whose affine transforms will be copied and converted + /// The xform index in each ember to convert + /// If 0 convert pre affine, else post affine. + /// The vec2 vector to store the polar angular values + /// The vec2 vector to store the polar magnitude values + /// The vec2 vector to store the polar translation values + static void ConvertLinearToPolar(vector>& embers, int xfi, int cflag, vector& cxAng, vector& cxMag, vector& cxTrn) + { + ConvertLinearToPolar(embers.data(), embers.size(), xfi, cflag, cxAng, cxMag, cxTrn); + } + + /// + /// Convert pre or post affine coordinates of the xform at a specific index in each ember from linear to polar and store as separate + /// vec2 components in the vector parameters cxAng, cxMag and cxTrn. + /// + /// The array of embers whose affine transforms will be copied and converted + /// The size of the embers array + /// The xform index in each ember to convert + /// If 0 convert pre affine, else post affine. + /// The vec2 vector to store the polar angular values + /// The vec2 vector to store the polar magnitude values + /// The vec2 vector to store the polar translation values + static void ConvertLinearToPolar(Ember* embers, size_t size, int xfi, int cflag, vector& cxAng, vector& cxMag, vector& cxTrn) + { + if (size == cxAng.size() && + size == cxMag.size() && + size == cxTrn.size()) + { + T c1[2], d, t, refang; + glm::length_t col, k; + int zlm[2]; + const char* loc = __FUNCTION__; + + for (k = 0; k < size; k++) + { + //Establish the angles and magnitudes for each component. + //Keep translation linear. + zlm[0] = zlm[1] = 0; + + if (Xform* xform = embers[k].GetTotalXform(xfi)) + { + for (col = 0; col < 2; col++) + { + if (cflag == 0) + { + c1[0] = xform->m_Affine.m_Mat[0][col];//a or b. + c1[1] = xform->m_Affine.m_Mat[1][col];//d or e. + t = xform->m_Affine.m_Mat[col][2];//c or f. + } + else + { + c1[0] = xform->m_Post.m_Mat[0][col]; + c1[1] = xform->m_Post.m_Mat[1][col]; + t = xform->m_Post.m_Mat[col][2]; + } + + cxAng[k][col] = atan2(c1[1], c1[0]); + cxMag[k][col] = sqrt(c1[0] * c1[0] + c1[1] * c1[1]); + + if (cxMag[k][col] == 0) + zlm[col] = 1; + + cxTrn[k][col] = t; + } + + if (zlm[0] == 1 && zlm[1] == 0) + cxAng[k][0] = cxAng[k][1]; + else if (zlm[0] == 0 && zlm[1] == 1) + cxAng[k][1] = cxAng[k][0]; + } + else + { + cout << loc << ": xform " << xfi << " is missing when it was expected, something is severely wrong." << endl; + } + } + + //Make sure the rotation is the shorter direction around the circle + //by adjusting each angle in succession, and rotate clockwise if 180 degrees. + for (col = 0; col < 2; col++) + { + for (k = 1; k < size; k++) + { + if (Xform* xform = embers[k].GetTotalXform(xfi)) + { + //Adjust angles differently if an asymmetric case. + if (xform->m_Wind[col] > 0 && cflag == 0) + { + //Adjust the angles to make sure that it's within wind : wind + 2pi. + refang = xform->m_Wind[col] - M_2PI; + + //Make sure both angles are within [refang refang + 2 * pi]. + while(cxAng[k - 1][col] < refang) + cxAng[k - 1][col] += M_2PI; + + while(cxAng[k - 1][col] > refang + M_2PI) + cxAng[k - 1][col] -= M_2PI; + + while(cxAng[k][col] < refang) + cxAng[k][col] += M_2PI; + + while(cxAng[k][col] > refang + M_2PI) + cxAng[k][col] -= M_2PI; + } + else + { + //Normal way of adjusting angles. + d = cxAng[k][col] - cxAng[k - 1][col]; + + //Adjust to avoid the -pi/pi discontinuity. + if (d > M_PI + EPS) + cxAng[k][col] -= M_2PI; + else if (d < -(M_PI - EPS))//Forces clockwise rotation at 180. + cxAng[k][col] += M_2PI; + } + } + else + { + cout << loc << ": xform " << xfi << " is missing when it was expected, something is severely wrong." << endl; + } + } + } + } + } + + /// + /// Never really understood what this did, but it has to do with winding. + /// + /// The array of embers + /// The size of the embers array + static void AsymmetricRefAngles(Ember* embers, unsigned int count) + { + unsigned int k, xfi, col; + T cxang[4][2], c1[2], d; + + for (xfi = 0; xfi < embers[0].XformCount(); xfi++)//Final xforms don't rotate regardless of their symmetry. + { + for (k = 0; k < count; k++) + { + //Establish the angle for each component. + //Should potentially functionalize. + for (col = 0; col < 2; col++) + { + c1[0] = embers[k].GetXform(xfi)->m_Affine.m_Mat[0][col];//A,D then B,E. + c1[1] = embers[k].GetXform(xfi)->m_Affine.m_Mat[1][col]; + + cxang[k][col] = atan2(c1[1], c1[0]); + } + } + + for (k = 1; k < count; k++) + { + for (col = 0; col < 2; col++) + { + int sym0, sym1; + int padSymFlag; + + d = cxang[k][col] - cxang[k-1][col]; + + //Adjust to avoid the -pi/pi discontinuity. + if (d > T(M_PI + EPS)) + cxang[k][col] -= 2 * T(M_PI); + else if (d < -T(M_PI - EPS) ) + cxang[k][col] += 2 * T(M_PI); + + //If this is an asymmetric case, store the NON-symmetric angle + //Check them pairwise and store the reference angle in the second + //to avoid overwriting if asymmetric on both sides. + padSymFlag = 0; + + sym0 = (embers[k - 1].GetXform(xfi)->m_Animate == 0 || (embers[k - 1].GetXform(xfi)->Empty() && padSymFlag)); + sym1 = (embers[k ].GetXform(xfi)->m_Animate == 0 || (embers[k ].GetXform(xfi)->Empty() && padSymFlag)); + + if (sym1 && !sym0) + embers[k].GetXform(xfi)->m_Wind[col] = cxang[k-1][col] + 2 * T(M_PI); + else if (sym0 && !sym1) + embers[k].GetXform(xfi)->m_Wind[col] = cxang[k][col] + 2 * T(M_PI); + } + } + } + } + + /// + /// Never really understood what this did. + /// + /// The coefficients vector + /// The vec2 vector to store the polar angular values + /// The vec2 vector to store the polar magnitude values + /// The vec2 vector to store the polar translation values + /// The Affine2D to store the inerpolated values in + static void InterpAndConvertBack(vector& coefs, vector& cxAng, vector& cxMag, vector& cxTrn, Affine2D& store) + { + size_t size = coefs.size(); + glm::length_t i, col, accmode[2] = { 0, 0 }; + T expmag, accang[2] = { 0, 0 }, accmag[2] = { 0, 0 }; + + //Accumulation mode defaults to logarithmic, but in special + //cases switch to linear accumulation. + for (col = 0; col < 2; col++) + { + for (i = 0; i < size; i++) + { + if (log(cxMag[i][col]) < -10) + accmode[col] = 1;//Mode set to linear interp. + } + } + + for (i = 0; i < size; i++) + { + for (col = 0; col < 2; col++) + { + accang[col] += coefs[i] * cxAng[i][col]; + + if (accmode[col] == 0) + accmag[col] += coefs[i] * log(cxMag[i][col]); + else + accmag[col] += coefs[i] * (cxMag[i][col]); + + //Translation is ready to go. + store.m_Mat[col][2] += coefs[i] * cxTrn[i][col]; + } + } + + //Convert the angle back to rectangular. + for (col = 0; col < 2; col++) + { + if (accmode[col] == 0) + expmag = exp(accmag[col]); + else + expmag = accmag[col]; + + store.m_Mat[0][col] = expmag * cos(accang[col]); + store.m_Mat[1][col] = expmag * sin(accang[col]); + } + } + + /// + /// Smooths the time values for animations. + /// + /// The time value to smooth + /// the smoothed time value + static inline T Smoother(T t) + { + return 3 * t * t - 2 * t * t * t; + } + + /// + /// Gets the stagger coef based on the position of the current xform among the others. + /// Never really understood what this did. + /// + /// The time value + /// The stagger percentage + /// The number xforms in the ember + /// The index of this xform within the ember + /// The stagger coefficient + static inline T GetStaggerCoef(T t, T staggerPercent, int numXforms, int thisXform) + { + //maxStag is the spacing between xform start times if staggerPercent = 1.0. + T maxStag = T(numXforms - 1) / numXforms; + + //Scale the spacing by staggerPercent. + T stagScaled = staggerPercent * maxStag; + + //t ranges from 1 to 0 (the contribution of cp[0] to the blend). + //The first line below makes the first xform interpolate first. + //The second line makes the last xform interpolate first. + T st = stagScaled * (numXforms - 1 - thisXform) / (numXforms - 1); + T et = st + (1 - stagScaled); + + if (t <= st) + return 0; + else if (t >= et) + return 1; + else + return Smoother((t - st) / (1 - stagScaled)); + } + + /// + /// Apply the specified motion function to a value. + /// + /// The function type to apply, sin, triangle or hill. + /// The time value to apply the motion function to + /// The new time value computed by applying the specified motion function to the time value + static T MotionFuncs(int funcNum, T timeVal) + { + //Motion funcs should be cyclic, and equal to 0 at integral time values + //abs peak values should be not be greater than 1. + if (funcNum == MOTION_SIN) + { + return sin(T(2.0) * T(M_PI) * timeVal); + } + else if (funcNum == MOTION_TRIANGLE) + { + T fr = fmod(timeVal, T(1.0)); + + if (fr < 0) + fr += 1; + + if (fr <= T(0.25)) + fr *= 4; + else if (fr <= T(0.75)) + fr = -4 * fr + 2; + else + fr = 4 * fr - 4; + + return fr; + } + else//MOTION_HILL + { + return ((1 - cos(T(2.0) * T(M_PI) * timeVal)) * T(0.5)); + } + } + + /* + //Will need to alter this to handle 2D palettes. + static bool InterpMissingColors(vector>& palette) + { + //Check for a non-full palette. + int minIndex, maxIndex; + int colorli, colorri; + int wrapMin, wrapMax; + int intl, intr; + int str, enr; + int i, j, k; + double prcr; + + if (palette.size() != 256) + return false; + + for (i = 0; i < 256; i++) + { + if (palette[i].m_Index >= 0) + { + minIndex = i; + break; + } + } + + if (i == 256) + { + //No colors. Set all indices properly. + for (i = 0; i < 256; i++) + palette[i].m_Index = (T)i; + + return false; + } + + wrapMin = minIndex + 256; + + for (i = 255; i >= 0; i--)//Moving backwards, ouch! + { + if (palette[i].m_Index >= 0) + { + maxIndex = i; + break; + } + } + + wrapMax = maxIndex - 256; + + //Loop over the indices looking for negs, + i = 0; + + while (i < 256) + { + if (palette[i].m_Index < 0) + { + //Start of a range of negs. + str = i; + intl = i - 1; + colorli = intl; + + while (palette[i].m_Index < 0 && i < 256) + { + enr = i; + intr = i + 1; + colorri = intr; + i++; + } + + if (intl == -1) + { + intl = wrapMax; + colorli = maxIndex; + } + + if (intr == 256) + { + intr = wrapMin; + colorri = minIndex; + } + + for (j = str; j <= enr; j++) + { + prcr = (j - intl) / T(intr - intl); + + for (k = 0; k <= 3; k++) + palette[j].Channels[k] = T(palette[colorli].Channels[k] * (1 - prcr) + palette[colorri].Channels[k] * prcr); + + palette[j].m_Index = T(j); + } + + i = colorri + 1; + } + else + i++; + } + + return true; + } + */ + + /// + /// Compare xforms for sorting based first on color speed and second on determinants if + /// color speeds are equal. + /// + /// The first xform to compare + /// The second xform to compare + /// true if a > b, else false. + static inline bool CompareXforms(const Xform& a, const Xform& b) + { + if (a.m_ColorSpeed > b.m_ColorSpeed) return true; + if (a.m_ColorSpeed < b.m_ColorSpeed) return false; + + //Original did this every time, even though it's only needed if the color speeds are equal. + m2T aMat2 = a.m_Affine.ToMat2ColMajor(); + m2T bMat2 = b.m_Affine.ToMat2ColMajor(); + + T ad = glm::determinant(aMat2); + T bd = glm::determinant(bMat2); + + if (a.m_ColorSpeed > 0) + { + if (ad < 0) return false; + if (bd < 0) return true; + + ad = atan2(a.m_Affine.A(), a.m_Affine.D()); + bd = atan2(b.m_Affine.A(), b.m_Affine.D()); + } + + return ad > bd; + } +}; +} \ No newline at end of file diff --git a/Source/Ember/Isaac.h b/Source/Ember/Isaac.h new file mode 100644 index 0000000..a3c97f6 --- /dev/null +++ b/Source/Ember/Isaac.h @@ -0,0 +1,386 @@ +#pragma once + +#include "EmberDefines.h" + +/// +/// C++ TEMPLATE VERSION OF Robert J. Jenkins Jr.'s +/// ISAAC Random Number Generator. +/// +/// Ported from vanilla C to to template C++ class +/// by Quinn Tyler Jackson on 16-23 July 1998. +/// +/// quinn@qtj.net +/// +/// The function for the expected period of this +/// random number generator, according to Jenkins is: +/// +/// f(a,b) = 2**((a+b*(3+2^^a)-1) +/// +/// (where a is ALPHA and b is bitwidth) +/// +/// So, for a bitwidth of 32 and an ALPHA of 8, +/// the expected period of ISAAC is: +/// +/// 2^^(8+32*(3+2^^8)-1) = 2^^8295 +/// +/// Jackson has been able to run implementations +/// with an ALPHA as high as 16, or +/// +/// 2^^2097263 +/// +/// -Modified by Matt Feemster to eliminate needless dynamic memory allocation and virtual functions and bring inline with Ember coding style. +/// + +#ifndef __ISAAC64 + typedef unsigned long int ISAAC_INT; + const ISAAC_INT GOLDEN_RATIO = ISAAC_INT(0x9e3779b9); +#else + typedef unsigned __int64 ISAAC_INT; + const ISAAC_INT GOLDEN_RATIO = ISAAC_INT(0x9e3779b97f4a7c13); +#endif + +namespace EmberNs +{ +/// +/// QTIsaac class which allows using ISAAC in an OOP manner. +/// +template +class EMBER_API QTIsaac +{ +public: + typedef unsigned char byte; + enum { N = (1 << ALPHA) }; + + /// + /// Global ISAAC RNG to be used from anywhere. This is not thread safe, so take caution to only + /// use it when no other threads are. + /// + static auto_ptr> GlobalRand; + + /// + /// The structure which holds all of the random information. + /// + struct EMBER_API randctx + { + T randcnt; + T randrsl[N]; + T randmem[N]; + T randa; + T randb; + T randc; + }; + + /// + /// Constructor which initialized the random context using the values passed in. + /// Leaving these as their defaults is fine, and will still give different + /// results because time is internally used if they are default. + /// However, specifying specific values is useful if you want to duplicate + /// a sequence of random numbers. + /// + /// First random seed. Default: 0. + /// Second random seed. Default: 0. + /// Third random seed. Default: 0. + /// Pointer to a buffer of 256 random integer seeds. Default: NULL. + QTIsaac(T a = 0, T b = 0, T c = 0, T* s = NULL) + { + Srand(a, b, c, s); + } + + /// + /// Return the next random integer. + /// + /// The next random integer + inline T Rand() + { + return (m_Rc.randcnt++ == N ? (Isaac(&m_Rc), m_Rc.randcnt=0, m_Rc.randrsl[m_Rc.randcnt]) : m_Rc.randrsl[m_Rc.randcnt]); + } + + /// + /// Return the next random integer between 0 and the value passed in. + /// + /// A value one greater than the maximum value that will be returned + inline T Rand(T upper) + { + return (upper == 0) ? Rand() : Rand() % upper; + } + + /// + /// Returns a random floating point value between the specified minimum and maximum. + /// Template argument expected to be float or double. + /// + /// The minimum value allowed, inclusive. + /// The maximum value allowed, inclusive. + /// A new random floating point value within the specified range, inclusive. + template + inline floatType Frand(floatType fMin, floatType fMax) + { + floatType f = (floatType)Rand() / (floatType)std::numeric_limits::max(); + return fMin + (f * (fMax - fMin)); + } + + /// + /// Thin wrapper around a call to Frand() with a range of 0-1. + /// Template argument expected to be float or double. + /// + /// A new random number in the range of 0-1, inclusive. + template + inline floatType Frand01() + { + return Frand(floatType(0), floatType(1)); + } + + /// + /// Thin wrapper around a call to Frand() with a range of -1-1. + /// Template argument expected to be float or double. + /// + /// A new random number in the range of -1-1, inclusive. + template + inline floatType Frand11() + { + return Frand(floatType(-1), floatType(1)); + } + + /// + /// Not sure what this does. + /// + /// Something that is golden + template + inline floatType GoldenBit() + { + return RandBit() ? floatType(0.38196) : floatType(0.61804); + } + + /// + /// Returns a random 0 or 1. + /// + /// A random 0 or 1 + inline unsigned int RandBit() + { + return Rand() & 1; + } + + /// + /// A different way of getting a floating point rand in the range -1-1. + /// Flam3 used this but it seems unnecessary now, keep around if it's ever needed. + /// + /// A new random number in the range of -1-1, inclusive. + //double drand11() + //{ + // return (((int)Rand() & 0xfffffff) - 0x7ffffff) / (double) 0x7ffffff; + //} + + /// + /// Initializes a random context. + /// Unsure exacly how this works, but it does. + /// + /// The random context to initialize + /// Whether to use the seeds passed in to the constructor, else zero. + void RandInit(randctx* ctx, bool useSeed) + { + int i; + T a, b, c, d, e, f, g, h; + T* m = ctx->randmem; + T* r = ctx->randrsl; + + a = b = c = d = e = f = g = h = GOLDEN_RATIO; + + if (!useSeed) + { + ctx->randa = 0; + ctx->randb = 0; + ctx->randc = 0; + } + + //Scramble it. + for (i = 0; i < 4; ++i) + { + Shuffle(a, b, c, d, e, f, g, h); + } + + if (useSeed) + { + //Initialize using the contents of r[] as the seed. + for (i = 0; i < N; i += 8) + { + a += r[i ]; b += r[i + 1]; c += r[i + 2]; d += r[i + 3]; + e += r[i + 4]; f += r[i + 5]; g += r[i + 6]; h += r[i + 7]; + + Shuffle(a, b, c, d, e, f, g, h); + + m[i ] = a; m[i + 1] = b; m[i + 2] = c; m[i + 3] = d; + m[i + 4] = e; m[i + 5] = f; m[i + 6] = g; m[i + 7] = h; + } + + //Do a second pass to make all of the seed affect all of m. + for (i = 0; i < N; i += 8) + { + a += m[i ]; b += m[i + 1]; c += m[i + 2]; d += m[i + 3]; + e += m[i + 4]; f += m[i + 5]; g += m[i + 6]; h += m[i + 7]; + + Shuffle(a, b, c, d, e, f, g, h); + + m[i ] = a; m[i + 1] = b; m[i + 2] = c; m[i + 3] = d; + m[i + 4] = e; m[i + 5] = f; m[i + 6] = g; m[i + 7] = h; + } + } + else + { + //Fill in mm[] with messy stuff. + Shuffle(a, b, c, d, e, f, g, h); + + m[i ] = a; m[i + 1] = b; m[i + 2] = c; m[i + 3] = d; + m[i + 4] = e; m[i + 5] = f; m[i + 6] = g; m[i + 7] = h; + } + + Isaac(ctx); //Fill in the first set of results. + ctx->randcnt = 0;//Prepare to use the first set of results. + } + + /// + /// Initialize the seeds of the member random context using the specified seeds. + /// If s is null, time plus index up to 256 is used for the random buffer. + /// + /// First random seed. Default: 0. + /// Second random seed. Default: 0. + /// Third random seed. Default: 0. + /// Pointer to a buffer of 256 random integer seeds. Default: NULL. + void Srand(T a = 0, T b = 0, T c = 0, T* s = NULL) + { + if (s == NULL)//Default to using time plus index as the seed if s was NULL. + { + for (int i = 0; i < N; i++) + m_Rc.randrsl[i] = (T)time(0) + i; + } + else + { + for (int i = 0; i < N; i++) + m_Rc.randrsl[i] = s[i]; + } + + if (a == 0 && b == 0 && c == 0) + { + m_Rc.randa = (T)time(0); + m_Rc.randb = (T)time(0) * (T)time(0); + m_Rc.randc = (T)time(0) * (T)time(0) * (T)time(0); + } + else + { + m_Rc.randa = a; + m_Rc.randb = b; + m_Rc.randc = c; + } + + RandInit(&m_Rc, true); + } + +protected: + /// + /// Compute the next batch of random numbers for a random context. + /// + /// The context to populate. + void Isaac(randctx* ctx) + { + T x,y; + + T* mm = ctx->randmem; + T* r = ctx->randrsl; + + T a = (ctx->randa); + T b = (ctx->randb + (++ctx->randc)); + + T* m = mm; + T* m2 = (m + (N / 2)); + T* mend = m2; + + for(; m < mend; ) + { + #ifndef __ISAAC64 + RngStep((a << 13), a, b, mm, m, m2, r, x, y); + RngStep((a >> 6) , a, b, mm, m, m2, r, x, y); + RngStep((a << 2) , a, b, mm, m, m2, r, x, y); + RngStep((a >> 16), a, b, mm, m, m2, r, x, y); + #else // __ISAAC64 + RngStep(~(a ^ (a << 21)), a, b, mm, m, m2, r, x, y); + RngStep( a ^ (a >> 5) , a, b, mm, m, m2, r, x, y); + RngStep( a ^ (a << 12) , a, b, mm, m, m2, r, x, y); + RngStep( a ^ (a >> 33) , a, b, mm, m, m2, r, x, y); + #endif // __ISAAC64 + } + + m2 = mm; + + for(; m2> 6) , a, b, mm, m, m2, r, x, y); + RngStep((a << 2) , a, b, mm, m, m2, r, x, y); + RngStep((a >> 16), a, b, mm, m, m2, r, x, y); + #else // __ISAAC64 + RngStep(~(a ^ (a << 21)), a, b, mm, m, m2, r, x, y); + RngStep( a ^ (a >> 5) , a, b, mm, m, m2, r, x, y); + RngStep( a ^ (a << 12) , a, b, mm, m, m2, r, x, y); + RngStep( a ^ (a >> 33) , a, b, mm, m, m2, r, x, y); + #endif // __ISAAC64 + } + + ctx->randb = b; + ctx->randa = a; + } + + /// + /// Retrieves a value using indirection. + /// + /// The buffer. + /// The offset. + /// A new value + inline T Ind(T* mm, T x) + { +#ifndef __ISAAC64 + return (*(T*)((byte*)(mm) + ((x) & ((N - 1) << 2)))); +#else // __ISAAC64 + return (*(T*)((byte*)(mm) + ((x) & ((N - 1) << 3)))); +#endif // __ISAAC64 + } + + /// + /// Unsure what this does. + /// + void RngStep(T mix, T& a, T& b, T*& mm, T*& m, T*& m2, T*& r, T& x, T& y) + { + x = *m; + a = (a ^ (mix)) + *(m2++); + *(m++) = y = Ind(mm, x) + a + b; + *(r++) = b = Ind(mm, y >> ALPHA) + x; + } + + /// + /// Unsure what this does. + /// + void Shuffle(T& a, T& b, T& c, T& d, T& e, T& f, T& g, T& h) + { +#ifndef __ISAAC64 + a ^= b << 11; d += a; b += c; + b ^= c >> 2; e += b; c += d; + c ^= d << 8; f += c; d += e; + d ^= e >> 16; g += d; e += f; + e ^= f << 10; h += e; f += g; + f ^= g >> 4; a += f; g += h; + g ^= h << 8; b += g; h += a; + h ^= a >> 9; c += h; a += b; +#else // __ISAAC64 + a -= e; f ^= h >> 9; h += a; + b -= f; g ^= a << 9; a += b; + c -= g; h ^= b >> 23; b += c; + d -= h; a ^= c << 15; c += d; + e -= a; b ^= d >> 14; d += e; + f -= b; c ^= e << 20; e += f; + g -= c; d ^= f >> 17; f += g; + h -= d; e ^= g << 14; g += h; +#endif // __ISAAC64 + } + +private: + randctx m_Rc;//The random context which holds all of the seed and state information as well as the random number values. +}; +} \ No newline at end of file diff --git a/Source/Ember/Iterator.h b/Source/Ember/Iterator.h new file mode 100644 index 0000000..9df347b --- /dev/null +++ b/Source/Ember/Iterator.h @@ -0,0 +1,541 @@ +#pragma once + +#include "Ember.h" + +/// +/// Iterator and derived classes. +/// + +//#define CHOOSE_XFORM_GRAIN 256 +#define CHOOSE_XFORM_GRAIN 10000//The size of xform random selection buffer. Multiply by the (number of non-final xforms present + 1) if xaos is used. + +namespace EmberNs +{ +/// +/// Iterator base class. +/// Iterating is one loop level outside of the inner xform application loop so it's still very important +/// to take every optimization possible here. +/// The original had many temporary assignments in order to feed the output of the current iteration +/// into the input of the next iteration. All unneccessary temporary assignments are eliminated by simply using i and i + 1 +/// as the input and output indices on the samples array passed to Xform.Apply(). +/// Note that the samples array is assigned to while fusing. Although this technically doesn't make sense +/// since values computed during fusing get thrown out, it doesn't matter because it will get overwritten +/// in the actual loop below it since the index counter is reset to zero when fusing is complete. +/// Flam3 needlessly computed the final xform on each fuse iteration only to throw it away. It's omitted here as an optimization. +/// Rather than place many conditionals inside the iteration loop, they are broken into separate classes depending +/// on what's contained in the ember's xforms. +/// The biggest difference is whether xaos is present or not it requires extra work when picking +/// the next random xform to use. Further, each of those is broken into two loops, one for embers with a final xform +/// and one without. +/// Last, the fuse loop and real loop are separated and duplicated to omit the conditional check for fuse inside the real loop. +/// Although this makes this file about four times as verbose as it would normally be, it does lead to performance improvements. +/// Template argument expected to be float or double. +/// +template +class EMBER_API Iterator +{ +public: + /// + /// Empty constructor. + /// + Iterator() + { + } + + /// + /// Empty virtual destructor so proper derived class destructors get called. + /// + virtual ~Iterator() + { + } + + /// + /// Accessors. + /// + const unsigned char* XformDistributions() const { return m_XformDistributions.empty() ? NULL : &m_XformDistributions[0]; } + const unsigned int XformDistributionsSize() const { return (unsigned int)m_XformDistributions.size(); } + + /// + /// Virtual empty iteration function that will be overidden in derived iterator classes. + /// + /// The ember whose xforms will be applied + /// The number of iterations to do + /// The number of times to fuse + /// The buffer to store the output points + /// The random context to use + /// The number of bad values + virtual unsigned int Iterate(Ember& ember, unsigned int count, unsigned int skip, Point* samples, QTIsaac& rand) { return 0; } + + /// + /// Initialize the xform selection vector by normalizing the weights of all xforms and + /// setting the corresponding percentage of elements in the vector to each xform's index in its + /// parent ember. + /// Note that this method of looking up and index in a vector is how flam3 did it and is about 10% + /// faster than using a while loop to check a random number against a normalized weight. + /// Also, the ember used to initialize this must be the same ember, unchanged, used to iterate. + /// If one is passed to this function, its parameters are changed and then it's passed to Iterate(), + /// the behavior is undefined. + /// + /// The ember whose xforms will be used to populate the distribution vector + /// True if success, else false. + bool InitDistributions(Ember& ember) + { + unsigned int i, j = 0; + unsigned int distribCount = ember.XaosPresent() ? (unsigned int)ember.XformCount() + 1 : 1; + const Xform* xforms = ember.Xforms(); + + if (m_XformDistributions.size() < CHOOSE_XFORM_GRAIN * distribCount) + m_XformDistributions.resize(CHOOSE_XFORM_GRAIN * distribCount); + + if (m_XformDistributions.size() < CHOOSE_XFORM_GRAIN * distribCount) + return false; + + for (unsigned int distrib = 0; distrib < distribCount; distrib++) + { + T totalDensity = 0; + + //First find the total densities of all xforms. + for (i = 0; i < ember.XformCount(); i++) + { + T d = xforms[i].m_Weight; + + if (distrib > 0) + d *= xforms[distrib - 1].Xaos(i); + + //Original returned false if any xform had 0 density, it's allowed here + //because it can be useful when experimenting to test the effects of removing an + //xform by setting its probability to 0. + + totalDensity += d; + } + + //Original returned false if all were 0, but it's allowed here + //which will just end up setting all elements to 0 which means + //only the first xform will get used. + + //Calculate how much of a fraction of a the total density each element represents. + T densityPerElement = totalDensity / CHOOSE_XFORM_GRAIN; + j = 0; + + //Assign xform indices in order to each element of m_XformDistributions. + //The number of elements assigned a given index is proportional to that xform's + //density relative to the sum of all densities. + for (i = 0; i < ember.XformCount(); i++) + { + T tempDensity = 0; + T currentDensityLimit = xforms[i].m_Weight; + + if (distrib > 0) + currentDensityLimit *= xforms[distrib - 1].Xaos(i); + + //Populate points corresponding to this xform's weight/density. + //Also check that j is within the bounds of the distribution array just to be safe in the case of a rounding error. + while (tempDensity <= currentDensityLimit && j < CHOOSE_XFORM_GRAIN) + { + m_XformDistributions[(distrib * CHOOSE_XFORM_GRAIN) + j] = i; + tempDensity += densityPerElement; + j++; + } + } + } + + return true; + } + +protected: + /// + /// When iterating, if the computed location of the point is either very close to zero, or very close to infinity, + /// it's considered a bad value. In that case, a new random input point is fed into a new randomly chosen xform. This + /// process is repeated up to 5 times until a good value is computed. If after 5 tries, a good value is not found, then + /// the coordinates of the output point are just set to a random number between -1 and 1. + /// + /// The xforms array + /// The counter for the total number of bad values this sub batch + /// The point which initially had the bad values and which will store the newly computed values + /// The random context this iterator is using + /// True if a good value was computed within 5 tries, else false + inline bool DoBadVals(Xform* xforms, unsigned int& badVals, Point* point, QTIsaac& rand) + { + unsigned int xformIndex, consec = 0; + Point firstBadPoint; + + while (consec < 5) + { + consec++; + badVals++; + firstBadPoint.m_X = rand.Frand11();//Re-randomize points, but keep the computed color and viz. + firstBadPoint.m_Y = rand.Frand11(); + firstBadPoint.m_Z = 0; + firstBadPoint.m_ColorX = point->m_ColorX; + firstBadPoint.m_VizAdjusted = point->m_VizAdjusted; + + xformIndex = NextXformFromIndex(rand.Rand()); + + if (!xforms[xformIndex].Apply(&firstBadPoint, point, rand)) + return true; + } + + //After 5 tries, nothing worked, so just assign random values between -1 and 1. + if (consec == 5) + { + point->m_X = rand.Frand11(); + point->m_Y = rand.Frand11(); + point->m_Z = 0; + } + + return false; + } + + /// + /// Apply the final xform. + /// Note that as stated in the paper, the output of the final xform is not fed back into the next iteration. + /// Rather, only the value computed from the randomly chosen xform is. However, the output of the final xform + /// is still saved in the output samples buffer and accumulated to the histogram later. + /// + /// The ember being iterated + /// The input point + /// The output point + /// The random context to use. + inline void DoFinalXform(Ember& ember, Point& tempPoint, Point* sample, QTIsaac& rand) + { + if (IsClose(ember.FinalXform()->m_Opacity, 1) || rand.Frand01() < ember.FinalXform()->m_Opacity) + { + T tempVizAdjusted = tempPoint.m_VizAdjusted; + + ember.NonConstFinalXform()->Apply(&tempPoint, sample, rand); + sample->m_VizAdjusted = tempVizAdjusted; + } + } + + /// + /// Retrieve an element in the distributions vector between 0 and CHOOSE_XFORM_GRAIN which will + /// contain the index of the next xform to use. When xaos is prsent, the offset is the index in + /// the ember of the previous xform used when. + /// + /// The index to retrieve + /// When xaos is prsent, the index of the previous xform used. Default: 0 (xaos not present). + /// + unsigned int NextXformFromIndex(unsigned int index, unsigned int distribOffset = 0) + { + return (unsigned int)m_XformDistributions[(index % CHOOSE_XFORM_GRAIN) + (CHOOSE_XFORM_GRAIN * distribOffset)]; + } + + vector m_XformDistributions; +}; + +/// +/// Derived iterator class for embers whose xforms do not use xaos. +/// +template +class EMBER_API StandardIterator : public Iterator +{ +public: + /// + /// Empty constructor. + /// + StandardIterator() + { + } + + /// + /// Overridden virtual function which iterates an ember a given number of times and does not use xaos. + /// + /// The ember whose xforms will be applied + /// The number of iterations to do + /// The number of times to fuse + /// The buffer to store the output points + /// The random context to use + /// The number of bad values + virtual unsigned int Iterate(Ember& ember, unsigned int count, unsigned int skip, Point* samples, QTIsaac& rand) + { + unsigned int i, badVals = 0; + Point tempPoint, p1; + Xform* xforms = ember.NonConstXforms(); + + if (ember.ProjBits()) + { + if (ember.UseFinalXform()) + { + p1 = samples[0]; + + for (i = 0; i < skip; i++)//Fuse. + { + if (xforms[NextXformFromIndex(rand.Rand())].Apply(&p1, &p1, rand)) + DoBadVals(xforms, badVals, &p1, rand); + } + + DoFinalXform(ember, p1, samples, rand);//Apply to last fuse point and store as the first element in samples. + ember.Proj(samples[0], rand); + + for (i = 1; i < count; i++)//Real loop. + { + if (xforms[NextXformFromIndex(rand.Rand())].Apply(&p1, &p1, rand)) + DoBadVals(xforms, badVals, &p1, rand); + + DoFinalXform(ember, p1, samples + i, rand); + ember.Proj(samples[i], rand); + } + } + else + { + p1 = samples[0]; + + for (i = 0; i < skip; i++)//Fuse. + { + if (xforms[NextXformFromIndex(rand.Rand())].Apply(&p1, &p1, rand)) + DoBadVals(xforms, badVals, &p1, rand); + } + + samples[0] = p1; + ember.Proj(samples[0], rand); + + for (i = 1; i < count; i++)//Real loop. + { + if (xforms[NextXformFromIndex(rand.Rand())].Apply(&p1, &samples[i], rand)) + DoBadVals(xforms, badVals, samples + i, rand); + + p1 = samples[i]; + ember.Proj(samples[i], rand); + } + } + } + else + { + if (ember.UseFinalXform()) + { + p1 = samples[0]; + + for (i = 0; i < skip; i++)//Fuse. + { + if (xforms[NextXformFromIndex(rand.Rand())].Apply(&p1, &p1, rand)) + DoBadVals(xforms, badVals, &p1, rand); + } + + DoFinalXform(ember, p1, samples, rand);//Apply to last fuse point and store as the first element in samples. + + for (i = 1; i < count; i++)//Real loop. + { + if (xforms[NextXformFromIndex(rand.Rand())].Apply(&p1, &p1, rand))//Feed the resulting value of applying the randomly selected xform back into the next iter, and not the result of applying the final xform. + DoBadVals(xforms, badVals, &p1, rand); + + DoFinalXform(ember, p1, samples + i, rand); + } + } + else + { + p1 = samples[0]; + + for (i = 0; i < skip; i++)//Fuse. + { + if (xforms[NextXformFromIndex(rand.Rand())].Apply(&p1, &p1, rand)) + DoBadVals(xforms, badVals, &p1, rand); + } + + samples[0] = p1; + + for (i = 0; i < count - 1; i++)//Real loop. + if (xforms[NextXformFromIndex(rand.Rand())].Apply(samples + i, samples + i + 1, rand)) + DoBadVals(xforms, badVals, samples + i + 1, rand); + } + } + + return badVals; + } +}; + +/// +/// Derived iterator class for embers whose xforms use xaos. +/// +template +class EMBER_API XaosIterator : public Iterator +{ +public: + /// + /// Empty constructor. + /// + XaosIterator() + { + } + + /// + /// Handler for bad values similar to the one in the base class, except it takes the last xform used + /// as a parameter and saves the xform used back out because this iterator is meant to be used with xaos. + /// + /// The xforms array + /// Index of the last used xform before calling this function + /// The saved index of the last xform used within this function + /// The counter for the total number of bad values this sub batch + /// The point which initially had the bad values and which will store the newly computed values + /// The random context this iterator is using + /// True if a good value was computed within 5 tries, else false + inline bool DoBadVals(Xform* xforms, unsigned int& xformIndex, unsigned int lastXformUsed, unsigned int& badVals, Point* point, QTIsaac& rand) + { + unsigned int consec = 0; + Point firstBadPoint; + + while (consec < 5) + { + consec++; + badVals++; + firstBadPoint.m_X = rand.Frand11();//Re-randomize points, but keep the computed color and viz. + firstBadPoint.m_Y = rand.Frand11(); + firstBadPoint.m_Z = 0; + firstBadPoint.m_ColorX = point->m_ColorX; + firstBadPoint.m_VizAdjusted = point->m_VizAdjusted; + + xformIndex = NextXformFromIndex(rand.Rand(), lastXformUsed); + + if (!xforms[xformIndex].Apply(&firstBadPoint, point, rand)) + return true; + } + + //After 5 tries, nothing worked, so just assign random. + if (consec == 5) + { + point->m_X = rand.Frand11(); + point->m_Y = rand.Frand11(); + point->m_Z = 0; + } + + return false; + } + + /// + /// Overridden virtual function which iterates an ember a given number of times and uses xaos. + /// + /// The ember whose xforms will be applied + /// The number of iterations to do + /// The number of times to fuse + /// The buffer to store the output points + /// The random context to use + /// The number of bad values + virtual unsigned int Iterate(Ember& ember, unsigned int count, unsigned int skip, Point* samples, QTIsaac& rand) + { + unsigned int i, xformIndex; + unsigned int lastXformUsed = 0; + unsigned int badVals = 0; + Point tempPoint, p1; + Xform* xforms = ember.NonConstXforms(); + + if (ember.ProjBits()) + { + if (ember.UseFinalXform()) + { + p1 = samples[0]; + + for (i = 0; i < skip; i++)//Fuse. + { + xformIndex = NextXformFromIndex(rand.Rand(), lastXformUsed); + + if (xforms[xformIndex].Apply(&p1, &p1, rand)) + DoBadVals(xforms, xformIndex, lastXformUsed, badVals, &p1, rand); + + lastXformUsed = xformIndex + 1;//Store the last used transform. + } + + DoFinalXform(ember, p1, samples, rand);//Apply to last fuse point and store as the first element in samples. + ember.Proj(samples[0], rand); + + for (i = 1; i < count; i++)//Real loop. + { + xformIndex = NextXformFromIndex(rand.Rand(), lastXformUsed); + + if (xforms[xformIndex].Apply(&p1, &p1, rand))//Feed the resulting value of applying the randomly selected xform back into the next iter, and not the result of applying the final xform. + DoBadVals(xforms, xformIndex, lastXformUsed, badVals, &p1, rand); + + DoFinalXform(ember, p1, samples + i, rand); + ember.Proj(samples[i], rand); + lastXformUsed = xformIndex + 1;//Store the last used transform. + } + } + else + { + p1 = samples[0]; + + for (i = 0; i < skip; i++)//Fuse. + { + xformIndex = NextXformFromIndex(rand.Rand(), lastXformUsed); + + if (xforms[xformIndex].Apply(&p1, &p1, rand)) + DoBadVals(xforms, xformIndex, lastXformUsed, badVals, &p1, rand); + + lastXformUsed = xformIndex + 1;//Store the last used transform. + } + + ember.Proj(p1, rand); + samples[0] = p1; + + for (i = 1; i < count; i++)//Real loop. + { + xformIndex = NextXformFromIndex(rand.Rand(), lastXformUsed); + + if (xforms[xformIndex].Apply(&p1, &p1, rand)) + DoBadVals(xforms, xformIndex, lastXformUsed, badVals, &p1, rand); + + samples[i] = p1; + ember.Proj(samples[i], rand); + lastXformUsed = xformIndex + 1;//Store the last used transform. + } + } + } + else + { + if (ember.UseFinalXform()) + { + p1 = samples[0]; + + for (i = 0; i < skip; i++)//Fuse. + { + xformIndex = NextXformFromIndex(rand.Rand(), lastXformUsed); + + if (xforms[xformIndex].Apply(&p1, &p1, rand)) + DoBadVals(xforms, xformIndex, lastXformUsed, badVals, &p1, rand); + + lastXformUsed = xformIndex + 1;//Store the last used transform. + } + + DoFinalXform(ember, p1, samples, rand);//Apply to last fuse point and store as the first element in samples. + + for (i = 1; i < count; i++)//Real loop. + { + xformIndex = NextXformFromIndex(rand.Rand(), lastXformUsed); + + if (xforms[xformIndex].Apply(&p1, &p1, rand))//Feed the resulting value of applying the randomly selected xform back into the next iter, and not the result of applying the final xform. + DoBadVals(xforms, xformIndex, lastXformUsed, badVals, &p1, rand); + + DoFinalXform(ember, p1, samples + i, rand); + lastXformUsed = xformIndex + 1;//Store the last used transform. + } + } + else + { + p1 = samples[0]; + + for (i = 0; i < skip; i++)//Fuse. + { + xformIndex = NextXformFromIndex(rand.Rand(), lastXformUsed); + + if (xforms[xformIndex].Apply(&p1, &p1, rand)) + DoBadVals(xforms, xformIndex, lastXformUsed, badVals, &p1, rand); + + lastXformUsed = xformIndex + 1;//Store the last used transform. + } + + samples[0] = p1; + + for (i = 0; i < count - 1; i++)//Real loop. + { + xformIndex = NextXformFromIndex(rand.Rand(), lastXformUsed); + + if (xforms[xformIndex].Apply(samples + i, samples + i + 1, rand)) + DoBadVals(xforms, xformIndex, lastXformUsed, badVals, samples + i + 1, rand); + + lastXformUsed = xformIndex + 1;//Store the last used transform. + } + } + } + + return badVals; + } +}; +} \ No newline at end of file diff --git a/Source/Ember/Palette.h b/Source/Ember/Palette.h new file mode 100644 index 0000000..f1f41e5 --- /dev/null +++ b/Source/Ember/Palette.h @@ -0,0 +1,571 @@ +#pragma once + +#include "Utils.h" +#include "Isaac.h" + +/// +/// Palette class. +/// + +namespace EmberNs +{ +/// +/// The palette stores a set of 256 colors which are what get accumulated to the histogram +/// for each iteration. The colors come from either the main palette Xml file or directly +/// from the ember parameter file. Either way, they come in as 0-255 and get normalized to 0-1. +/// In the future, 2D palette support might be added in which case this class will have to be modified. +/// Template argument expected to be float or double. +/// +template +class EMBER_API Palette +{ +public: + /// + /// Constructor which sets the palette index to random and allocates space to hold the color entries. + /// + Palette() + { + m_Name = "-"; + m_Index = -1; + m_Entries.resize(COLORMAP_LENGTH); + Clear(); + } + + /// + /// Constructor that takes a name various parameters. If no color buffer is specified, a default is used. + /// This is a safety fallback, and it's highly recommended to always supply a buffer of color entries. + /// + /// The name of the palette + /// The index in the palette file + /// The size of the palette which should be 256 + /// A pointer to 256 color entries + Palette(string name, int index, unsigned int size, v4T* xmlPaletteEntries) + { + m_Name = name; + m_Index = index; + m_Entries.resize(size); + + if (xmlPaletteEntries) + { + memcpy(&m_Entries[0], xmlPaletteEntries, Size() * sizeof(m_Entries[0])); + } + else//They passed in null, so just fill with hard coded values so they at least have something. + { + //Palette 15 used in the test ember file. + unsigned char palette15[COLORMAP_LENGTH * 4] = { +0x00, 0xda, 0xde, 0xbc, 0x00, 0xee, 0xe6, 0xc5, 0x00, 0xee, 0xf2, 0xce, 0x00, 0xee, 0xf2, 0xcf, 0x00, 0xe6, 0xee, 0xe1, 0x00, 0xea, 0xee, 0xd8, 0x00, 0xf2, 0xf1, 0xeb, 0x00, 0xf2, 0xf5, 0xd8, +0x00, 0xe6, 0xf2, 0xce, 0x00, 0xde, 0xea, 0xc5, 0x00, 0xd6, 0xda, 0xc6, 0x00, 0xce, 0xd2, 0xbc, 0x00, 0xc2, 0xca, 0xa9, 0x00, 0xbe, 0xca, 0xa0, 0x00, 0xce, 0xd6, 0xaa, 0x00, 0xde, 0xe2, 0xc5, +0x00, 0xea, 0xed, 0xce, 0x00, 0xea, 0xf2, 0xc5, 0x00, 0xde, 0xe2, 0xc5, 0x00, 0xc2, 0xca, 0xaa, 0x00, 0xae, 0xbe, 0xaa, 0x00, 0xa5, 0xb2, 0x96, 0x00, 0xa2, 0xa9, 0x8d, 0x00, 0x96, 0xa2, 0x84, +0x00, 0x8d, 0x8d, 0x7a, 0x00, 0x85, 0x89, 0x71, 0x00, 0x85, 0x8d, 0x71, 0x00, 0x85, 0x85, 0x67, 0x00, 0x79, 0x7d, 0x67, 0x00, 0x79, 0x7d, 0x67, 0x00, 0x71, 0x79, 0x5e, 0x00, 0x65, 0x6d, 0x55, +0x00, 0x4d, 0x5d, 0x42, 0x00, 0x34, 0x40, 0x25, 0x00, 0x30, 0x40, 0x25, 0x00, 0x30, 0x38, 0x1c, 0x00, 0x2c, 0x3c, 0x1c, 0x00, 0x2c, 0x34, 0x1c, 0x00, 0x24, 0x2c, 0x12, 0x00, 0x24, 0x24, 0x00, +0x00, 0x24, 0x2c, 0x09, 0x00, 0x28, 0x34, 0x09, 0x00, 0x38, 0x40, 0x12, 0x00, 0x30, 0x40, 0x1c, 0x00, 0x40, 0x50, 0x2f, 0x00, 0x55, 0x69, 0x42, 0x00, 0x65, 0x75, 0x55, 0x00, 0x6c, 0x7d, 0x5e, +0x00, 0x74, 0x8d, 0x71, 0x00, 0x74, 0x89, 0x84, 0x00, 0x74, 0x8d, 0x84, 0x00, 0x78, 0x8d, 0x84, 0x00, 0x79, 0x89, 0x7a, 0x00, 0x79, 0x85, 0x71, 0x00, 0x75, 0x7d, 0x67, 0x00, 0x71, 0x79, 0x5e, +0x00, 0x6c, 0x71, 0x5e, 0x00, 0x6d, 0x70, 0x5e, 0x00, 0x6c, 0x79, 0x5e, 0x00, 0x68, 0x75, 0x5e, 0x00, 0x69, 0x71, 0x55, 0x00, 0x6d, 0x75, 0x55, 0x00, 0x6d, 0x75, 0x55, 0x00, 0x69, 0x71, 0x55, +0x00, 0x65, 0x71, 0x55, 0x00, 0x69, 0x6d, 0x55, 0x00, 0x64, 0x71, 0x5e, 0x00, 0x68, 0x70, 0x67, 0x00, 0x68, 0x70, 0x67, 0x00, 0x68, 0x6c, 0x67, 0x00, 0x6c, 0x6c, 0x5e, 0x00, 0x71, 0x71, 0x5e, +0x00, 0x79, 0x79, 0x67, 0x00, 0x81, 0x85, 0x71, 0x00, 0x7d, 0x91, 0x71, 0x00, 0x85, 0x92, 0x7a, 0x00, 0x85, 0x92, 0x7a, 0x00, 0x7d, 0x92, 0x84, 0x00, 0x79, 0x92, 0x84, 0x00, 0x78, 0x92, 0x8d, +0x00, 0x78, 0x8d, 0x8d, 0x00, 0x74, 0x8d, 0x84, 0x00, 0x74, 0x92, 0x84, 0x00, 0x75, 0x92, 0x7a, 0x00, 0x6c, 0x85, 0x67, 0x00, 0x64, 0x79, 0x5e, 0x00, 0x59, 0x69, 0x4b, 0x00, 0xaa, 0x57, 0x00, +0x00, 0x38, 0x44, 0x1c, 0x00, 0x30, 0x3c, 0x1c, 0x00, 0x2c, 0x3c, 0x1c, 0x00, 0x34, 0x40, 0x25, 0x00, 0x50, 0x61, 0x4b, 0x00, 0x5d, 0x6d, 0x5e, 0x00, 0x64, 0x71, 0x5e, 0x00, 0x60, 0x71, 0x5e, +0x00, 0x60, 0x75, 0x5e, 0x00, 0x68, 0x75, 0x5e, 0x00, 0x6c, 0x79, 0x5e, 0x00, 0x6c, 0x79, 0x5e, 0x00, 0x71, 0x79, 0x67, 0x00, 0x70, 0x79, 0x67, 0x00, 0x6c, 0x7d, 0x67, 0x00, 0x68, 0x79, 0x67, +0x00, 0x6c, 0x79, 0x67, 0x00, 0x6c, 0x75, 0x67, 0x00, 0x71, 0x75, 0x5e, 0x00, 0x71, 0x75, 0x5e, 0x00, 0x75, 0x79, 0x5e, 0x00, 0x75, 0x7d, 0x5e, 0x00, 0x81, 0x8d, 0x5e, 0x00, 0x8d, 0x92, 0x5e, +0x00, 0x8d, 0x92, 0x67, 0x00, 0x9a, 0x9a, 0x71, 0x00, 0x9a, 0xa2, 0x7a, 0x00, 0x9a, 0xa2, 0x7a, 0x00, 0x9a, 0xa1, 0x7a, 0x00, 0x92, 0x9a, 0x71, 0x00, 0x89, 0x92, 0x67, 0x00, 0x81, 0x85, 0x5e, +0x00, 0x7d, 0x7d, 0x55, 0x00, 0x69, 0x79, 0x4b, 0x00, 0x61, 0x6d, 0x42, 0x00, 0x44, 0x4c, 0x25, 0x00, 0x38, 0x44, 0x1c, 0x00, 0x40, 0x51, 0x25, 0x00, 0x45, 0x4d, 0x25, 0x00, 0x71, 0x6d, 0x42, +0x00, 0x79, 0x7d, 0x4b, 0x00, 0x81, 0x7d, 0x55, 0x00, 0x79, 0x79, 0x55, 0x00, 0x6d, 0x75, 0x55, 0x00, 0x69, 0x7d, 0x55, 0x00, 0x6c, 0x79, 0x5e, 0x00, 0x65, 0x79, 0x54, 0x00, 0x68, 0x79, 0x5e, +0x00, 0x64, 0x79, 0x67, 0x00, 0x64, 0x79, 0x67, 0x00, 0x68, 0x75, 0x5e, 0x00, 0x64, 0x71, 0x5e, 0x00, 0x64, 0x6c, 0x5e, 0x00, 0x65, 0x6d, 0x55, 0x00, 0x4d, 0x58, 0x42, 0x00, 0x34, 0x40, 0x25, +0x00, 0x2c, 0x38, 0x1c, 0x00, 0x20, 0x28, 0x1c, 0x00, 0x1c, 0x14, 0x09, 0x00, 0x18, 0x18, 0x00, 0x00, 0x04, 0x14, 0x00, 0x00, 0x08, 0x10, 0x00, 0x00, 0x0c, 0x18, 0x00, 0x00, 0x1c, 0x28, 0x09, +0x00, 0x24, 0x30, 0x12, 0x00, 0x3c, 0x44, 0x25, 0x00, 0x5d, 0x65, 0x55, 0x00, 0x75, 0x79, 0x55, 0x00, 0x85, 0x89, 0x5e, 0x00, 0x89, 0x91, 0x71, 0x00, 0x96, 0xa2, 0x71, 0x00, 0x9a, 0xa2, 0x7a, +0x00, 0x9e, 0xaa, 0x7a, 0x00, 0x9e, 0xaa, 0x7a, 0x00, 0xaa, 0xae, 0x71, 0x00, 0xa6, 0xaa, 0x7a, 0x00, 0xa2, 0xaa, 0x7a, 0x00, 0xa1, 0xa5, 0x7a, 0x00, 0x96, 0x9e, 0x7a, 0x00, 0x85, 0x96, 0x7a, +0x00, 0x81, 0x92, 0x7a, 0x00, 0x78, 0x92, 0x7a, 0x00, 0x75, 0x92, 0x7a, 0x00, 0x75, 0x8d, 0x7a, 0x00, 0x70, 0x81, 0x67, 0x00, 0x7d, 0x7d, 0x67, 0x00, 0x89, 0x89, 0x67, 0x00, 0x92, 0x9a, 0x71, +0x00, 0x9e, 0xaa, 0x7a, 0x00, 0xaa, 0xb6, 0x84, 0x00, 0xb2, 0xb6, 0x8d, 0x00, 0xb6, 0xba, 0x97, 0x00, 0xc2, 0xca, 0x97, 0x00, 0xb2, 0xbe, 0x8d, 0x00, 0xb2, 0xb6, 0x8d, 0x00, 0xaa, 0xb2, 0x8d, +0x00, 0xa2, 0xae, 0x84, 0x00, 0x9a, 0xa6, 0x7a, 0x00, 0x92, 0x9e, 0x7a, 0x00, 0x85, 0x9a, 0x7a, 0x00, 0x7d, 0x96, 0x7a, 0x00, 0x7d, 0x92, 0x7a, 0x00, 0x7d, 0x92, 0x84, 0x00, 0x7d, 0x92, 0x84, +0x00, 0x81, 0x96, 0x84, 0x00, 0x85, 0x96, 0x84, 0x00, 0x85, 0x96, 0x84, 0x00, 0x81, 0x92, 0x84, 0x00, 0x85, 0x9a, 0x84, 0x00, 0x85, 0x9a, 0x84, 0x00, 0x8d, 0x9a, 0x84, 0x00, 0x92, 0x96, 0x84, +0x00, 0x9e, 0xa9, 0x84, 0x00, 0xae, 0xb2, 0x84, 0x00, 0xaa, 0xba, 0x84, 0x00, 0xb2, 0xbe, 0x8d, 0x00, 0xb6, 0xc2, 0xa0, 0x00, 0xc6, 0xca, 0xa0, 0x00, 0xc6, 0xce, 0xaa, 0x00, 0xd6, 0xda, 0xb3, +0x00, 0xda, 0xe2, 0xc5, 0x00, 0xd2, 0xd6, 0xbc, 0x00, 0xbe, 0xc2, 0xa0, 0x00, 0xaa, 0xb6, 0x8d, 0x00, 0x9e, 0xa6, 0x7a, 0x00, 0x92, 0x9a, 0x71, 0x00, 0x89, 0x89, 0x71, 0x00, 0x81, 0x7d, 0x67, +0x00, 0x7d, 0x7d, 0x67, 0x00, 0x81, 0x78, 0x67, 0x00, 0x7d, 0x7d, 0x5e, 0x00, 0x79, 0x79, 0x5e, 0x00, 0x79, 0x81, 0x5e, 0x00, 0x81, 0x7d, 0x67, 0x00, 0x81, 0x7d, 0x67, 0x00, 0x81, 0x81, 0x67, +0x00, 0x81, 0x89, 0x71, 0x00, 0x85, 0x91, 0x7a, 0x00, 0x89, 0x92, 0x7a, 0x00, 0x96, 0x9d, 0x7a, 0x00, 0x96, 0x9e, 0x7a, 0x00, 0x92, 0x96, 0x84, 0x00, 0x96, 0x9a, 0x8d, 0x00, 0x92, 0x92, 0x84, +0x00, 0x89, 0x91, 0x84, 0x00, 0x81, 0x92, 0x84, 0x00, 0x7d, 0x92, 0x8d, 0x00, 0x78, 0x92, 0x8d, 0x00, 0x74, 0x92, 0x8d, 0x00, 0x78, 0x92, 0x8d, 0x00, 0x78, 0x96, 0x97, 0x00, 0x81, 0x96, 0x8d, +0x00, 0x81, 0x96, 0x8d, 0x00, 0x81, 0x9a, 0x8d, 0x00, 0x85, 0x9a, 0x8d, 0x00, 0x89, 0x9e, 0x8d, 0x00, 0x89, 0x9e, 0x8d, 0x00, 0x8d, 0xa2, 0x97, 0x00, 0x95, 0xa2, 0x97, 0x00, 0x8d, 0xa2, 0x97, +0x00, 0x96, 0xa6, 0x8d, 0x00, 0x9a, 0xa1, 0x8d, 0x00, 0x9e, 0xa9, 0x84, 0x00, 0x9e, 0xa6, 0x7a, 0x00, 0xa2, 0xa5, 0x71, 0x00, 0x9e, 0xa6, 0x71, 0x00, 0x9a, 0xa6, 0x71, 0x00, 0x95, 0x9d, 0x71 }; + + for (unsigned int i = 0; i < size; i++) + { + m_Entries[i].a = (T)palette15[i * 4 + 0]; + m_Entries[i].r = (T)palette15[i * 4 + 1]; + m_Entries[i].g = (T)palette15[i * 4 + 2]; + m_Entries[i].b = (T)palette15[i * 4 + 3]; + } + } + } + + /// + /// Default copy constructor. + /// + /// The Palette object to copy + Palette(const Palette& palette) + { + Palette::operator=(palette); + } + + /// + /// Copy constructor to copy a Palette object of type U. + /// + /// The Palette object to copy + template + Palette(const Palette& palette) + { + Palette::operator=(palette); + } + + /// + /// Default assignment operator. + /// + /// The Palette object to copy + Palette& operator = (const Palette& palette) + { + if (this != &palette) + Palette::operator=(palette); + + return *this; + } + + /// + /// Assignment operator to assign a Palette object of type U. + /// + /// The Palette object to copy + /// Reference to updated self + template + Palette& operator = (const Palette& palette) + { + m_Index = palette.m_Index; + m_Name = palette.m_Name; + CopyVec(m_Entries, palette.m_Entries); + + return *this; + } + + /// + /// Convenience [] operator to index into the color entries vector. + /// + /// The index to get + /// The color value at the specified index + v4T& operator[] (size_t i) + { + return m_Entries[i]; + } + + /// + /// Convenience * operator to get a pointer to the beginning of the color entries vector. + /// + /// The address of the first element in the color entries vector + inline v4T* operator() (void) + { + return &m_Entries[0]; + } + + /// + /// The size of the color entries vector. + /// + /// The size of the color entries vector + size_t Size() { return m_Entries.size(); } + + /// + /// Set all colors to either black or white, including the alpha channel. + /// + /// Set all colors to black if true, else white + void Clear(bool black = true) + { + for (glm::length_t i = 0; i < Size(); i++) + { + for (glm::length_t j = 0; j < 4; j++) + { + if (black) + m_Entries[i][j] = 0; + else + m_Entries[i][j] = 1; + } + } + } + + /// + /// Make a copy of this palette, adjust for hue and store in the passed in palette. + /// This is used because one way an ember Xml can specify color is with an index in the + /// palette Xml file and a hue rotation value. + /// + /// The palette to store the results in + /// The hue rotation to apply + void MakeHueAdjustedPalette(Palette& palette, T hue) + { + palette.m_Index = m_Index; + palette.m_Name = m_Name; + palette.m_Entries.resize(Size()); + + for (unsigned int i = 0; i < Size(); i++) + { + size_t ii = (i * 256) / COLORMAP_LENGTH; + T rgb[3], hsv[3]; + + rgb[0] = m_Entries[ii].r; + rgb[1] = m_Entries[ii].g; + rgb[2] = m_Entries[ii].b; + + RgbToHsv(rgb, hsv); + hsv[0] += hue * T(6.0); + HsvToRgb(hsv, rgb); + + //Alpha serves as merely a hit counter that gets incremented by 1 each time, see Renderer::Accumulate() for its usage. + //Removing it saves no memory since it's 16 byte aligned. This also means alpha is not used. + palette[i].r = rgb[0]; + palette[i].g = rgb[1]; + palette[i].b = rgb[2]; + palette[i].a = 1; + } + } + + /// + /// More advanced adjustment than MakeHueAdjustedPalette() provides. + /// Adjustments are applied in the order: + /// Frequency, index rotation, hue rotation, saturation, brightness, contrast, blur. + /// + /// The palette to store the result in + /// Index rotation. + /// Hue rotation -5 - 5 + /// Saturation 0 - 1 + /// Brightness 0 - 1 + /// Contrast -1 - 2 + /// Blur 0 - 127 + /// Frequency 1 - 10 + void MakeAdjustedPalette(Palette& palette, int rot, T hue, T sat, T bright, T cont, unsigned int blur, unsigned int freq) + { + T rgb[3], hsv[3]; + + if (freq > 1) + { + size_t n = Size() / freq; + + for (size_t j = 0; j <= freq; j++) + { + for (size_t i = 0; i <= n; i++) + { + if ((i + j * n) < Size()) + { + palette[i + j * n].r = m_Entries[i * freq].r; + palette[i + j * n].g = m_Entries[i * freq].g; + palette[i + j * n].b = m_Entries[i * freq].b; + } + } + } + + palette.m_Name = m_Name; + } + else + { + palette = *this; + } + + for (size_t i = 0; i < Size(); i++) + { + size_t ii = (i * 256) / COLORMAP_LENGTH; + + rgb[0] = palette[(COLORMAP_LENGTH + ii - rot) % COLORMAP_LENGTH].r;//Rotation. + rgb[1] = palette[(COLORMAP_LENGTH + ii - rot) % COLORMAP_LENGTH].g; + rgb[2] = palette[(COLORMAP_LENGTH + ii - rot) % COLORMAP_LENGTH].b; + RgbToHsv(rgb, hsv); + hsv[0] += hue * T(6.0);//Hue. + hsv[1] = Clamp(hsv[1] + sat, 0, 1);//Saturation. + HsvToRgb(hsv, rgb); + rgb[0] = Clamp(rgb[0] + bright, 0, 1);//Brightness. + rgb[1] = Clamp(rgb[1] + bright, 0, 1); + rgb[2] = Clamp(rgb[2] + bright, 0, 1); + rgb[0] = Clamp(((rgb[0] - T(0.5)) * (cont + T(1.0))) + T(0.5), 0, 1);//Contrast. + rgb[1] = Clamp(((rgb[1] - T(0.5)) * (cont + T(1.0))) + T(0.5), 0, 1); + rgb[2] = Clamp(((rgb[2] - T(0.5)) * (cont + T(1.0))) + T(0.5), 0, 1); + + //Alpha serves as merely a hit counter that gets incremented by 1 each time, see Renderer::Accumulate() for its usage. + //Removing it saves no memory since it's 16 byte aligned. + palette[i].r = rgb[0]; + palette[i].g = rgb[1]; + palette[i].b = rgb[2]; + palette[i].a = 1; + } + + if (blur > 0) + { + Palette blurPal = palette; + + for (int i = 0; i < 256; i++) + { + int n = -1; + + rgb[0] = 0; + rgb[1] = 0; + rgb[2] = 0; + + for (int j = i - (int)blur; j <= i + (int)blur; j++) + { + n++; + int k = (256 + j) % 256; + + if (k != i) + { + rgb[0] = rgb[0] + blurPal[k].r; + rgb[1] = rgb[1] + blurPal[k].g; + rgb[2] = rgb[2] + blurPal[k].b; + } + } + + if (n != 0) + { + palette[i].r = rgb[0] / n; + palette[i].g = rgb[1] / n; + palette[i].b = rgb[2] / n; + } + } + } + } + + /// + /// Make a copy of this palette and multiply all RGB values by a scalar. + /// + /// The palette to store the result in + /// The color scalar to multiply each RGB value by + template + void MakeDmap(Palette& palette, T colorScalar = 1) + { + palette.m_Index = m_Index; + palette.m_Name = m_Name; + + if (palette.Size() != Size()) + palette.m_Entries.resize(Size()); + + for (unsigned int j = 0; j < palette.Size(); j++) + { + palette.m_Entries[j] = m_Entries[j] * colorScalar; + palette.m_Entries[j].a = 1; + } + } + + /// + /// Make a buffer with the color values of this palette scaled to 255 + /// and repeated for a number of rows. + /// Convenience function for displaying this palette on a GUI. + /// + /// The height of the output block + /// A vector holding the color values + vector MakeRgbPaletteBlock(unsigned int height) + { + size_t width = Size(); + vector v(height * width * 3); + + if (v.size() == (height * Size() * 3)) + { + for (unsigned int i = 0; i < height; i++) + { + for (unsigned int j = 0; j < width; j++) + { + v[(width * 3 * i) + (j * 3)] = (unsigned char)(m_Entries[j][0] * T(255));//Palettes are as [0..1], so convert to [0..255] here since it's for GUI display. + v[(width * 3 * i) + (j * 3) + 1] = (unsigned char)(m_Entries[j][1] * T(255)); + v[(width * 3 * i) + (j * 3) + 2] = (unsigned char)(m_Entries[j][2] * T(255)); + } + } + } + + return v; + } + + /// + /// Convert RGB to HSV. + /// + /// Red 0 - 1 + /// Green 0 - 1 + /// Blue 0 - 1 + /// Hue 0 - 6 + /// Saturation 0 - 1 + /// Value 0 - 1 + static void RgbToHsv(T r, T g, T b, T& h, T& s, T& v) + { + T max, min, del, rc, gc, bc; + + max = std::max(std::max(r, g), b);//Compute maximum of r, g, b. + min = std::min(std::min(r, g), b);//Compute minimum of r, g, b. + + del = max - min; + v = max; + s = (max != 0) ? (del / max) : 0; + h = 0; + + if (s != 0) + { + rc = (max - r) / del; + gc = (max - g) / del; + bc = (max - b) / del; + + if (r == max) + h = bc - gc; + else if (g == max) + h = 2 + rc - bc; + else if (b == max) + h = 4 + gc - rc; + + if (h < 0) + h += 6; + } + } + + /// + /// Wrapper around RgbToHsv() which takes buffers as parameters instead of individual components. + /// + /// The RGB buffer + /// The HSV buffer + static void RgbToHsv(T* rgb, T* hsv) + { + RgbToHsv(rgb[0], rgb[1], rgb[2], hsv[0], hsv[1], hsv[2]); + } + + /// + /// Convert HSV to RGB. + /// + /// Hue 0 - 6 + /// Saturation 0 - 1 + /// Value 0 - 1 + /// Red 0 - 1 + /// Green 0 - 1 + /// Blue 0 - 1 + static void HsvToRgb(T h, T s, T v, T& r, T& g, T& b) + { + int j; + T f, p, q, t; + + while (h >= 6) + h -= 6; + + while (h < 0) + h += 6; + + j = Floor(h); + f = h - j; + p = v * (1 - s); + q = v * (1 - (s * f)); + t = v * (1 - (s * (1 - f))); + + switch (j) + { + case 0: r = v; g = t; b = p; break; + case 1: r = q; g = v; b = p; break; + case 2: r = p; g = v; b = t; break; + case 3: r = p; g = q; b = v; break; + case 4: r = t; g = p; b = v; break; + case 5: r = v; g = p; b = q; break; + default: r = v; g = t; b = p; break; + } + } + + /// + /// Wrapper around HsvToRgb() which takes buffers as parameters instead of individual components. + /// + /// The HSV buffer + /// The RGB buffer + static void HsvToRgb(T* hsv, T* rgb) + { + HsvToRgb(hsv[0], hsv[1], hsv[2], rgb[0], rgb[1], rgb[2]); + } + + /// + /// Calculates the alpha. + /// Used for gamma correction in final accumulation. + /// Not the slightest clue what this is doing. + /// + /// Density + /// Gamma + /// Linear range + /// Alpha + static T CalcAlpha(T density, T gamma, T linrange) + { + T frac, alpha; + T funcval = pow(linrange, gamma); + + if (density > 0) + { + if (density < linrange) + { + frac = density / linrange; + alpha = (T(1.0) - frac) * density * (funcval / linrange) + frac * pow(density, gamma); + } + else + alpha = pow(density, gamma); + } + else + alpha = 0; + + return alpha; + } + + /// + /// Calculates the new RGB and stores in the supplied buffer. + /// Used for gamma correction in final accumulation. + /// Not the slightest clue what this is doing. + /// + /// The input RGB color buffer 0 - 1 + /// Log scaling + /// Highlight power, -1 - 1 + /// Newly computed RGB value + template + static void CalcNewRgb(bucketT* cBuf, T ls, T highPow, bucketT* newRgb) + { + int rgbi; + T newls, lsratio; + bucketT newhsv[3]; + T maxa, maxc; + T adjustedHighlight; + + if (ls == 0 || (cBuf[0] == 0 && cBuf[1] == 0 && cBuf[2] == 0)) + { + newRgb[0] = 0; + newRgb[1] = 0; + newRgb[2] = 0; + return; + } + + //Identify the most saturated channel. + maxc = max(max(cBuf[0], cBuf[1]), cBuf[2]); + maxa = ls * maxc; + + //If a channel is saturated and highlight power is non-negative + //modify the color to prevent hue shift. + if (maxa > 255 && highPow >= 0) + { + newls = T(255.0) / maxc; + lsratio = pow(newls / ls, highPow); + + //Calculate the max-value color (ranged 0 - 1). + for (rgbi = 0; rgbi < 3; rgbi++) + newRgb[rgbi] = (bucketT)newls * cBuf[rgbi] / bucketT(255.0); + + //Reduce saturation by the lsratio. + Palette::RgbToHsv(newRgb, newhsv); + newhsv[1] *= (bucketT)lsratio; + Palette::HsvToRgb(newhsv, newRgb); + + for (rgbi = 0; rgbi < 3; rgbi++) + newRgb[rgbi] *= T(255.0); + } + else + { + newls = T(255.0) / maxc; + adjustedHighlight = -highPow; + + if (adjustedHighlight > 1) + adjustedHighlight = 1; + + if (maxa <= 255) + adjustedHighlight = 1; + + //Calculate the max-value color (ranged 0 - 1) interpolated with the old behavior. + for (rgbi = 0; rgbi < 3; rgbi++) + newRgb[rgbi] = bucketT((T(1.0) - adjustedHighlight) * newls + adjustedHighlight * ls) * cBuf[rgbi]; + } + } + + int m_Index;//Index in the xml palette file of this palette, use -1 for random. + string m_Name;//Name of this palette. + vector m_Entries;//Storage for the color values. +}; +} diff --git a/Source/Ember/PaletteList.h b/Source/Ember/PaletteList.h new file mode 100644 index 0000000..c89c45a --- /dev/null +++ b/Source/Ember/PaletteList.h @@ -0,0 +1,226 @@ +#pragma once + +#include "Palette.h" + +/// +/// PaletteList class. +/// + +namespace EmberNs +{ +/// +/// Holds a list of palettes read from an Xml file. Since the default list from flam3-palettes.xml is fairly large at 700 palettes, +/// the list member is kept as a static. This class derives from EmberReport in order to report any errors that occurred while reading the Xml. +/// Note that although the Xml color values are expected to be 0-255, they are converted and stored as normalized colors, with values from 0-1. +/// Template argument expected to be float or double. +/// +template +class EMBER_API PaletteList : public EmberReport +{ +public: + /// + /// Empty constructor which does nothing. + /// + PaletteList() + { + } + + /// + /// Read an Xml palette file into memory. + /// This must be called before any palette file usage. + /// + /// The full path to the file to read + /// If true, override the initialization state and force a read, else observe the initialization state. + /// The initialization state + bool Init(string filename, bool force = false) + { + if (!m_Init || force) + { + const char* loc = __FUNCTION__; + + m_Init = false; + m_Palettes.clear(); + m_ErrorReport.clear(); + string buf; + + if (ReadFile(filename.c_str(), buf)) + { + xmlDocPtr doc = xmlReadMemory((const char*)buf.data(), (int)buf.size(), filename.c_str(), NULL, XML_PARSE_NONET); + + if (doc != NULL) + { + xmlNode* rootNode = xmlDocGetRootElement(doc); + + m_Palettes.reserve(buf.size() / 2048);//Roughly what it takes per palette. + ParsePalettes(rootNode); + xmlFreeDoc(doc); + m_Init = m_ErrorReport.empty(); + } + else + { + m_ErrorReport.push_back(string(loc) + " : Couldn't load xml doc"); + } + } + else + { + m_ErrorReport.push_back(string(loc) + " : Couldn't read palette file " + filename); + } + } + + return m_Init; + } + + /// + /// Gets the palette at a specified index. + /// + /// The index of the palette to read. A value of -1 indicates a random palette. + /// A pointer to the requested palette if the index was in range, else NULL. + Palette* GetPalette(int i) + { + if (!m_Palettes.empty()) + { + if (i == -1) + return &m_Palettes[QTIsaac::GlobalRand->Rand() % Count()]; + else if (i < (int)m_Palettes.size()) + return &m_Palettes[i]; + } + + return NULL; + } + + /// + /// Gets a pointer to a palette with a specified name. + /// + /// The name of the palette to retrieve + /// A pointer to the palette if found, else NULL + Palette* GetPaletteByName(string& name) + { + for (unsigned int i = 0; i < Count(); i++) + if (m_Palettes[i].m_Name == name) + return &m_Palettes[i]; + + return NULL; + } + + /// + /// Gets a copy of the palette at a specified index with its hue adjusted by the specified amount. + /// + /// The index of the palette to read. A value of -1 indicates a random palette. + /// The hue adjustment to apply + /// The palette to store the output + /// True if successful, else false. + bool GetHueAdjustedPalette(int i, T hue, Palette& palette) + { + bool b = false; + Palette* unadjustedPal = GetPalette(i); + + if (unadjustedPal) + { + unadjustedPal->MakeHueAdjustedPalette(palette, hue); + b = true; + } + + return b; + } + + /// + /// Clear the palette list and reset the initialization state. + /// + void Clear() + { + m_Palettes.clear(); + m_Init = false; + } + + /// + /// Accessors. + /// + bool Init() { return m_Init; } + unsigned int Count() { return (unsigned int)m_Palettes.size(); } + +private: + /// + /// Parses an Xml node for all palettes present and stores in the palette list. + /// Note that although the Xml color values are expected to be 0-255, they are converted and + /// stored as normalized colors, with values from 0-1. + /// + /// The parent note of all palettes in the Xml file. + void ParsePalettes(xmlNode* node) + { + bool hexError = false; + char* val; + const char* loc = __FUNCTION__; + xmlAttrPtr attr; + + while (node) + { + if (node->type == XML_ELEMENT_NODE && !Compare(node->name, "palette")) + { + attr = node->properties; + Palette palette; + + while (attr) + { + val = (char*)xmlGetProp(node, attr->name); + + if (!Compare(attr->name, "data")) + { + int colorIndex = 0; + int r, g, b; + int colorCount = 0; + hexError = false; + + do + { + int ret = sscanf_s((char*)&(val[colorIndex]),"00%2x%2x%2x", &r, &g, &b); + + if (ret != 3) + { + m_ErrorReport.push_back(string(loc) + " : Problem reading hexadecimal color data " + string(&val[colorIndex])); + hexError = true; + break; + } + + colorIndex += 8; + + while (isspace((int)val[colorIndex])) + colorIndex++; + + palette[colorCount].r = T(r) / T(255);//Store as normalized colors in the range of 0-1. + palette[colorCount].g = T(g) / T(255); + palette[colorCount].b = T(b) / T(255); + + colorCount++; + } while (colorCount < COLORMAP_LENGTH); + } + else if (!Compare(attr->name, "number")) + { + palette.m_Index = atoi(val); + } + else if (!Compare(attr->name, "name")) + { + palette.m_Name = string(val); + } + + xmlFree(val); + attr = attr->next; + } + + if (!hexError) + { + m_Palettes.push_back(palette); + } + } + else + { + ParsePalettes(node->children); + } + + node = node->next; + } + } + + static bool m_Init;//Initialized to false in Ember.cpp, and will be set to true upon successful reading of an Xml palette file. + static vector> m_Palettes;//The vector that stores the palettes. +}; +} \ No newline at end of file diff --git a/Source/Ember/Point.h b/Source/Ember/Point.h new file mode 100644 index 0000000..989282a --- /dev/null +++ b/Source/Ember/Point.h @@ -0,0 +1,217 @@ +#pragma once + +#include "EmberDefines.h" +#include "Affine2D.h" +#include "Timing.h" + +/// +/// Basic point and color structures used in iteration. +/// + +namespace EmberNs +{ +/// +/// The point used to store the result of each iteration, which is +/// a spatial coordinate, a color index/coordinate and a visibility value. +/// Note that a Y color coordinate is not used at the moment because +/// only 1D palettes are supported like the original. However, in the future +/// 2D palettes may be supported like Fractron does. +/// Template argument expected to be float or double. +/// +template +class EMBER_API Point +{ +public: + /// + /// Constructor to initialize spatial and color coordinates to zero, with full visibility. + /// + Point() + { + Init(); + } + + /// + /// Default copy constructor. + /// + /// The Point object to copy + Point(const Point& point) + { + Point::operator=(point); + } + + /// + /// Copy constructor to copy a Point object of type U. + /// + /// The Point object to copy + template + Point(const Point& point) + { + Point::operator=(point); + } + + /// + /// Default assignment operator. + /// + /// The Point object to copy + Point& operator = (const Point& point) + { + if (this != &point) + Point::operator=(point); + + return *this; + } + + /// + /// Assignment operator to assign a Point object of type U. + /// + /// The Point object to copy. + /// Reference to updated self + template + Point& operator = (const Point& point) + { + m_X = point.m_X; + m_Y = point.m_Y; + m_Z = point.m_Z; + m_ColorX = point.m_ColorX; + //m_ColorY = point.m_ColorY; + m_VizAdjusted = point.m_VizAdjusted; + + return *this; + } + + /// + /// Set spatial and color coordinates to zero, with full visibility. + /// + void Init() + { + m_X = 0; + m_Y = 0; + m_Z = 0; + m_ColorX = 0; + //m_ColorY = 0; + m_VizAdjusted = 1; + } + + T m_X; + T m_Y; + T m_Z; + T m_ColorX; + //T m_ColorY; + T m_VizAdjusted; +}; + +/// +/// Comparer used for sorting the results of iteration by their spatial x coordinates. +/// +/// The first point to compare +/// The second point to compare +/// 1 if the first point had an x coordinate less than the second point, else 0 +template +static int SortPointByX(const Point& a, const Point& b) +{ + return a.m_X < b.m_X; +} + +/// +/// Comparer used for sorting the results of iteration by their spatial y coordinates. +/// +/// The first point to compare +/// The second point to compare +/// 1 if the first point had an y coordinate less than the second point, else 0 +template +static int SortPointByY(const Point& a, const Point& b) +{ + return a.m_Y < b.m_Y; +} + +/// +/// Thin override of a glm::vec4 which adds a couple of functions +/// specific to color handling. +/// +template +class EMBER_API Color : public v4T +{ +public: + /// + /// Constructor to set color values to zero, with full visibility. + /// + Color() + { + Reset(); + } + + /// + /// Default copy constructor. + /// + /// The Color object to copy + Color(const Color& color) + { + Color::operator=(color); + } + + /// + /// Copy constructor to copy a Color object of type U. + /// + /// The Color object to copy + template + Color(const Color& color) + { + Color::operator=(color); + } + + /// + /// Default assignment operator. + /// + /// The Color object to copy + Color& operator = (const Color& color) + { + if (this != &color) + Color::operator=(color); + + return *this; + } + + /// + /// Assignment operator to assign a Color object of type U. + /// + /// The Color object to copy. + /// Reference to updated self + template + Color& operator = (const Color& color) + { + v4T::operator=(color); + return *this; + } + + /// + /// Member-wise constructor. + /// + Color(T rr, T gg, T bb, T aa) + : v4T(rr, gg, bb, aa) + { + } + + /// + /// Set color values and visibility to zero. + /// + inline void Clear() + { + r = 0; + g = 0; + b = 0; + a = 0; + } + + /// + /// Set color values to zero, with full visibility. + /// + /// If norm is true, the color fields are expected to have a range of 0-1, else 0-255 + inline void Reset(bool norm = true) + { + r = 0; + g = 0; + b = 0; + a = norm ? T(1) : T(255); + } +}; +} \ No newline at end of file diff --git a/Source/Ember/Renderer.cpp b/Source/Ember/Renderer.cpp new file mode 100644 index 0000000..1fc8229 --- /dev/null +++ b/Source/Ember/Renderer.cpp @@ -0,0 +1,2222 @@ +#include "EmberPch.h" +#include "Renderer.h" + +namespace EmberNs +{ +/// +/// Constructor that sets default values and allocates iterators. +/// The thread count is set to the number of cores detected on the system. +/// +template +Renderer::Renderer() +{ + m_Abort = false; + m_LockAccum = false; + m_EarlyClip = false; + m_InsertPalette = false; + m_ReclaimOnResize = false; + m_SubBatchSize = 1024 * 10; + m_NumChannels = 3; + m_BytesPerChannel = 1; + m_SuperSize = 0; + m_PixelAspectRatio = 1; + m_Transparency = false; + ThreadCount(Timing::ProcessorCount()); + m_StandardIterator = auto_ptr>(new StandardIterator()); + m_XaosIterator = auto_ptr>(new XaosIterator()); + m_Iterator = m_StandardIterator.get(); + m_Callback = NULL; + m_ProgressParameter = NULL; + m_LastPass = 0; + m_LastTemporalSample = 0; + m_LastIter = 0; + m_LastIterPercent = 0; + m_InteractiveFilter = FILTER_LOG; + m_ProcessState = NONE; + m_ProcessAction = FULL_RENDER; + m_InRender = false; + m_InFinalAccum = false; +} + +/// +/// Virtual destructor so derived class destructors get called. +/// +template +Renderer::~Renderer() +{ +} + +/// +/// Compute the bounds of the histogram and density filtering buffers. +/// These are affected by the final requested dimensions, spatial and density +/// filter sizes and supersampling. +/// +template +void Renderer::ComputeBounds() +{ + unsigned int maxDEFilterWidth = 0; + + m_GutterWidth = ClampGte((m_SpatialFilter->FinalFilterWidth() - Supersample()) / 2, 0u); + + //Check the size of the density estimation filter. + //If the radius of the density estimation filter is greater than the + //gutter width, have to pad with more. Otherwise, use the same value. + for (unsigned int i = 0; i < m_Embers.size(); i++) + maxDEFilterWidth = max((unsigned int)(ceil(m_Embers[i].m_MaxRadDE) * m_Ember.m_Supersample), maxDEFilterWidth); + + //Need an extra ss = (int)floor(m_Supersample / 2.0) of pixels so that a local iteration count for DE can be determined.//SMOULDER + if (maxDEFilterWidth > 0) + maxDEFilterWidth += (unsigned int)Floor(m_Ember.m_Supersample / T(2)); + + //To have a fully present set of pixels for the spatial filter, must + //add the DE filter width to the spatial filter width.//SMOULDER + m_DensityFilterOffset = maxDEFilterWidth; + m_GutterWidth += m_DensityFilterOffset; + + m_SuperRasW = (Supersample() * FinalRasW()) + (2 * m_GutterWidth); + m_SuperRasH = (Supersample() * FinalRasH()) + (2 * m_GutterWidth); + m_SuperSize = m_SuperRasW * m_SuperRasH; +} + +/// +/// Compute the camera. +/// This sets up the bounds of the cartesian plane that the raster bounds correspond to. +/// This must be called after ComputeBounds() which sets up the raster bounds. +/// +template +void Renderer::ComputeCamera() +{ + m_Scale = pow(T(2.0), Zoom()); + m_ScaledQuality = Quality() * m_Scale * m_Scale; + + m_PixelsPerUnitX = PixelsPerUnit() * m_Scale; + m_PixelsPerUnitY = m_PixelsPerUnitX; + m_PixelsPerUnitX /= PixelAspectRatio(); + + T shift = 0; + T t0 = T(m_GutterWidth) / (Supersample() * m_PixelsPerUnitX); + T t1 = T(m_GutterWidth) / (Supersample() * m_PixelsPerUnitY); + + //These go from ll to ur, moving from negative to positive. + m_LowerLeftX = CenterX() - FinalRasW() / m_PixelsPerUnitX / T(2.0); + m_LowerLeftY = CenterY() - FinalRasH() / m_PixelsPerUnitY / T(2.0); + m_UpperRightX = m_LowerLeftX + FinalRasW() / m_PixelsPerUnitX; + m_UpperRightY = m_LowerLeftY + FinalRasH() / m_PixelsPerUnitY; + + T carLlX = m_LowerLeftX - t0; + T carLlY = m_LowerLeftY - t1 + shift; + T carUrX = m_UpperRightX + t0; + T carUrY = m_UpperRightY + t1 + shift; + + m_RotMat.MakeID(); + m_RotMat.Rotate(-Rotate()); + m_CarToRas.Init(carLlX, carLlY, carUrX, carUrY, m_SuperRasW, m_SuperRasH, PixelAspectRatio()); +} + +/// +/// Abort the render and call a function to do something, most likely change a value. +/// Then update the current process action to the one specified. +/// The current process action will only be set if it makes sense based +/// on the current process state. If the value specified doesn't make sense +/// the next best choice will be made. If nothing makes sense, a complete +/// re-render will be triggered on the next call to Run(). +/// +/// The function to execute +/// The desired process action +template +void Renderer::ChangeVal(std::function func, eProcessAction action) +{ + Abort(); + EnterRender(); + func(); + + //If they want a full render, don't bother inspecting process state, just start over. + if (action == FULL_RENDER) + { + m_ProcessState = NONE; + m_ProcessAction = FULL_RENDER; + } + //Keep iterating is when rendering has completed and the user increases the quality. + //Rendering can be started where it left off by adding just the difference between the + //new and old quality values. + else if (action == KEEP_ITERATING) + { + if (m_ProcessState == ACCUM_DONE && TemporalSamples() == 1 && Passes() == 1) + { + m_ProcessState = ITER_STARTED; + m_ProcessAction = KEEP_ITERATING; + } + else//Invaid process state to handle KEEP_ITERATING, so just start over. + { + m_ProcessState = NONE; + m_ProcessAction = FULL_RENDER; + } + } + else if (action == FILTER_AND_ACCUM) + { + //If in the middle of a render, cannot skip to filtering or accum, so just start over. + if (m_ProcessState == NONE || m_ProcessState == ITER_STARTED) + { + m_ProcessState = NONE; + m_ProcessAction = FULL_RENDER; + } + //If passes == 1, set the state to ITER_DONE and the next process action to FILTER_AND_ACCUM. + else + { + m_ProcessState = Passes() == 1 ? ITER_DONE : NONE; + m_ProcessAction = Passes() == 1 ? FILTER_AND_ACCUM : FULL_RENDER;//Cannot just filter if passes > 1 because filtering is done with each pass. + } + } + //Run accum only. + else if (action == ACCUM_ONLY) + { + //Doesn't make sense if in the middle of iterating, so just start over. + if (m_ProcessState == NONE || m_ProcessState == ITER_STARTED) + { + m_ProcessAction = FULL_RENDER; + } + else if (m_ProcessState == ITER_DONE)//If iterating is done, can start at density filtering and proceed. + { + m_ProcessAction = FILTER_AND_ACCUM; + } + else if (m_ProcessState == FILTER_DONE)//Density filtering is done, so the process action is assigned as desired. + { + m_ProcessAction = ACCUM_ONLY; + } + else if (m_ProcessState == ACCUM_DONE)//Final accum is done, so back up and run final accum again. + { + m_ProcessState = FILTER_DONE; + m_ProcessAction = ACCUM_ONLY; + } + } + + LeaveRender(); +} + +/// +/// Set the current ember. +/// This will also populate the vector of embers with a single element copy +/// of the ember passed in. +/// Temporal samples will be set to 1 since there's only a single ember. +/// +/// The ember to assign +/// The requested process action. Note that it's critical the user supply the proper value here. +/// For example: Changing dimensions without setting action to FULL_RENDER will crash the program. +/// However, changing only the brightness and setting action to ACCUM_ONLY is perfectly fine. +/// +template +void Renderer::SetEmber(Ember& ember, eProcessAction action) +{ + ChangeVal([&] + { + m_Embers.clear(); + m_Embers.push_back(ember); + m_Embers[0].m_TemporalSamples = 1;//Set temporal samples here to 1 because using the real value only makes sense when using a vector of Embers for animation. + m_Ember = m_Embers[0]; + }, action); +} + +/// +/// Set the vector of embers and set the m_Ember member to a copy of the first element. +/// Reset the rendering process. +/// +/// The vector of embers +template +void Renderer::SetEmber(vector>& embers) +{ + ChangeVal([&] + { + m_Embers = embers; + + if (!m_Embers.empty()) + m_Ember = m_Embers[0]; + }, FULL_RENDER); +} + +/// +/// Add an ember to the end of the embers vector and reset the rendering process. +/// Reset the rendering process. +/// +/// The ember to add +template +void Renderer::AddEmber(Ember& ember) +{ + ChangeVal([&] + { + m_Embers.push_back(ember); + + if (m_Embers.size() == 1) + m_Ember = m_Embers[0]; + }, FULL_RENDER); +} + +/// +/// Create the temporal filter if the current filter parameters differ +/// from the last temporal filter created. +/// +/// True if a new filter instance was created, else false. +/// True if the filter is not NULL (whether a new one was created or not), else false. +template +bool Renderer::CreateTemporalFilter(bool& newAlloc) +{ + newAlloc = false; + + //Use intelligent testing so it isn't created every time a new ember is passed in. + if ((m_TemporalFilter.get() == NULL) || + (m_Ember.m_Passes != m_LastEmber.m_Passes) || + (m_Ember.m_TemporalSamples != m_LastEmber.m_TemporalSamples) || + (m_Ember.m_TemporalFilterType != m_TemporalFilter->FilterType()) || + (m_Ember.m_TemporalFilterWidth != m_LastEmber.m_TemporalFilterWidth) || + (m_Ember.m_TemporalFilterExp != m_LastEmber.m_TemporalFilterExp)) + { + m_TemporalFilter = auto_ptr>( + TemporalFilterCreator::Create(m_Ember.m_TemporalFilterType, m_Ember.m_Passes, m_Ember.m_TemporalSamples, m_Ember.m_TemporalFilterWidth, m_Ember.m_TemporalFilterExp)); + newAlloc = true; + } + + return m_TemporalFilter.get() != NULL; +} + +/// +/// Resize the passed in vector to be large enough to handle the output image. +/// If m_ReclaimOnResize is true, and the vector is already larger than needed, +/// it will be shrunk to the needed size. However if m_ReclaimOnResize is false, +/// it will be left alone if already large enough. +/// ComputeBounds() must be called before calling this function. +/// +/// The vector to allocate +/// True if the vector contains enough space to hold the output image +template +bool Renderer::PrepFinalAccumVector(vector& pixels) +{ + EnterResize(); + size_t size = FinalBufferSize(); + + if (m_ReclaimOnResize) + { + if (pixels.size() != size) + pixels.resize(size); + + if (m_ReclaimOnResize) + pixels.shrink_to_fit(); + } + else + { + if (pixels.size() < size) + pixels.resize(size); + } + + LeaveResize(); + + return pixels.size() >= size;//Ensure allocation went ok. +} + +/// +/// The main render loop. This is the core of the algorithm. +/// The processing steps are: Iterating, density filtering, final accumulation. +/// Various functions in it are virtual so they will resolve +/// to whatever overrides are provided in derived classes. This +/// future-proofs the algorithm for GPU-based renderers. +/// If the caller calls Abort() at any time, or the progress function returns 0, +/// the entire rendering process will exit as soon as it can. +/// The loop structure is: +/// { +/// Passes (Default 1) +/// { +/// Temporal Samples (Default 1 for single image) +/// { +/// Iterate (Either to completion or to a specified number of iterations) +/// { +/// } +/// } +/// } +/// +/// Density filtering (Basic log, or full density estimation) +/// Final accumulation (Color correction and spatial filtering) +/// } +/// This loop structure has admittedly been severely butchered from what +/// flam3 did. The reason is that it was made to support interactive rendering +/// that can exit the process and pick up where it left off in response to the +/// user changing values in a fractal flame GUI editor. +/// To achieve this, each step in the rendering process is given an enumeration state +/// as well as a goto label. This allows the renderer to pick up in the state it left +/// off in if no changes prohibiting that have been made. +/// It also allows for the bare minimum amount of processing needed to complete the requested +/// action. For example, if the process has completed and the user only adjusts the brightness +/// of the last rendered ember then there is no need to perform the entire iteration process +/// over again. Rather, only final accumulation is needed. +/// +/// Storage for the final image. It will be allocated if needed. +/// The time if animating, else ignored. +/// Run a specified number of sub batches. Default: 0, meaning run to completion. +/// True to force rendering a complete image even if iterating is not complete, else don't. Default: false. +/// Offset in finalImage to store the pixels to. Default: 0. +/// True if nothing went wrong, else false. +template +eRenderStatus Renderer::Run(vector& finalImage, double time, unsigned int subBatchCountOverride, bool forceOutput, size_t finalOffset) +{ + m_InRender = true; + EnterRender(); + m_Abort = false; + bool filterAndAccumOnly = (m_ProcessAction == FILTER_AND_ACCUM && Passes() == 1); + bool accumOnly = m_ProcessAction == ACCUM_ONLY; + bool resume = m_ProcessState != NONE; + bool newFilterAlloc; + unsigned int temporalSample, pass; + eRenderStatus success = RENDER_OK; + //Timing it; + + //Reset timers and progress percent if: Beginning anew or only filtering and/or accumulating. + if (!resume || accumOnly || filterAndAccumOnly) + { + if (!resume)//Only set this if it's the first run through. + m_ProcessState = ITER_STARTED; + + m_RenderTimer.Tic(); + m_ProgressTimer.Tic(); + } + + if (!resume)//Beginning, reset everything. + { + m_LastPass = 0; + m_LastTemporalSample = 0; + m_LastIter = 0; + m_LastIterPercent = 0; + m_Stats.m_Iters = 0; + m_Stats.m_Badvals = 0; + m_Gamma = 0; + m_Vibrancy = 0;//Accumulate these after each temporal sample. + m_VibGamCount = 0; + m_Background.Clear(); + } + //User requested an increase in quality after finishing. + else if (m_ProcessState == ITER_STARTED && m_ProcessAction == KEEP_ITERATING && TemporalSamples() == 1 && Passes() == 1) + { + m_LastPass = 0; + m_LastTemporalSample = 0; + m_LastIter = m_Stats.m_Iters; + m_LastIterPercent = 0;//Might skip a progress update, but shouldn't matter. + m_Gamma = 0; + m_Vibrancy = 0; + m_VibGamCount = 0; + m_Background.Clear(); + } + + pass = (resume ? m_LastPass : 0); + + //Make sure values are within valid range. + ClampGteRef(m_Ember.m_Passes, 1u); + ClampGteRef(m_Ember.m_Supersample, 1u); + + //Make sure to get most recent update since loop won't be entered to call Interp(). + //Vib, gam and background are normally summed for each temporal sample. However if iteration is skipped, make sure to get the latest. + if ((filterAndAccumOnly || accumOnly) && TemporalSamples() == 1)//Disallow jumping when temporal samples > 1. + { + m_Ember = m_Embers[0]; + m_Vibrancy = m_Ember.m_Vibrancy; + m_Gamma = m_Ember.m_Gamma; + m_Background = m_Ember.m_Background; + + if (filterAndAccumOnly) + goto FilterAndAccum; + + if (accumOnly) + goto AccumOnly; + } + + //it.Tic(); + //Interpolate. + if (m_Embers.size() > 1) + Interpolater::Interpolate(m_Embers, T(time), 0, m_Ember); + //it.Toc("Interp 1"); + + //Save only for palette insertion. + if (m_InsertPalette && BytesPerChannel() == 1) + m_TempEmber = m_Ember; + + //Field would go here, however Ember omits it. Would need temps for width and height if ever implemented. + CreateSpatialFilter(newFilterAlloc); + CreateTemporalFilter(newFilterAlloc); + ComputeBounds(); + + if (m_SpatialFilter.get() == NULL || m_TemporalFilter.get() == NULL) + { + m_ErrorReport.push_back("Spatial and temporal filter allocations failed, aborting.\n"); + success = RENDER_ERROR; + goto Finish; + } + + if (!resume && !Alloc()) + { + m_ErrorReport.push_back("Histogram, accumulator and samples buffer allocations failed, aborting.\n"); + success = RENDER_ERROR; + goto Finish; + } + + if (!resume) + ResetBuckets(true, false);//Only reset hist here and do accum when needed later on. + + double iterationTime = 0; + double accumulationTime = 0; + + //Passes, outermost loop 1. + for (; (pass < Passes()) && !m_Abort;) + { + T deTime = T(time) + m_TemporalFilter->Deltas()[pass * m_Ember.m_TemporalSamples]; + + //Interpolate and get an ember for DE purposes. + //Additional interpolation will be done in the temporal samples loop. + //it.Tic(); + if (m_Embers.size() > 1) + Interpolater::Interpolate(m_Embers, deTime, 0, m_Ember); + //it.Toc("Interp 2"); + + ClampGte(m_Ember.m_MinRadDE, 0); + ClampGte(m_Ember.m_MaxRadDE, 0); + + if (!CreateDEFilter(newFilterAlloc)) + { + m_ErrorReport.push_back("Density filter creation failed, aborting.\n"); + success = RENDER_ERROR; + goto Finish; + } + + //Temporal samples, loop 2. + temporalSample = resume ? m_LastTemporalSample : 0; + for (; (temporalSample < TemporalSamples()) && !m_Abort;) + { + T colorScalar = m_TemporalFilter->Filter()[pass * TemporalSamples() + temporalSample]; + T temporalTime = T(time) + m_TemporalFilter->Deltas()[pass * TemporalSamples() + temporalSample]; + + //Interpolate again. + //it.Tic(); + if (m_Embers.size() > 1) + Interpolater::Interpolate(m_Embers, temporalTime, 0, m_Ember);//This will perform all necessary precalcs via the ember/xform/variation assignment operators. + //it.Toc("Interp 3"); + + if (!resume && !AssignIterator()) + { + m_ErrorReport.push_back("Iterator assignment failed, aborting.\n"); + success = RENDER_ERROR; + goto Finish; + } + + ComputeCamera(); + + //For each temporal sample, the palette m_Dmap needs to be re-created with color scalar. 1 if no temporal samples. + MakeDmap(colorScalar); + + //The actual number of times to iterate. Each thread will get (totalIters / ThreadCount) iters to do. + //This is based on zoom and scale calculated in ComputeCamera(). + //Note that the iter count is based on the final image dimensions, and not the super sampled dimensions. + unsigned __int64 totalIterCount = TotalIterCount(); + unsigned __int64 itersPerTemporalSample = ItersPerTemporalSample();//The total number of iterations for this temporal sample in this pass without overrides. + unsigned __int64 sampleItersToDo;//The number of iterations to actually do in this sample in this pass, considering overrides. + + if (subBatchCountOverride > 0) + sampleItersToDo = subBatchCountOverride * SubBatchSize() * ThreadCount();//Run a specific number of sub batches. + else + sampleItersToDo = itersPerTemporalSample;//Run as many iters as specified to complete this temporal sample. + + sampleItersToDo = min(sampleItersToDo, itersPerTemporalSample - m_LastIter); + EmberStats stats = Iterate(sampleItersToDo, pass, temporalSample);//The heavy work is done here. + + //If no iters were executed, something went catastrophically wrong. + if (stats.m_Iters == 0) + { + m_ErrorReport.push_back("Zero iterations ran, rendering failed, aborting.\n"); + success = RENDER_ERROR; + Abort(); + goto Finish; + } + + if (m_Abort) + { + success = RENDER_ABORT; + goto Finish; + } + + //Accumulate stats whether this batch ran to completion or exited prematurely. + m_LastIter += stats.m_Iters;//Sum of iter count of all threads, reset each temporal sample. + m_Stats.m_Iters += stats.m_Iters;//Sum of iter count of all threads, cumulative from beginning to end. + m_Stats.m_Badvals += stats.m_Badvals; + + //After each temporal sample, accumulate these. + //Allow for incremental rendering by only taking action if the iter loop for this temporal sample is completely done. + if (m_LastIter >= itersPerTemporalSample) + { + m_Vibrancy += m_Ember.m_Vibrancy; + m_Gamma += m_Ember.m_Gamma; + m_Background.r += m_Ember.m_Background.r; + m_Background.g += m_Ember.m_Background.g; + m_Background.b += m_Ember.m_Background.b; + m_VibGamCount++; + m_LastIter = 0; + temporalSample++; + } + + m_LastTemporalSample = temporalSample; + + if (subBatchCountOverride > 0)//Don't keep going through this loop if only doing an incremental render. + break; + }//Temporal samples. + + //If we've completed all temporal samples and all passes, then it was a complete render, so report progress. + if ((Passes() == 1 || pass == Passes() - 1) && (temporalSample >= TemporalSamples())) + { + m_ProcessState = ITER_DONE; + + if (m_Callback && !m_Callback->ProgressFunc(m_Ember, m_ProgressParameter, 100.0, 0, 0)) + { + Abort(); + success = RENDER_ABORT; + goto Finish; + } + } + +FilterAndAccum: + if (filterAndAccumOnly || temporalSample >= TemporalSamples() || forceOutput) + { + //t.Toc("Iterating and accumulating"); + //Compute k1 and k2. + eRenderStatus fullRun = RENDER_OK;//Whether density filtering was run to completion without aborting prematurely or triggering an error. + T passFilter = T(1) / T(Passes());//Original used an array, but every element in the array had the same value, so just use a single value here. + + T area = FinalRasW() * FinalRasH() / (m_PixelsPerUnitX * m_PixelsPerUnitY);//Need to use temps from field if ever implemented. + m_K1 = (Brightness() * T(268.0) * passFilter) / 256; + + //When doing an interactive render, force output early on in the render process, before all iterations are done. + //This presents a problem with the normal calculation of K2 since it relies on the quality value; it will scale the colors + //to be very dark. Correct it by pretending the number of iters done is the exact quality desired and then scale according to that. + if (forceOutput) + { + T quality = ((T)m_Stats.m_Iters / (T)FinalDimensions()) * (m_Scale * m_Scale); + m_K2 = (Supersample() * Supersample() * Passes()) / (area * quality * m_TemporalFilter->SumFilt()); + } + else + m_K2 = (Supersample() * Supersample() * Passes()) / (area * m_ScaledQuality * m_TemporalFilter->SumFilt()); + + if (filterAndAccumOnly || pass == 0) + ResetBuckets(false, true);//Only the histogram was reset above, now reset the density filtering buffer. + //t.Tic(); + + //Apply appropriate filter if iterating is complete. + if (filterAndAccumOnly || temporalSample >= TemporalSamples()) + { + fullRun = m_DensityFilter.get() ? GaussianDensityFilter() : LogScaleDensityFilter(); + } + else + { + //Apply requested filter for a forced output during interactive rendering. + if (m_DensityFilter.get() && m_InteractiveFilter == FILTER_DE) + fullRun = GaussianDensityFilter(); + else if (!m_DensityFilter.get() || m_InteractiveFilter == FILTER_LOG) + fullRun = LogScaleDensityFilter(); + } + + //Only update state if iterating and filtering finished completely (didn't arrive here via forceOutput). + if (fullRun == RENDER_OK && m_ProcessState == ITER_DONE && (Passes() == 1 || pass == Passes() - 1)) + m_ProcessState = FILTER_DONE; + + //Take special action if filtering exited prematurely. + if (fullRun != RENDER_OK) + { + if (Passes() > 1)//Since all filtering is cummulative with passes > 1, must restart the entire process. + { + m_ProcessState = NONE; + m_ProcessAction = FULL_RENDER; + } + + ResetBuckets(false, true);//Reset the accumulator, come back and try again on the next call. + success = fullRun; + goto Finish; + } + + if (m_Abort) + { + success = RENDER_ABORT; + goto Finish; + } + //t.Toc("Density estimation filtering time: ", true); + } + + //Only increment pass if the temporal samples loop has been completed, which could have been done incrementally. + //Also skip if rendering jumped straight here after completely finishing beforehand. + if (!filterAndAccumOnly && temporalSample >= TemporalSamples())//This may not work if filtering was prematurely exited. + pass++; + + if (!filterAndAccumOnly) + m_LastPass = pass; + + if (subBatchCountOverride > 0)//Don't keep going through this loop if only doing an incremental render. + break; + }//Passes. + +AccumOnly: + if (m_ProcessState == FILTER_DONE || forceOutput) + { + if (m_Callback && !m_Callback->ProgressFunc(m_Ember, m_ProgressParameter, 0, 2, 0))//Original only allowed stages 0 and 1. Add 2 to mean final accum. + { + Abort(); + success = RENDER_ABORT; + goto Finish; + } + + //Make sure a filter has been created. + CreateSpatialFilter(newFilterAlloc); + + if (AccumulatorToFinalImage(finalImage, finalOffset) == RENDER_OK) + { + m_Stats.m_RenderSeconds = m_RenderTimer.Toc() / 1000.0;//Record total time from the very beginning to the very end, including all intermediate calls. + + //Even though the ember changes throughought the inner loops because of interpolation, it's probably ok to assign here. + //This will hold the last interpolated value (even though spatial and temporal filters were created based off of one of the first interpolated values). + m_LastEmber = m_Ember; + + if (m_ProcessState == FILTER_DONE)//Only update state if gotten here legitimately, and not via forceOutput. + { + m_ProcessState = ACCUM_DONE; + + if (m_Callback && !m_Callback->ProgressFunc(m_Ember, m_ProgressParameter, 100.0, 2, 0))//Finished. + { + Abort(); + success = RENDER_ABORT; + goto Finish; + } + } + } + else + { + success = RENDER_ERROR; + } + } +Finish: + if (success == RENDER_OK && m_Abort)//If everything ran ok, but they've aborted, record abort as the status. + success = RENDER_ABORT; + else if (success != RENDER_OK)//Regardless of abort status, if there was an error, leave that as the return status. + Abort(); + + LeaveRender(); + m_InRender = false; + return success; +} + +/// +/// Return EmberImageComments object with image comments filled out. +/// Run() should have completed before calling this. +/// +/// The depth of the edit tags +/// If true use integers instead of floating point numbers when embedding a non-hex formatted palette, else use floating point numbers. +/// If true, embed a hexadecimal palette instead of Xml Color tags, else use Xml color tags. +/// The EmberImageComments object with image comments filled out +template +EmberImageComments Renderer::ImageComments(unsigned int printEditDepth, bool intPalette, bool hexPalette) +{ + ostringstream ss; + EmberImageComments comments; + + ss.imbue(std::locale("")); + comments.m_Genome = m_EmberToXml.ToString(m_Ember, "", printEditDepth, false, intPalette, hexPalette); + ss << ((double)m_Stats.m_Badvals / (double)m_Stats.m_Iters);//Percentage of bad values to iters. + comments.m_Badvals = ss.str(); ss.str(""); + ss << m_Stats.m_Iters; + comments.m_NumIters = ss.str(); ss.str("");//Total iters. + ss << m_Stats.m_RenderSeconds; + comments.m_Runtime = ss.str();//Number of seconds for iterating, accumulating and filtering. + + return comments; +} + +/// +/// Return the amount of memory needed to render the current ember. +/// Optionally include the memory needed for the final output image. +/// +/// If true include the memory needed for the final output image, else don't. +/// The memory required to render the current ember +template +unsigned __int64 Renderer::MemoryRequired(bool includeFinal) +{ + bool newFilterAlloc = false; + + CreateSpatialFilter(newFilterAlloc); + CreateTemporalFilter(newFilterAlloc); + ComputeBounds(); + + //Because ComputeBounds() was called, this includes gutter. + unsigned __int64 histSize = SuperSize() * sizeof(glm::detail::tvec4); + + return (histSize * 2) + (includeFinal ? FinalBufferSize() : 0);//Multiply hist by 2 to account for the density filtering buffer which is the same size as the histogram. +} + +/// +/// Virtual functions to be overriden in derived renderers that use the GPU. +/// + +/// +/// The amount of RAM available to render with. +/// +/// An unsigned 64-bit integer specifying how much memory is available +template +unsigned __int64 Renderer::MemoryAvailable() +{ + unsigned __int64 memAvailable = 0; + +#ifdef WIN32 + + MEMORYSTATUSEX stat; + + stat.dwLength = sizeof(stat); + GlobalMemoryStatusEx(&stat); + memAvailable = stat.ullTotalPhys; + +#elif defined(_SC_PHYS_PAGES) && defined(_SC_PAGESIZE) + + memAvailable = sysconf(_SC_PHYS_PAGES) * sysconf(_SC_PAGESIZE); + +#elif defined __APPLE__ + + #ifdef __LP64__ + long physmem; + size_t len = sizeof(physmem); + static int mib[2] = { CTL_HW, HW_MEMSIZE }; + #else + unsigned int physmem; + size_t len = sizeof(physmem); + static int mib[2] = { CTL_HW, HW_PHYSMEM }; + #endif + + if (sysctl(mib, 2, &physmem, &len, NULL, 0) == 0 && len == sizeof(physmem)) + { + memAvailable = physmem; + } + else + { + cout << "Warning: unable to determine physical memory." << endl; + memAvailable = 4e9; + } + +#else + + cout << "Warning: unable to determine physical memory." << endl; + memAvailable = 4e9; + +#endif + + return memAvailable; +} + +/// +/// Stop rendering, ensure all locks are exited and reset the rendering state. +/// +template +void Renderer::Reset() +{ + Abort(); + EnterRender(); + EnterFinalAccum(); + LeaveFinalAccum(); + LeaveRender(); + m_ProcessState = NONE; + m_ProcessAction = FULL_RENDER; +} + +/// +/// Get a status indicating whether this renderer is ok. +/// Return true for this class, derived classes will inspect GPU hardware +/// to determine if they are ok. +/// +/// Always true for this class +template +bool Renderer::Ok() const +{ + return true; +} + +/// +/// Create the density filter if the current filter parameters differ +/// from the last density filter created. +/// The filter will be deleted if the max DE radius is 0, in which case regular +/// log scale filtering will be used. +/// +/// True if a new filter instance was created, else false. +/// True if the filter is not NULL (whether a new one was created or not) or if max rad is 0, else false. +template +bool Renderer::CreateDEFilter(bool& newAlloc) +{ + //If they wanted DE, create it if needed, else clear the last DE filter which means we'll do regular log filtering after iters are done. + newAlloc = false; + + if (m_Ember.m_MaxRadDE > 0) + { + //Use intelligent testing so it isn't created every time a new ember is passed in. + if ((m_DensityFilter.get() == NULL) || + (m_Ember.m_MinRadDE != m_DensityFilter->MinRad()) || + (m_Ember.m_MaxRadDE != m_DensityFilter->MaxRad()) || + (m_Ember.m_CurveDE != m_DensityFilter->Curve()) || + (m_Ember.m_Supersample != m_LastEmber.m_Supersample)) + { + m_DensityFilter = auto_ptr>(new DensityFilter(m_Ember.m_MinRadDE, m_Ember.m_MaxRadDE, m_Ember.m_CurveDE, m_Ember.m_Supersample)); + newAlloc = true; + } + + if (newAlloc) + { + if (!m_DensityFilter.get()) { return false; }//Did object creation succeed? + if (!m_DensityFilter->Create()) { return false; }//Object creation succeeded, did filter creation succeed? + //cout << m_DensityFilter->ToString() << endl; + } + else + if (!m_DensityFilter->Valid()) { return false; }//Previously created, are values ok? + } + else + { + m_DensityFilter.reset();//They want to do log filtering. Return true because even though the filter is being deleted, nothing went wrong. + } + + return true; +} + +/// +/// Create the spatial filter if the current filter parameters differ +/// from the last spatial filter created. +/// +/// True if a new filter instance was created, else false. +/// True if the filter is not NULL (whether a new one was created or not), else false. +template +bool Renderer::CreateSpatialFilter(bool& newAlloc) +{ + newAlloc = false; + + //Use intelligent testing so it isn't created every time a new ember is passed in. + if ((m_SpatialFilter.get() == NULL) || + (m_Ember.m_SpatialFilterType != m_SpatialFilter->FilterType()) || + (m_Ember.m_SpatialFilterRadius != m_SpatialFilter->FilterRadius()) || + (m_Ember.m_Supersample != m_LastEmber.m_Supersample) || + (m_PixelAspectRatio != m_SpatialFilter->PixelAspectRatio())) + { + m_SpatialFilter = auto_ptr>( + SpatialFilterCreator::Create(m_Ember.m_SpatialFilterType, m_Ember.m_SpatialFilterRadius, m_Ember.m_Supersample, m_PixelAspectRatio)); + newAlloc = true; + } + + return m_SpatialFilter.get() != NULL; +} + +/// +/// Get the sub batch size. This is the size of of the chunks that the iteration +/// trajectory will be broken up into. +/// Default: 10k. +/// +/// The sub batch size +template +unsigned int Renderer::SubBatchSize() const { return m_SubBatchSize; } + +/// +/// Set the sub batch size. This is the size of of the chunks that the iteration +/// trajectory will be broken up into. +/// Reset the rendering process. +/// +/// The sub batch size to set +template +void Renderer::SubBatchSize(unsigned int sbs) +{ + ChangeVal([&] { m_SubBatchSize = sbs; }, FULL_RENDER); +} + +/// +/// Get the number of channels per pixel in the output image. 3 for RGB images +/// like Bitmap and Jpeg, 4 for Png. +/// Default is 3. +/// +/// The number of channels per pixel in the output image +template unsigned int Renderer::NumChannels() const { return m_NumChannels; } + +/// +/// Set the number of channels per pixel in the output image. 3 for RGB images +/// like Bitmap and Jpeg, 4 for Png. +/// Default is 3. +/// Set the render state to ACCUM_ONLY. +/// +/// The number of channels per pixel in the output image +template +void Renderer::NumChannels(unsigned int numChannels) +{ + ChangeVal([&] { m_NumChannels = numChannels; }, ACCUM_ONLY); +} + +/// +/// Get the renderer type enum. +/// CPU_RENDERER for this class, other values for derived classes. +/// +/// CPU_RENDERER +template +eRendererType Renderer::RendererType() const { return CPU_RENDERER; } + +/// +/// Get the number of threads used when rendering. +/// Default: use all avaliable cores. +/// +/// The number of threads used when rendering +template +unsigned int Renderer::ThreadCount() const { return m_ThreadsToUse; } + +/// +/// Set the number of threads to use when rendering. +/// This will also reset the vector of random contexts to be the same size +/// as the number of specified threads. +/// Since this is where they get set up, the caller can optionally pass in +/// a seed string, however it's only used if threads is 1. +/// This is useful for debugging since it will run the same point trajectory +/// every time. +/// Reset the rendering process. +/// +/// The number of threads to use +/// The seed string to use if threads is 1. Default: NULL. +template +void Renderer::ThreadCount(unsigned int threads, const char* seedString) +{ + ChangeVal([&] + { + Timing t; + unsigned int i, size; + const unsigned int isaacSize = 1 << ISAAC_SIZE; + ISAAC_INT seeds[isaacSize]; + m_ThreadsToUse = threads > 0 ? threads : 1; + m_Rand.clear(); + m_SubBatch.clear(); + m_SubBatch.resize(m_ThreadsToUse); + m_BadVals.resize(m_ThreadsToUse); + + if (seedString) + { + memset(seeds, 0, isaacSize * sizeof(ISAAC_INT)); + memcpy((char*)seeds, seedString, min(strlen(seedString), isaacSize * sizeof(ISAAC_INT))); + } + + //This is critical for multithreading, otherwise the threads all happen + //too close to each other in time, resulting in bad randomization. + while (m_Rand.size() < m_ThreadsToUse) + { + size = (unsigned int)m_Rand.size(); + + if (seedString) + { + unsigned int newSize = size + 5; + + QTIsaac isaac(newSize, newSize * newSize, newSize * newSize * newSize, seeds); + + m_Rand.push_back(isaac); + + for (i = 0; i < (isaacSize * sizeof(ISAAC_INT)); i++) + ((unsigned char*)seeds)[i]++; + } + else + { + for (i = 0; i < isaacSize; i++) + { + t.Toc(); + seeds[i] = (ISAAC_INT)(t.EndTime() * i) + (size + 1); + } + + t.Toc(); + ISAAC_INT r = (size * i) + i + (ISAAC_INT)t.EndTime(); + QTIsaac isaac(r, r * 2, r * 3, seeds); + + m_Rand.push_back(isaac); + } + } + }, FULL_RENDER); +} + +/// +/// Set the callback object. +/// +/// The callback object to set +template +void Renderer::Callback(RenderCallback* callback) +{ + m_Callback = callback; +} + +/// +/// Virtual functions to be overriden in derived renderers that use the GPU, but not accessed outside. +/// + +/// +/// Make the final palette used for iteration. +/// +/// The color scalar to multiply the ember's palette by +template +void Renderer::MakeDmap(T colorScalar) +{ + m_Ember.m_Palette.MakeDmap(m_Dmap, colorScalar); +} + +/// +/// Allocate various buffers if the image dimensions, thread count, or sub batch size +/// has changed. +/// +/// True if success, else false +template +bool Renderer::Alloc() +{ + bool b = true; + bool lock = + (m_SuperSize != m_HistBuckets.size()) || + (m_SuperSize != m_AccumulatorBuckets.size()) || + (m_ThreadsToUse != m_Samples.size()) || + (m_Samples[0].size() != m_SubBatchSize); + + if (lock) + EnterResize(); + + if (m_SuperSize != m_HistBuckets.size()) + { + m_HistBuckets.resize(m_SuperSize); + + if (m_ReclaimOnResize) + m_HistBuckets.shrink_to_fit(); + + b &= (m_HistBuckets.size() == m_SuperSize); + } + + if (m_SuperSize != m_AccumulatorBuckets.size()) + { + m_AccumulatorBuckets.resize(m_SuperSize); + + if (m_ReclaimOnResize) + m_AccumulatorBuckets.shrink_to_fit(); + + b &= (m_AccumulatorBuckets.size() == m_SuperSize); + } + + if (m_ThreadsToUse != m_Samples.size()) + { + m_Samples.resize(m_ThreadsToUse); + + if (m_ReclaimOnResize) + m_Samples.shrink_to_fit(); + + b &= (m_Samples.size() == m_ThreadsToUse); + } + + for (unsigned int i = 0; i < m_Samples.size(); i++) + { + if (m_Samples[i].size() != m_SubBatchSize) + { + m_Samples[i].resize(m_SubBatchSize); + + if (m_ReclaimOnResize) + m_Samples[i].shrink_to_fit(); + + b &= (m_Samples[i].size() == m_SubBatchSize); + } + } + + if (lock) + LeaveResize(); + + return b; +} + +/// +/// Clear histogram and/or density filtering buffers to all zeroes. +/// +/// Clear histogram if true, else don't. +/// Clear density filtering buffer if true, else don't. +/// True if anything was cleared, else false. +template +bool Renderer::ResetBuckets(bool resetHist, bool resetAccum) +{ + //parallel_invoke( + //[&] + //{ + if (resetHist && !m_HistBuckets.empty()) + memset((void*)m_HistBuckets.data(), 0, m_HistBuckets.size() * sizeof(m_HistBuckets[0])); + //}, + //[&] + //{ + if (resetAccum && !m_AccumulatorBuckets.empty()) + memset(m_AccumulatorBuckets.data(), 0, m_AccumulatorBuckets.size() * sizeof(m_AccumulatorBuckets[0])); + //}); + + return resetHist || resetAccum; +} + +/// +/// Perform log scale density filtering. +/// Base case for simple log scale density estimation as discussed (mostly) in the paper +/// in section 4, p. 6-9. +/// +/// True if not prematurely aborted, else false. +template +eRenderStatus Renderer::LogScaleDensityFilter() +{ + unsigned int startRow = 0; + unsigned int endRow = m_SuperRasH; + unsigned int startCol = 0; + unsigned int endCol = m_SuperRasW; + //Timing t(4); + + //Original didn't parallelize this, doing so gives a 50-75% speedup. + //If there is only one pass, the value can be directly assigned, which is quicker than summing. + if (Passes() == 1) + { + parallel_for(startRow, endRow, [&] (unsigned int j) + { + unsigned int row = j * m_SuperRasW; + //__m128 logm128;//Figure out SSE at some point. + //__m128 bucketm128; + //__m128 scaledBucket128; + + for (unsigned int i = startCol; (i < endCol) && !m_Abort; i++) + { + unsigned int index = row + i; + + //Check for visibility first before doing anything else to avoid all possible unnecessary calculations. + if (m_HistBuckets[index].a != 0) + { + T logScale = (m_K1 * log(1 + m_HistBuckets[index].a * m_K2)) / m_HistBuckets[index].a; + + //Original did a temporary assignment, then *= logScale, then passed the result to bump_no_overflow(). + //Combine here into one operation for a slight speedup. + m_AccumulatorBuckets[index] = (m_HistBuckets[index] * (bucketT)logScale); + } + } + }); + } + else//Passes > 1, so sum. + { + parallel_for(startRow, endRow, [&] (unsigned int j) + { + unsigned int row = j * m_SuperRasW; + + for (unsigned int i = startCol; (i < endCol) && !m_Abort; i++) + { + unsigned int index = row + i; + + //Check for visibility first before doing anything else to avoid all possible unnecessary calculations. + if (m_HistBuckets[index].a != 0) + { + //Figure out SSE at some point. + //__declspec(align(16)) + T logScale = (m_K1 * log(1 + m_HistBuckets[index].a * m_K2)) / m_HistBuckets[index].a; + //logm128 = _mm_load1_ps(&logScale); + //bucketm128 = _mm_load_ps(m_HistBuckets[index].Channels); + //scaledBucket128 = _mm_mul_ps(logm128, bucketm128); + + m_AccumulatorBuckets[index] += (m_HistBuckets[index] * bucketT(logScale)); + } + } + }); + } + //t.Toc(__FUNCTION__); + + return m_Abort ? RENDER_ABORT : RENDER_OK; +} + +/// +/// Perform the more advanced Gaussian density filter. +/// More advanced density estimation filtering given less mention in the paper, but used +/// much more in practice as it gives the best results. +/// Section 8, p. 11-13. +/// +/// True if not prematurely aborted, else false. +template +eRenderStatus Renderer::GaussianDensityFilter() +{ + Timing totalTime, localTime; + int scf = !(Supersample() & 1); + unsigned int ss = Floor(Supersample() / T(2)); + T scfact = pow(Supersample() / (Supersample() + T(1.0)), T(2.0)); + + unsigned int threads = m_ThreadsToUse; + unsigned int startRow = Supersample() - 1; + unsigned int endRow = m_SuperRasH - (Supersample() - 1);//Original did + which is most likely wrong. + unsigned int startCol = Supersample() - 1; + unsigned int endCol = m_SuperRasW - (Supersample() - 1); + unsigned int chunkSize = (unsigned int)ceil(double(endRow - startRow) / double(threads)); + + //parallel_for scales very well, dividing the work almost perfectly among all processors. + parallel_for((unsigned int)0, threads, [&] (unsigned int threadIndex) + { + unsigned int pixelNumber = 0; + unsigned int localStartRow = min(startRow + (threadIndex * chunkSize), endRow - 1); + unsigned int localEndRow = min(localStartRow + chunkSize, endRow); + unsigned int pixelsThisThread = (localEndRow - localStartRow) * m_SuperRasW; + double lastPercent = 0; + glm::detail::tvec4 logScaleBucket; + + for (unsigned int j = localStartRow; (j < localEndRow) && !m_Abort; j++) + { + unsigned int bucketRowStart = j * m_SuperRasW;//Pull out of inner loop for optimization. + const glm::detail::tvec4* bucket; + const glm::detail::tvec4* buckets = m_HistBuckets.data(); + const T* filterCoefs = m_DensityFilter->Coefs(); + const T* filterWidths = m_DensityFilter->Widths(); + + for (unsigned int i = startCol; i < endCol; i++) + { + int ii, jj, arrFilterWidth; + unsigned int filterSelectInt, filterCoefIndex; + T filterSelect = 0; + bucket = buckets + bucketRowStart + i; + + //Don't do anything if there's no hits here. Must also put this first to avoid dividing by zero below. + if (bucket->a == 0) + continue; + + T cacheLog = (m_K1 * log(T(1.0) + bucket->a * m_K2)) / bucket->a;//Caching this calculation gives a 30% speedup. + + if (ss == 0) + { + filterSelect = bucket->a; + } + else + { + //The original contained a glaring flaw as it would run past the boundaries of the buffers + //when calculating the density for a box centered on the last row or column. + //Clamp here to not run over the edge. + int densityBoxLeftX = i - min(i, ss); + int densityBoxRightX = i + min(ss, m_SuperRasW - i - 1); + int densityBoxTopY = j - min(j, ss); + int densityBoxBottomY = j + min(ss, m_SuperRasH - j - 1); + + //Count density in ssxss area. + //Original went one col at a time, which is cache inefficient. Go one row at at time here for a slight speedup. + for (jj = densityBoxTopY; jj <= densityBoxBottomY; jj++) + for (ii = densityBoxLeftX; ii <= densityBoxRightX; ii++) + filterSelect += buckets[ii + (jj * m_SuperRasW)].a;//Original divided by 255 in every iteration. Omit here because colors are already in the range of [0..1]. + } + + //Scale if supersample > 1 for equal iters. + if (scf) + filterSelect *= scfact; + + if (filterSelect > m_DensityFilter->MaxFilteredCounts()) + filterSelectInt = m_DensityFilter->MaxFilterIndex(); + else if (filterSelect <= DE_THRESH) + filterSelectInt = (int)ceil(filterSelect) - 1; + else + filterSelectInt = (int)DE_THRESH + Floor(pow(filterSelect - DE_THRESH, m_DensityFilter->Curve())); + + //If the filter selected below the min specified clamp it to the min. + if (filterSelectInt > m_DensityFilter->MaxFilterIndex()) + filterSelectInt = m_DensityFilter->MaxFilterIndex(); + + //Only have to calculate the values for ~1/8 of the square. + filterCoefIndex = filterSelectInt * m_DensityFilter->KernelSize(); + arrFilterWidth = (int)ceil(filterWidths[filterSelectInt]) - 1; + + for (jj = 0; jj <= arrFilterWidth; jj++) + { + for (ii = 0; ii <= jj; ii++, filterCoefIndex++) + { + //Skip if coef is 0. + if (filterCoefs[filterCoefIndex] == 0) + continue; + + T logScale = filterCoefs[filterCoefIndex] * cacheLog; + + //Original first assigned the fields, then scaled them. Combine into a single step for a 1% optimization. + logScaleBucket = (*bucket * bucketT(logScale)); + + if (jj == 0 && ii == 0) + { + AddToAccum(logScaleBucket, i, ii, j, jj); + } + else if (ii == 0) + { + AddToAccum(logScaleBucket, i, 0, j, -jj); + AddToAccum(logScaleBucket, i, -jj, j, 0); + AddToAccum(logScaleBucket, i, jj, j, 0); + AddToAccum(logScaleBucket, i, 0, j, jj); + } + else if (jj == ii) + { + AddToAccum(logScaleBucket, i, -ii, j, -jj); + AddToAccum(logScaleBucket, i, ii, j, -jj); + AddToAccum(logScaleBucket, i, -ii, j, jj); + AddToAccum(logScaleBucket, i, ii, j, jj); + } + else + { + //Attempting to optimize cache access by putting these in order makes no difference, even on large images, but do it anyway. + AddToAccum(logScaleBucket, i, -ii, j, -jj); + AddToAccum(logScaleBucket, i, ii, j, -jj); + AddToAccum(logScaleBucket, i, -jj, j, -ii); + AddToAccum(logScaleBucket, i, jj, j, -ii); + AddToAccum(logScaleBucket, i, -jj, j, ii); + AddToAccum(logScaleBucket, i, jj, j, ii); + AddToAccum(logScaleBucket, i, -ii, j, jj); + AddToAccum(logScaleBucket, i, ii, j, jj); + } + } + } + } + + if (m_Callback && threadIndex == 0) + { + pixelNumber += m_SuperRasW; + double percent = (double(pixelNumber) / double(pixelsThisThread)) * 100.0; + double percentDiff = percent - lastPercent; + double toc = localTime.Toc(); + + if (percentDiff >= 10 || (toc > 1000 && percentDiff >= 1)) + { + double etaMs = ((100.0 - percent) / percent) * totalTime.Toc(); + + if (!m_Callback->ProgressFunc(m_Ember, m_ProgressParameter, percent, 1, etaMs)) + Abort(); + + lastPercent = percent; + localTime.Tic(); + } + } + } + }); + + if (m_Callback && !m_Abort) + m_Callback->ProgressFunc(m_Ember, m_ProgressParameter, 100.0, 1, 0); + + //totalTime.Toc(__FUNCTION__); + return m_Abort ? RENDER_ABORT : RENDER_OK; +} + +/// +/// Thin wrapper around AccumulatorToFinalImage(). +/// +/// The pixel vector to allocate and store the final image in +/// Offset in the buffer to store the pixels to +/// True if not prematurely aborted, else false. +template +eRenderStatus Renderer::AccumulatorToFinalImage(vector& pixels, size_t finalOffset) +{ + if (PrepFinalAccumVector(pixels)) + return AccumulatorToFinalImage(pixels.data(), finalOffset); + + return RENDER_ERROR; +} + +/// +/// Produce a final, visible image by clipping, gamma correcting and spatial filtering the color values +/// in the density filtering buffer and save to the passed in buffer. +/// +/// The pre-allocated pixel buffer to store the final image in +/// Offset in the buffer to store the pixels to. Default: 0. +/// True if not prematurely aborted, else false. +template +eRenderStatus Renderer::AccumulatorToFinalImage(unsigned char* pixels, size_t finalOffset) +{ + if (!pixels) + return RENDER_ERROR; + + EnterFinalAccum(); + //Timing t(4); + unsigned int filterWidth = m_SpatialFilter->FinalFilterWidth(); + T g, linRange, vibrancy; + Color background; + + pixels += finalOffset; + PrepFinalAccumVals(background, g, linRange, vibrancy); + + //If early clip, go through the entire accumulator and perform gamma correction first. + //The original does it this way as well and it's roughly 11 times faster to do it this way than inline below with each pixel. + if (EarlyClip()) + { + parallel_for((unsigned int)0, m_SuperRasH, [&] (unsigned int j) + { + unsigned int rowStart = j * m_SuperRasW;//Pull out of inner loop for optimization. + + for (unsigned int i = 0; i < m_SuperRasW && !m_Abort; i++) + { + GammaCorrection(m_AccumulatorBuckets[i + rowStart], background, g, linRange, vibrancy, true, false, &(m_AccumulatorBuckets[i + rowStart][0]));//Write back in place. + } + }); + } + + if (m_Abort) + { + LeaveFinalAccum(); + return RENDER_ABORT; + } + + //Note that abort is not checked here. The final accumulation must run to completion + //otherwise artifacts that resemble page tearing will occur in an interactive run. It's + //critical to never exit this loop prematurely. + //for (unsigned int j = 0; j < FinalRasH(); j++)//Keep around for debugging. + parallel_for((unsigned int)0, FinalRasH(), [&] (unsigned int j) + { + Color newBucket; + int pixelsRowStart = j * FinalRowSize();//Pull out of inner loop for optimization. + unsigned int y = m_DensityFilterOffset + (j * Supersample());//Start at the beginning row of each super sample block. + unsigned short* p16; + + for (unsigned int i = 0; i < FinalRasW(); i++, pixelsRowStart += PixelSize()) + { + unsigned int ii, jj; + unsigned int x = m_DensityFilterOffset + (i * Supersample());//Start at the beginning column of each super sample block. + newBucket.Clear(); + + //Original was iterating column-wise, which is slow. + //Here, iterate one row at a time, giving a 10% speed increase. + for (jj = 0; jj < filterWidth; jj++) + { + unsigned int filterKRowIndex = jj * filterWidth; + unsigned int accumRowIndex = (y + jj) * m_SuperRasW;//Pull out of inner loop for optimization. + + for (ii = 0; ii < filterWidth; ii++) + { + //Need to dereference the spatial filter pointer object to use the [] operator. Makes no speed difference. + bucketT k = bucketT((*m_SpatialFilter)[ii + filterKRowIndex]); + + newBucket += (m_AccumulatorBuckets[(x + ii) + accumRowIndex] * k); + } + } + + if (BytesPerChannel() == 2) + { + p16 = (unsigned short*)(pixels + pixelsRowStart); + + if (EarlyClip()) + { + p16[0] = (unsigned short)(Clamp(newBucket.r, 0, 255) * bucketT(256)); + p16[1] = (unsigned short)(Clamp(newBucket.g, 0, 255) * bucketT(256)); + p16[2] = (unsigned short)(Clamp(newBucket.b, 0, 255) * bucketT(256)); + + if (NumChannels() > 3) + { + if (Transparency()) + p16[3] = (unsigned char)(Clamp(newBucket.a, 0, 1) * bucketT(65535.0)); + else + p16[3] = 65535; + } + } + else + { + GammaCorrection(*(glm::detail::tvec4*)(&newBucket), background, g, linRange, vibrancy, NumChannels() > 3, true, p16); + } + } + else + { + if (EarlyClip()) + { + pixels[pixelsRowStart] = (unsigned char)Clamp(newBucket.r, 0, 255); + pixels[pixelsRowStart + 1] = (unsigned char)Clamp(newBucket.g, 0, 255); + pixels[pixelsRowStart + 2] = (unsigned char)Clamp(newBucket.b, 0, 255); + + if (NumChannels() > 3) + { + if (Transparency()) + pixels[pixelsRowStart + 3] = (unsigned char)(Clamp(newBucket.a, 0, 1) * bucketT(255.0)); + else + pixels[pixelsRowStart + 3] = 255; + } + } + else + { + GammaCorrection(*(glm::detail::tvec4*)(&newBucket), background, g, linRange, vibrancy, NumChannels() > 3, true, pixels + pixelsRowStart); + } + } + } + }); + + //Insert the palette into the image for debugging purposes. Only works with 8bpc. + if (m_InsertPalette && BytesPerChannel() == 1) + { + unsigned int i, j, ph = 100; + + if (ph >= FinalRasH()) + ph = FinalRasH(); + + for (j = 0; j < ph; j++) + { + for (i = 0; i < FinalRasW(); i++) + { + unsigned char* p = pixels + (NumChannels() * (i + j * FinalRasW())); + + p[0] = (unsigned char)(m_TempEmber.m_Palette[i * 256 / FinalRasW()][0] * WHITE);//The palette is [0..1], output image is [0..255]. + p[1] = (unsigned char)(m_TempEmber.m_Palette[i * 256 / FinalRasW()][1] * WHITE); + p[2] = (unsigned char)(m_TempEmber.m_Palette[i * 256 / FinalRasW()][2] * WHITE); + } + } + } + //t.Toc(__FUNCTION__); + + LeaveFinalAccum(); + return m_Abort ? RENDER_ABORT : RENDER_OK; +} + +//#define TG 1 +//#define NEWSUBBATCH 1 + +/// +/// Run the iteration algorithm for the specified number of iterations. +/// This is only called after all other setup has been done. +/// This function will be called multiple times for an interactive rendering, and +/// once for a straight through render. +/// The iteration is reset and fused in each thread after each sub batch is done +/// which by default is 10,000 iterations. +/// +/// The number of iterations to run +/// The pass this is running for +/// The temporal sample within the current pass this is running for +/// Rendering statistics +template +EmberStats Renderer::Iterate(unsigned __int64 iterCount, unsigned int pass, unsigned int temporalSample) +{ + //Timing t2(4); + unsigned int fuse = EarlyClip() ? 100 : 15;//EarlyClip was one way of detecting a later version of flam3, so it used 100 which is a better value. + unsigned __int64 totalItersPerThread = (unsigned __int64)ceil((double)iterCount / (double)m_ThreadsToUse); + double percent, etaMs; + EmberStats stats; + +#ifdef TG + unsigned int threadIndex; + + for (unsigned int i = 0; i < m_ThreadsToUse; i++) + { + threadIndex = i; + m_TaskGroup.run([&, threadIndex] () { +#else + parallel_for((unsigned int)0, m_ThreadsToUse, [&] (unsigned int threadIndex) + { +#endif + Timing t; + unsigned __int64 subBatchSize = (unsigned int)min(totalItersPerThread, (unsigned __int64)m_SubBatchSize); + + m_BadVals[threadIndex] = 0; + + //Sub batch iterations, loop 3. + for (m_SubBatch[threadIndex] = 0; (m_SubBatch[threadIndex] < totalItersPerThread) && !m_Abort; m_SubBatch[threadIndex] += subBatchSize) + { + //Must recalculate the number of iters to run on each sub batch because the last batch will most likely have less than m_SubBatchSize iters. + //For example, if 51,000 are requested, and the sbs is 10,000, it should run 5 sub batches of 10,000 iters, and one final sub batch of 1,000 iters. + subBatchSize = min(subBatchSize, totalItersPerThread - m_SubBatch[threadIndex]); + + //Use first as random point, the rest are iterated points. + //Note that this gets reset with a new random point for each subBatchSize iterations. + //This helps correct if iteration happens to be on a bad trajectory. + m_Samples[threadIndex][0].m_X = (T)m_Rand[threadIndex].Frand11(); + m_Samples[threadIndex][0].m_Y = (T)m_Rand[threadIndex].Frand11(); + m_Samples[threadIndex][0].m_Z = 0;//m_Ember.m_CamZPos;//Apo set this to 0, then made the user use special variations to kick it. It seems easier to just set it to zpos. + m_Samples[threadIndex][0].m_ColorX = (T)m_Rand[threadIndex].Frand01(); + + //Finally, iterate. + //t.Tic(); + //Iterating, loop 4. + m_BadVals[threadIndex] += (unsigned __int64)m_Iterator->Iterate(m_Ember, (unsigned int)subBatchSize, fuse, m_Samples[threadIndex].data(), m_Rand[threadIndex]); + //iterationTime += t.Toc(); + + if (m_LockAccum) + m_AccumCs.Enter(); + //t.Tic(); + //Map temp buffer samples into the histogram using the palette for color. + Accumulate(m_Samples[threadIndex].data(), (unsigned int)subBatchSize, &m_Dmap); + //accumulationTime += t.Toc(); + if (m_LockAccum) + m_AccumCs.Leave(); + + if (m_Callback && threadIndex == 0) + { + percent = 100.0 * + double + ( + double + ( + double + ( + double + ( + //Takes progress of current thread and multiplies by thread count. + //This assumes the threads progress at roughly the same speed. + double(m_LastIter + (m_SubBatch[threadIndex] * m_ThreadsToUse)) / double(ItersPerTemporalSample()) + ) + temporalSample + ) / (double)TemporalSamples() + ) + (double)pass + ) / (double)Passes(); + + double percentDiff = percent - m_LastIterPercent; + double toc = m_ProgressTimer.Toc(); + + if (percentDiff >= 10 || (toc > 1000 && percentDiff >= 1))//Call callback function if either 10% has passed, or one second (and 1%). + { + etaMs = ((100.0 - percent) / percent) * m_RenderTimer.Toc(); + + if (!m_Callback->ProgressFunc(m_Ember, m_ProgressParameter, percent, 0, etaMs)) + Abort(); + + m_LastIterPercent = percent; + m_ProgressTimer.Tic(); + } + } + } + }); +#ifdef TG + } + + m_TaskGroup.wait(); +#endif + + stats.m_Iters = std::accumulate(m_SubBatch.begin(), m_SubBatch.end(), 0ULL);//Sum of iter count of all threads. + stats.m_Badvals += std::accumulate(m_BadVals.begin(), m_BadVals.end(), 0ULL); + //t2.Toc(__FUNCTION__); + return stats; +} + +/// +/// Accessors for render properties. +/// + +/// +/// Get a copy of the vector of random contexts. +/// Useful for debugging because the returned vector can be used for future renders to +/// produce the exact same output. +/// +/// The vector of random contexts to assign +template +vector> Renderer::RandVec() { return m_Rand; }; + +/// +/// Set the vector of random contexts. +/// Assignment will only take place if the size of the vector matches +/// the number of threads used for rendering. +/// Reset the rendering process. +/// +/// The vector of random contexts to assign +/// True if the size of the vector matched the number of threads used for rendering, else false. +template +bool Renderer::RandVec(vector>& randVec) +{ + bool b = false; + + if (randVec.size() == ThreadCount()) + { + ChangeVal([&] + { + m_Rand = randVec; + b = true; + }, FULL_RENDER); + } + + return b; +}; + +/// +/// Get whether the histogram is locked during accumulation. +/// This is to prevent two threads from writing to the same histogram +/// bucket at once. +/// The current implementation matches flam3 and is very innefficient +/// to the point of negating any gains gotten from multi-threading. +/// Future workarounds may be tried in the future. +/// Default: false. +/// +/// True if the histogram is locked during accumulation, else false. +template +bool Renderer::LockAccum() const { return m_LockAccum; } + +/// +/// Set whether the histogram is locked during accumulation. +/// This is to prevent two threads from writing to the same histogram +/// bucket at once. +/// The current implementation matches flam3 and is very innefficient +/// to the point of negating any gains gotten from multi-threading. +/// Different workarounds may be tried in the future. +/// Reset the rendering process. +/// +/// True if the histogram should be locked when accumulating, else false +template +void Renderer::LockAccum(bool lockAccum) +{ + ChangeVal([&] { m_LockAccum = lockAccum; }, FULL_RENDER); +} + +/// +/// Get whether color clipping and gamma correction is done before +/// or after spatial filtering. +/// Default: false. +/// +/// True if early clip, else false. +template +bool Renderer::EarlyClip() const { return m_EarlyClip; } + +/// +/// Set whether color clipping and gamma correction is done before +/// or after spatial filtering. +/// Set the render state to FILTER_AND_ACCUM. +/// +/// True if early clip, else false. +template +void Renderer::EarlyClip(bool earlyClip) +{ + ChangeVal([&] { m_EarlyClip = earlyClip; }, FILTER_AND_ACCUM); +} + +/// +/// Get whether to insert the palette as a block of colors in the final output image. +/// This is useful for debugging palette issues. +/// Default: 1. +/// +/// True if inserting the palette, else false. +template +bool Renderer::InsertPalette() const { return m_InsertPalette; } + +/// +/// Set whether to insert the palette as a block of colors in the final output image. +/// This is useful for debugging palette issues. +/// Set the render state to ACCUM_ONLY. +/// +/// True if inserting the palette, else false. +template +void Renderer::InsertPalette(bool insertPalette) +{ + ChangeVal([&] { m_InsertPalette = insertPalette; }, ACCUM_ONLY); +} + +/// +/// Get whether to reclaim unused memory in the final output buffer +/// when a smaller size is requested than has been previously allocated. +/// Default: false. +/// +/// True if reclaim, else false. +template +bool Renderer::ReclaimOnResize() const { return m_ReclaimOnResize; } + +/// +/// Set whether to reclaim unused memory in the final output buffer +/// when a smaller size is requested than has been previously allocated. +/// Reset the rendering process. +/// +/// True if reclaim, else false. +template +void Renderer::ReclaimOnResize(bool reclaimOnResize) +{ + ChangeVal([&] { m_ReclaimOnResize = reclaimOnResize; }, FULL_RENDER); +} + +/// +/// Get whether to use transparency in the alpha channel. +/// This only applies when the number of channels is 4 and the output +/// image is Png. +/// Default: false. +/// +/// True if using transparency, else false. +template bool Renderer::Transparency() const { return m_Transparency; } + +/// +/// Set whether to use transparency in the alpha channel. +/// This only applies when the number of channels is 4 and the output +/// image is Png. +/// Set the render state to ACCUM_ONLY. +/// +/// True if using transparency, else false. +template +void Renderer::Transparency(bool transparency) +{ + ChangeVal([&] { m_Transparency = transparency; }, ACCUM_ONLY); +} + +/// +/// Get the bytes per channel of the output image. +/// The only acceptable values are 1 and 2, and 2 is only +/// used when the output is Png. +/// Default: 1. +/// +/// +template unsigned int Renderer::BytesPerChannel() const { return m_BytesPerChannel; } + +/// +/// Set the bytes per channel of the output image. +/// The only acceptable values are 1 and 2, and 2 is only +/// used when the output is Png. +/// Set the render state to ACCUM_ONLY. +/// +/// The bytes per channel. +template +void Renderer::BytesPerChannel(unsigned int bytesPerChannel) +{ + ChangeVal([&] + { + if (bytesPerChannel == 0 || bytesPerChannel > 2) + m_BytesPerChannel = 1; + else + m_BytesPerChannel = bytesPerChannel; + }, ACCUM_ONLY); +} + +/// +/// Get the pixel aspect ratio of the output image. +/// Default: 1. +/// +/// The pixel aspect ratio. +template T Renderer::PixelAspectRatio() const { return m_PixelAspectRatio; } + +/// +/// Set the pixel aspect ratio of the output image. +/// Reset the rendering process. +/// +/// The pixel aspect ratio. +template +void Renderer::PixelAspectRatio(T pixelAspectRatio) +{ + ChangeVal([&] { m_PixelAspectRatio = pixelAspectRatio; }, FULL_RENDER); +} + +/// +/// Get the type of filter to use for preview renders during interactive rendering. +/// Using basic log scaling is quicker, but doesn't provide any bluring. +/// Full DE is much slower, but provides a more realistic preview of what the final image +/// will look like. +/// Default: FILTER_LOG. +/// +/// The type of filter to use +template eInteractiveFilter Renderer::InteractiveFilter() const { return m_InteractiveFilter; } + +/// +/// Set the type of filter to use for preview renders during interactive rendering. +/// Using basic log scaling is quicker, but doesn't provide any bluring. +/// Full DE is much slower, but provides a more realistic preview of what the final image +/// will look like. +/// Reset the rendering process. +/// +/// The filter. +template +void Renderer::InteractiveFilter(eInteractiveFilter filter) +{ + ChangeVal([&] { m_InteractiveFilter = filter; }, FULL_RENDER); +} + +/// +/// Non-virtual functions that might be needed by a derived class. +/// + +/// +/// Prepare various values needed for producing a final output image. +/// +/// The computed background value, which may differ from the background member +/// The computed gamma +/// The computed linear range +/// The computed vibrancy +template +void Renderer::PrepFinalAccumVals(Color& background, T& g, T& linRange, T& vibrancy) +{ + //If they are doing incremental rendering, they can get here without doing a full temporal + //sample, which means the values will be zero. + vibrancy = m_Vibrancy == 0 ? m_Ember.m_Vibrancy : m_Vibrancy; + unsigned int vibGamCount = m_VibGamCount == 0 ? 1 : m_VibGamCount; + T gamma = m_Gamma == 0 ? m_Ember.m_Gamma : m_Gamma; + g = T(1.0) / ClampGte(gamma / vibGamCount, T(0.01));//Ensure a divide by zero doesn't occur. + linRange = GammaThresh(); + vibrancy /= vibGamCount; + + background.r = (IsNearZero(m_Background.r) ? m_Ember.m_Background.r : m_Background.r) / (vibGamCount / T(256.0));//Background is [0, 1]. + background.g = (IsNearZero(m_Background.g) ? m_Ember.m_Background.g : m_Background.g) / (vibGamCount / T(256.0)); + background.b = (IsNearZero(m_Background.b) ? m_Ember.m_Background.b : m_Background.b) / (vibGamCount / T(256.0)); +} + +/// +/// Miscellaneous functions used only in this class. +/// + +/// +/// Accumulate the samples to the histogram. +/// To be called after a sub batch is finished iterating. +/// +/// The samples to accumulate +/// The number of samples +/// The palette to use +template +void Renderer::Accumulate(Point* samples, unsigned int sampleCount, const Palette* palette) +{ + unsigned int histIndex, intColorIndex, histSize = (unsigned int)m_HistBuckets.size(); + bucketT colorIndex, colorIndexFrac; + const glm::detail::tvec4* dmap = &(palette->m_Entries[0]); + + //It's critical to understand what's going on here as it's one of the most important parts of the algorithm. + //A color value gets retrieved from the palette and + //its RGB values are added to the existing RGB values in the histogram bucket. + //Alpha is always 1 in the palettes, so that serves as the hit count. + //This differs from the original since redundantly adding both an alpha component and a hit count is omitted. + //This will eventually leave us with large values for pixels with many hits, which will be log scaled down later. + //Original used a function called bump_no_overflow(). Just do a straight add because the type will always be float or double. + //Doing so gives a 25% speed increase. + //Splitting these conditionals into separate loops makes no speed difference. + for (unsigned int i = 0; i < sampleCount && !m_Abort; i++) + { + if (Rotate() != 0) + { + T p00 = samples[i].m_X - CenterX(); + T p11 = samples[i].m_Y - CenterY(); + + samples[i].m_X = (p00 * m_RotMat.A()) + (p11 * m_RotMat.B()) + CenterX(); + samples[i].m_Y = (p00 * m_RotMat.D()) + (p11 * m_RotMat.E()) + CenterY(); + } + + //Checking this first before converting gives better performance than converting and checking a single value, which the original did. + //Second, an interesting optimization observation is that when keeping the bounds vars within m_CarToRas and calling its InBounds() member function, + //rather than here as members, about a 7% speedup is achieved. This is possibly due to the fact that data from m_CarToRas is accessed + //right after the call to Convert(), so some caching efficiencies get realized. + if (m_CarToRas.InBounds(samples[i])) + { + if (samples[i].m_VizAdjusted != 0) + { + m_CarToRas.Convert(samples[i], histIndex); + + //There is a very slim chance that a point will be right on the border and will technically be in bounds, passing the InBounds() test, + //but ends up being mapped to a histogram bucket that is out of bounds due to roundoff error. Perform one final check before proceeding. + //This will result in a few points at the very edges getting discarded, but prevents a crash and doesn't seem to make a speed difference. + if (histIndex < histSize) + { + //Linear is a linear scale for when the color index is not a whole number, which is most of the time. + //It uses a portion of the value of the index, and the remainder of the next index. + //Example: index = 25.7 + //Fraction = 0.7 + //Color = (dmap[25] * 0.3) + (dmap[26] * 0.7) + //Use overloaded addition and multiplication operators in vec4 to perform the accumulation. + if (PaletteMode() == PALETTE_LINEAR) + { + colorIndex = (bucketT)samples[i].m_ColorX * COLORMAP_LENGTH; + intColorIndex = (unsigned int)colorIndex; + + if (intColorIndex < 0) + { + intColorIndex = 0; + colorIndexFrac = 0; + } + else if (intColorIndex >= COLORMAP_LENGTH_MINUS_1) + { + intColorIndex = COLORMAP_LENGTH_MINUS_1 - 1; + colorIndexFrac = 1; + } + else + { + colorIndexFrac = colorIndex - (bucketT)intColorIndex;//Interpolate between intColorIndex and intColorIndex + 1. + } + + if (samples[i].m_VizAdjusted == 1) + m_HistBuckets[histIndex] += ((dmap[intColorIndex] * (1 - colorIndexFrac)) + (dmap[intColorIndex + 1] * colorIndexFrac)); + else + m_HistBuckets[histIndex] += (((dmap[intColorIndex] * (1 - colorIndexFrac)) + (dmap[intColorIndex + 1] * colorIndexFrac)) * (bucketT)samples[i].m_VizAdjusted); + } + else if (PaletteMode() == PALETTE_STEP) + { + intColorIndex = Clamp((unsigned int)(samples[i].m_ColorX * COLORMAP_LENGTH), 0, COLORMAP_LENGTH_MINUS_1); + + if (samples[i].m_VizAdjusted == 1) + m_HistBuckets[histIndex] += dmap[intColorIndex]; + else + m_HistBuckets[histIndex] += (dmap[intColorIndex] * (bucketT)samples[i].m_VizAdjusted); + } + } + } + } + } +} + +/// +/// Add a value to the density filtering buffer with a bounds check. +/// +/// The bucket being filtered +/// The column of the bucket +/// The offset to add to the column +/// The row of the bucket +/// The offset to add to the row +template +void Renderer::AddToAccum(const glm::detail::tvec4& bucket, int i, int ii, int j, int jj) +{ + if (j + jj >= 0 && j + jj < (int)m_SuperRasH && i + ii >= 0 && i + ii < (int)m_SuperRasW) + m_AccumulatorBuckets[(i + ii) + ((j + jj) * m_SuperRasW)] += bucket; +} + +/// +/// Clip and gamma correct a pixel. +/// Because this code is used in both early and late clipping, a few extra arguments are passed +/// to specify what actions to take. Coupled with an additional template argument, this allows +/// using one function to perform all color clipping, gamma correction and final accumulation. +/// Template argument accumT is expected to match T for the case of early clipping, unsigned char for late clip for +/// images with one byte per channel and unsigned short for images with two bytes per channel. +/// +/// The pixel to correct +/// The background color +/// The gamma to use +/// The linear range to use +/// The vibrancy to use +/// True if either early clip, or late clip with 4 channel output, else false. +/// True if late clip, else false. +/// The storage space for the corrected values to be written to +template +template +void Renderer::GammaCorrection(glm::detail::tvec4& bucket, Color& background, T g, T linRange, T vibrancy, bool doAlpha, bool scale, accumT* correctedChannels) +{ + T alpha, ls, a; + bucketT newRgb[3];//Would normally use a Color, but don't want to call a needless constructor every time this function is called, which is once per pixel. + static T scaleVal = (numeric_limits::max() + 1) / T(256.0); + + if (bucket.a <= 0) + { + alpha = 0; + ls = 0; + } + else + { + alpha = Palette::CalcAlpha(bucket.a, g, linRange); + ls = vibrancy * T(255) * alpha / bucket.a; + ClampRef(alpha, 0, 1); + } + + Palette::CalcNewRgb(&bucket[0], ls, HighlightPower(), newRgb); + + for (unsigned int rgbi = 0; rgbi < 3; rgbi++) + { + a = newRgb[rgbi] + ((T(1.0) - vibrancy) * T(255) * pow(T(bucket[rgbi]), g)); + + if (NumChannels() <= 3 || !Transparency()) + { + a += ((T(1.0) - alpha) * background[rgbi]); + } + else + { + if (alpha > 0) + a /= alpha; + else + a = 0; + } + + if (!scale) + correctedChannels[rgbi] = (accumT)Clamp(a, 0, 255);//Early clip, just assign directly. + else + correctedChannels[rgbi] = (accumT)(Clamp(a, 0, 255) * scaleVal);//Final accum, multiply by 1 for 8 bpc, or 256 for 16 bpc. + } + + if (doAlpha) + { + if (!scale) + correctedChannels[3] = (accumT)alpha;//Early clip, just assign alpha directly. + else if (Transparency()) + correctedChannels[3] = (accumT)(alpha * numeric_limits::max());//Final accum, 4 channels, using transparency. Scale alpha from 0-1 to 0-255 for 8 bpc or 0-65535 for 16 bpc. + else + correctedChannels[3] = numeric_limits::max();//Final accum, 4 channels, but not using transparency. 255 for 8 bpc, 65535 for 16 bpc. + } +} + +/// +/// Set the m_Iterator member to point to the appropriate +/// iterator based on whether the ember currently being rendered +/// contains xaos. +/// After assigning, initialize the xform selection buffer. +/// +/// True if assignment and distribution initialization succeeded, else false. +template +bool Renderer::AssignIterator() +{ + //Setup iterator and distributions. + //Both iterator types were setup in the constructor (add more in the future if needed). + //So simply assign the pointer to the correct type and re-initialize its distributions + //based on the current ember. + if (XaosPresent()) + m_Iterator = m_XaosIterator.get(); + else + m_Iterator = m_StandardIterator.get(); + + //Timing t; + return m_Iterator->InitDistributions(m_Ember); + //t.Toc("Distrib creation"); +} + +/// +/// Threading control. +/// + +template void Renderer::EnterRender() { m_RenderingCs.Enter(); } +template void Renderer::LeaveRender() { m_RenderingCs.Leave(); } + +template void Renderer::EnterFinalAccum() { m_FinalAccumCs.Enter(); m_InFinalAccum = true; } +template void Renderer::LeaveFinalAccum() { m_FinalAccumCs.Leave(); m_InFinalAccum = false; } + +template void Renderer::EnterResize() { m_ResizeCs.Enter(); } +template void Renderer::LeaveResize() { m_ResizeCs.Leave(); } + +template void Renderer::Abort() { m_Abort = true; } +template bool Renderer::Aborted() { return m_Abort; } + +template bool Renderer::InRender() { return m_InRender; } +template bool Renderer::InFinalAccum() { return m_InFinalAccum; } + +/// +/// Renderer properties, getters only. +/// + +template unsigned int Renderer::SuperRasW() const { return m_SuperRasW; } +template unsigned int Renderer::SuperRasH() const { return m_SuperRasH; } +template unsigned int Renderer::SuperSize() const { return m_SuperSize; } +template unsigned int Renderer::FinalBufferSize() const { return FinalRowSize() * FinalRasH(); } +template unsigned int Renderer::FinalRowSize() const { return FinalRasW() * PixelSize(); } +template unsigned int Renderer::FinalDimensions() const { return FinalRasW() * FinalRasH(); } +template unsigned int Renderer::PixelSize() const { return NumChannels() * BytesPerChannel(); } +template unsigned int Renderer::GutterWidth() const { return m_GutterWidth; } +template unsigned int Renderer::DensityFilterOffset() const { return m_DensityFilterOffset; } +template double Renderer::ScaledQuality() const { return m_ScaledQuality; } +template T Renderer::Scale() const { return m_Scale; } +template T Renderer::PixelsPerUnitX() const { return m_PixelsPerUnitX; } +template T Renderer::PixelsPerUnitY() const { return m_PixelsPerUnitY; } +template double Renderer::LowerLeftX(bool gutter) const { return gutter ? m_CarToRas.CarLlX() : m_LowerLeftX; } +template double Renderer::LowerLeftY(bool gutter) const { return gutter ? m_CarToRas.CarLlY() : m_LowerLeftY; } +template double Renderer::UpperRightX(bool gutter) const { return gutter ? m_CarToRas.CarUrX() : m_UpperRightX; } +template double Renderer::UpperRightY(bool gutter) const { return gutter ? m_CarToRas.CarUrY() : m_UpperRightY; } +template T Renderer::K1() const { return m_K1; } +template T Renderer::K2() const { return m_K2; } +template unsigned __int64 Renderer::TotalIterCount() const { return (unsigned __int64)((unsigned __int64)Round(m_ScaledQuality) * (unsigned __int64)FinalRasW() * (unsigned __int64)FinalRasH()); }//Use Round() because there can be some roundoff error when interpolating. +template unsigned __int64 Renderer::ItersPerTemporalSample() const { return (unsigned __int64)ceil(double(TotalIterCount()) / double(Passes() * TemporalSamples())); } +template eProcessState Renderer::ProcessState() const { return m_ProcessState; } +template eProcessAction Renderer::ProcessAction() const { return m_ProcessAction; } +template EmberStats Renderer::Stats() const { return m_Stats; } +template const CarToRas* Renderer::CoordMap() const { return &m_CarToRas; } +template glm::detail::tvec4* Renderer::HistBuckets() { return m_HistBuckets.data(); } +template glm::detail::tvec4* Renderer::AccumulatorBuckets() { return m_AccumulatorBuckets.data(); } +template SpatialFilter* Renderer::GetSpatialFilter() { return m_SpatialFilter.get(); } +template TemporalFilter* Renderer::GetTemporalFilter() { return m_TemporalFilter.get(); } +template DensityFilter* Renderer::GetDensityFilter() { return m_DensityFilter.get(); } + +/// +/// Ember wrappers, getters only. +/// + +template bool Renderer::XaosPresent() { return m_Ember.XaosPresent(); } +template unsigned int Renderer::FinalRasW() const { return m_Ember.m_FinalRasW; } +template unsigned int Renderer::FinalRasH() const { return m_Ember.m_FinalRasH; } +template unsigned int Renderer::Supersample() const { return m_Ember.m_Supersample; } +template unsigned int Renderer::Passes() const { return m_Ember.m_Passes; } +template unsigned int Renderer::TemporalSamples() const { return m_Ember.m_TemporalSamples; } +template unsigned int Renderer::PaletteIndex() const { return m_Ember.PaletteIndex(); } +template T Renderer::Time() const { return m_Ember.m_Time; } +template T Renderer::Quality() const { return m_Ember.m_Quality; } +template T Renderer::SpatialFilterRadius() const { return m_Ember.m_SpatialFilterRadius; } +template T Renderer::PixelsPerUnit() const { return m_Ember.m_PixelsPerUnit; } +template T Renderer::Zoom() const { return m_Ember.m_Zoom; } +template T Renderer::CenterX() const { return m_Ember.m_CenterX; } +template T Renderer::CenterY() const { return m_Ember.m_CenterY; } +template T Renderer::Rotate() const { return m_Ember.m_Rotate; } +template T Renderer::Hue() const { return m_Ember.m_Hue; } +template T Renderer::Brightness() const { return m_Ember.m_Brightness; } +template T Renderer::Gamma() const { return m_Ember.m_Gamma; } +template T Renderer::Vibrancy() const { return m_Ember.m_Vibrancy; } +template T Renderer::GammaThresh() const { return m_Ember.m_GammaThresh; } +template T Renderer::HighlightPower() const { return m_Ember.m_HighlightPower; } +template Color Renderer::Background() const { return m_Ember.m_Background; } +template const Xform* Renderer::Xforms() const { return m_Ember.Xforms(); } +template Xform* Renderer::NonConstXforms() { return m_Ember.NonConstXforms(); } +template unsigned int Renderer::XformCount() const { return m_Ember.XformCount(); } +template const Xform* Renderer::FinalXform() const { return m_Ember.FinalXform(); } +template Xform* Renderer::NonConstFinalXform() { return m_Ember.NonConstFinalXform(); } +template bool Renderer::UseFinalXform() const { return m_Ember.UseFinalXform(); } +template const Palette* Renderer::GetPalette() const { return &m_Ember.m_Palette; } +template ePaletteMode Renderer::PaletteMode() const { return m_Ember.m_PaletteMode; } + +/// +/// Iterator wrappers. +/// + +template const unsigned char* Renderer::XformDistributions() const { return m_Iterator != NULL ? m_Iterator->XformDistributions() : NULL; } +template const unsigned int Renderer::XformDistributionsSize() const { return m_Iterator != NULL ? m_Iterator->XformDistributionsSize() : 0; } +template Point* Renderer::Samples(unsigned int threadIndex) const { return threadIndex < m_Samples.size() ? (Point*)m_Samples[threadIndex].data() : NULL; } +} \ No newline at end of file diff --git a/Source/Ember/Renderer.h b/Source/Ember/Renderer.h new file mode 100644 index 0000000..77c25fc --- /dev/null +++ b/Source/Ember/Renderer.h @@ -0,0 +1,412 @@ +#pragma once + +#include "Ember.h" +#include "Iterator.h" +#include "Utils.h" +#include "SpatialFilter.h" +#include "DensityFilter.h" +#include "TemporalFilter.h" +#include "Interpolate.h" +#include "CarToRas.h" +#include "EmberToXml.h" + +/// +/// Renderer, RenderCallback and EmberStats classes. +/// + +namespace EmberNs +{ +/// +/// Function pointers present a major restriction when dealing +/// with member functions, and that is they can only point to +/// static ones. So instead of a straight function pointer, use +/// a callback class with a single virtual callback +/// member function. +/// Template argument expected to be float or double. +/// +class EMBER_API RenderCallback +{ +public: + /// + /// Virtual destructor to ensure anything declared in derived classes gets cleaned up. + /// + virtual ~RenderCallback() { } + + /// + /// Empty progress function to be implemented in derived classes to take action on progress updates. + /// + /// The ember currently being rendered + /// An extra dummy parameter + /// The progress fraction from 0-100 + /// The stage of iteration. 1 is iterating, 2 is density filtering, 2 is final accumulation. + /// The estimated milliseconds to completion of the current stage + /// Override should return 0 if an abort is requested, else 1 to continue rendering + virtual int ProgressFunc(Ember& ember, void* foo, double fraction, int stage, double etaMs) { return 0; } + virtual int ProgressFunc(Ember& ember, void* foo, double fraction, int stage, double etaMs) { return 0; } +}; + +/// +/// Render statistics for the number of iterations ran, +/// number of bad values calculated during iteration, and +/// the total time for the entire render from the start of +/// iteration to the end of final accumulation. +/// +class EMBER_API EmberStats +{ +public: + /// + /// Constructor which sets all values to 0. + /// + EmberStats() + { + m_Iters = 0; + m_Badvals = 0; + m_RenderSeconds = 0; + } + + unsigned __int64 m_Iters, m_Badvals; + double m_RenderSeconds; +}; + +/// +/// The types of available renderers. +/// Add more in the future as different rendering methods are experimented with. +/// Possible values might be: CPU+OpenGL, Particle, Inverse. +/// +enum eRendererType { CPU_RENDERER, OPENCL_RENDERER }; + +/// +/// A base class with virtual functions to allow both templating and polymorphism to work together. +/// Derived classes will implement all of these functions. +/// Note that functions which return a decimal number use the most precise type, double. +/// +class EMBER_API RendererBase : public EmberReport +{ +public: + RendererBase() { } + virtual ~RendererBase() { } + virtual void SetEmber(Ember& ember, eProcessAction action = FULL_RENDER) { } + virtual void SetEmber(vector>& embers) { } + virtual void SetEmber(Ember& ember, eProcessAction action = FULL_RENDER) { } + virtual void SetEmber(vector>& embers) { } + virtual void Callback(RenderCallback* callback) { } + virtual bool CreateSpatialFilter(bool& newAlloc) { return false; } + virtual bool CreateTemporalFilter(bool& newAlloc) { return false; } + virtual void ComputeBounds() { } + virtual bool Ok() const { return false; } + virtual void Reset() { } + virtual void EnterRender() { } + virtual void LeaveRender() { } + virtual void EnterFinalAccum() { } + virtual void LeaveFinalAccum() { } + virtual void EnterResize() { } + virtual void LeaveResize() { } + virtual void Abort() { } + virtual bool Aborted() { return false; } + virtual bool InRender() { return false; } + virtual bool InFinalAccum() { return false; } + virtual unsigned int NumChannels() const { return 0; } + virtual void NumChannels(unsigned int numChannels) { } + virtual eRendererType RendererType() const { return CPU_RENDERER; } + virtual void ReclaimOnResize(bool reclaimOnResize) { } + virtual bool EarlyClip() const { return false; } + virtual void EarlyClip(bool earlyClip) { } + virtual void ThreadCount(unsigned int threads, const char* seedString = NULL) { } + virtual void Transparency(bool transparency) { } + virtual void InteractiveFilter(eInteractiveFilter filter) { } + virtual unsigned int FinalRasW() const { return 0; } + virtual unsigned int FinalRasH() const { return 0; } + virtual unsigned int SuperRasW() const { return 0; } + virtual unsigned int SuperRasH() const { return 0; } + virtual unsigned int FinalBufferSize() const { return 0; } + virtual unsigned int GutterWidth() const { return 0; } + virtual double ScaledQuality() const { return 0; } + virtual double LowerLeftX(bool gutter = true) const { return 0; } + virtual double LowerLeftY(bool gutter = true) const { return 0; } + virtual double UpperRightX(bool gutter = true) const { return 0; } + virtual double UpperRightY(bool gutter = true) const { return 0; } + virtual unsigned __int64 MemoryRequired(bool includeFinal) { return 0; } + virtual unsigned __int64 MemoryAvailable() { return 0; } + virtual bool PrepFinalAccumVector(vector& pixels) { return false; } + virtual eProcessState ProcessState() const { return NONE; } + virtual eProcessAction ProcessAction() const { return NOTHING; } + virtual EmberStats Stats() const { EmberStats stats; return stats; } + virtual eRenderStatus Run(vector& finalImage, double time = 0, unsigned int subBatchCountOverride = 0, bool forceOutput = false, size_t finalOffset = 0) { return RENDER_ERROR; } + virtual EmberImageComments ImageComments(unsigned int printEditDepth = 0, bool intPalette = false, bool hexPalette = true) { EmberImageComments comments; return comments; } + virtual DensityFilterBase* GetDensityFilter() { return NULL; } +}; + +/// +/// Renderer is the main class where all of the execution takes place. +/// It is intended that the program have one instance of it that it +/// keeps around for its duration. After a user sets up an ember, it's passed +/// in to be rendered. +/// This class derives from EmberReport, so the caller is able +/// to retrieve a text dump of error information if any errors occur. +/// The final image output vector is also passed in because the calling code has more +/// use for it than this class does. +/// Several functions are made virtual and have a default CPU-based implementation +/// that roughly matches what flam3 did. However they can be overriden in derived classes +/// to provide alternative rendering implementations, such as using the GPU. +/// Since this is a templated class, it's supposed to be entirely implemented in this .h file. +/// However, VC++ 2010 has very crippled support for lambdas, which Renderer makes use of. +/// If too many lambdas are used in a .h file, it will crash the compiler when another library +/// tries to link to it. To work around the bug, only declarations are here and all implementations +/// are in the .cpp file. It's unclear at what point it starts/stops working. But it seems that once +/// enough code is placed in the .h file, the compiler crashes. So for the sake of consistency, everything +/// is moved to the .cpp, even simple getters. One drawback however, is that the class must be +/// explicitly exported at the bottom of the file. +/// Also, despite explicitly doing this, the compiler throws a C4661 warning +/// for every single function in this class, saying it can't find the implementation. This warning +/// can be safely ignored. +/// Template argument T expected to be float or double. +/// Template argument bucketT was originally used to experiment with different types for the histogram, however +/// the only types that work are float and double, so it's useless and should always match what T is. +/// Mismatched types between T and bucketT are undefined. +/// +template +class EMBER_API Renderer : public RendererBase +{ +public: + Renderer(); + virtual ~Renderer(); + + virtual void ComputeBounds(); + void ComputeCamera(); + void ChangeVal(std::function func, eProcessAction action); + virtual void SetEmber(Ember& ember, eProcessAction action = FULL_RENDER); + virtual void SetEmber(vector>& embers); + void AddEmber(Ember& ember); + bool CreateTemporalFilter(bool& newAlloc); + bool AssignIterator(); + virtual bool PrepFinalAccumVector(vector& pixels); + virtual eRenderStatus Run(vector& finalImage, double time = 0, unsigned int subBatchCountOverride = 0, bool forceOutput = false, size_t finalOffset = 0); + virtual EmberImageComments ImageComments(unsigned int printEditDepth = 0, bool intPalette = false, bool hexPalette = true); + virtual unsigned __int64 MemoryRequired(bool includeFinal); + + //Virtual functions to be overriden in derived renderers that use the GPU. + virtual unsigned __int64 MemoryAvailable(); + virtual void Reset(); + virtual bool Ok() const; + virtual bool CreateDEFilter(bool& newAlloc); + virtual bool CreateSpatialFilter(bool& newAlloc); + virtual unsigned int SubBatchSize() const; + virtual void SubBatchSize(unsigned int sbs); + virtual unsigned int NumChannels() const; + virtual void NumChannels(unsigned int numChannels); + virtual eRendererType RendererType() const; + virtual unsigned int ThreadCount() const; + virtual void ThreadCount(unsigned int threads, const char* seedString = NULL); + virtual void Callback(RenderCallback* callback); + +protected: + //Virtual functions to be overriden in derived renderers that use the GPU, but not accessed outside. + virtual void MakeDmap(T colorScalar); + virtual bool Alloc(); + virtual bool ResetBuckets(bool resetHist = true, bool resetAccum = true); + virtual eRenderStatus LogScaleDensityFilter(); + virtual eRenderStatus GaussianDensityFilter(); + virtual eRenderStatus AccumulatorToFinalImage(vector& pixels, size_t finalOffset); + virtual eRenderStatus AccumulatorToFinalImage(unsigned char* pixels, size_t finalOffset); + virtual EmberStats Iterate(unsigned __int64 iterCount, unsigned int pass, unsigned int temporalSample); + +public: + //Accessors for render properties. + vector> RandVec(); + bool RandVec(vector>& randVec); + + inline bool LockAccum() const; + void LockAccum(bool lockAccum); + + virtual bool EarlyClip() const; + virtual void EarlyClip(bool earlyClip); + + inline bool InsertPalette() const; + void InsertPalette(bool insertPalette); + + inline bool ReclaimOnResize() const; + virtual void ReclaimOnResize(bool reclaimOnResize); + + inline bool Transparency() const; + virtual void Transparency(bool transparency); + + inline unsigned int BytesPerChannel() const; + void BytesPerChannel(unsigned int bytesPerChannel); + + inline T PixelAspectRatio() const; + void PixelAspectRatio(T pixelAspectRatio); + + inline eInteractiveFilter InteractiveFilter() const; + virtual void InteractiveFilter(eInteractiveFilter filter); + + //Threading control. + virtual void EnterRender(); + virtual void LeaveRender(); + virtual void EnterFinalAccum(); + virtual void LeaveFinalAccum(); + virtual void EnterResize(); + virtual void LeaveResize(); + virtual void Abort(); + virtual bool Aborted(); + virtual bool InRender(); + virtual bool InFinalAccum(); + + //Renderer properties, getters only. + virtual unsigned int SuperRasW() const; + virtual unsigned int SuperRasH() const; + inline unsigned int SuperSize() const; + virtual unsigned int FinalBufferSize() const; + inline unsigned int FinalRowSize() const; + inline unsigned int FinalDimensions() const; + inline unsigned int PixelSize() const; + virtual unsigned int GutterWidth() const; + inline unsigned int DensityFilterOffset() const; + virtual double ScaledQuality() const; + inline T Scale() const; + inline T PixelsPerUnitX() const; + inline T PixelsPerUnitY() const; + virtual double LowerLeftX(bool gutter = true) const; + virtual double LowerLeftY(bool gutter = true) const; + virtual double UpperRightX(bool gutter = true) const; + virtual double UpperRightY(bool gutter = true) const; + inline T K1() const; + inline T K2() const; + inline unsigned __int64 TotalIterCount() const; + inline unsigned __int64 ItersPerTemporalSample() const; + virtual eProcessState ProcessState() const; + virtual eProcessAction ProcessAction() const; + virtual EmberStats Stats() const; + inline const CarToRas* CoordMap() const; + inline glm::detail::tvec4* HistBuckets(); + inline glm::detail::tvec4* AccumulatorBuckets(); + inline SpatialFilter* GetSpatialFilter(); + inline TemporalFilter* GetTemporalFilter(); + virtual DensityFilter* GetDensityFilter(); + + //Ember wrappers, getters only. + inline bool XaosPresent(); + virtual inline unsigned int FinalRasW() const; + virtual inline unsigned int FinalRasH() const; + inline unsigned int Supersample() const; + inline unsigned int Passes() const; + inline unsigned int TemporalSamples() const; + inline unsigned int PaletteIndex() const; + inline T Time() const; + inline T Quality() const; + inline T SpatialFilterRadius() const; + inline T PixelsPerUnit() const; + inline T Zoom() const; + inline T CenterX() const; + inline T CenterY() const; + inline T Rotate() const; + inline T Hue() const; + inline T Brightness() const; + inline T Contrast() const; + inline T Gamma() const; + inline T Vibrancy() const; + inline T GammaThresh() const; + inline T HighlightPower() const; + inline Color Background() const; + inline const Xform* Xforms() const; + inline Xform* NonConstXforms(); + inline unsigned int XformCount() const; + inline const Xform* FinalXform() const; + inline Xform* NonConstFinalXform(); + inline bool UseFinalXform() const; + inline const Palette* GetPalette() const; + inline ePaletteMode PaletteMode() const; + + //Iterator wrappers. + const unsigned char* XformDistributions() const; + const unsigned int XformDistributionsSize() const; + Point* Samples(unsigned int threadIndex) const; + + void* m_ProgressParameter; + +protected: + //Non-virtual functions that might be needed by a derived class. + void PrepFinalAccumVals(Color& background, T& g, T& linRange, T& vibrancy); + +private: + //Miscellaneous functions used only in this class. + void Accumulate(Point* samples, unsigned int sampleCount, const Palette* palette); + inline void AddToAccum(const glm::detail::tvec4& bucket, int i, int ii, int j, int jj); + template void GammaCorrection(glm::detail::tvec4& bucket, Color& background, T g, T linRange, T vibrancy, bool doAlpha, bool scale, accumT* correctedChannels); + +protected: + bool m_EarlyClip; + bool m_Transparency; + unsigned int m_SuperRasW; + unsigned int m_SuperRasH; + unsigned int m_SuperSize; + unsigned int m_GutterWidth; + unsigned int m_DensityFilterOffset; + unsigned int m_NumChannels; + unsigned int m_BytesPerChannel; + unsigned int m_SubBatchSize; + unsigned int m_ThreadsToUse; + T m_ScaledQuality; + T m_Scale; + T m_PixelsPerUnitX; + T m_PixelsPerUnitY; + T m_PixelAspectRatio; + T m_LowerLeftX; + T m_LowerLeftY; + T m_UpperRightX; + T m_UpperRightY; + T m_K1; + T m_K2; + T m_Vibrancy;//Accumulate these after each temporal sample. + T m_Gamma; + Color m_Background; + Affine2D m_RotMat; + + volatile bool m_Abort; + bool m_LockAccum; + bool m_InRender; + bool m_InFinalAccum; + bool m_InsertPalette; + bool m_ReclaimOnResize; + unsigned int m_VibGamCount; + unsigned int m_LastPass; + unsigned int m_LastTemporalSample; + unsigned __int64 m_LastIter; + double m_LastIterPercent; + eProcessAction m_ProcessAction; + eProcessState m_ProcessState; + eInteractiveFilter m_InteractiveFilter; + EmberStats m_Stats; + Ember m_Ember; + Ember m_TempEmber; + Ember m_LastEmber; + vector> m_Embers; + CarToRas m_CarToRas; + RenderCallback* m_Callback; + Iterator* m_Iterator; + auto_ptr> m_StandardIterator; + auto_ptr> m_XaosIterator; + Palette m_Dmap; + vector> m_HistBuckets; + vector> m_AccumulatorBuckets; + auto_ptr> m_SpatialFilter; + auto_ptr> m_TemporalFilter; + auto_ptr> m_DensityFilter; + vector>> m_Samples; + vector m_SubBatch; + vector m_BadVals; + vector> m_Rand; + tbb::task_group m_TaskGroup; + CriticalSection m_RenderingCs, m_AccumCs, m_FinalAccumCs, m_ResizeCs; + Timing m_RenderTimer, m_ProgressTimer; + EmberToXml m_EmberToXml; +}; + +//This class had to be implemented in a cpp file because the compiler was breaking. +//So the explicit instantiation must be declared here rather than in Ember.cpp where +//all of the other classes are done. +template EMBER_API class Renderer; + +#ifdef DO_DOUBLE + template EMBER_API class Renderer; +#endif +} \ No newline at end of file diff --git a/Source/Ember/SheepTools.h b/Source/Ember/SheepTools.h new file mode 100644 index 0000000..eaadedd --- /dev/null +++ b/Source/Ember/SheepTools.h @@ -0,0 +1,1394 @@ +#pragma once + +#include "EmberDefines.h" +#include "Isaac.h" +#include "VariationList.h" +#include "Renderer.h" + +/// +/// SheepTools class. +/// + +namespace EmberNs +{ +/// +/// Mutation mode enum. +/// +enum eMutateMode +{ + MUTATE_NOT_SPECIFIED = -1, + MUTATE_ALL_VARIATIONS = 0, + MUTATE_ONE_XFORM_COEFS = 1, + MUTATE_ADD_SYMMETRY = 2, + MUTATE_POST_XFORMS = 3, + MUTATE_COLOR_PALETTE = 4, + MUTATE_DELETE_XFORM = 5, + MUTATE_ALL_COEFS = 6 +}; + +/// +/// Cross mode enum. +/// +enum eCrossMode +{ + CROSS_NOT_SPECIFIED = -1, + CROSS_UNION = 0, + CROSS_INTERPOLATE = 1, + CROSS_ALTERNATE = 2 +}; + +/// +/// SheepTools contains miscellaneous functions for mutating, rotating +/// crossing and randomizing embers. It is named so because these functions +/// are used in the electric sheep genome mutation process. +/// Most functions in this class perform a particular action and return +/// a string describing what it did so it can be recorded in an Xml edit doc +/// to be saved with the ember when converting to Xml. +/// Since it's members can occupy significant memory space and also have +/// hefty initialization sequences, it's important to declare one instance +/// and reuse it for the duration of the program instead of creating and deleting +/// them as local variables. +/// Template argument expected to be float or double. +/// +template +class EMBER_API SheepTools +{ +public: + /// + /// Constructor which takes a palette path and pre-constructed renderer. + /// This class will take over ownership of the passed in renderer so the + /// caller should not delete it. + /// + /// The full path and filename of the palette file + /// A pre-constructed renderer to use. The caller should not delete this. + SheepTools(string palettePath, Renderer* renderer) + { + Timing t; + + m_Smooth = true; + m_SheepGen = -1; + m_SheepId = -1; + m_Stagger = 0; + m_OffsetX = 0; + m_OffsetY = 0; + + m_PaletteList.Init(palettePath); + m_StandardIterator = auto_ptr>(new StandardIterator()); + m_XaosIterator = auto_ptr>(new XaosIterator()); + m_Renderer = auto_ptr>(renderer); + m_Rand = QTIsaac(ISAAC_INT(t.Tic()), ISAAC_INT(t.Tic() * 2), ISAAC_INT(t.Tic() * 3)); + } + + /// + /// Create the linear default ember with a random palette. + /// + /// The newly constructed linear default ember + Ember CreateLinearDefault() + { + Ember ember; + + Xform xform1(T(0.25), T(1), T(0.5), T(1), T(0.5), T(0), T(0), T(0.5), T(0.5), T(0.25)); + Xform xform2(T(0.25), T(0.66), T(0.5), T(1), T(0.5), T(0), T(0), T(0.5), T(-0.5), T(0.25)); + Xform xform3(T(0.25), T(0.33), T(0.5), T(1), T(0.5), T(0), T(0), T(0.5), T(0.0), T(-0.5)); + + xform1.AddVariation(new LinearVariation()); + xform2.AddVariation(new LinearVariation()); + xform3.AddVariation(new LinearVariation()); + + ember.AddXform(xform1); + ember.AddXform(xform2); + ember.AddXform(xform3); + + if (m_PaletteList.Init()) + ember.m_Palette = *m_PaletteList.GetPalette(-1); + + return ember; + } + + /// + /// Ensure all xforms, including final, have no more than the specified number of variations. + /// Remove variations in order of smallest weight to largest weight. + /// Also remove all xforms whose density is less than 0.001. + /// + /// The ember whose xforms will be truncated + /// The maximum number of variations each xform can have + /// A string describing what was done + string TruncateVariations(Ember& ember, unsigned int maxVars) + { + int smallest; + unsigned int i, j, numVars; + T sv = 0; + ostringstream os; + + //First clear out any xforms that are not the final, and have a density of less than 0.001. + for (i = 0; i < ember.XformCount(); i++) + { + Xform* xform = ember.GetXform(i); + + if (xform->m_Weight < T(0.001)) + { + os << "trunc_density " << i; + ember.DeleteXform(i); + i = 0;//Size will have changed, so start over. + } + } + + //Now consider all xforms, including final. + for (i = 0; i < ember.TotalXformCount(); i++) + { + Xform* xform = ember.GetTotalXform(i); + + do + { + Variation* var = NULL; + Variation* smallestVar = NULL; + + numVars = 0; + smallest = -1; + + for (j = 0; j < xform->TotalVariationCount(); j++) + { + var = xform->GetVariation(j); + + if (var && var->m_Weight != 0.0) + { + T v = var->m_Weight; + numVars++; + + if (smallest == -1 || fabs(v) < sv) + { + smallest = j; + smallestVar = var; + sv = fabs(v); + } + } + } + + if (numVars > maxVars) + { + os << " trunc " << i << " " << smallest; + + if (smallestVar) + xform->DeleteVariationById(smallestVar->VariationId()); + } + } while (numVars > maxVars); + } + + return os.str(); + } + + /// + /// Mutate the ember using the specified mode. + /// + /// The ember to mutate + /// The mutation mode + /// The variations to use if the mutation mode is random + /// The type of symmetry to add if random specified. If 0, it will be added randomly. + /// The speed to multiply the pre affine transforms by if the mutate mode is MUTATE_ALL_COEFS, else ignored. + /// A string describing what was done + string Mutate(Ember& ember, eMutateMode mode, vector& useVars, int sym, T speed) + { + unsigned int i, j, k, x, done, modXform; + char ministr[32]; + T randSelect; + ostringstream os; + Ember mutation; + + mutation.Clear(); + + //If mutate_mode = -1, choose a random mutation mode. + if (mode == MUTATE_NOT_SPECIFIED) + { + randSelect = m_Rand.Frand01(); + + if (randSelect < T(0.1)) + mode = MUTATE_ALL_VARIATIONS; + else if (randSelect < T(0.3)) + mode = MUTATE_ONE_XFORM_COEFS; + else if (randSelect < T(0.5)) + mode = MUTATE_ADD_SYMMETRY; + else if (randSelect < T(0.6)) + mode = MUTATE_POST_XFORMS; + else if (randSelect < T(0.7)) + mode = MUTATE_COLOR_PALETTE; + else if (randSelect < T(0.8)) + mode = MUTATE_DELETE_XFORM; + else + mode = MUTATE_ALL_COEFS; + } + + if (mode == MUTATE_ALL_VARIATIONS) + { + os << "mutate all variations"; + + do + { + //Create a random flame, and use the variations to replace those in the original. + Random(mutation, useVars, sym, ember.TotalXformCount()); + + for (i = 0; i < ember.TotalXformCount(); i++) + { + Xform* xform1 = ember.GetTotalXform(i); + Xform* xform2 = mutation.GetTotalXform(i); + + if (xform1 && xform2) + { + for (j = 0; j < xform1->TotalVariationCount(); j++) + { + Variation* var1 = xform1->GetVariation(j); + Variation* var2 = xform2->GetVariationById(var1->VariationId()); + + if ((var1 == NULL) ^ (var2 == NULL))//If any of them are different, clear the first and copy all of the second and exit the while loop. + { + xform1->ClearAndDeleteVariations(); + + for (k = 0; k < xform2->TotalVariationCount(); k++) + xform1->AddVariation(xform2->GetVariation(k)->Copy()); + + done = 1; + } + } + } + } + } while (!done); + } + else if (mode == MUTATE_ONE_XFORM_COEFS) + { + //Generate a 2-xform random. + Random(mutation, useVars, sym, 2); + + //Which xform to mutate? + modXform = m_Rand.Rand() % ember.TotalXformCount(); + Xform* xform1 = ember.GetTotalXform(modXform); + Xform* xform2 = mutation.GetTotalXform(0); + os << "mutate xform " << modXform << " coefs"; + + //If less than 3 xforms, then change only the translation part. + if (ember.TotalXformCount() < 2) + { + xform1->m_Affine.C(xform2->m_Affine.C()); + xform1->m_Affine.F(xform2->m_Affine.F()); + } + else + { + for (i = 0; i < 2; i++) + for (j = 0; j < 3; j++) + xform1->m_Affine.m_Mat[i][j] = xform2->m_Affine.m_Mat[i][j]; + } + } + else if (mode == MUTATE_ADD_SYMMETRY) + { + os << "mutate symmetry"; + ember.AddSymmetry(0, m_Rand); + } + else if (mode == MUTATE_POST_XFORMS) + { + unsigned int b = 1 + m_Rand.Rand() % 6; + unsigned int same = m_Rand.Rand() & 3;//25% chance of using the same post for all of them. + + sprintf_s(ministr, 32, "(%d%s)", b, (same > 0) ? " same" : ""); + os << "mutate post xforms " << ministr; + + for (i = 0; i < ember.TotalXformCount(); i++) + { + int copy = (i > 0) && same; + Xform* xform = ember.GetTotalXform(i); + + if (copy)//Copy the post from the first xform to the rest of them. + { + xform->m_Post = ember.GetTotalXform(0)->m_Post; + } + else + { + //50% chance. + if (b & 1) + { + T f = T(M_PI) * m_Rand.Frand11(); + T a, b, d, e; + + a = (xform->m_Affine.A() * cos(f) + xform->m_Affine.B() * -sin(f)); + d = (xform->m_Affine.A() * sin(f) + xform->m_Affine.D() * cos(f)); + b = (xform->m_Affine.B() * cos(f) + xform->m_Affine.E() * -sin(f)); + e = (xform->m_Affine.B() * sin(f) + xform->m_Affine.E() * cos(f)); + + xform->m_Affine.A(a); + xform->m_Affine.B(b); + xform->m_Affine.D(d); + xform->m_Affine.E(e); + + f *= -1; + + a = (xform->m_Post.A() * cos(f) + xform->m_Post.B() * -sin(f)); + d = (xform->m_Post.A() * sin(f) + xform->m_Post.D() * cos(f)); + b = (xform->m_Post.B() * cos(f) + xform->m_Post.E() * -sin(f)); + e = (xform->m_Post.B() * sin(f) + xform->m_Post.E() * cos(f)); + + xform->m_Post.A(a); + xform->m_Post.B(b); + xform->m_Post.D(d); + xform->m_Post.E(e); + } + + //33% chance. + if (b & 2) + { + T f = T(0.2) + m_Rand.Frand01(); + T g = T(0.2) + m_Rand.Frand01(); + + if (m_Rand.RandBit()) + f = 1 / f; + + if (m_Rand.RandBit()) + g = f; + else + if (m_Rand.RandBit()) + g = 1 / g; + + xform->m_Affine.A(xform->m_Affine.A() / f); + xform->m_Affine.D(xform->m_Affine.D() / f); + xform->m_Affine.B(xform->m_Affine.B() / g); + xform->m_Affine.E(xform->m_Affine.E() / g); + xform->m_Post.A(xform->m_Post.A() * f); + xform->m_Post.B(xform->m_Post.B() * f); + xform->m_Post.D(xform->m_Post.D() * g); + xform->m_Post.E(xform->m_Post.E() * g); + } + + if (b & 4)//16% chance. + { + T f = m_Rand.Frand11(); + T g = m_Rand.Frand11(); + + xform->m_Affine.C(xform->m_Affine.C() - f); + xform->m_Affine.F(xform->m_Affine.F() - g); + xform->m_Post.C(xform->m_Post.C() + f); + xform->m_Post.F(xform->m_Post.F() + g); + } + } + } + } + else if (mode == MUTATE_COLOR_PALETTE) + { + T s = m_Rand.Frand01(); + + if (s < T(0.4))//Randomize xform color coords. + { + ImproveColors(ember, 100, false, 10); + os << "mutate color coords"; + } + else if (s < T(0.8))//Randomize xform color coords and palette. + { + ImproveColors(ember, 25, true, 10); + os << "mutate color all"; + } + else//Randomize palette only. + { + Palette palette; + + if (m_PaletteList.Init()) + palette = *m_PaletteList.GetPalette(-1); + + palette.MakeHueAdjustedPalette(ember.m_Palette, ember.m_Hue); + + //If the palette retrieval fails, skip the mutation. + if (ember.m_Palette.m_Index >= 0) + { + os << "mutate color palette"; + } + else + { + palette.Clear(false); + ember.m_Palette = palette; + cout << "Failure getting random palette, palette set to white\n"; + } + } + } + else if (mode == MUTATE_DELETE_XFORM) + { + unsigned int nx = m_Rand.Rand() % ember.TotalXformCount(); + os << "mutate delete xform " << nx; + + if (ember.TotalXformCount() > 1) + ember.DeleteTotalXform(nx); + } + else if (mode == MUTATE_ALL_COEFS) + { + os << "mutate all coefs"; + Random(mutation, useVars, sym, ember.TotalXformCount()); + + //Change all the coefs by a fraction of the random. + for (x = 0; x < ember.TotalXformCount(); x++) + { + Xform* xform1 = ember.GetTotalXform(x); + Xform* xform2 = mutation.GetTotalXform(x); + + for (i = 0; i < 2; i++) + for (j = 0; j < 3; j++) + xform1->m_Affine.m_Mat[i][j] += speed * xform2->m_Affine.m_Mat[i][j]; + + //Eventually, mutate the parametric variation parameters here. + } + } + + return os.str(); + } + + /// + /// Crosse the two embers and place the result in emberOut. + /// + /// The first ember to cross + /// The second ember to cross + /// The result ember + /// The cross mode + /// A string describing what was done + string Cross(Ember& ember0, Ember& ember1, Ember& emberOut, int crossMode) + { + unsigned int i, rb; + T t; + ostringstream os; + char ministr[32]; + + if (crossMode == CROSS_NOT_SPECIFIED) + { + T s = m_Rand.Frand01(); + + if (s < 0.1) + crossMode = CROSS_UNION; + else if (s < 0.2) + crossMode = CROSS_INTERPOLATE; + else + crossMode = CROSS_ALTERNATE; + } + + if (crossMode == CROSS_UNION) + { + //Make a copy of the first ember. + emberOut = ember0; + + //Copy all xforms in the second ember except the final. Default behavior keeps the final from parent0. + for (i = 0; i < ember1.XformCount(); i++) + emberOut.AddXform(*ember1.GetXform(i)); + + os << "cross union"; + } + else if (crossMode == CROSS_INTERPOLATE) + { + //Linearly interpolate somewhere between the two. + Ember parents[2]; + //t = 0.5;//If you ever need to test. + t = m_Rand.Frand01(); + + parents[0] = ember0; + parents[1] = ember1; + parents[0].m_Time = T(0); + parents[1].m_Time = T(1); + Interpolater::Interpolate(parents, 2, t, 0, emberOut); + + for (i = 0; i < emberOut.TotalXformCount(); i++) + emberOut.GetTotalXform(i)->DeleteMotionElements(); + + sprintf_s(ministr, 32, "%7.5g", t); + os << "cross interpolate " << ministr; + } + else//Alternate mode. + { + int got0, got1, usedParent; + string trystr; + + //Each xform comes from a random parent, possible for an entire parent to be excluded. + do + { + got0 = got1 = 0; + rb = m_Rand.RandBit(); + os << rb << ":"; + + //Copy the parent, sorting the final xform to the end if it's present. + emberOut = rb ? ember1 : ember0; + usedParent = rb; + + //Only replace non-final xforms. + for (i = 0; i < emberOut.XformCount(); i++) + { + rb = m_Rand.RandBit(); + + //Replace xform if bit is 1. + if (rb == 1) + { + if (usedParent == 0) + { + if (i < ember1.XformCount() && ember1.GetXform(i)->m_Weight > 0) + { + Xform* xform = emberOut.GetXform(i); + *xform = *ember1.GetXform(i); + os << " 1"; + got1 = 1; + } + else + { + os << " 0"; + got0 = 1; + } + } + else + { + if (i < ember0.XformCount() && ember0.GetXform(i)->m_Weight > 0) + { + Xform* xform = emberOut.GetXform(i); + *xform = *ember0.GetXform(i); + os << " 0"; + got0 = 1; + } + else + { + os << " 1"; + got1 = 1; + } + } + } + else + { + os << " " << usedParent; + + if (usedParent) + got1 = 1; + else + got0 = 1; + } + } + + if (usedParent == 0 && ember0.UseFinalXform()) + got0 = 1; + else if (usedParent == 1 && ember1.UseFinalXform()) + got1 = 1; + + } while ((i > 1) && !(got0 && got1)); + + os << "cross alternate "; + os << trystr; + } + + //Reset color coords. + for (i = 0; i < emberOut.TotalXformCount(); i++) + { + emberOut.GetTotalXform(i)->m_ColorX = T(i & 1);//Original pingponged between 0 and 1, which gives bad coloring but is useful for testing. + //emberOut.GetTotalXform(i)->m_ColorX = m_Rand.Frand01();//Do rand which gives better coloring but produces different results every time it's run. + //emberOut.GetTotalXform(i)->m_ColorY = ?????;//Will need to update this if 2D coordinates are ever supported. + } + + //Potentially genetically cross the two palettes together. + if (m_Rand.Frand01() < T(0.4)) + { + //Select the starting parent. + unsigned int ci, startParent = m_Rand.RandBit(); + + os << " cmap_cross " << startParent << ":"; + + //Loop over the entries, switching to the other parent 1% of the time. + for (ci = 0; ci < 256; ci++)//Will need to update this if 2D coordinates are ever supported. + { + if (m_Rand.Frand01() < T(.01)) + { + startParent = 1 - startParent; + os << " " << ci; + } + + emberOut.m_Palette.m_Entries[ci] = startParent ? ember1.m_Palette.m_Entries[ci] : ember0.m_Palette.m_Entries[ci]; + } + } + + return os.str(); + } + + /// + /// Thin wrapper around Random() that passes an empty vector for useVars, a random value for symmetry and 0 for max xforms. + /// + /// The newly created random ember + void Random(Ember& ember) + { + vector useVars; + + Random(ember, useVars, (int)m_Rand.Frand(-2, 2), 0); + } + + /// + /// Create a random ember. + /// + /// The newly created random ember + /// A list of variations to use. If empty, any variation can be used. + /// The symmetry type to use from -2 to 2 + /// The number of xforms to use. If 0, a quasi random count is used. + void Random(Ember& ember, vector& useVars, int sym, int specXforms) + { + bool postid, addfinal = false; + int var, samed, multid, samepost; + unsigned int i, j, k, n, varCount = (unsigned int)m_VariationList.Size(); + Palette palette; + + static unsigned int xformDistrib[] = + { + 2, 2, 2, 2, + 3, 3, 3, 3, + 4, 4, 4, + 5, 5, + 6 + }; + + ember.Clear(); + ember.m_Hue = (m_Rand.Rand() & 7) ? 0 : m_Rand.Frand01(); + + if (m_PaletteList.Init()) + palette = *m_PaletteList.GetPalette(-1); + + palette.MakeHueAdjustedPalette(ember.m_Palette, ember.m_Hue); + ember.m_Time = 0; + ember.m_Interp = EMBER_INTERP_LINEAR; + ember.m_PaletteInterp = INTERP_HSV; + + //Choose the number of xforms. + if (specXforms > 0) + { + ember.AddXforms(specXforms); + } + else + { + ember.AddXforms(xformDistrib[m_Rand.Rand() % Vlen(xformDistrib)]); + addfinal = m_Rand.Frand01() < T(0.15);//Add a final xform 15% of the time. + + if (addfinal) + { + Xform xform; + + xform.m_Affine.A(T(1.1));//Just put something in there so it doesn't show up as being an empty final xform. + ember.SetFinalXform(xform); + } + } + + //If first input variation is -1 random choose one to use or decide to use multiple. + if (useVars.empty() || useVars[0] == -1) + var = m_Rand.RandBit() ? m_Rand.Rand() % varCount : -1; + else + var = -2; + + samed = m_Rand.RandBit(); + multid = m_Rand.RandBit(); + postid = m_Rand.Frand01() < T(0.6); + samepost = m_Rand.RandBit(); + + //Loop over xforms. + for (i = 0; i < ember.TotalXformCount(); i++) + { + Xform* xform = ember.GetTotalXform(i); + + xform->m_Weight = T(1) / ember.TotalXformCount(); + xform->m_ColorX = m_Rand.Frand01();//Original pingponged between 0 and 1, which gives bad coloring. Ember does random instead. + xform->m_ColorY = m_Rand.Frand01();//Will need to update this if 2D coordinates are ever supported. + xform->m_ColorSpeed = T(0.5); + xform->m_Animate = 1; + xform->m_Affine.A(m_Rand.Frand11()); + xform->m_Affine.B(m_Rand.Frand11()); + xform->m_Affine.C(m_Rand.Frand11()); + xform->m_Affine.D(m_Rand.Frand11()); + xform->m_Affine.E(m_Rand.Frand11()); + xform->m_Affine.F(m_Rand.Frand11()); + xform->m_Post.MakeID(); + + if (!ember.IsFinalXform(xform)) + { + if (!postid) + { + for (j = 0; j < 2; j++) + { + for (k = 0; k < 3; k++) + { + if (samepost || (i == 0)) + xform->m_Post.m_Mat[j][k] = m_Rand.Frand11(); + else + xform->m_Post.m_Mat[j][k] = ember.GetTotalXform(0)->m_Post.m_Mat[j][k]; + } + } + } + + if (var > -1) + { + xform->AddVariation(m_VariationList.GetVariation(var)->Copy());//Use only one variation specified for all xforms. + } + else if (multid && var == -1) + { + xform->AddVariation(m_VariationList.GetVariation(m_Rand.Rand() % varCount)->Copy());//Choose a random var for this xform. + } + else + { + if (samed && i > 0) + { + //Copy the same variations from the previous xform. + Xform* prevXform = ember.GetXform(i - 1); + + for (j = 0; j < prevXform->TotalVariationCount(); j++) + xform->AddVariation(prevXform->GetVariation(j)->Copy()); + } + else + { + //Choose a random number of vars to use, at least 2 + //but less than varCount. Probability leans + //towards fewer variations. + n = 2; + while (m_Rand.RandBit() && (n < varCount)) + n++; + + //Randomly choose n variations, and change their weights. + //A var can be selected more than once, further reducing + //the probability that multiple vars are used. + for (j = 0; j < n; j++) + { + if (var != -2) + { + //Pick a random variation and use a random weight from 0-1. + Variation* v = m_VariationList.GetVariationCopy((size_t)(m_Rand.Rand() % varCount), m_Rand.Frand(T(0.001), 1)); + + if (v && !xform->AddVariation(v)) + delete v;//It already existed and therefore was not added. + } + else + { + //Pick a random variation from the suppled IDs and use a random weight from 0-1. + Variation* v = m_VariationList.GetVariationCopy(useVars[m_Rand.Rand() % useVars.size()], m_Rand.Frand(T(0.001), 1)); + + if (v && !xform->AddVariation(v)) + delete v; + } + } + + xform->NormalizeVariationWeights();//Normalize weights to 1.0 total. + } + } + } + else + { + //Handle final xform randomness. + n = 1; + + if (m_Rand.RandBit()) + n++; + + //Randomly choose n variations, and change their weights. + //A var can be selected more than once, further reducing + //the probability that multiple vars are used. + for (j = 0; j < n; j++) + { + if (var != -2) + { + //Pick a random variation and use a random weight from 0-1. + xform->AddVariation(m_VariationList.GetVariationCopy((size_t)(m_Rand.Rand() % varCount), m_Rand.Frand(T(0.001), 1))); + } + else + { + //Pick a random variation from the suppled IDs and use a random weight from 0-1. + xform->AddVariation(m_VariationList.GetVariationCopy(useVars[m_Rand.Rand() % useVars.size()], m_Rand.Frand(T(0.001), 1))); + } + } + + xform->NormalizeVariationWeights();//Normalize weights to 1.0 total. + } + + //Randomize parametric variations. + for (j = 0; j < xform->TotalVariationCount(); j++) + xform->GetVariation(j)->Random(m_Rand); + } + + //Randomly add symmetry (but not if we've already added a final xform). + if (sym || (!(m_Rand.Rand() % 4) && !addfinal)) + ember.AddSymmetry(sym, m_Rand); + else + ember.m_Symmetry = 0; + } + + /// + /// Attempt to make colors better by doing some test renders. + /// + /// The ember to render + /// The number of test renders to try before giving up + /// Change palette if true, else keep trying with the same palette. + /// The resolution of the test histogram. This value ^ 3 will be used for the total size. Common value is 10. + void ImproveColors(Ember& ember, int tries, bool changePalette, int colorResolution) + { + int i; + T best, b; + Ember bestEmber = ember; + + best = TryColors(ember, colorResolution); + + if (best < 0) + { + cout << "Error in TryColors(), skipping ImproveColors()" << endl; + return; + } + + for (i = 0; i < tries; i++) + { + ChangeColors(ember, changePalette); + b = TryColors(ember, colorResolution); + + if (b < 0) + { + cout << "Error in TryColors, aborting tries." << endl; + break; + } + + if (b > best) + { + best = b; + bestEmber = ember; + } + } + + ember = bestEmber; + } + + /// + /// Run a test render to improve the colors. + /// + /// The ember to render + /// The resolution of the test histogram. This value ^ 3 will be used for the total size. Common value is 10. + /// The number of histogram cells that weren't black + T TryColors(Ember& ember, int colorResolution) + { + unsigned char* p; + unsigned int i, hits = 0, res = colorResolution; + unsigned int pixTotal, res3 = res * res * res; + T scalar; + Ember adjustedEmber = ember; + + adjustedEmber.m_Quality = 1; + adjustedEmber.m_Supersample = 1; + adjustedEmber.m_MaxRadDE = 0; + + //Scale the image so that the total number of pixels is ~10,000. + pixTotal = ember.m_FinalRasW * ember.m_FinalRasH; + scalar = sqrt(T(10000) / pixTotal); + adjustedEmber.m_FinalRasW = (unsigned int)(ember.m_FinalRasW * scalar); + adjustedEmber.m_FinalRasH = (unsigned int)(ember.m_FinalRasH * scalar); + adjustedEmber.m_PixelsPerUnit *= scalar; + adjustedEmber.m_Passes = 1; + adjustedEmber.m_TemporalSamples = 1; + + m_Renderer->SetEmber(adjustedEmber); + m_Renderer->BytesPerChannel(1); + m_Renderer->EarlyClip(true); + m_Renderer->PixelAspectRatio(1); + m_Renderer->ThreadCount(Timing::ProcessorCount()); + m_Renderer->SubBatchSize(10000); + m_Renderer->Callback(NULL); + + if (m_Renderer->Run(m_FinalImage) != RENDER_OK) + { + cout << "Error rendering test image for TryColors(). Aborting." << endl; + return -1; + } + + m_Hist.resize(res3); + memset(m_Hist.data(), 0, res3); + p = m_FinalImage.data(); + + for (i = 0; i < m_Renderer->FinalDimensions(); i++) + { + m_Hist[(p[0] * res / 256) + + (p[1] * res / 256) * res + + (p[2] * res / 256) * res * res]++; + p += m_Renderer->NumChannels(); + } + + for (i = 0; i < res3; i++) + { + if (m_Hist[i]) + hits++; + } + + return T(hits / res3); + } + + /// + /// Change around color coordinates. Optionall change out the entire palette. + /// + /// The ember whose xform's color coordinates will be changed + /// Change palette if true, else don't + void ChangeColors(Ember& ember, bool changePalette) + { + unsigned int i; + Xform* xform0; + Xform* xform1; + + if (changePalette) + { + Palette* palette; + + ember.m_Hue = 0.0; + + if (m_PaletteList.Init()) + palette = m_PaletteList.GetPalette(-1); + + if (palette) + { + palette->MakeHueAdjustedPalette(ember.m_Palette, ember.m_Hue); + } + else + { + ember.m_Palette.Clear(false); + cout << "Error retrieving random palette, setting to all white" << endl; + } + } + + for (i = 0; i < ember.TotalXformCount(); i++) + { + ember.GetTotalXform(i)->m_ColorX = m_Rand.Frand01(); + ember.GetTotalXform(i)->m_ColorY = m_Rand.Frand01(); + } + + xform0 = RandomXform(ember, -1); + xform1 = RandomXform(ember, ember.GetXformIndex(xform0)); + + if (xform0 && (m_Rand.Rand() & 1)) + { + xform0->m_ColorX = 0; + xform0->m_ColorY = 0; + } + + if (xform1 && (m_Rand.Rand() & 1)) + { + xform1->m_ColorX = 1; + xform1->m_ColorY = 1; + } + } + + /// + /// Try to get a random xform from the ember, including final, whose density is non-zero. + /// Give up after 100 tries. + /// + /// The ember to get a random xform from + /// Optionally exclude an xform. Pass -1 to include all for consideration. + /// The random xform if successful, else NULL. + Xform* RandomXform(Ember& ember, int excluded) + { + int ntries = 0; + + while (ntries++ < 100) + { + int i = m_Rand.Rand() % ember.TotalXformCount(); + + if (i != excluded) + { + Xform* xform = ember.GetTotalXform(i); + + if (xform->m_Weight > 0) + return xform; + } + } + + return NULL; + } + + /// + /// Rotate affine transforms and optionally apply motion elements, + /// and store the result in rotated. + /// + /// The ember to rotate + /// The rotated xform + /// The time percentage value which dictates how much of a percentage of 360 degrees it should be rotated and the time position for the motion elements + void Loop(Ember& ember, Ember& rotated, T blend) + { + rotated = ember; + + //Insert motion magic here : + //If there are motion elements, modify the contents of + //the result xforms before rotate is called. + for (unsigned int i = 0; i < ember.TotalXformCount(); i++) + { + Xform* xform1 = ember.GetTotalXform(i); + Xform* xform2 = rotated.GetTotalXform(i); + + if (!xform1->m_Motion.empty()) + xform2->ApplyMotion(*xform1, blend); + + xform2->DeleteMotionElements(); + } + + rotated.RotateAffines(-blend * 360);//Rotate the affines. + } + + /// + /// Interpolate two embers and place the output in result. + /// The embers parameter is expected to be a pointer to an array of at least 2 elements. + /// + /// The embers to interpolate + /// The result of the interpolation + /// The interpolation time + /// True if embers points to the first or last ember in the entire sequence, else false. + void Edge(Ember* embers, Ember& result, T blend, bool seqFlag) + { + unsigned int i, si; + Ember spun[2], prealign[2]; + + //Insert motion magic here : + //If there are motion elements, modify the contents of + //the result xforms before rotate is called. + for (si = 0; si < 2; si++) + { + prealign[si] = embers[si]; + + for (i = 0; i < embers[si].TotalXformCount(); i++) + { + Xform* xform = embers[si].GetTotalXform(i); + + if (!xform->m_Motion.empty()) + xform->ApplyMotion(*(prealign[si].GetTotalXform(i)), blend);//Apply motion parameters to result.xform[i] using blend parameter. + } + } + + //Use the un-padded original for blend=0 when creating a sequence. + //This keeps the original interpolation type intact. + if (seqFlag && blend == 0) + { + result = prealign[0]; + } + else + { + //Align what's going to be interpolated. + Interpolater::Align(prealign, spun, 2); + + spun[0].m_Time = 0; + spun[1].m_Time = 1; + + //Call this first to establish the asymmetric reference angles. + Interpolater::AsymmetricRefAngles(spun, 2); + + //Rotate the aligned xforms. + spun[0].RotateAffines(-blend * 360); + spun[1].RotateAffines(-blend * 360); + + Interpolater::Interpolate(spun, 2, m_Smooth ? Interpolater::Smoother(blend) : blend, m_Stagger, result); + } + + //Make sure there are no motion elements in the result. + result.DeleteMotionElements(); + } + + /// + /// Spin the specified ember, optionally apply a template ember, and place the output in result. + /// Create auto-generated name + /// Append edits using the Nick, Url and Id members. + /// Apply subpixel jitter to center using offset members. + /// + /// The ember to spin + /// The template to apply if not NULL, else ignore. + /// The result of the spin + /// The frame in the sequence to be stored in the m_Time member of result + /// The interpolation time + void Spin(Ember& parent, Ember* templ, Ember& result, int frame, T blend) + { + char temp[50]; + + //Spin the parent blend degrees. + Loop(parent, result, blend); + + //Apply the template if necessary. + if (templ) + ApplyTemplate(result, *templ); + + //Set ember parameters accordingly. + result.m_Time = T(frame); + result.m_Interp = EMBER_INTERP_LINEAR; + result.m_PaletteInterp = INTERP_HSV; + + //Create the edit doc xml. + sprintf_s(temp, 50, "rotate %g", blend * 360.0); + result.ClearEdit(); + result.m_Edits = m_EmberToXml.CreateNewEditdoc(&parent, NULL, temp, m_Nick, m_Url, m_Id, m_Comment, m_SheepGen, m_SheepId); + + //Subpixel jitter. + Offset(result, m_OffsetX, m_OffsetY); + + //Make the name of the flame the time. + sprintf_s(temp, 50, "%f", result.m_Time); + result.m_Name = string(temp); + } + + /// + /// Call Edge() on parents, optionally apply a template ember, and place the output in result. + /// Create auto-generated name + /// Append edits using the Nick, Url and Id members. + /// Apply subpixel jitter to center using offset members. + /// + /// The embers to interpolate + /// The template to apply if not NULL, else ignore. + /// The result of the spin + /// The frame in the sequence to be stored in the m_Time member of result + /// True if embers points to the first or last ember in the entire sequence, else false. + /// The interpolation time + void SpinInter(Ember* parents, Ember* templ, Ember& result, int frame, bool seqFlag, T blend) + { + char temp[50]; + + //Interpolate between spun parents. + Edge(parents, result, blend, seqFlag); + + //Original did an interpolated palette hack here for random palettes, but it was never used anywhere so ember omits it.//ORIG + + //Apply the template if necessary. + if (templ) + ApplyTemplate(result, *templ); + + //Set ember parameters accordingly. + result.m_Time = T(frame); + + //Create the edit doc xml. + sprintf_s(temp, 50, "interpolate %g", blend * 360.0); + result.ClearEdit(); + result.m_Edits = m_EmberToXml.CreateNewEditdoc(&parents[0], &parents[1], temp, m_Nick, m_Url, m_Id, m_Comment, m_SheepGen, m_SheepId); + + //Subpixel jitter. + Offset(result, m_OffsetX, m_OffsetY); + + //Make the name of the flame the time. + sprintf_s(temp, 50, "%f", result.m_Time); + result.m_Name = string(temp); + } + + /// + /// Apply a template to an ember. + /// + /// The ember to apply the template to + /// The template to apply + void ApplyTemplate(Ember& ember, Ember& templ) + { + //Check for invalid values - only replace those with valid ones. + for (unsigned int i = 0; i < 3; i++) + if (templ.m_Background[i] >= 0) + ember.m_Background[i] = templ.m_Background[i]; + + if (templ.m_Zoom < 999999998) + ember.m_Zoom = templ.m_Zoom; + + if (templ.m_Supersample > 0) + ember.m_Supersample = templ.m_Supersample; + + if (templ.m_SpatialFilterRadius >= 0) + ember.m_SpatialFilterRadius = templ.m_SpatialFilterRadius; + + if (templ.m_Quality > 0) + ember.m_Quality = templ.m_Quality; + + if (templ.m_Passes > 0) + ember.m_Passes = templ.m_Passes; + + if (templ.m_TemporalSamples > 0) + ember.m_TemporalSamples = templ.m_TemporalSamples; + + if (templ.m_FinalRasW > 0) + { + //Preserving scale should be an option. + ember.m_PixelsPerUnit = ember.m_PixelsPerUnit * templ.m_FinalRasW / ember.m_FinalRasW; + ember.m_FinalRasW = templ.m_FinalRasW; + } + + if (templ.m_FinalRasH > 0) + ember.m_FinalRasH = templ.m_FinalRasH; + + if (templ.m_MaxRadDE >= 0) + ember.m_MaxRadDE = templ.m_MaxRadDE; + + if (templ.m_MinRadDE >= 0) + ember.m_MinRadDE = templ.m_MinRadDE; + + if (templ.m_CurveDE >= 0) + ember.m_CurveDE = templ.m_CurveDE; + + if (templ.m_GammaThresh >= 0) + ember.m_GammaThresh = templ.m_GammaThresh; + + if (templ.m_Passes > 0) + ember.m_Passes = templ.m_Passes; + + if (templ.m_SpatialFilterType > 0) + ember.m_SpatialFilterType = templ.m_SpatialFilterType; + + if (templ.m_Interp >= 0) + ember.m_Interp = templ.m_Interp; + + if (templ.m_AffineInterp >= 0) + ember.m_AffineInterp = templ.m_AffineInterp; + + if (templ.m_TemporalFilterType >= 0) + ember.m_TemporalFilterType = templ.m_TemporalFilterType; + + if (templ.m_TemporalFilterWidth > 0) + ember.m_TemporalFilterWidth = templ.m_TemporalFilterWidth; + + if (templ.m_TemporalFilterExp > -900) + ember.m_TemporalFilterExp = templ.m_TemporalFilterExp; + + if (templ.m_HighlightPower >= 0) + ember.m_HighlightPower = templ.m_HighlightPower; + + if (templ.m_PaletteMode >= 0) + ember.m_PaletteMode = templ.m_PaletteMode; + } + + /// + /// Move the center of the ember by the specified amount. + /// + /// The ember to move + /// The x offset. + /// The y offset. + void Offset(Ember& ember, T offsetX, T offsetY) + { + if (!IsNearZero(offsetX)) + ember.m_CenterX += offsetX / (ember.m_PixelsPerUnit * ember.m_Supersample); + + if (!IsNearZero(offsetY)) + ember.m_CenterY += offsetY / (ember.m_PixelsPerUnit * ember.m_Supersample); + } + + /// + /// Translate the first center point by the second, rotate it, translate back. + /// + /// The new center x + /// The new center y + /// The old center x + /// The old center y + /// The angle to rotate by + void RotateOldCenterBy(T& newCenterX, T& newCenterY, T oldCenterX, T oldCenterY, T by) + { + T r[2]; + T th = by * 2 * T(M_PI) / 360; + T c = cos(th); + T s = -sin(th); + + newCenterX -= oldCenterX; + newCenterY -= oldCenterY; + r[0] = c * newCenterX - s * newCenterY; + r[1] = s * newCenterX + c * newCenterY; + newCenterX = r[0] + oldCenterX; + newCenterY = r[1] + oldCenterY; + } + + /// + /// Find a 2D bounding box that does not enclose eps of the fractal density in each compass direction. + /// This will run the inner loops of iteration without all of the surrounding interpolation and filtering. + /// + /// The ember to iterate + /// The eps + /// The number samples to iterate + /// The bmin[0] and bmin[1] will be the minimum x and y values. + /// The bmax[0] and bmax[1] will be the maximum x and y values. + /// The number of iterations ran + unsigned __int64 EstimateBoundingBox(Ember& ember, T eps, unsigned int samples, T* bmin, T* bmax) + { + unsigned int i, lowTarget, highTarget; + T min[2], max[2]; + + if (ember.XaosPresent()) + m_Iterator = m_XaosIterator.get(); + else + m_Iterator = m_StandardIterator.get(); + + m_Iterator->InitDistributions(ember); + m_Samples.resize(samples); + + unsigned __int64 bv = m_Iterator->Iterate(ember, samples, 20, m_Samples.data(), m_Rand);//Use a special fuse of 20, all other calls to this will use 15, or 100. + + if (bv / T(samples) > eps) + eps = 3 * bv / T(samples); + + if (eps > T(0.3)) + eps = T(0.3); + + lowTarget = (int)(samples * eps); + highTarget = samples - lowTarget; + + min[0] = min[1] = 1e10; + max[0] = max[1] = -1e10; + + for (i = 0; i < samples; i++) + { + if (m_Samples[i].m_X < min[0]) min[0] = m_Samples[i].m_X; + if (m_Samples[i].m_Y < min[1]) min[1] = m_Samples[i].m_Y; + if (m_Samples[i].m_X > max[0]) max[0] = m_Samples[i].m_X; + if (m_Samples[i].m_Y > max[1]) max[1] = m_Samples[i].m_Y; + } + + if (lowTarget == 0) + { + bmin[0] = min[0]; + bmin[1] = min[1]; + bmax[0] = max[0]; + bmax[1] = max[1]; + + return bv; + } + + std::sort(m_Samples.begin(), m_Samples.end(), &SortPointByX); + bmin[0] = m_Samples[lowTarget].m_X; + bmax[0] = m_Samples[highTarget].m_X; + + std::sort(m_Samples.begin(), m_Samples.end(), &SortPointByY); + bmin[1] = m_Samples[lowTarget + 1].m_Y; + bmax[1] = m_Samples[highTarget + 1].m_Y; + + return bv; + } + + /// + /// When doing spin or edge, an edit doc is made to record what was done. + /// Doing so takes many extra parameters such as name and url. Passing these every + /// time is cumbersome and are unlikely to change for the duration of a program run, so + /// they are made to be member variables. After setting these, their values will be used + /// in all edits within this class. + /// + /// Use smoothing if true, else false + /// Use stagger if > 0, else false + /// X amount of subpixel jitter to apply in Spin() and Edge() + /// Y amount of subpixel jitter to apply in Spin() and Edge() + /// The nickname of the author + /// The Url of the author + /// The id of the author + /// The comment to include + /// The sheep generation used if > 0. Default: 0. + /// The sheep id used if > 0. Default: 0. + void SetSpinParams(bool smooth, T stagger, T offsetX, T offsetY, string nick, string url, string id, string comment, int sheepGen, int sheepId) + { + m_Smooth = smooth; + m_SheepGen = sheepGen; + m_SheepId = sheepId; + m_Stagger = stagger; + m_OffsetX = offsetX; + m_OffsetY = offsetY; + m_Nick = nick; + m_Url = url; + m_Id = id; + m_Comment = comment; + } + +private: + bool m_Smooth; + int m_SheepGen; + int m_SheepId; + T m_Stagger; + T m_OffsetX; + T m_OffsetY; + string m_Nick; + string m_Url; + string m_Id; + string m_Comment; + + vector> m_Samples; + vector m_FinalImage; + vector m_Hist; + EmberToXml m_EmberToXml; + Iterator* m_Iterator; + auto_ptr> m_StandardIterator; + auto_ptr> m_XaosIterator; + auto_ptr> m_Renderer; + QTIsaac m_Rand; + PaletteList m_PaletteList; + VariationList m_VariationList; +}; +} \ No newline at end of file diff --git a/Source/Ember/SpatialFilter.h b/Source/Ember/SpatialFilter.h new file mode 100644 index 0000000..213e208 --- /dev/null +++ b/Source/Ember/SpatialFilter.h @@ -0,0 +1,909 @@ +#pragma once + +#include "EmberDefines.h" + +/// +/// SpatialFilter base, derived and factory classes. +/// + +namespace EmberNs +{ +/// +/// The types of spatial filters available. +/// +enum eSpatialFilterType +{ + GAUSSIAN_SPATIAL_FILTER = 0, + HERMITE_SPATIAL_FILTER = 1, + BOX_SPATIAL_FILTER = 2, + TRIANGLE_SPATIAL_FILTER = 3, + BELL_SPATIAL_FILTER = 4, + BSPLINE_SPATIAL_FILTER = 5, + LANCZOS3_SPATIAL_FILTER = 6, + LANCZOS2_SPATIAL_FILTER = 7, + MITCHELL_SPATIAL_FILTER = 8, + BLACKMAN_SPATIAL_FILTER = 9, + CATROM_SPATIAL_FILTER = 10, + HAMMING_SPATIAL_FILTER = 11, + HANNING_SPATIAL_FILTER = 12, + QUADRATIC_SPATIAL_FILTER = 13 +}; + +/// +/// Spatial filtering is done in the final accumulation stage to add some additional +/// bluring to smooth out noisy areas. +/// The bulk of the work is done in this base class Create() function. +/// Because it calls the virtual Filter() function, it cannot be automatically called in the constructor. +/// So the caller must manually call it after constructing the filter object. +/// Each derived class will implement an override of Filter() which +/// contains the specific filter calculation for the algorithm whose name the class matches. +/// Template argument expected to be float or double. +/// +template +class EMBER_API SpatialFilter +{ +public: + /// + /// Assign basic parameters for creating a spatial filter. The caller must still call Create(). + /// + /// Type of filter to create + /// Miscellaneous value + /// The filter radius + /// The supersample of the ember being rendered + /// The pixel aspect ratio being used to render. Default: 1. + SpatialFilter(eSpatialFilterType filterType, T support, T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0)) + { + m_FilterType = filterType; + m_Support = support; + m_FilterRadius = filterRadius; + m_Supersample = superSample; + m_PixelAspectRatio = pixelAspectRatio; + //Sadly, cannot call create here because it calls the Filter() virtual function and unlike C#, the vtables + //are not yet set up in C++ constructors. The code that instantiates this object must explicitly call Create(). + } + + /// + /// Copy constructor. + /// + /// The SpatialFilter object to copy + SpatialFilter(const SpatialFilter& filter) + { + *this = filter; + } + + /// + /// Virtual destructor so derived class destructors get called. + /// + virtual ~SpatialFilter() + { + } + + /// + /// Assignment operator. + /// + /// The SpatialFilter object to copy. + /// Reference to updated self + SpatialFilter& operator = (const SpatialFilter& filter) + { + if (this != &filter) + { + m_FinalFilterWidth = filter.m_FinalFilterWidth; + m_Supersample = filter.m_Supersample; + m_Support = filter.m_Support; + m_FilterRadius = filter.m_FilterRadius; + m_PixelAspectRatio = filter.m_PixelAspectRatio; + m_FilterType = filter.m_FilterType; + m_Filter = filter.m_Filter; + } + + return *this; + } + + /// + /// Allocates and populates the filter buffer with virtual calls to derived Filter() functions. + /// The caller must manually call this after construction. + /// + void Create() + { + T fw = T(2.0) * m_Support * m_Supersample * m_FilterRadius / m_PixelAspectRatio; + T adjust, ii, jj; + + int fwidth = ((int)fw) + 1; + int i, j; + + //Make sure the filter kernel has same parity as oversample. + if ((fwidth ^ m_Supersample) & 1) + fwidth++; + + //Calculate the coordinate scaling factor for the kernel values. + if (fw > 0.0) + adjust = m_Support * fwidth / fw; + else + adjust = T(1.0); + + m_Filter.resize(fwidth * fwidth); + + //Fill in the coefs. + for (i = 0; i < fwidth; i++) + { + for (j = 0; j < fwidth; j++) + { + //Calculate the function inputs for the kernel function. + ii = ((T(2.0) * i + T(1.0)) / T(fwidth) - T(1.0)) * adjust; + jj = ((T(2.0) * j + T(1.0)) / T(fwidth) - T(1.0)) * adjust; + + //Adjust for aspect ratio. + jj /= m_PixelAspectRatio; + + m_Filter[i + j * fwidth] = Filter(ii) * Filter(jj);//Call virtual Filter(), implemented in specific derived filter classes. + } + } + + //Normalize, and return a bad value if the values were too small. + if (!Normalize()) + m_FinalFilterWidth = -1; + else + m_FinalFilterWidth = fwidth; + } + + /// + /// Return a string representation of this filter. + /// + /// The string representation of this filter + string ToString() const + { + unsigned int i; + stringstream ss; + + ss + << "Spatial Filter:" << endl + << " Support: " << m_Support << endl + << " Filter radius: " << m_FilterRadius << endl + << " Supersample: " << m_Supersample << endl + << "Pixel aspect ratio: " << m_PixelAspectRatio << endl + << "Final filter width: " << m_FinalFilterWidth << endl + << "Filter buffer size: " << m_Filter.size() << endl; + + ss << "Filter: " << endl; + + for (i = 0; i < m_Filter.size(); i++) + { + ss << "Filter[" << i << "]: " << m_Filter[i] << endl; + } + + return ss.str(); + } + + /// + /// Accessors. + /// + void Apply() { } + inline int FinalFilterWidth() const { return m_FinalFilterWidth; } + inline unsigned int Supersample() const { return m_Supersample; } + inline unsigned int BufferSize() const { return (unsigned int)m_Filter.size(); } + inline unsigned int BufferSizeBytes() const { return BufferSize() * sizeof(T); } + inline T Support() const { return m_Support; } + inline T FilterRadius() const { return m_FilterRadius; } + inline T PixelAspectRatio() const { return m_PixelAspectRatio; } + inline eSpatialFilterType FilterType() const { return m_FilterType; } + inline T* Filter() { return m_Filter.data(); } + inline const T& operator[] (size_t index) const { return m_Filter[index]; } + virtual T Filter(T t) const = 0; + +protected: + /// + /// Calculation function used in Lanczos filters. + /// + /// The x + /// The calculated value + static T Sinc(T x) + { + x *= T(M_PI); + + if (x != 0) + return sin(x) / x; + + return 1.0; + } + +private: + /// + /// Normalize all filter values. + /// + /// True if any value was non-zero, else false if all were zero. + bool Normalize() + { + size_t i; + T t = T(0.0); + + for (i = 0; i < m_Filter.size(); i++) + t += m_Filter[i]; + + if (t == 0.0) + return false; + + t = T(1.0) / t; + + for (i = 0; i < m_Filter.size(); i++) + m_Filter[i] *= t; + + return true; + } + + int m_FinalFilterWidth;//The final width that the filter ends up being. + unsigned int m_Supersample;//The supersample value of the ember using this filter to render. + T m_Support;//Extra value. + T m_FilterRadius;//The requested filter radius. + T m_PixelAspectRatio;//The aspect ratio of the ember using this filter to render, usually 1. + eSpatialFilterType m_FilterType;//The type of filter this is. + vector m_Filter;//The vector holding the calculated filter values. +}; + +/// +/// Derivation for Gaussian filter. +/// +template +class EMBER_API GaussianFilter : public SpatialFilter +{ +public: + /// + /// Constructor which does nothing but pass values to the base class. The caller must still call Create(). + /// Support = 1.5. + /// + /// The filter radius + /// The supersample of the ember being rendered + /// The pixel aspect ratio being used to render. Default: 1. + GaussianFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0)) + : SpatialFilter(GAUSSIAN_SPATIAL_FILTER, T(1.5), filterRadius, superSample, pixelAspectRatio) { } + + /// + /// Apply Gaussian filter to t parameter and return. + /// + /// The value to apply the filter to + /// The filtered value + virtual T Filter(T t) const + { + return exp(-2 * t * t) * sqrt(2 / T(M_PI)); + } +}; + +/// +/// Derivation for Hermite filter. +/// +template +class EMBER_API HermiteFilter : public SpatialFilter +{ +public: + /// + /// Constructor which does nothing but pass values to the base class. The caller must still call Create(). + /// Support = 1. + /// + /// The filter radius + /// The supersample of the ember being rendered + /// The pixel aspect ratio being used to render. Default: 1. + HermiteFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0)) + : SpatialFilter(HERMITE_SPATIAL_FILTER, T(1.0), filterRadius, superSample, pixelAspectRatio) { } + + /// + /// Apply Hermite filter to t parameter and return. + /// f(t) = 2|t|^3 - 3|t|^2 + 1, -1 <= t <= 1. + /// + /// The value to apply the filter to + /// The filtered value + virtual T Filter(T t) const + { + if (t < 0) + t = -t; + + if (t < 1) + return ((T(2.0) * t - T(3.0)) * t * t + T(1.0)); + + return 0; + } +}; + +/// +/// Derivation for Box filter. +/// +template +class EMBER_API BoxFilter : public SpatialFilter +{ +public: + /// + /// Constructor which does nothing but pass values to the base class. The caller must still call Create(). + /// Support = 0.5. + /// + /// The filter radius + /// The supersample of the ember being rendered + /// The pixel aspect ratio being used to render. Default: 1. + BoxFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0)) + : SpatialFilter(BOX_SPATIAL_FILTER, T(0.5), filterRadius, superSample, pixelAspectRatio) { } + + /// + /// Apply Box filter to t parameter and return. + /// + /// The value to apply the filter to + /// The filtered value + virtual T Filter(T t) const + { + if ((t > T(-0.5)) && (t <= T(0.5))) + return 1; + + return 0; + } +}; + +/// +/// Derivation for Triangle filter. +/// +template +class EMBER_API TriangleFilter : public SpatialFilter +{ +public: + /// + /// Constructor which does nothing but pass values to the base class. The caller must still call Create(). + /// Support = 1. + /// + /// The filter radius + /// The supersample of the ember being rendered + /// The pixel aspect ratio being used to render. Default: 1. + TriangleFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0)) + : SpatialFilter(TRIANGLE_SPATIAL_FILTER, T(1.0), filterRadius, superSample, pixelAspectRatio) { } + + /// + /// Apply Triangle filter to t parameter and return. + /// + /// The value to apply the filter to + /// The filtered value + virtual T Filter(T t) const + { + if (t < 0) + t = -t; + + if (t < 1) + return 1 - t; + + return 0; + } +}; + +/// +/// Derivation for Bell filter. +/// +template +class EMBER_API BellFilter : public SpatialFilter +{ +public: + /// + /// Constructor which does nothing but pass values to the base class. The caller must still call Create(). + /// Support = 1.5. + /// + /// The filter radius + /// The supersample of the ember being rendered + /// The pixel aspect ratio being used to render. Default: 1. + BellFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0)) + : SpatialFilter(BELL_SPATIAL_FILTER, T(1.5), filterRadius, superSample, pixelAspectRatio) { } + + /// + /// Apply Bell filter to t parameter and return. + /// + /// The value to apply the filter to + /// The filtered value + virtual T Filter(T t) const + { + //box (*) box (*) box. + if (t < 0) + t = -t; + + if (t < T(0.5)) + return (T(0.75) - (t * t)); + + if (t < T(1.5)) + { + t = (t - T(1.5)); + return (T(0.5) * (t * t)); + } + + return 0; + } +}; + +/// +/// Derivation for B Spline filter. +/// +template +class EMBER_API BsplineFilter : public SpatialFilter +{ +public: + /// + /// Constructor which does nothing but pass values to the base class. The caller must still call Create(). + /// Support = 2. + /// + /// The filter radius + /// The supersample of the ember being rendered + /// The pixel aspect ratio being used to render. Default: 1. + BsplineFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0)) + : SpatialFilter(BSPLINE_SPATIAL_FILTER, T(2.0), filterRadius, superSample, pixelAspectRatio) { } + + /// + /// Apply B Spline filter to t parameter and return. + /// + /// The value to apply the filter to + /// The filtered value + virtual T Filter(T t) const + { + //box (*) box (*) box (*) box. + T tt; + + if (t < 0) + t = -t; + + if (t < 1) + { + tt = t * t; + return ((T(0.5) * tt * t) - tt + (T(2.0) / T(3.0))); + } + else if (t < 2) + { + t = 2 - t; + return ((T(1.0) / T(6.0)) * (t * t * t)); + } + + return 0; + } +}; + +/// +/// Derivation for Lanczos 3 filter. +/// +template +class EMBER_API Lanczos3Filter : public SpatialFilter +{ +public: + /// + /// Constructor which does nothing but pass values to the base class. The caller must still call Create(). + /// Support = 3. + /// + /// The filter radius + /// The supersample of the ember being rendered + /// The pixel aspect ratio being used to render. Default: 1. + Lanczos3Filter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0)) + : SpatialFilter(LANCZOS3_SPATIAL_FILTER, T(3.0), filterRadius, superSample, pixelAspectRatio) { } + + /// + /// Apply Lanczos 3 filter to t parameter and return. + /// + /// The value to apply the filter to + /// The filtered value + virtual T Filter(T t) const + { + if (t < 0) + t = -t; + + if (t < 3) + return Sinc(t) * Sinc(t / 3); + + return 0; + } +}; + +/// +/// Derivation for Lanczos 2 filter. +/// +template +class EMBER_API Lanczos2Filter : public SpatialFilter +{ +public: + /// + /// Constructor which does nothing but pass values to the base class. The caller must still call Create(). + /// Support = 2. + /// + /// The filter radius + /// The supersample of the ember being rendered + /// The pixel aspect ratio being used to render. Default: 1. + Lanczos2Filter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0)) + : SpatialFilter(LANCZOS2_SPATIAL_FILTER, T(2.0), filterRadius, superSample, pixelAspectRatio) { } + + /// + /// Apply Lanczos 2 filter to t parameter and return. + /// + /// The value to apply the filter to + /// The filtered value + virtual T Filter(T t) const + { + if (t < 0) + t = -t; + + if (t < 2) + return Sinc(t) * Sinc(t / 2); + + return 0; + } +}; + +/// +/// Derivation for Mitchell filter. +/// +template +class EMBER_API MitchellFilter : public SpatialFilter +{ +public: + /// + /// Constructor which does nothing but pass values to the base class. The caller must still call Create(). + /// Support = 2. + /// + /// The filter radius + /// The supersample of the ember being rendered + /// The pixel aspect ratio being used to render. Default: 1. + MitchellFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0)) + : SpatialFilter(MITCHELL_SPATIAL_FILTER, T(2.0), filterRadius, superSample, pixelAspectRatio) { } + + /// + /// Apply Mitchell filter to t parameter and return. + /// + /// The value to apply the filter to + /// The filtered value + virtual T Filter(T t) const + { + T tt = t * t; + const T b = T(1) / T(3); + const T c = T(1) / T(3); + + if (t < 0) + t = -t; + + if (t < 1) + { + t = (((T(12.0) - T(9.0) * b - T(6.0) * c) * (t * tt)) + + ((T(-18.0) + T(12.0) * b + T(6.0) * c) * tt) + + (T(6.0) - 2 * b)); + + return t / 6; + } + else if (t < 2) + { + t = (((T(-1.0) * b - T(6.0) * c) * (t * tt)) + + ((T(6.0) * b + T(30.0) * c) * tt) + + ((T(-12.0) * b - T(48.0) * c) * t) + + (T(8.0) * b + 24 * c)); + + return t / 6; + } + + return 0; + } +}; + +/// +/// Derivation for Blackman filter. +/// +template +class EMBER_API BlackmanFilter : public SpatialFilter +{ +public: + /// + /// Constructor which does nothing but pass values to the base class. The caller must still call Create(). + /// Support = 1. + /// + /// The filter radius + /// The supersample of the ember being rendered + /// The pixel aspect ratio being used to render. Default: 1. + BlackmanFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0)) + : SpatialFilter(BLACKMAN_SPATIAL_FILTER, T(1.0), filterRadius, superSample, pixelAspectRatio) { } + + /// + /// Apply Blackman filter to t parameter and return. + /// + /// The value to apply the filter to + /// The filtered value + virtual T Filter(T t) const + { + return (T(0.42) + T(0.5) * cos(T(M_PI) * t) + T(0.08) * cos(2 * T(M_PI) * t)); + } +}; + +/// +/// Derivation for Catmull-Rom filter. +/// +template +class EMBER_API CatromFilter : public SpatialFilter +{ +public: + /// + /// Constructor which does nothing but pass values to the base class. The caller must still call Create(). + /// Support = 2. + /// + /// The filter radius + /// The supersample of the ember being rendered + /// The pixel aspect ratio being used to render. Default: 1. + CatromFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0)) + : SpatialFilter(CATROM_SPATIAL_FILTER, T(2.0), filterRadius, superSample, pixelAspectRatio) { } + + /// + /// Apply Catmull-Rom filter to t parameter and return. + /// + /// The value to apply the filter to + /// The filtered value + virtual T Filter(T t) const + { + if (t < 0) + return 0; + + if (t < -1) + return T(0.5) * (4 + t * (8 + t * (5 + t))); + + if (t < 0) + return T(0.5) * (2 + t * t * (-5 - 3 * t)); + + if (t < 1) + return T(0.5) * (2 + t * t * (-5 + 3 * t)); + + if (t < 2) + return T(0.5) * (4 + t * (-8 + t * (5 - t))); + + return 0; + } +}; + +/// +/// Derivation for Hamming filter. +/// +template +class EMBER_API HammingFilter : public SpatialFilter +{ +public: + /// + /// Constructor which does nothing but pass values to the base class. The caller must still call Create(). + /// Support = 1. + /// + /// The filter radius + /// The supersample of the ember being rendered + /// The pixel aspect ratio being used to render. Default: 1. + HammingFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0)) + : SpatialFilter(HAMMING_SPATIAL_FILTER, T(1.0), filterRadius, superSample, pixelAspectRatio) { } + + /// + /// Apply Hamming filter to t parameter and return. + /// + /// The value to apply the filter to + /// The filtered value + virtual T Filter(T t) const + { + return T(0.54) + T(0.46) * cos(T(M_PI) * t); + } +}; + +/// +/// Derivation for Hanning filter. +/// +template +class EMBER_API HanningFilter : public SpatialFilter +{ +public: + /// + /// Constructor which does nothing but pass values to the base class. The caller must still call Create(). + /// Support = 1. + /// + /// The filter radius + /// The supersample of the ember being rendered + /// The pixel aspect ratio being used to render. Default: 1. + HanningFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0)) + : SpatialFilter(HANNING_SPATIAL_FILTER, T(1.0), filterRadius, superSample, pixelAspectRatio) { } + + /// + /// Apply Hanning filter to t parameter and return. + /// + /// The value to apply the filter to + /// The filtered value + virtual T Filter(T t) const + { + return T(0.5) + T(0.5) * cos(T(M_PI) * t); + } +}; + +/// +/// Derivation for Quadratic filter. +/// +template +class EMBER_API QuadraticFilter : public SpatialFilter +{ +public: + /// + /// Constructor which does nothing but pass values to the base class. The caller must still call Create(). + /// Support = 1.5. + /// + /// The filter radius + /// The supersample of the ember being rendered + /// The pixel aspect ratio being used to render. Default: 1. + QuadraticFilter(T filterRadius, unsigned int superSample, T pixelAspectRatio = T(1.0)) + : SpatialFilter(QUADRATIC_SPATIAL_FILTER, T(1.5), filterRadius, superSample, pixelAspectRatio) { } + + /// + /// Apply Quadratic filter to t parameter and return. + /// + /// The value to apply the filter to + /// The filtered value + virtual T Filter(T t) const + { + if (t < -1.5) + return 0.0; + + if (t < -0.5) + return T(0.5) * (t + T(1.5)) * (t + T(1.5)); + + if (t < 0.5) + return T(0.75) - (t * t); + + if (t < 1.5) + return T(0.5) * (t - T(1.5)) * (t - T(1.5)); + + return 0; + } +}; + +/// +/// Convenience class to assist in converting between filter names and the filter objects themselves. +/// +template +class EMBER_API SpatialFilterCreator +{ +public: + /// + /// Creates the specified filter type based on the filterType enum parameter. + /// + /// Type of the filter + /// The filter radius + /// The supersample value of the ember using this filter to render + /// The aspect ratio of the ember using this filter to render. Default: 1. + /// A pointer to the newly created filter object + static SpatialFilter* Create(eSpatialFilterType filterType, T filterRadius, unsigned int superSample, T pixelAspectRatio = 1) + { + SpatialFilter* filter = NULL; + + if (filterType == GAUSSIAN_SPATIAL_FILTER) + filter = new GaussianFilter(filterRadius, superSample, pixelAspectRatio); + else if (filterType == HERMITE_SPATIAL_FILTER) + filter = new HermiteFilter(filterRadius, superSample, pixelAspectRatio); + else if (filterType == BOX_SPATIAL_FILTER) + filter = new BoxFilter(filterRadius, superSample, pixelAspectRatio); + else if (filterType == TRIANGLE_SPATIAL_FILTER) + filter = new TriangleFilter(filterRadius, superSample, pixelAspectRatio); + else if (filterType == BELL_SPATIAL_FILTER) + filter = new BellFilter(filterRadius, superSample, pixelAspectRatio); + else if (filterType == BSPLINE_SPATIAL_FILTER) + filter = new BsplineFilter(filterRadius, superSample, pixelAspectRatio); + else if (filterType == LANCZOS3_SPATIAL_FILTER) + filter = new Lanczos3Filter(filterRadius, superSample, pixelAspectRatio); + else if (filterType == LANCZOS2_SPATIAL_FILTER) + filter = new Lanczos2Filter(filterRadius, superSample, pixelAspectRatio); + else if (filterType == MITCHELL_SPATIAL_FILTER) + filter = new MitchellFilter(filterRadius, superSample, pixelAspectRatio); + else if (filterType == BLACKMAN_SPATIAL_FILTER) + filter = new BlackmanFilter(filterRadius, superSample, pixelAspectRatio); + else if (filterType == CATROM_SPATIAL_FILTER) + filter = new CatromFilter(filterRadius, superSample, pixelAspectRatio); + else if (filterType == HAMMING_SPATIAL_FILTER) + filter = new HammingFilter(filterRadius, superSample, pixelAspectRatio); + else if (filterType == HANNING_SPATIAL_FILTER) + filter = new HanningFilter(filterRadius, superSample, pixelAspectRatio); + else if (filterType == QUADRATIC_SPATIAL_FILTER) + filter = new QuadraticFilter(filterRadius, superSample, pixelAspectRatio); + else + filter = new GaussianFilter(filterRadius, superSample, pixelAspectRatio); + + if (filter) + filter->Create(); + + return filter; + } + + /// + /// Return a string vector of the available filter types. + /// + /// A vector of strings populated with the available filter types + static vector FilterTypes() + { + vector v; + + v.reserve(14); + v.push_back("Gaussian"); + v.push_back("Hermite"); + v.push_back("Box"); + v.push_back("Triangle"); + v.push_back("Bell"); + v.push_back("Bspline"); + v.push_back("Lanczos3"); + v.push_back("Lanczos2"); + v.push_back("Mitchell"); + v.push_back("Blackman"); + v.push_back("Catrom"); + v.push_back("Hamming"); + v.push_back("Hanning"); + v.push_back("Quadratic"); + + return v; + } + + /// + /// Convert between the filter name string and its type enum. + /// + /// The string name of the filter + /// The filter type enum + static eSpatialFilterType FromString(string filterType) + { + if (!_stricmp(filterType.c_str(), "Gaussian")) + return GAUSSIAN_SPATIAL_FILTER; + else if (!_stricmp(filterType.c_str(), "Hermite")) + return HERMITE_SPATIAL_FILTER; + else if (!_stricmp(filterType.c_str(), "Box")) + return BOX_SPATIAL_FILTER; + else if (!_stricmp(filterType.c_str(), "Triangle")) + return TRIANGLE_SPATIAL_FILTER; + else if (!_stricmp(filterType.c_str(), "Bell")) + return BELL_SPATIAL_FILTER; + else if (!_stricmp(filterType.c_str(), "Bspline")) + return BSPLINE_SPATIAL_FILTER; + else if (!_stricmp(filterType.c_str(), "Lanczos3")) + return LANCZOS3_SPATIAL_FILTER; + else if (!_stricmp(filterType.c_str(), "Lanczos2")) + return LANCZOS2_SPATIAL_FILTER; + else if (!_stricmp(filterType.c_str(), "Mitchell")) + return MITCHELL_SPATIAL_FILTER; + else if (!_stricmp(filterType.c_str(), "Blackman")) + return BLACKMAN_SPATIAL_FILTER; + else if (!_stricmp(filterType.c_str(), "Catrom")) + return CATROM_SPATIAL_FILTER; + else if (!_stricmp(filterType.c_str(), "Hamming")) + return HAMMING_SPATIAL_FILTER; + else if (!_stricmp(filterType.c_str(), "Hanning")) + return HANNING_SPATIAL_FILTER; + else if (!_stricmp(filterType.c_str(), "Quadratic")) + return QUADRATIC_SPATIAL_FILTER; + else + return GAUSSIAN_SPATIAL_FILTER; + } + + /// + /// Convert between the filter type enum and its name string. + /// + /// The filter type enum + /// The string name of the filter + static string ToString(eSpatialFilterType filterType) + { + string filter; + + if (filterType == GAUSSIAN_SPATIAL_FILTER) + filter = "Gaussian"; + else if (filterType == HERMITE_SPATIAL_FILTER) + filter = "Hermite"; + else if (filterType == BOX_SPATIAL_FILTER) + filter = "Box"; + else if (filterType == TRIANGLE_SPATIAL_FILTER) + filter = "Triangle"; + else if (filterType == BELL_SPATIAL_FILTER) + filter = "Bell"; + else if (filterType == BSPLINE_SPATIAL_FILTER) + filter = "Bspline"; + else if (filterType == LANCZOS3_SPATIAL_FILTER) + filter = "Lanczos3"; + else if (filterType == LANCZOS2_SPATIAL_FILTER) + filter = "Lanczos2"; + else if (filterType == MITCHELL_SPATIAL_FILTER) + filter = "Mitchell"; + else if (filterType == BLACKMAN_SPATIAL_FILTER) + filter = "Blackman"; + else if (filterType == CATROM_SPATIAL_FILTER) + filter = "Catrom"; + else if (filterType == HAMMING_SPATIAL_FILTER) + filter = "Hamming"; + else if (filterType == HANNING_SPATIAL_FILTER) + filter = "Hanning"; + else if (filterType == QUADRATIC_SPATIAL_FILTER) + filter = "Quadratic"; + else + filter = "Gaussian"; + + return filter; + } +}; +} \ No newline at end of file diff --git a/Source/Ember/TemporalFilter.h b/Source/Ember/TemporalFilter.h new file mode 100644 index 0000000..e4d8e93 --- /dev/null +++ b/Source/Ember/TemporalFilter.h @@ -0,0 +1,347 @@ +#pragma once + +#include "EmberDefines.h" + +/// +/// TemporalFilter base, derived and factory classes. +/// + +namespace EmberNs +{ +/// +/// The types of temporal filters available. +/// +enum eTemporalFilterType +{ + BOX_TEMPORAL_FILTER = 0, + GAUSSIAN_TEMPORAL_FILTER = 1, + EXP_TEMPORAL_FILTER = 2 +}; + +/// +/// Temporal filter is for doing motion blur while rendering a series of frames for animation. +/// The filter created is used as a vector of scalar values to multiply the time value by in between embers. +/// There are three possible types: Gaussian, Box and Exp. +/// Template argument expected to be float or double. +/// +template +class EMBER_API TemporalFilter +{ +public: + /// + /// Constructor to set up basic filtering parameters, allocate buffers and calculate deltas. + /// Derived class constructors will complete the final part of filter setup. + /// + /// Type of the filter. + /// The number of passes used in the ember being rendered + /// The number of temporal samples in the ember being rendered + /// The width of the filter. + TemporalFilter(eTemporalFilterType filterType, unsigned int passes, unsigned int temporalSamples, T filterWidth) + { + unsigned int i, steps = passes * temporalSamples; + + m_Deltas.resize(steps); + m_Filter.resize(steps); + m_FilterType = filterType; + + if (steps == 1) + { + m_SumFilt = 1; + m_Deltas[0] = 0; + m_Filter[0] = 1; + } + else + { + //Define the temporal deltas. + for (i = 0; i < steps; i++) + m_Deltas[i] = (T(i) / T(steps - 1) - T(0.5)) * filterWidth; + } + } + + /// + /// Copy constructor. + /// + /// The TemporalFilter object to copy + TemporalFilter(const TemporalFilter& filter) + { + *this = filter; + } + + /// + /// Virtual destructor so derived class destructors get called. + /// + virtual ~TemporalFilter() + { + } + + /// + /// Assignment operator. + /// + /// The TemporalFilter object to copy. + /// Reference to updated self + TemporalFilter& operator = (const TemporalFilter& filter) + { + if (this != &filter) + { + m_SumFilt = filter.m_SumFilt; + m_Deltas = filter.m_Deltas; + m_Filter = filter.m_Filter; + m_FilterType = filter.m_FilterType; + } + + return *this; + } + + /// + /// Return a string representation of this filter. + /// + /// The string representation of this filter + string ToString() const + { + unsigned int i; + stringstream ss; + + ss << "Temporal Filter:" << endl + << " Size: " << Size() << endl + << " Type: " << TemporalFilterCreator::ToString(m_FilterType) << endl + << " Sum Filt: " << SumFilt() << endl; + + ss << "Deltas: " << endl; + + for (i = 0; i < m_Deltas.size(); i++) + { + ss << "Deltas[" << i << "]: " << m_Deltas[i] << endl; + } + + ss << "Filter: " << endl; + + for (i = 0; i < m_Filter.size(); i++) + { + ss << "Filter[" << i << "]: " << m_Filter[i] << endl; + } + + return ss.str(); + } + + /// + /// Accessors. + /// + size_t Size() const { return m_Filter.size(); } + T SumFilt() const { return m_SumFilt; } + T* Deltas() { return &m_Deltas[0]; } + T* Filter() { return &m_Filter[0]; } + eTemporalFilterType FilterType() const { return m_FilterType; } + +protected: + /// + /// Normalize the filter and the sum filt. + /// + /// The maximum filter value contained in the filter vector after it was created + void FinishFilter(T maxFilt) + { + m_SumFilt = 0; + + for (unsigned int i = 0; i < Size(); i++) + { + m_Filter[i] /= maxFilt; + m_SumFilt += m_Filter[i]; + } + + m_SumFilt /= Size(); + } + + T m_SumFilt;//The sum of all filter values. + vector m_Deltas;//Delta vector. + vector m_Filter;//Filter vector. + eTemporalFilterType m_FilterType;//The type of filter this is. +}; + +/// +/// Derivation which implements the Exp filter. +/// +template +class EMBER_API ExpTemporalFilter : public TemporalFilter +{ +public: + /// + /// Constructor to create an Exp filter. + /// + /// The number of passes used in the ember being rendered + /// The number of temporal samples in the ember being rendered + /// The width of the filter. + /// The filter exp. + ExpTemporalFilter(unsigned int passes, unsigned int temporalSamples, T filterWidth, T filterExp) + : TemporalFilter(BOX_TEMPORAL_FILTER, passes, temporalSamples, filterWidth) + { + if (Size() > 1) + { + T slpx, maxFilt = 0; + + for (unsigned int i = 0; i < Size(); i++) + { + if (filterExp >= 0) + slpx = (T(i) + 1) / Size(); + else + slpx = T(Size() - i) / Size(); + + //Scale the color based on these values. + m_Filter[i] = pow(slpx, fabs(filterExp)); + + //Keep the max. + if (m_Filter[i] > maxFilt) + maxFilt = m_Filter[i]; + } + + FinishFilter(maxFilt); + } + } +}; + +/// +/// Derivation which implements the Gaussian filter. +/// +template +class EMBER_API GaussianTemporalFilter : public TemporalFilter +{ +public: + /// + /// Constructor to create a Gaussian filter. + /// + /// The number of passes used in the ember being rendered + /// The number of temporal samples in the ember being rendered + /// The width of the filter. + GaussianTemporalFilter(unsigned int passes, unsigned int temporalSamples, T filterWidth) + : TemporalFilter(GAUSSIAN_TEMPORAL_FILTER, passes, temporalSamples, filterWidth) + { + if (Size() > 1) + { + T maxFilt = 0, halfSteps = T(Size()) / T(2); + GaussianFilter gaussian(1, 1);//Just pass dummy values, they are unused in this case. + + for (unsigned int i = 0; i < Size(); i++) + { + m_Filter[i] = gaussian.Filter(gaussian.Support() * fabs(i - halfSteps) / halfSteps); + + //Keep the max. + if (m_Filter[i] > maxFilt) + maxFilt = m_Filter[i]; + } + + FinishFilter(maxFilt); + } + } +}; + +/// +/// Derivation which implements the Box filter. +/// +template +class EMBER_API BoxTemporalFilter : public TemporalFilter +{ +public: + /// + /// Constructor to create a Box filter. + /// + /// The number of passes used in the ember being rendered + /// The number of temporal samples in the ember being rendered + /// The width of the filter. + BoxTemporalFilter(unsigned int passes, unsigned int temporalSamples, T filterWidth) + : TemporalFilter(BOX_TEMPORAL_FILTER, passes, temporalSamples, filterWidth) + { + if (Size() > 1) + { + for (unsigned int i = 0; i < Size(); i++) + m_Filter[i] = 1; + + FinishFilter(1); + } + } +}; + +/// +/// Convenience class to assist in converting between filter names and the filter objects themselves. +/// +template +class EMBER_API TemporalFilterCreator +{ +public: + /// + /// Creates the specified filter type based on the filterType enum parameter. + /// + /// Type of the filter + /// The number of passes used in the ember being rendered + /// The number of temporal samples in the ember being rendered + /// The width of the filter + /// The filter exp, only used with Exp filter, otherwise ignored. + /// A pointer to the newly created filter object + static TemporalFilter* Create(eTemporalFilterType filterType, unsigned int passes, unsigned int temporalSamples, T filterWidth, T filterExp = 1) + { + TemporalFilter* filter = NULL; + + if (filterType == BOX_TEMPORAL_FILTER) + filter = new BoxTemporalFilter(passes, temporalSamples, filterWidth); + else if (filterType == GAUSSIAN_TEMPORAL_FILTER) + filter = new GaussianTemporalFilter(passes, temporalSamples, filterWidth); + else if (filterType == EXP_TEMPORAL_FILTER) + filter = new ExpTemporalFilter(passes, temporalSamples, filterWidth, filterExp); + else + filter = new BoxTemporalFilter(passes, temporalSamples, filterWidth);//Default to box if bad enum passed in. + + return filter; + } + + /// + /// Return a string vector of the available filter types. + /// + /// A vector of strings populated with the available filter types + static vector FilterTypes() + { + vector v; + + v.reserve(3); + v.push_back("Box"); + v.push_back("Gaussian"); + v.push_back("Exp"); + + return v; + } + + /// + /// Convert between the filter name string and its type enum. + /// + /// The string name of the filter + /// The filter type enum + static eTemporalFilterType FromString(string filterType) + { + if (!_stricmp(filterType.c_str(), "box")) + return BOX_TEMPORAL_FILTER; + else if (!_stricmp(filterType.c_str(), "gaussian")) + return GAUSSIAN_TEMPORAL_FILTER; + else if (!_stricmp(filterType.c_str(), "exp")) + return EXP_TEMPORAL_FILTER; + else + return BOX_TEMPORAL_FILTER; + } + + /// + /// Convert between the filter type enum and its name string. + /// + /// The filter type enum + /// The string name of the filter + static string ToString(eTemporalFilterType filterType) + { + string filter; + + if (filterType == BOX_TEMPORAL_FILTER) + filter = "Box"; + else if (filterType == GAUSSIAN_TEMPORAL_FILTER) + filter = "Gaussian"; + else if (filterType == EXP_TEMPORAL_FILTER) + filter = "Exp"; + else + filter = "Box"; + + return filter; + } +}; +} \ No newline at end of file diff --git a/Source/Ember/Timing.cpp b/Source/Ember/Timing.cpp new file mode 100644 index 0000000..1734bbd --- /dev/null +++ b/Source/Ember/Timing.cpp @@ -0,0 +1,7 @@ +#include "stdafx.h" +#include "Timing.h" + +namespace Flam3 +{ + +} \ No newline at end of file diff --git a/Source/Ember/Timing.h b/Source/Ember/Timing.h new file mode 100644 index 0000000..f3858cc --- /dev/null +++ b/Source/Ember/Timing.h @@ -0,0 +1,231 @@ +#pragma once + +#include "EmberDefines.h" + +/// +/// Timing and CriticalSection classes. +/// + +namespace EmberNs +{ +/// +/// Since the algorithm is so computationally intensive, timing and benchmarking are an integral portion +/// of both the development process and the execution results. This class provides an easy way to time +/// things by simply calling its Tic() and Toc() member functions. It also assists with formatting the +/// elapsed time as a string. +/// +class EMBER_API Timing +{ +public: + /// + /// Constructor that takes an optional precision argument which specifies how many digits after the decimal place should be printed for seconds. + /// As a convenience, the Tic() function is called automatically. + /// + /// The precision of the seconds field of the elapsed time. Default: 2. + Timing(int precision = 2) + { + m_Precision = precision; + Init(); + Tic(); + } + + /// + /// Set the begin time. + /// + /// The quad part of the begin time cast to a double + double Tic() + { + QueryPerformanceCounter(&m_BeginTime); + return BeginTime(); + } + + /// + /// Set the end time and optionally output a string showing the elapsed time. + /// + /// The string to output. Default: NULL. + /// If true, output the string verbatim, else output the text " processing time: " in between str and the formatted time. + /// The elapsed time in milliseconds as a double + double Toc(const char* str = NULL, bool fullString = false) + { + QueryPerformanceCounter(&m_EndTime); + double ms = ElapsedTime(); + + if (str != NULL) + { + cout << string(str) << (fullString ? "" : " processing time: ") << Format(ms) << endl; + } + + return ms; + } + + /// + /// Return the quad part of the begin time as a double. + /// + /// + double BeginTime() { return (double)m_BeginTime.QuadPart; } + + /// + /// Return the quad part of the end time as a double. + /// + /// + double EndTime() { return (double)m_EndTime.QuadPart; } + + /// + /// Return the elapsed time in milliseconds. + /// + /// The elapsed time in milliseconds as a double + double ElapsedTime() { return double(m_EndTime.QuadPart - m_BeginTime.QuadPart) * 1000.0 / double(m_Freq.QuadPart); } + + /// + /// Formats a specified milliseconds value as a string. + /// This uses some intelligence to determine what to return depending on how much time has elapsed. + /// Days, hours and minutes are only included if 1 or more of them has elapsed. Seconds are always + /// included as a decimal value with the precision the user specified in the constructor. + /// + /// The ms + /// The formatted string + string Format(double ms) + { + stringstream ss; + + double x = ms / 1000; + double secs = fmod(x, 60); + x /= 60; + double mins = fmod(x, 60); + x /= 60; + double hours = fmod(x, 24); + x /= 24; + double days = x; + + if (days >= 1) + ss << (int)days << "d "; + + if (hours >= 1) + ss << (int)hours << "h "; + + if (mins >= 1) + ss << (int)mins << "m "; + + ss << std::fixed << std::setprecision(m_Precision) << secs << "s"; + return ss.str(); + } + + /// + /// Return the frequency of the clock as a double. + /// + /// + static double Freq() + { + Init(); + return (double)m_Freq.QuadPart; + } + + /// + /// Return the number of cores in the system. + /// + /// The number of cores in the system + static int ProcessorCount() + { + Init(); + return m_ProcessorCount; + } + +private: + /// + /// Query and store the performance info of the system. + /// Since it will never change it only needs to be queried once. + /// This is achieved by keeping static state and performance variables. + /// + static void Init() + { + if (!m_TimingInit) + { + SYSTEM_INFO sysinfo; + + QueryPerformanceFrequency(&m_Freq); + GetSystemInfo(&sysinfo); + m_ProcessorCount = sysinfo.dwNumberOfProcessors; + m_TimingInit = true; + } + } + + int m_Precision;//How many digits after the decimal place to print for seconds. + LARGE_INTEGER m_BeginTime;//The start of the timing, set with Tic(). + LARGE_INTEGER m_EndTime;//The end of the timing, set with Toc(). + static bool m_TimingInit;//Whether the performance info has bee queried. + static int m_ProcessorCount;//The number of cores on the system, set in Init(). + static LARGE_INTEGER m_Freq;//The clock frequency, set in Init(). +}; + +/// +/// Cross platform critical section class which can be used for thread locking. +/// +class EMBER_API CriticalSection +{ +#ifdef _WIN32 + +public: + /// + /// Constructor which initialized the underlying CRITICAL_SECTION object. + /// + CriticalSection() { InitializeCriticalSection(&m_CriticalSection); } + + /// + /// Constructor which initialized the underlying CRITICAL_SECTION object + /// with the specified spin count value. + /// + /// The spin count. + CriticalSection(DWORD spinCount) { InitializeCriticalSectionAndSpinCount(&m_CriticalSection, spinCount); } + + /// + /// Deletes the underlying CRITICAL_SECTION object. + /// + ~CriticalSection() { DeleteCriticalSection(&m_CriticalSection); } + + /// + /// Lock the critical section. + /// + void Enter() { EnterCriticalSection(&m_CriticalSection); } + + /// + /// Unlock the critical section. + /// + void Leave() { LeaveCriticalSection(&m_CriticalSection); } + +private: + CRITICAL_SECTION m_CriticalSection;//The Windows specific critical section object. + +#else + + /// + /// Constructor which initialized the underlying pthread_mutex_t object. + /// + CriticalSection() + { + pthread_mutexattr_t attr; + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); + pthread_mutex_init(&m_CriticalSection, &attr); + pthread_mutexattr_destroy(&attr); + } + + /// + /// Deletes the underlying pthread_mutex_t object. + /// + ~CriticalSection() { pthread_mutex_destroy(&m_CriticalSection); } + + /// + /// Lock the critical section. + /// + void Enter() { pthread_mutex_lock(&m_CriticalSection); } + + /// + /// Unlock the critical section. + /// + void Leave() { pthread_mutex_unlock(&m_CriticalSection); } + +private: + pthread_mutex_t m_CriticalSection;//The *nix/pthread specific critical section object. + +#endif +}; +} \ No newline at end of file diff --git a/Source/Ember/Utils.h b/Source/Ember/Utils.h new file mode 100644 index 0000000..fb29df7 --- /dev/null +++ b/Source/Ember/Utils.h @@ -0,0 +1,887 @@ +#pragma once + +#include "Isaac.h" + +/// +/// Global utility classes and functions that don't really fit anywhere else, but are +/// too small to justify being in their own file. +/// +namespace EmberNs +{ +/// +/// After a run completes, information about what was run can be saved as strings to the comments +/// section of a jpg or png file. This class is just a container for those values. +/// +class EMBER_API EmberImageComments +{ +public: + /// + /// Set all values to the empty string. + /// + void Clear() + { + m_Genome = ""; + m_Badvals = ""; + m_NumIters = ""; + m_Runtime = ""; + } + + string m_Genome; + string m_Badvals; + string m_NumIters; + string m_Runtime; +}; + +/// +/// Since running is an incredibly complex process with multiple points of possible failure, +/// it's important that as much information as possible is captured if something goes wrong. +/// Classes wishing to capture this failure information will derive from this class and populate +/// the vector of strings with any useful error information. Note that a small complication can occur +/// when a class derives from this class, yet also has one or more members which do too. In that case, they should +/// override the methods to aggregate the error information from themselves, as well as their members. +/// +class EMBER_API EmberReport +{ +public: + /// + /// Write the entire error report as a single string to the console. + /// Derived classes with members that also derive from EmberReport should override this to capture + /// their error information as well as that of their members. + /// + virtual void DumpErrorReport() { cout << ErrorReportString(); } + + /// + /// Clear the error report string vector. + /// Derived classes with members that also derive from EmberReport should override this to clear + /// their error information as well as that of their members. + /// + virtual void ClearErrorReport() { m_ErrorReport.clear(); } + + /// + /// Return the entire error report as a single string. + /// Derived classes with members that also derive from EmberReport should override this to capture + /// their error information as well as that of their members. + /// + /// The entire error report as a single string. Empty if no errors. + virtual string ErrorReportString() { return StaticErrorReportString(m_ErrorReport); } + + /// + /// Return the entire error report as a vector of strings. + /// Derived classes with members that also derive from EmberReport should override this to capture + /// their error information as well as that of their members. + /// + /// The entire error report as a vector of strings. Empty if no errors. + virtual vector ErrorReport() { return m_ErrorReport; } + + /// + /// Add string to report. + /// + /// The string to add + virtual void AddToReport(string s) { m_ErrorReport.push_back(s); } + + /// + /// Add a vector of strings to report. + /// + /// The vector of strings to add + virtual void AddToReport(vector& vec) { m_ErrorReport.insert(m_ErrorReport.end(), vec.begin(), vec.end()); } + + /// + /// Static function to dump a vector of strings passed in. + /// + /// The vector of strings to dump + static void StaticDumpErrorReport(vector& errorReport) { cout << StaticErrorReportString(errorReport); } + + /// + /// Static function to return the entire error report passed in as a single string. + /// + /// The vector of strings to concatenate + /// A string containing all strings in the vector passed in separated by newlines + static string StaticErrorReportString(vector& errorReport) + { + stringstream ss; + + std::for_each(errorReport.begin() , errorReport.end() , [&](string s) { ss << s << endl; }); + + return ss.str(); + } + +protected: + vector m_ErrorReport; +}; + +/// +/// Open a file in binary mode and read its entire contents into a vector of unsigned chars. Optionally null terminate. +/// +/// The full path to the file to read +/// The vector which will be populated with the file's contents +/// Whether to append a NULL character as the last element of the vector. Needed when reading text files. Default: true. +/// True if successfully read and populated, else false +static bool ReadFile(const char* filename, string& buf, bool nullTerminate = true) +{ + bool b = false; + FILE* f; + + try + { + fopen_s(&f, filename, "rb");//Open in binary mode. + + if (f != NULL) + { + struct _stat statBuf; + int statResult = _fstat(f->_file, &statBuf);//Get data associated with file. + + if (statResult == 0)//Check if statistics are valid. + { + buf.resize(statBuf.st_size + (nullTerminate ? 1 : 0));//Allocate vector to be the size of the entire file, with an optional additional character for NULL. + + if (buf.size() == statBuf.st_size + 1)//Ensure allocation succeeded. + { + size_t bytesRead = fread(&buf[0], 1, statBuf.st_size, f);//Read the entire file at once. + + if (bytesRead == statBuf.st_size)//Ensure the number of bytes read matched what was requested. + { + if (nullTerminate)//Optionally NULL terminate if they want to treat it as a string. + buf[buf.size() - 1] = NULL; + + b = true;//Success. + } + } + } + + fclose(f); + } + } + catch (...) + { + if (f != NULL) + fclose(f); + + b = false; + } + + return b; +} + +/// +/// Clear dest and copy all of the elements of vector source with elements of type U to the vector +/// dest with elements of type T. +/// +/// The vector of type T to copy to +/// The vector of type U to copy from +template +void CopyVec(vector& dest, const vector& source) +{ + dest.clear(); + dest.resize(source.size()); + + for (size_t i = 0; i < source.size(); i++) + dest[i] = T(source[i]);//Valid assignment operator between T and U types must be defined somewhere. +} + +/// +/// Clear a vector of pointers to any type by checking each element for NULL and calling delete on it, then clearing the entire vector. +/// Optionally call array delete if the elements themselves are pointers to dynamically allocated arrays. +/// +/// The vector to be cleared +/// Whether to call delete or delete []. Default: false. +template +static void ClearVec(vector& vec, bool arrayDelete = false) +{ + for (unsigned int i = 0; i < vec.size(); i++) + { + if (vec[i] != NULL) + { + if (arrayDelete) + delete [] vec[i]; + else + delete vec[i]; + } + + vec[i] = NULL; + } + + vec.clear(); +} + +/// +/// Convert an RGBA buffer to an RGB buffer. +/// +/// The RGBA buffer +/// The RGB buffer +/// The width of the image in pixels +/// The height of the image in pixels +static void RgbaToRgb(vector& rgba, vector& rgb, unsigned int width, unsigned int height) +{ + rgb.resize(width * height * 3); + + for (unsigned int i = 0, j = 0; i < (width * height * 4); i += 4, j += 3) + { + rgb[j] = rgba[i]; + rgb[j + 1] = rgba[i + 1]; + rgb[j + 2] = rgba[i + 2]; + } +} + +/// +/// Clamp and return a value to be greater than or equal to a specified minimum and less than +/// or equal to a specified maximum. +/// +/// The value to be clamped +/// A value which the clamped value must be greater than or equal to +/// A value which the clamped value must be less than or equal to +/// The clamped value +template +static inline T Clamp(T val, T min, T max) +{ + if (val < min) + return min; + else if (val > max) + return max; + else + return val; +} + +/// +/// Clamp and return a value to be greater than or equal to a specified minimum and less than +/// or equal to a specified maximum. If lesser, the value is fmod(val - min, max - min). If greater, +/// the value is max - fmod(max - val, max - min). +/// +/// The value to be clamped +/// A value which the clamped value must be greater than or equal to +/// A value which the clamped value must be less than or equal to +/// The clamped and modded value +template +static inline T ClampMod(T val, T min, T max) +{ + if (val < min) + return min + fmod(val - min, max - min); + else if (val > max) + return max - fmod(max - val, max - min); + else + return val; +} + +/// +/// Similar to Clamp(), but clamps a reference value in place rather than returning. +/// +/// The reference value to be clamped in place +/// A value which the clamped value must be greater than or equal to +/// A value which the clamped value must be less than or equal to +template +static inline void ClampRef(T& val, T min, T max) +{ + if (val < min) + val = min; + else if (val > max) + val = max; +} + +/// +/// Similar to Clamp(), but clamps a reference value in place rather than returning. +/// +/// The reference value to be clamped in place +/// A value which the clamped value must be less than or equal to +template +static inline void ClampLteRef(T& val, T lte) +{ + if (val > lte) + val = lte; +} + +/// +/// Clamp and return a value to be greater than or equal to a specified value. +/// Useful for ensuring something is not less than zero. +/// +/// The value to be clamped +/// A value which the clamped value must be greater than or equal to +/// The clamped value +template +static inline T ClampGte(T val, T gte) +{ + return (val < gte) ? gte : val; +} + +/// +/// Similar to Clamp(), but clamps a reference value in place rather than returning. +/// +/// The reference value to be clamped in place +/// A value which the clamped value must be greater than or equal to +template +static inline void ClampGteRef(T& val, T gte) +{ + if (val < gte) + val = gte; +} + +/// +/// Thin wrapper around a call to ClampGte() with a gte value of zero. +/// +/// The value to be clamped +/// The clamped value +template +static inline T ClampGte0(T val) +{ + return ClampGte(val, 0); +} + +/// +/// Thin wrapper around a call to ClampGteRef() with a gte value of zero. +/// +/// The reference value to be clamped in place +template +static inline void ClampGte0Ref(T& val) +{ + ClampGteRef(val, 0); +} + +/// +/// Return a value rounded up or down. Works for positive and negative numbers. +/// +/// The value to round +/// The rounded value +template +T Round(T r) +{ + return (r > 0) ? (T)Floor(r + T(0.5)) : ceil(r - T(0.5)); +} + +/// +/// Special rounding for certain variations, gotten from Apophysis. +/// +/// The value to round +/// The rounded value +inline float LRint(float x) +{ + int temp = (x >= 0 ? (int)(x + 0.5f) : (int)(x - 0.5f)); + return (float)temp; +} + +/// +/// Special rounding for certain variations, gotten from Apophysis. +/// +/// The value to round +/// The rounded value +inline double LRint(double x) +{ + __int64 temp = (x >= 0 ? (__int64)(x + 0.5) : (__int64)(x - 0.5)); + return (double)temp; +} + +/// +/// System floor() extremely slow because it accounts for various error conditions. +/// This is a much faster version that works on data that is not NaN. +/// +/// The value to return the floor of +/// The floored value +template +static inline int Floor(T val) +{ + if (val >= 0) + { + return (int)val; + } + else + { + int i = (int)val;//Truncate. + return i - (i > val);//Convert trunc to floor. + } +} + +/// +/// Never really understood what this did. +/// +/// The value to round +/// The rounded value +template +static inline T Round6(T r) +{ + r *= 1e6; + + if (r < 0) + r -= 1; + + return T(1e-6 * (int)(r + T(0.5))); +} + +/// +/// Return -1 if the value is less than 0, 1 if it's greater and +/// 0 if it's equal to 0. +/// +/// The value to inspect +/// -1, 0 or 1 +template +static inline T Sign(T v) +{ + return (v < 0) ? T(-1) : (v > 0) ? T(1) : T(0); +} + +/// +/// Return -1 if the value is less than 0, 1 if it's greater. +/// This differs from Sign() in that it doesn't return 0. +/// +/// The value to inspect +/// -1 or 1 +template +static inline T SignNz(T v) +{ + return (v < 0) ? T(-1) : T(1); +} + +/// +/// Return the square of the passed in value. +/// This is useful when the value is a result of a computation +/// rather than a fixed number. Otherwise, use the SQR macro. +/// +/// The value to square +/// The squared value +template +static inline T Sqr(T t) +{ + return t * t; +} + +/// +/// Taking the square root of numbers close to zero is dangerous. If x is negative +/// due to floating point errors, it can return NaN results. +/// +template +static inline T SafeSqrt(T x) +{ + if (x <= 0) + return 0; + + return sqrt(x); +} + +/// +/// Return the cube of the passed in value. +/// This is useful when the value is a result of a computation +/// rather than a fixed number. Otherwise, use the CUBE macro. +/// +/// The value to cube +/// The cubed value +template +static inline T Cube(T t) +{ + return t * t * t; +} + +/// +/// Return the hypotenuse of the passed in values. +/// +/// The x distance +/// The y distance +/// The hypotenuse +template +static inline T Hypot(T x, T y) +{ + return sqrt(SQR(x) + SQR(y)); +} + +/// +/// Spread the values. +/// +/// The x distance +/// The y distance +/// The spread +template +static inline T Spread(T x, T y) +{ + return Hypot(x, y) * ((x) > 0 ? 1 : -1); +} + +/// +/// Unsure. +/// +/// The x distance +/// The y distance +/// The powq4 +template +static inline T Powq4(T x, T y) +{ + return pow(fabs(x), y) * SignNz(x); +} + +/// +/// Unsure. +/// +/// The x distance +/// The y distance +/// The powq4c +template +static inline T Powq4c(T x, T y) +{ + return y == 1 ? x : Powq4(x, y); +} + +/// +/// Return EPS6 if the passed in value was zero, else return the value. +/// +/// The value +/// The y distance +/// EPS6 or the value if it was non-zero +template +static inline T Zeps(T x) +{ + return x == 0 ? EPS6 : x; +} + +/// +/// Interpolate a given percentage between two values. +/// +/// The first value to interpolate between. +/// The secod value to interpolate between. +/// The percentage between the two values to calculate. +/// The interpolated value. +template +static inline T Lerp(T a, T b, T p) +{ + return a + (b - a) * p; +} + +/// +/// Thin wrapper around a call to modf that discards the integer portion +/// and returns the signed fractional portion. +/// +/// The value to retrieve the signed fractional portion of. +/// The signed fractional portion of v. +template +static inline T Fabsmod(T v) +{ + T dummy; + + return modf(v, &dummy); +} + +/// +/// Unsure. +/// +/// Unsure. +/// Unsure. +/// Unsure. +/// Unsure. +template +static inline T Fosc(T p, T amp, T ph) +{ + return T(0.5) - cos(p * amp + ph) * T(0.5); +} + +/// +/// Unsure. +/// +/// Unsure. +/// Unsure. +/// Unsure. +template +static inline T Foscn(T p, T ph) +{ + return T(0.5) - cos(p + ph) * T(0.5); +} + +/// +/// Log scale from Apophysis. +/// +/// The value to log scale +/// The log scaled value +template +static inline T LogScale(T x) +{ + return x == 0 ? 0 : log((fabs(x) + 1) * T(M_E)) * SignNz(x) / T(M_E); +} + +/// +/// Log map from Apophysis. +/// +/// The value to log map +/// The log mapped value +template +static inline T LogMap(T x) +{ + return x == 0 ? 0 : (T(M_E) + log(x * T(M_E))) * T(0.25) * SignNz(x); +} + +/// +/// Thin wrapper around calling xmlStrcmp() on an Xml tag to tell +/// if its name is a given value. +/// +/// The name of the tag of the to inspect +/// The value compare against +/// True if the comparison matched, else false +static inline bool Compare(const xmlChar* name, char* val) +{ + return xmlStrcmp(name, XC val) != 0; +} + +/// +/// Determine whether the specified value is very close to zero. +/// This is useful for determining equality of float/double types. +/// +/// The value to compare against +/// The tolerance. Default: 1e-6. +/// True if the value was very close to zero, else false +template +static inline bool IsNearZero(T val, T tolerance = 1e-6) +{ + return (val > -tolerance && val < tolerance); +} + +/// +/// Determine whether a specified value is very close to another value. +/// This is useful for determining equality of float/double types. +/// +/// The first value. +/// The second value. +/// The tolerance. Default: 1e-6. +/// True if the values were very close to each other, else false +template +static bool IsClose(T val1, T val2, T tolerance = 1e-6) +{ + return IsNearZero(val1 - val2, tolerance); +} + +/// +/// Put an angular measurement in degrees into the range of -180 - 180. +/// +/// The angle to normalize +/// The normalized angle in a range of -180 - 180 +template +static inline T NormalizeDeg180(T angle) +{ + angle = fmod(angle, 360); + + if (angle > 180) + { + angle -= 360; + } + else if (angle < -180) + { + angle += 360; + } + + return angle; +} + +/// +/// Put an angular measurement in degrees into the range of 0 - 360. +/// +/// The angle to normalize +/// The normalized angle in a range of 0 - 360 +template +static inline T NormalizeDeg360(T angle) +{ + if (angle > 360 || angle < -360) + angle = fmod(angle, 360); + + if (angle < 0) + angle += 360; + + return angle; +} + +/// +/// Return a lower case copy of a string. +/// +/// The string to copy and make lower case +/// The lower case string +static inline string ToLower(string& str) +{ + string lower; + + lower.resize(str.size());//Allocate the destination space. + std::transform(str.begin(), str.end(), lower.begin(), ::tolower);//Convert the source string to lower case storing the result in the destination string. + return lower; +} + +/// +/// Return an upper case copy of a string. +/// +/// The string to copy and make upper case +/// The upper case string +static inline string ToUpper(string& str) +{ + string upper; + + upper.resize(str.size());//Allocate the destination space. + std::transform(str.begin(), str.end(), upper.begin(), ::toupper);//Convert the source string to lower case storing the result in the destination string. + return upper; +} + +/// +/// Return a copy of a string with leading and trailing occurrences of a specified character removed. +/// The default character is a space. +/// +/// The string to trim +/// The character to trim. Default: space. +/// The trimmed string +static inline string Trim(string& str, char ch = ' ') +{ + string ret; + + if (str != "") + { + size_t firstChar = str.find_first_not_of(ch); + size_t lastChar = str.find_last_not_of(ch); + + if (firstChar == string::npos) + firstChar = 0; + + if (lastChar == string::npos) + lastChar = str.size(); + + ret = str.substr(firstChar, lastChar - firstChar + 1); + } + + return ret; +} + +/// +/// Placeholder for a templated function to query the value of a specified system environment variable +/// of a specific type. This function does nothing as the functions for specific types implement the behavior +/// via template specialization. +/// +/// The name of the environment variable to query +/// The default value to return if the environment variable was not present +/// The value of the specified environment variable if found, else default +template +static T Arg(char* name, T def) +{ + T t; + return t; +} + +/// +/// Template specialization for Arg<>() with a type of int. +/// +/// The name of the environment variable to query +/// The default value to return if the environment variable was not present +/// The value of the specified environment variable if found, else default +template <> +static int Arg(char* name, int def) +{ + char* ch; + int returnVal; + size_t len; + errno_t err = _dupenv_s(&ch, &len, name); + + if (err || !ch) + returnVal = def; + else + returnVal = atoi(ch); + + free(ch); + return returnVal; +} + +/// +/// Template specialization for Arg<>() with a type of unsigned int. +/// +/// The name of the environment variable to query +/// The default value to return if the environment variable was not present +/// The value of the specified environment variable if found, else default +template <> +static unsigned int Arg(char* name, unsigned int def) +{ + return Arg(name, (int)def); +} + +/// +/// Template specialization for Arg<>() with a type of bool. +/// +/// The name of the environment variable to query +/// The default value to return if the environment variable was not present +/// The value of the specified environment variable if found, else default +template <> +static bool Arg(char* name, bool def) +{ + return (Arg(name, -999) != -999) ? true : def; +} + +/// +/// Template specialization for Arg<>() with a type of double. +/// +/// The name of the environment variable to query +/// The default value to return if the environment variable was not present +/// The value of the specified environment variable if found, else default +template <> +static double Arg(char* name, double def) +{ + char* ch; + double returnVal; + size_t len; + errno_t err = _dupenv_s(&ch, &len, name); + + if (err || !ch) + returnVal = def; + else + returnVal = atof(ch); + + free(ch); + return returnVal; +} + +/// +/// Template specialization for Arg<>() with a type of string. +/// +/// The name of the environment variable to query +/// The default value to return if the environment variable was not present +/// The value of the specified environment variable if found, else default +template <> +static string Arg(char* name, string def) +{ + char* ch; + string returnVal; + size_t len; + errno_t err = _dupenv_s(&ch, &len, name); + + if (err || !ch) + { + if (def != "") + returnVal = def; + } + else + returnVal = string(ch); + + free(ch); + return returnVal; +} + +/// +/// Replaces all instances of a value within a collection, with the specified value. +/// Taken from a StackOverflow.com post. +/// Modified to account for the scenario where the find and replace strings each start with +/// the same character. +/// Template argument should be any STL container. +/// +/// Collection to replace values in +/// The value to replace +/// The value to replace with +/// The number of instances replaced +template +unsigned int inline FindAndReplace(T& source, const T& find, const T& replace) +{ + unsigned int replaceCount = 0; + typename T::size_type fLen = find.size(); + typename T::size_type rLen = replace.size(); + + for (typename T::size_type pos = 0; (pos = source.find(find, pos)) != T::npos; pos += rLen) + { + typename T::size_type pos2 = source.find(replace, pos); + + if (pos != pos2) + { + replaceCount++; + source.replace(pos, fLen, replace); + } + } + + return replaceCount; +} + +/// +/// Return a character pointer to a version string composed of the EMBER_OS and EMBER_VERSION values. +/// +static char* EmberVersion() +{ + return EMBER_OS "-" EMBER_VERSION; +} +} \ No newline at end of file diff --git a/Source/Ember/Variation.h b/Source/Ember/Variation.h new file mode 100644 index 0000000..8442f9d --- /dev/null +++ b/Source/Ember/Variation.h @@ -0,0 +1,2168 @@ +#pragma once + +#include "Point.h" +#include "Isaac.h" + +/// +/// Base variation classes. Individual variations will be grouped into files of roughly 50 +/// to avoid a single file becoming too unweildy. +/// + +namespace EmberNs +{ +/// +/// Xform and Variation need each other, but each can't include the other. +/// So Xform includes this file, and use a forward declaration here. +/// +template class Xform; + +/// +/// The type of variation: regular, pre or post. +/// +enum eVariationType +{ + VARTYPE_REG, + VARTYPE_PRE, + VARTYPE_POST, +}; + +/// +/// How to handle the results of the variation when it's a pre or post. +/// If the calculation involved the input points, then it should be directly assigned +/// to the output. However, if they did not involve the input points, they should be added +/// to the output. +/// +enum eVariationAssignType +{ + ASSIGNTYPE_SET, + ASSIGNTYPE_SUM +}; + +/// +/// Complete list of every variation class ID. +/// +enum eVariationId +{ + VAR_ARCH , + VAR_AUGER , + VAR_BARYCENTROID , + VAR_BCIRCLE , + VAR_BCOLLIDE , + VAR_BENT , + VAR_BENT2 , + VAR_BIPOLAR , + VAR_BISPLIT , + VAR_BLADE , + VAR_BLADE3D , + VAR_BLOB , + VAR_BLOB2 , + VAR_BLOB3D , + VAR_BLOCKY , + VAR_BLUR , + VAR_BLUR_CIRCLE , + VAR_BLUR_HEART , + VAR_BLUR_LINEAR , + VAR_BLUR_PIXELIZE , + VAR_BLUR_SQUARE , + VAR_BLUR_ZOOM , + VAR_BLUR3D , + VAR_BMOD , + VAR_BOARDERS , + VAR_BOARDERS2 , + VAR_BSWIRL , + VAR_BTRANSFORM , + VAR_BUBBLE , + VAR_BUBBLE2 , + VAR_BUTTERFLY , + VAR_BWRAPS , + VAR_CARDIOID , + VAR_CELL , + VAR_CHECKS , + VAR_CIRCLEBLUR , + VAR_CIRCLECROP , + VAR_CIRCLELINEAR , + VAR_CIRCLERAND , + VAR_CIRCLETRANS1 , + VAR_CIRCLIZE , + VAR_CIRCLIZE2 , + VAR_CIRCUS , + VAR_COLLIDEOSCOPE , + VAR_CONIC , + VAR_COS , + VAR_COS_WRAP , + VAR_COSH , + VAR_COSHQ , + VAR_COSINE , + VAR_COSQ , + VAR_COT , + VAR_COTH , + VAR_COTHQ , + VAR_COTQ , + VAR_CPOW , + VAR_CPOW2 , + VAR_CRESCENTS , + VAR_CROP , + VAR_CROPN , + VAR_CROSS , + VAR_CSC , + VAR_CSCH , + VAR_CSCHQ , + VAR_CSCQ , + VAR_CUBIC3D , + VAR_CUBIC_LATTICE3D, + VAR_CURL , + VAR_CURL3D , + VAR_CURL_SP , + VAR_CURVATURE , + VAR_CURVE , + VAR_CYLINDER , + VAR_DELTA_A , + VAR_DEPTH , + VAR_DIAMOND , + VAR_DISC , + VAR_DISC2 , + VAR_DISC3D , + VAR_ECLIPSE , + VAR_ECOLLIDE , + VAR_EDISC , + VAR_EJULIA , + VAR_ELLIPTIC , + VAR_EMOD , + VAR_EMOTION , + VAR_ENNEPERS , + VAR_EPISPIRAL , + VAR_EPUSH , + VAR_EROTATE , + VAR_ESCALE , + VAR_ESCHER , + VAR_ESTIQ , + VAR_ESWIRL , + VAR_EX , + VAR_EXP , + VAR_EXPO , + VAR_EXPONENTIAL , + VAR_EXTRUDE , + VAR_EYEFISH , + VAR_FALLOFF , + VAR_FALLOFF2 , + VAR_FALLOFF3 , + VAR_FAN , + VAR_FAN2 , + VAR_FARBLUR , + VAR_FDISC , + VAR_FIBONACCI , + VAR_FIBONACCI2 , + VAR_FISHEYE , + VAR_FLATTEN , + VAR_FLIP_CIRCLE , + VAR_FLIP_Y , + VAR_FLOWER , + VAR_FLUX , + VAR_FOCI , + VAR_FOCI3D , + VAR_FOURTH , + VAR_FUNNEL , + VAR_GAUSSIAN_BLUR , + VAR_GDOFFS , + VAR_GLYNNIA , + VAR_GLYNNSIM1 , + VAR_GLYNNSIM2 , + VAR_GLYNNSIM3 , + VAR_GRIDOUT , + VAR_HANDKERCHIEF , + VAR_HEART , + VAR_HEAT , + VAR_HEMISPHERE , + VAR_HO , + VAR_HOLE , + VAR_HORSESHOE , + VAR_HYPERBOLIC , + VAR_HYPERTILE , + VAR_HYPERTILE1 , + VAR_HYPERTILE2 , + VAR_HYPERTILE3D , + VAR_HYPERTILE3D1 , + VAR_HYPERTILE3D2 , + VAR_IDISC , + VAR_INTERFERENCE2 , + VAR_JULIA , + VAR_JULIA3D , + VAR_JULIA3DQ , + VAR_JULIA3DZ , + VAR_JULIAC , + VAR_JULIAN , + VAR_JULIAN2 , + VAR_JULIAN3DX , + VAR_JULIANAB , + VAR_JULIAQ , + VAR_JULIASCOPE , + VAR_KALEIDOSCOPE , + VAR_LAZY_TRAVIS , + VAR_LAZYSUSAN , + VAR_LINE , + VAR_LINEAR , + VAR_LINEAR_T , + VAR_LINEAR_T3D , + //VAR_LINEAR_XZ , + //VAR_LINEAR_YZ , + VAR_LINEAR3D , + VAR_LISSAJOUS , + VAR_LOG , + VAR_LOQ , + VAR_LOONIE , + VAR_LOONIE3D , + VAR_MASK , + VAR_MCARPET , + VAR_MIRROR_X , + VAR_MIRROR_Y , + VAR_MIRROR_Z , + VAR_MOBIQ , + VAR_MOBIUS , + VAR_MOBIUS_STRIP , + VAR_MOBIUSN , + VAR_MODULUS , + VAR_MURL , + VAR_MURL2 , + VAR_NGON , + VAR_NOISE , + VAR_NPOLAR , + VAR_OCTAGON , + VAR_ORTHO , + VAR_OSCILLOSCOPE , + VAR_OVOID , + VAR_OVOID3D , + VAR_PARABOLA , + VAR_PDJ , + VAR_PERSPECTIVE , + VAR_PETAL , + VAR_PHOENIX_JULIA , + VAR_PIE , + VAR_PIE3D , + VAR_POINCARE , + VAR_POINCARE3D , + VAR_POLAR , + VAR_POLAR2 , + VAR_POLYNOMIAL , + VAR_POPCORN , + VAR_POPCORN2 , + VAR_POPCORN23D , + VAR_POW_BLOCK , + VAR_POWER , + VAR_PSPHERE , + VAR_Q_ODE , + VAR_RADIAL_BLUR , + VAR_RATIONAL3 , + VAR_RAYS , + VAR_RBLUR , + VAR_RECTANGLES , + VAR_RINGS , + VAR_RINGS2 , + VAR_RIPPLE , + VAR_RIPPLED , + VAR_ROTATE_X , + VAR_ROTATE_Y , + VAR_ROTATE_Z , + VAR_ROUNDSPHER , + VAR_ROUNDSPHER3D , + VAR_SCRY , + VAR_SCRY3D , + VAR_SEC , + VAR_SECANT2 , + VAR_SECH , + VAR_SECHQ , + VAR_SECQ , + VAR_SEPARATION , + VAR_SHRED_RAD , + VAR_SHRED_LIN , + VAR_SIGMOID , + VAR_SIN , + VAR_SINEBLUR , + VAR_SINH , + VAR_SINHQ , + VAR_SINQ , + VAR_SINTRANGE , + VAR_SINUS_GRID , + VAR_SINUSOIDAL , + VAR_SINUSOIDAL3D , + VAR_SPHERICAL , + VAR_SPHERICAL3D , + VAR_SPHERICALN , + VAR_SPHERIVOID , + VAR_SPHYP3D , + VAR_SPIRAL , + VAR_SPIRAL_WING , + VAR_SPIROGRAPH , + VAR_SPLIT , + VAR_SPLIT_BRDR , + VAR_SPLITS , + VAR_SQUARE , + VAR_SQUARE3D , + VAR_SQUARIZE , + VAR_SQUIRREL , + VAR_SQUISH , + VAR_SSCHECKS , + VAR_STARBLUR , + VAR_STRIPES , + VAR_STWIN , + VAR_SUPER_SHAPE , + VAR_SUPER_SHAPE3D , + VAR_SVF , + VAR_SWIRL , + VAR_TAN , + VAR_TANCOS , + VAR_TANGENT , + VAR_TANH , + VAR_TANHQ , + VAR_TANQ , + VAR_TARGET , + VAR_TAURUS , + VAR_TRADE , + VAR_TRUCHET , + VAR_TWINTRIAN , + VAR_TWO_FACE , + VAR_UNPOLAR , + VAR_VORON , + VAR_WAFFLE , + VAR_WAVES , + VAR_WAVES2 , + VAR_WAVES23D , + VAR_WAVESN , + VAR_WDISC , + VAR_WEDGE , + VAR_WEDGE_JULIA , + VAR_WEDGE_SPH , + VAR_WHORL , + VAR_XHEART , + VAR_XTRB , + VAR_ZBLUR , + VAR_ZCONE , + VAR_ZSCALE , + VAR_ZTRANSLATE , + + VAR_PRE_ARCH, + VAR_PRE_AUGER, + VAR_PRE_BARYCENTROID, + VAR_PRE_BCIRCLE, + VAR_PRE_BCOLLIDE, + VAR_PRE_BENT, + VAR_PRE_BENT2, + VAR_PRE_BIPOLAR, + VAR_PRE_BISPLIT, + VAR_PRE_BLADE, + VAR_PRE_BLADE3D, + VAR_PRE_BLOB, + VAR_PRE_BLOB2, + VAR_PRE_BLOB3D, + VAR_PRE_BLOCKY, + VAR_PRE_BLUR, + VAR_PRE_BLUR_CIRCLE, + VAR_PRE_BLUR_HEART, + VAR_PRE_BLUR_LINEAR, + VAR_PRE_BLUR_PIXELIZE, + VAR_PRE_BLUR_SQUARE, + VAR_PRE_BLUR_ZOOM, + VAR_PRE_BLUR3D, + VAR_PRE_BMOD, + VAR_PRE_BOARDERS, + VAR_PRE_BOARDERS2, + VAR_PRE_BSWIRL, + VAR_PRE_BTRANSFORM, + VAR_PRE_BUBBLE, + VAR_PRE_BUBBLE2, + VAR_PRE_BUTTERFLY, + VAR_PRE_BWRAPS, + VAR_PRE_CARDIOID, + VAR_PRE_CELL, + VAR_PRE_CHECKS, + VAR_PRE_CIRCLEBLUR, + VAR_PRE_CIRCLECROP, + VAR_PRE_CIRCLELINEAR, + VAR_PRE_CIRCLERAND, + VAR_PRE_CIRCLETRANS1, + VAR_PRE_CIRCLIZE, + VAR_PRE_CIRCLIZE2, + VAR_PRE_CIRCUS, + VAR_PRE_COLLIDEOSCOPE, + VAR_PRE_CONIC, + VAR_PRE_COS, + VAR_PRE_COS_WRAP, + VAR_PRE_COSH, + VAR_PRE_COSHQ, + VAR_PRE_COSINE, + VAR_PRE_COSQ, + VAR_PRE_COT, + VAR_PRE_COTH, + VAR_PRE_COTHQ, + VAR_PRE_COTQ, + VAR_PRE_CPOW, + VAR_PRE_CPOW2, + VAR_PRE_CRESCENTS, + VAR_PRE_CROP, + VAR_PRE_CROPN, + VAR_PRE_CROSS, + VAR_PRE_CSC, + VAR_PRE_CSCH, + VAR_PRE_CSCHQ, + VAR_PRE_CSCQ, + VAR_PRE_CUBIC3D, + VAR_PRE_CUBIC_LATTICE3D, + VAR_PRE_CURL, + VAR_PRE_CURL3D, + VAR_PRE_CURL_SP, + VAR_PRE_CURVATURE, + VAR_PRE_CURVE, + VAR_PRE_CYLINDER, + VAR_PRE_DELTA_A, + VAR_PRE_DEPTH, + VAR_PRE_DIAMOND, + VAR_PRE_DISC, + VAR_PRE_DISC2, + VAR_PRE_DISC3D, + VAR_PRE_ECLIPSE, + VAR_PRE_ECOLLIDE, + VAR_PRE_EDISC, + VAR_PRE_EJULIA, + VAR_PRE_ELLIPTIC, + VAR_PRE_EMOD, + VAR_PRE_EMOTION, + VAR_PRE_ENNEPERS, + VAR_PRE_EPISPIRAL, + VAR_PRE_EPUSH, + VAR_PRE_EROTATE, + VAR_PRE_ESCALE, + VAR_PRE_ESCHER, + VAR_PRE_ESTIQ, + VAR_PRE_ESWIRL, + VAR_PRE_EX, + VAR_PRE_EXP, + VAR_PRE_EXPO, + VAR_PRE_EXPONENTIAL, + VAR_PRE_EXTRUDE, + VAR_PRE_EYEFISH, + VAR_PRE_FALLOFF, + VAR_PRE_FALLOFF2, + VAR_PRE_FALLOFF3, + VAR_PRE_FAN, + VAR_PRE_FAN2, + VAR_PRE_FARBLUR, + VAR_PRE_FDISC, + VAR_PRE_FIBONACCI, + VAR_PRE_FIBONACCI2, + VAR_PRE_FISHEYE, + VAR_PRE_FLATTEN, + VAR_PRE_FLIP_CIRCLE, + VAR_PRE_FLIP_Y, + VAR_PRE_FLOWER, + VAR_PRE_FLUX, + VAR_PRE_FOCI, + VAR_PRE_FOCI3D, + VAR_PRE_FOURTH, + VAR_PRE_FUNNEL, + VAR_PRE_GAUSSIAN_BLUR, + VAR_PRE_GDOFFS, + VAR_PRE_GLYNNIA, + VAR_PRE_GLYNNSIM1, + VAR_PRE_GLYNNSIM2, + VAR_PRE_GLYNNSIM3, + VAR_PRE_GRIDOUT, + VAR_PRE_HANDKERCHIEF, + VAR_PRE_HEART, + VAR_PRE_HEAT, + VAR_PRE_HEMISPHERE, + VAR_PRE_HO, + VAR_PRE_HOLE, + VAR_PRE_HORSESHOE, + VAR_PRE_HYPERBOLIC, + VAR_PRE_HYPERTILE, + VAR_PRE_HYPERTILE1, + VAR_PRE_HYPERTILE2, + VAR_PRE_HYPERTILE3D, + VAR_PRE_HYPERTILE3D1, + VAR_PRE_HYPERTILE3D2, + VAR_PRE_IDISC, + VAR_PRE_INTERFERENCE2, + VAR_PRE_JULIA, + VAR_PRE_JULIA3D, + VAR_PRE_JULIA3DQ, + VAR_PRE_JULIA3DZ, + VAR_PRE_JULIAC, + VAR_PRE_JULIAN, + VAR_PRE_JULIAN2, + VAR_PRE_JULIAN3DX, + VAR_PRE_JULIANAB, + VAR_PRE_JULIAQ, + VAR_PRE_JULIASCOPE, + VAR_PRE_KALEIDOSCOPE, + VAR_PRE_LAZY_TRAVIS, + VAR_PRE_LAZYSUSAN, + VAR_PRE_LINE, + VAR_PRE_LINEAR, + VAR_PRE_LINEAR_T, + VAR_PRE_LINEAR_T3D, + //VAR_PRE_LINEAR_XZ, + //VAR_PRE_LINEAR_YZ, + VAR_PRE_LINEAR3D, + VAR_PRE_LISSAJOUS, + VAR_PRE_LOG, + VAR_PRE_LOQ, + VAR_PRE_LOONIE, + VAR_PRE_LOONIE3D, + VAR_PRE_MASK, + VAR_PRE_MCARPET, + VAR_PRE_MIRROR_X, + VAR_PRE_MIRROR_Y, + VAR_PRE_MIRROR_Z, + VAR_PRE_MOBIQ, + VAR_PRE_MOBIUS, + VAR_PRE_MOBIUS_STRIP, + VAR_PRE_MOBIUSN, + VAR_PRE_MODULUS, + VAR_PRE_MURL, + VAR_PRE_MURL2, + VAR_PRE_NGON, + VAR_PRE_NOISE, + VAR_PRE_NPOLAR, + VAR_PRE_OCTAGON, + VAR_PRE_ORTHO, + VAR_PRE_OSCILLOSCOPE, + VAR_PRE_OVOID, + VAR_PRE_OVOID3D, + VAR_PRE_PARABOLA, + VAR_PRE_PDJ, + VAR_PRE_PERSPECTIVE, + VAR_PRE_PETAL, + VAR_PRE_PHOENIX_JULIA, + VAR_PRE_PIE, + VAR_PRE_PIE3D, + VAR_PRE_POINCARE, + VAR_PRE_POINCARE3D, + VAR_PRE_POLAR, + VAR_PRE_POLAR2, + VAR_PRE_POLYNOMIAL, + VAR_PRE_POPCORN, + VAR_PRE_POPCORN2, + VAR_PRE_POPCORN23D, + VAR_PRE_POW_BLOCK, + VAR_PRE_POWER, + VAR_PRE_PSPHERE, + VAR_PRE_Q_ODE, + VAR_PRE_RADIAL_BLUR, + VAR_PRE_RATIONAL3, + VAR_PRE_RAYS, + VAR_PRE_RBLUR, + VAR_PRE_RECTANGLES, + VAR_PRE_RINGS, + VAR_PRE_RINGS2, + VAR_PRE_RIPPLE, + VAR_PRE_RIPPLED, + VAR_PRE_ROTATE_X, + VAR_PRE_ROTATE_Y, + VAR_PRE_ROTATE_Z, + VAR_PRE_ROUNDSPHER, + VAR_PRE_ROUNDSPHER3D, + VAR_PRE_SCRY, + VAR_PRE_SCRY3D, + VAR_PRE_SEC, + VAR_PRE_SECANT2, + VAR_PRE_SECH, + VAR_PRE_SECHQ, + VAR_PRE_SECQ, + VAR_PRE_SEPARATION, + VAR_PRE_SHRED_RAD, + VAR_PRE_SHRED_LIN, + VAR_PRE_SIGMOID, + VAR_PRE_SIN, + VAR_PRE_SINEBLUR, + VAR_PRE_SINH, + VAR_PRE_SINHQ, + VAR_PRE_SINQ, + VAR_PRE_SINTRANGE, + VAR_PRE_SINUS_GRID, + VAR_PRE_SINUSOIDAL, + VAR_PRE_SINUSOIDAL3D, + VAR_PRE_SPHERICAL, + VAR_PRE_SPHERICAL3D, + VAR_PRE_SPHERICALN, + VAR_PRE_SPHERIVOID, + VAR_PRE_SPHYP3D, + VAR_PRE_SPIRAL, + VAR_PRE_SPIRAL_WING, + VAR_PRE_SPIROGRAPH, + VAR_PRE_SPLIT, + VAR_PRE_SPLIT_BRDR, + VAR_PRE_SPLITS, + VAR_PRE_SQUARE, + VAR_PRE_SQUARE3D, + VAR_PRE_SQUARIZE, + VAR_PRE_SQUIRREL, + VAR_PRE_SQUISH, + VAR_PRE_SSCHECKS, + VAR_PRE_STARBLUR, + VAR_PRE_STRIPES, + VAR_PRE_STWIN, + VAR_PRE_SUPER_SHAPE, + VAR_PRE_SUPER_SHAPE3D, + VAR_PRE_SVF, + VAR_PRE_SWIRL, + VAR_PRE_TAN, + VAR_PRE_TANCOS, + VAR_PRE_TANGENT, + VAR_PRE_TANH, + VAR_PRE_TANHQ, + VAR_PRE_TANQ, + VAR_PRE_TARGET, + VAR_PRE_TAURUS, + VAR_PRE_TRADE, + VAR_PRE_TRUCHET, + VAR_PRE_TWINTRIAN, + VAR_PRE_TWO_FACE, + VAR_PRE_UNPOLAR, + VAR_PRE_VORON, + VAR_PRE_WAFFLE, + VAR_PRE_WAVES, + VAR_PRE_WAVES2, + VAR_PRE_WAVES23D, + VAR_PRE_WAVESN, + VAR_PRE_WDISC, + VAR_PRE_WEDGE, + VAR_PRE_WEDGE_JULIA, + VAR_PRE_WEDGE_SPH, + VAR_PRE_WHORL, + VAR_PRE_XHEART, + VAR_PRE_XTRB, + VAR_PRE_ZBLUR, + VAR_PRE_ZCONE, + VAR_PRE_ZSCALE, + VAR_PRE_ZTRANSLATE, + + VAR_POST_ARCH, + VAR_POST_AUGER, + VAR_POST_BARYCENTROID, + VAR_POST_BCIRCLE, + VAR_POST_BCOLLIDE, + VAR_POST_BENT, + VAR_POST_BENT2, + VAR_POST_BIPOLAR, + VAR_POST_BISPLIT, + VAR_POST_BLADE, + VAR_POST_BLADE3D, + VAR_POST_BLOB, + VAR_POST_BLOB2, + VAR_POST_BLOB3D, + VAR_POST_BLOCKY, + VAR_POST_BLUR, + VAR_POST_BLUR_CIRCLE, + VAR_POST_BLUR_HEART, + VAR_POST_BLUR_LINEAR, + VAR_POST_BLUR_PIXELIZE, + VAR_POST_BLUR_SQUARE, + VAR_POST_BLUR_ZOOM, + VAR_POST_BLUR3D, + VAR_POST_BMOD, + VAR_POST_BOARDERS, + VAR_POST_BOARDERS2, + VAR_POST_BSWIRL, + VAR_POST_BTRANSFORM, + VAR_POST_BUBBLE, + VAR_POST_BUBBLE2, + VAR_POST_BUTTERFLY, + VAR_POST_BWRAPS, + VAR_POST_CARDIOID, + VAR_POST_CELL, + VAR_POST_CHECKS, + VAR_POST_CIRCLEBLUR, + VAR_POST_CIRCLECROP, + VAR_POST_CIRCLELINEAR, + VAR_POST_CIRCLERAND, + VAR_POST_CIRCLETRANS1, + VAR_POST_CIRCLIZE, + VAR_POST_CIRCLIZE2, + VAR_POST_CIRCUS, + VAR_POST_COLLIDEOSCOPE, + VAR_POST_CONIC, + VAR_POST_COS, + VAR_POST_COS_WRAP, + VAR_POST_COSH, + VAR_POST_COSHQ, + VAR_POST_COSINE, + VAR_POST_COSQ, + VAR_POST_COT, + VAR_POST_COTH, + VAR_POST_COTHQ, + VAR_POST_COTQ, + VAR_POST_CPOW, + VAR_POST_CPOW2, + VAR_POST_CRESCENTS, + VAR_POST_CROP, + VAR_POST_CROPN, + VAR_POST_CROSS, + VAR_POST_CSC, + VAR_POST_CSCH, + VAR_POST_CSCHQ, + VAR_POST_CSCQ, + VAR_POST_CUBIC3D, + VAR_POST_CUBIC_LATTICE3D, + VAR_POST_CURL, + VAR_POST_CURL3D, + VAR_POST_CURL_SP, + VAR_POST_CURVATURE, + VAR_POST_CURVE, + VAR_POST_CYLINDER, + VAR_POST_DELTA_A, + VAR_POST_DEPTH, + VAR_POST_DIAMOND, + VAR_POST_DISC, + VAR_POST_DISC2, + VAR_POST_DISC3D, + VAR_POST_ECLIPSE, + VAR_POST_ECOLLIDE, + VAR_POST_EDISC, + VAR_POST_EJULIA, + VAR_POST_ELLIPTIC, + VAR_POST_EMOD, + VAR_POST_EMOTION, + VAR_POST_ENNEPERS, + VAR_POST_EPISPIRAL, + VAR_POST_EPUSH, + VAR_POST_EROTATE, + VAR_POST_ESCALE, + VAR_POST_ESCHER, + VAR_POST_ESTIQ, + VAR_POST_ESWIRL, + VAR_POST_EX, + VAR_POST_EXP, + VAR_POST_EXPO, + VAR_POST_EXPONENTIAL, + VAR_POST_EXTRUDE, + VAR_POST_EYEFISH, + VAR_POST_FALLOFF, + VAR_POST_FALLOFF2, + VAR_POST_FALLOFF3, + VAR_POST_FAN, + VAR_POST_FAN2, + VAR_POST_FARBLUR, + VAR_POST_FDISC, + VAR_POST_FIBONACCI, + VAR_POST_FIBONACCI2, + VAR_POST_FISHEYE, + VAR_POST_FLATTEN, + VAR_POST_FLIP_CIRCLE, + VAR_POST_FLIP_Y, + VAR_POST_FLOWER, + VAR_POST_FLUX, + VAR_POST_FOCI, + VAR_POST_FOCI3D, + VAR_POST_FOURTH, + VAR_POST_FUNNEL, + VAR_POST_GAUSSIAN_BLUR, + VAR_POST_GDOFFS, + VAR_POST_GLYNNIA, + VAR_POST_GLYNNSIM1, + VAR_POST_GLYNNSIM2, + VAR_POST_GLYNNSIM3, + VAR_POST_GRIDOUT, + VAR_POST_HANDKERCHIEF, + VAR_POST_HEART, + VAR_POST_HEAT, + VAR_POST_HEMISPHERE, + VAR_POST_HO, + VAR_POST_HOLE, + VAR_POST_HORSESHOE, + VAR_POST_HYPERBOLIC, + VAR_POST_HYPERTILE, + VAR_POST_HYPERTILE1, + VAR_POST_HYPERTILE2, + VAR_POST_HYPERTILE3D, + VAR_POST_HYPERTILE3D1, + VAR_POST_HYPERTILE3D2, + VAR_POST_IDISC, + VAR_POST_INTERFERENCE2, + VAR_POST_JULIA, + VAR_POST_JULIA3D, + VAR_POST_JULIA3DQ, + VAR_POST_JULIA3DZ, + VAR_POST_JULIAC, + VAR_POST_JULIAN, + VAR_POST_JULIAN2, + VAR_POST_JULIAN3DX, + VAR_POST_JULIANAB, + VAR_POST_JULIAQ, + VAR_POST_JULIASCOPE, + VAR_POST_KALEIDOSCOPE, + VAR_POST_LAZY_TRAVIS, + VAR_POST_LAZYSUSAN, + VAR_POST_LINE, + VAR_POST_LINEAR, + VAR_POST_LINEAR_T, + VAR_POST_LINEAR_T3D, + //VAR_POST_LINEAR_XZ, + //VAR_POST_LINEAR_YZ, + VAR_POST_LINEAR3D, + VAR_POST_LISSAJOUS, + VAR_POST_LOG, + VAR_POST_LOQ, + VAR_POST_LOONIE, + VAR_POST_LOONIE3D, + VAR_POST_MASK, + VAR_POST_MCARPET, + VAR_POST_MIRROR_X, + VAR_POST_MIRROR_Y, + VAR_POST_MIRROR_Z, + VAR_POST_MOBIQ, + VAR_POST_MOBIUS, + VAR_POST_MOBIUS_STRIP, + VAR_POST_MOBIUSN, + VAR_POST_MODULUS, + VAR_POST_MURL, + VAR_POST_MURL2, + VAR_POST_NGON, + VAR_POST_NOISE, + VAR_POST_NPOLAR, + VAR_POST_OCTAGON, + VAR_POST_ORTHO, + VAR_POST_OSCILLOSCOPE, + VAR_POST_OVOID, + VAR_POST_OVOID3D, + VAR_POST_PARABOLA, + VAR_POST_PDJ, + VAR_POST_PERSPECTIVE, + VAR_POST_PETAL, + VAR_POST_PHOENIX_JULIA, + VAR_POST_PIE, + VAR_POST_PIE3D, + VAR_POST_POINCARE, + VAR_POST_POINCARE3D, + VAR_POST_POLAR, + VAR_POST_POLAR2, + VAR_POST_POLYNOMIAL, + VAR_POST_POPCORN, + VAR_POST_POPCORN2, + VAR_POST_POPCORN23D, + VAR_POST_POW_BLOCK, + VAR_POST_POWER, + VAR_POST_PSPHERE, + VAR_POST_Q_ODE, + VAR_POST_RADIAL_BLUR, + VAR_POST_RATIONAL3, + VAR_POST_RAYS, + VAR_POST_RBLUR, + VAR_POST_RECTANGLES, + VAR_POST_RINGS, + VAR_POST_RINGS2, + VAR_POST_RIPPLE, + VAR_POST_RIPPLED, + VAR_POST_ROTATE_X, + VAR_POST_ROTATE_Y, + VAR_POST_ROTATE_Z, + VAR_POST_ROUNDSPHER, + VAR_POST_ROUNDSPHER3D, + VAR_POST_SCRY, + VAR_POST_SCRY3D, + VAR_POST_SEC, + VAR_POST_SECANT2, + VAR_POST_SECH, + VAR_POST_SECHQ, + VAR_POST_SECQ, + VAR_POST_SEPARATION, + VAR_POST_SHRED_RAD, + VAR_POST_SHRED_LIN, + VAR_POST_SIGMOID, + VAR_POST_SIN, + VAR_POST_SINEBLUR, + VAR_POST_SINH, + VAR_POST_SINHQ, + VAR_POST_SINQ, + VAR_POST_SINTRANGE, + VAR_POST_SINUS_GRID, + VAR_POST_SINUSOIDAL, + VAR_POST_SINUSOIDAL3D, + VAR_POST_SPHERICAL, + VAR_POST_SPHERICAL3D, + VAR_POST_SPHERICALN, + VAR_POST_SPHERIVOID, + VAR_POST_SPHYP3D, + VAR_POST_SPIRAL, + VAR_POST_SPIRAL_WING, + VAR_POST_SPIROGRAPH, + VAR_POST_SPLIT, + VAR_POST_SPLIT_BRDR, + VAR_POST_SPLITS, + VAR_POST_SQUARE, + VAR_POST_SQUARE3D, + VAR_POST_SQUARIZE, + VAR_POST_SQUIRREL, + VAR_POST_SQUISH, + VAR_POST_SSCHECKS, + VAR_POST_STARBLUR, + VAR_POST_STRIPES, + VAR_POST_STWIN, + VAR_POST_SUPER_SHAPE, + VAR_POST_SUPER_SHAPE3D, + VAR_POST_SVF, + VAR_POST_SWIRL, + VAR_POST_TAN, + VAR_POST_TANCOS, + VAR_POST_TANGENT, + VAR_POST_TANH, + VAR_POST_TANHQ, + VAR_POST_TANQ, + VAR_POST_TARGET, + VAR_POST_TAURUS, + VAR_POST_TRADE, + VAR_POST_TRUCHET, + VAR_POST_TWINTRIAN, + VAR_POST_TWO_FACE, + VAR_POST_UNPOLAR, + VAR_POST_VORON, + VAR_POST_WAFFLE, + VAR_POST_WAVES, + VAR_POST_WAVES2, + VAR_POST_WAVES23D, + VAR_POST_WAVESN, + VAR_POST_WDISC, + VAR_POST_WEDGE, + VAR_POST_WEDGE_JULIA, + VAR_POST_WEDGE_SPH, + VAR_POST_WHORL, + VAR_POST_XHEART, + VAR_POST_XTRB, + VAR_POST_ZBLUR, + VAR_POST_ZCONE, + VAR_POST_ZSCALE, + VAR_POST_ZTRANSLATE, + + //Direct color are special and only some have pre/post counterparts. + VAR_DC_BUBBLE, + VAR_DC_CARPET, + VAR_DC_CUBE, + VAR_DC_CYLINDER, + VAR_DC_GRIDOUT, + VAR_DC_LINEAR, + VAR_DC_TRIANGLE, + VAR_DC_ZTRANSL, + + VAR_PRE_DC_CARPET, + VAR_PRE_DC_CUBE, + VAR_PRE_DC_GRIDOUT, + VAR_PRE_DC_TRIANGLE, + VAR_PRE_DC_ZTRANSL, + + VAR_POST_DC_CARPET, + VAR_POST_DC_CUBE, + VAR_POST_DC_GRIDOUT, + VAR_POST_DC_TRIANGLE, + VAR_POST_DC_ZTRANSL, + + LAST_VAR = VAR_POST_DC_ZTRANSL + 1 +}; + +/// +/// Translated and precalculated values that get passed to each variation's virtual function. +/// Note that this must be passed in and not a member because multiple threads will be calling +/// the variation functions simultaneously. Each thread will get its own IteratorHelper object. +/// Template argument expected to be float or double. +/// +template +class EMBER_API IteratorHelper +{ +public: + v2T m_Color; + T m_TransX, m_TransY, m_TransZ;//Translated point gotten by applying the affine transform to the input point gotten from the output of the previous iteration (excluding final). + T m_PrecalcSumSquares;//Precalculated value of the sum of the squares of the translated point. + T m_PrecalcSqrtSumSquares;//Precalculated value of the square root of m_PrecalcSumSquares. + T m_PrecalcSina;//Precalculated value of m_TransX / m_PrecalcSqrtSumSquares. + T m_PrecalcCosa;//Precalculated value of m_TransY / m_PrecalcSqrtSumSquares. + T m_PrecalcAtanxy;//Precalculated value of atan2(m_TransX, m_TransY). + T m_PrecalcAtanyx;//Precalculated value of atan2(m_TransY, m_TransX). + v4T In, Out; +}; + +/// +/// The base variation class from which all variations will derive. +/// Each has a unique ID, name and weight, as well as a virtual function Func() which +/// does the actual calculations. +/// Each also has boolean values that specify whether precalculations are needed. +/// These precalc flags are used by the parent Xform to determine which values to +/// precalculate in each iteration. +/// Template argument expected to be float or double. +/// +template +class EMBER_API Variation +{ +public: + /// + /// Constructor which takes parameters. + /// + /// The unique name of the variation + /// The unique ID of the variation + /// The weight. Default: 1. + /// Whether it uses the precalc sum squares value in its calculations. Default: false. + /// Whether it uses the sqrt precalc sum squares value in its calculations. Default: false. + /// Whether it uses the precalc sin and cos values in its calculations. Default: false. + /// Whether it uses the precalc atan XY value in its calculations. Default: false. + /// Whether it uses the precalc atan YX value in its calculations. Default: false. + Variation(const char* name, eVariationId id, T weight = 1.0, + bool needPrecalcSumSquares = false, + bool needPrecalcSqrtSumSquares = false, + bool needPrecalcAngles = false, + bool needPrecalcAtanXY = false, + bool needPrecalcAtanYX = false) + : m_Name(name)//Omit unnecessary default constructor call. + { + m_Xform = NULL; + m_VariationId = id; + m_Weight = weight; + m_NeedPrecalcSumSquares = needPrecalcSumSquares; + m_NeedPrecalcSqrtSumSquares = needPrecalcSqrtSumSquares; + m_NeedPrecalcAngles = needPrecalcAngles; + m_NeedPrecalcAtanXY = needPrecalcAtanXY; + m_NeedPrecalcAtanYX = needPrecalcAtanYX; + + //Make absolutely sure that flag logic makes sense. + if (m_NeedPrecalcSqrtSumSquares) + m_NeedPrecalcSumSquares = true; + + if (m_NeedPrecalcAngles) + { + m_NeedPrecalcSumSquares = true; + m_NeedPrecalcSqrtSumSquares = true; + } + + m_AssignType = ASSIGNTYPE_SET; + SetType(); + } + + /// + /// Default copy constructor. + /// + /// The Variation object to copy + Variation(const Variation& variation) + { + Variation::operator=(variation); + } + + /// + /// Copy constructor to copy a Variation object of type U. + /// + /// The Variation object to copy + template + Variation(const Variation& variation) + { + Variation::operator=(variation); + } + + /// + /// Empty virtual destructor. + /// Note that even though this is empty, it must be present + /// and be virtual for the derived classes to properly get destroyed. + /// + virtual ~Variation() + { + } + + /// + /// Default assignment operator. + /// + /// The Variation object to copy + Variation& operator = (const Variation& variation) + { + if (this != &variation) + Variation::operator=(variation); + + return *this; + } + + /// + /// Assignment operator to assign a Variation object of type U. + /// + /// The Variation object to copy. + /// Reference to updated self + template + Variation& operator = (const Variation& variation) + { + m_Name = variation.Name(); + m_VarType = variation.VarType(); + m_AssignType = variation.AssignType(); + m_VariationId = variation.VariationId(); + m_Weight = T(variation.m_Weight); + m_Xform = typeid(T) == typeid(U) ? (Xform*)variation.ParentXform() : NULL; + m_NeedPrecalcSumSquares = variation.NeedPrecalcSumSquares(); + m_NeedPrecalcSqrtSumSquares = variation.NeedPrecalcSqrtSumSquares(); + m_NeedPrecalcAngles = variation.NeedPrecalcAngles(); + m_NeedPrecalcAtanXY = variation.NeedPrecalcAtanXY(); + m_NeedPrecalcAtanYX = variation.NeedPrecalcAtanYX(); + + return *this; + } + + /// + /// Per-variation precalc used for pre and post variations. + /// + /// The helper to read values from in the case of pre, and store precalc values to in both cases. + /// The point to read values from in the case of post, ignored for pre. + void Precalc(IteratorHelper& iteratorHelper, Point* point) + { + if (m_VarType == VARTYPE_PRE) + { + if (m_NeedPrecalcSumSquares) + { + iteratorHelper.m_PrecalcSumSquares = SQR(iteratorHelper.m_TransX) + SQR(iteratorHelper.m_TransY); + + if (m_NeedPrecalcSqrtSumSquares) + { + iteratorHelper.m_PrecalcSqrtSumSquares = sqrt(iteratorHelper.m_PrecalcSumSquares); + + if (m_NeedPrecalcAngles) + { + iteratorHelper.m_PrecalcSina = iteratorHelper.m_TransX / iteratorHelper.m_PrecalcSqrtSumSquares; + iteratorHelper.m_PrecalcCosa = iteratorHelper.m_TransY / iteratorHelper.m_PrecalcSqrtSumSquares; + } + } + } + + if (m_NeedPrecalcAtanXY) + iteratorHelper.m_PrecalcAtanxy = atan2(iteratorHelper.m_TransX, iteratorHelper.m_TransY); + + if (m_NeedPrecalcAtanYX) + iteratorHelper.m_PrecalcAtanyx = atan2(iteratorHelper.m_TransY, iteratorHelper.m_TransX); + } + else if (m_VarType == VARTYPE_POST) + { + if (m_NeedPrecalcSumSquares) + { + iteratorHelper.m_PrecalcSumSquares = SQR(point->m_X) + SQR(point->m_Y); + + if (m_NeedPrecalcSqrtSumSquares) + { + iteratorHelper.m_PrecalcSqrtSumSquares = sqrt(iteratorHelper.m_PrecalcSumSquares); + + if (m_NeedPrecalcAngles) + { + iteratorHelper.m_PrecalcSina = point->m_X / iteratorHelper.m_PrecalcSqrtSumSquares; + iteratorHelper.m_PrecalcCosa = point->m_Y / iteratorHelper.m_PrecalcSqrtSumSquares; + } + } + } + + if (m_NeedPrecalcAtanXY) + iteratorHelper.m_PrecalcAtanxy = atan2(point->m_X, point->m_Y); + + if (m_NeedPrecalcAtanYX) + iteratorHelper.m_PrecalcAtanyx = atan2(point->m_Y, point->m_X); + } + } + + /// + /// Per-variation precalc OpenCL string used for pre and post variations. + /// + /// The per-variation OpenCL precalc string + string PrecalcOpenCLString() + { + ostringstream ss; + + if (m_VarType == VARTYPE_PRE) + { + if (m_NeedPrecalcSumSquares) + { + ss << "\tprecalcSumSquares = SQR(transX) + SQR(transY);\n"; + + if (m_NeedPrecalcSqrtSumSquares) + { + ss << "\tprecalcSqrtSumSquares = sqrt(precalcSumSquares);\n"; + + if (m_NeedPrecalcAngles) + { + ss << "\tprecalcSina = transX / precalcSqrtSumSquares;\n"; + ss << "\tprecalcCosa = transY / precalcSqrtSumSquares;\n"; + } + } + } + + if (m_NeedPrecalcAtanXY) + ss << "\tprecalcAtanxy = atan2(transX, transY);\n"; + + if (m_NeedPrecalcAtanYX) + ss << "\tprecalcAtanyx = atan2(transY, transX);\n"; + } + else if (m_VarType == VARTYPE_POST) + { + if (m_NeedPrecalcSumSquares) + { + ss << "\tprecalcSumSquares = SQR(outPoint->m_X) + SQR(outPoint->m_Y);\n"; + + if (m_NeedPrecalcSqrtSumSquares) + { + ss << "\tprecalcSqrtSumSquares = sqrt(precalcSumSquares);\n"; + + if (m_NeedPrecalcAngles) + { + ss << "\tprecalcSina = outPoint->m_X / precalcSqrtSumSquares;\n"; + ss << "\tprecalcCosa = outPoint->m_Y / precalcSqrtSumSquares;\n"; + } + } + } + + if (m_NeedPrecalcAtanXY) + ss << "\tprecalcAtanxy = atan2(outPoint->m_X, outPoint->m_Y);\n"; + + if (m_NeedPrecalcAtanYX) + ss << "\tprecalcAtanyx = atan2(outPoint->m_Y, outPoint->m_X);\n"; + } + + if (NeedAnyPrecalc()) + ss << "\n"; + + return ss.str(); + } + + /// + /// Return the name and weight of the variation as a string. + /// + /// The name and weight of the variation + virtual string ToString() const + { + ostringstream ss; + + ss << m_Name << "(" << m_Weight << ")"; + + return ss.str(); + } + + /// + /// Abstract copy function. Derived classes must implement. + /// + /// A copy of this object + virtual Variation* Copy() = 0; + + /// + /// Create a new Variation, store it in the pointer reference passed in and + /// copy the this Variation's values into it. + /// Note this is a severe hack to overcome two shortcomings in C++. + /// One is that templated functions cannot be virtual. + /// The second is that function overloading only works when parameters differ, not just return types. + /// In an ideal world, all copy functionality would be consolidated into a single function that looked like: + /// template virtual Variation Copy(); + /// Since that isn't possible, the only way to do what's needed is to create two functions to do this, one for + /// Variation and another for Variation. + /// This further offends design sensiblities since it requires this template class to know which types it's going to + /// be instantiated for. Sadly, there is no alternative and it must be done this way. Fortunately, we know it will + /// only ever be used with float and double. + /// + /// A reference to a pointer which will store the newly created Variation* + virtual void Copy(Variation*& var) const = 0; + +#ifdef DO_DOUBLE + /// + /// See description for Copy(Variation*& var). + /// + /// A reference to a pointer which will store the newly created Variation* + virtual void Copy(Variation*& var) const = 0; +#endif + + /// + /// Abstract function where the actual work takes place. Derived classes must implement. + /// + /// The IteratorHelper object which holds translated and precalculated values + /// The point to store the result in + /// The random number generator to use. + virtual void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) = 0; + + /// + /// Return a string which performs the equivalent calculation in Func(), but on the GPU in OpenCL. + /// Derived classes will implement this. + /// + /// The OpenCL string to perform the equivalent calculation on the GPU in OpenCL + virtual string OpenCLString() { return ""; } + + /// + /// If the OpenCL string depends on any functions specific to this variation, return them. + /// + /// The OpenCL string for functions specific to this variation + virtual string OpenCLFuncsString() { return ""; } + + /// + /// In addition to the standard precalculation stored in the IteratorHelper object, some + /// variations have additional precalculation work to do that can save processing time while iterating. + /// For most this is left empty, however a few will override. + /// + virtual void Precalc() { } + + /// + /// When creating random embers, the variations are placed in a random state. + /// For most this base implementation will be used, however a few will override. + /// + /// The rand. + virtual void Random(QTIsaac& rand) + { + m_Weight = rand.Frand11(); + } + + /// + /// Returns the string prefix to be used with params and the variation name. + /// + /// pre_, post_ or the empty string + string Prefix() const + { + if (m_VarType == VARTYPE_PRE) + return "pre_"; + else if (m_VarType == VARTYPE_POST) + return "post_"; + else + return ""; + } + + string BaseName() const + { + string prefix = Prefix(); + + if (prefix != "" && m_Name.find(prefix) == 0) + return m_Name.substr(prefix.size(), m_Name.size() - prefix.size()); + else + return m_Name; + } + + /// + /// Accessors. + /// + inline bool NeedPrecalcSumSquares() const { return m_NeedPrecalcSumSquares; } + inline bool NeedPrecalcSqrtSumSquares() const { return m_NeedPrecalcSqrtSumSquares; } + inline bool NeedPrecalcAngles() const { return m_NeedPrecalcAngles; } + inline bool NeedPrecalcAtanXY() const { return m_NeedPrecalcAtanXY; } + inline bool NeedPrecalcAtanYX() const { return m_NeedPrecalcAtanYX; } + inline bool NeedAnyPrecalc() const { return NeedPrecalcSumSquares() || NeedPrecalcSqrtSumSquares() || NeedPrecalcAngles() || NeedPrecalcAtanXY() || NeedPrecalcAtanYX(); } + eVariationId VariationId() const { return m_VariationId; } + string Name() const { return m_Name; } + eVariationType VarType() const { return m_VarType; } + eVariationAssignType AssignType() const { return m_AssignType; } + const Xform* ParentXform() const { return m_Xform; } + void ParentXform(Xform* xform) { m_Xform = xform; } + int IndexInXform() { return m_Xform ? m_Xform->GetVariationIndex(this) : -1; } + int XformIndexInEmber() { return m_Xform ? m_Xform->IndexInParentEmber() : -1; } + + T m_Weight;//The weight of the variation. + +protected: + void SetType() + { + if (m_Name.find("pre_") == 0) + m_VarType = VARTYPE_PRE; + else if (m_Name.find("post_") == 0) + m_VarType = VARTYPE_POST; + else + m_VarType = VARTYPE_REG; + } + + Xform* m_Xform;//The parent Xform that this variation is a child of. + eVariationId m_VariationId;//The unique ID of this variation. + string m_Name;//The unique name of this variation. + eVariationType m_VarType;//The type of variation: regular, pre or post. + eVariationAssignType m_AssignType;//Whether to assign the results for pre/post, or sum them. + +private: + bool m_NeedPrecalcSumSquares;//Whether this variation uses the precalc sum squares value in its calculations. + bool m_NeedPrecalcSqrtSumSquares;//Whether it uses the sqrt precalc sum squares value in its calculations. + bool m_NeedPrecalcAngles;//Whether it uses the precalc sin and cos values in its calculations. + bool m_NeedPrecalcAtanXY;//Whether it uses the precalc atan XY value in its calculations. + bool m_NeedPrecalcAtanYX;//Whether it uses the precalc atan YX value in its calculations. +}; + +/// +/// The type of parameter represented by ParamWithName. +/// This allows restricting of certain parameters to sensible values. +/// +enum eParamType +{ + REAL, + REAL_CYCLIC, + REAL_NONZERO, + INTEGER, + INTEGER_NONZERO +}; + +template class ParametricVariation; + +/// +/// Parametric variations use parameters in addition to weight. +/// These values are stored in members of classes derived from ParametricVariation, +/// however for easy access, pointers to them are also stored in a vector +/// of ParamWithName in ParametricVariation. +/// Each of these takes the form of a name string and a pointer to a value. +/// Also, some of them can be considered precalculated values, rather than +/// formal parameters. +/// This class encapsulates a single parameter. +/// Template argument expected to be float or double. +/// +template +class EMBER_API ParamWithName +{ + friend ParametricVariation; + +public: + /// + /// Default constructor. + /// + ParamWithName() + { + Init(NULL, "", 0, REAL, TLOW, TMAX); + } + + /// + /// Constructor for a precalc param that takes arguments. + /// + /// Whether the parameter is actually a precalculated value. Default: false. + /// A pointer to the parameter + /// The name of the parameter + ParamWithName(bool isPrecalc, + T* param, + string name) + { + Init(param, name, 0, REAL, TLOW, TMAX, true); + } + + /// + /// Constructor for a non-precalc param that takes arguments. + /// + /// A pointer to the parameter + /// The name of the parameter + /// The default value of the parameter + /// The type of the parameter + /// The minimum value the parameter can be + /// The maximum value the parameter can be + ParamWithName(T* param, string name, T def = 0, eParamType type = REAL, T min = TLOW, T max = TMAX) + { + Init(param, name, def, type, min, max); + } + + /// + /// Copy constructor. + /// Note this constructor does not take an additional template parameter + /// like the others do. This is because there is not way to assign the + /// param pointer from one type to another. Luckily, such functionality is not needed + /// with this class. + /// + /// The ParamWithName object to copy + ParamWithName(const ParamWithName& paramWithName) + { + *this = paramWithName; + } + + /// + /// Assignment operator. + /// Note this assignment operator does not take an additional template parameter + /// like the others do. This is because there is not way to assign the + /// param pointer from one type to another. Luckily, such functionality is not needed + /// with this class. + /// + /// The ParamWithName object to copy. + /// Reference to updated self + ParamWithName& operator = (const ParamWithName& paramWithName) + { + if (this != ¶mWithName) + { + m_Param = paramWithName.m_Param; + m_Def = paramWithName.m_Def; + m_Min = paramWithName.m_Min; + m_Max = paramWithName.m_Max; + m_Type = paramWithName.m_Type; + m_Name = paramWithName.m_Name; + m_IsPrecalc = paramWithName.m_IsPrecalc; + } + + return *this; + } + + /// + /// Constructor that takes arguments. + /// + /// A pointer to the parameter + /// The name of the parameter + /// The default value of the parameter + /// The type of the parameter + /// The minimum value the parameter can be + /// The maximum value the parameter can be + /// Whether the parameter is actually a precalculated value. Default: false. + void Init(T* param, string name, T def = 0, eParamType type = REAL, T min = TLOW, T max = TMAX, bool isPrecalc = false) + { + m_Param = param; + m_Def = def; + m_Min = min; + m_Max = max; + m_Type = type; + m_Name = name; + m_IsPrecalc = isPrecalc; + + Set(m_Def);//Initial value. + } + + /// + /// Set this parameter to the val. + /// Depending on the type that was specified in the constructor, various restrictions + /// will be put on the value. + /// + /// The value to set the parameter to + void Set(T val) + { + switch (m_Type) + { + case REAL : + { + *m_Param = max(min(val, m_Max), m_Min); + break; + } + + case REAL_CYCLIC : + { + if (val > m_Max) + *m_Param = m_Min + fmod(val - m_Min, m_Max - m_Min); + else if (val < m_Min) + *m_Param = m_Max - fmod(m_Max - val, m_Max - m_Min); + else + *m_Param = val; + + break; + } + + case REAL_NONZERO : + { + T vd = max(min(val, m_Max), m_Min); + + if (IsNearZero(vd)) + *m_Param = EPS6 * SignNz(vd); + else + *m_Param = vd; + + break; + } + + case INTEGER : + { + *m_Param = T((int)max(min((T)Floor(val + T(0.5)), m_Max), m_Min)); + break; + } + + case INTEGER_NONZERO : + { + int vi = (int)max(min((T)Floor(val + T(0.5)), m_Max), m_Min); + + if (vi == 0) + vi = (int)SignNz(val); + + *m_Param = T(vi); + break; + } + } + } + + /// + /// Return the values of the ParamWithName as a string. + /// + /// The ParamWithName values as a string + string ToString() const + { + ostringstream ss; + + ss << "Param Name: " << m_Name << endl + << "Param Pointer: " << m_Param << endl + << "Param Value: " << *m_Param << endl + << "Param Def: " << m_Def << endl + << "Param Min: " << m_Min << endl + << "Param Max: " << m_Max << endl + << "Param Type: " << m_Type << endl + << "Is Precalc: " << m_IsPrecalc << endl; + + return ss.str(); + } + + /// + /// Accessors. + /// + T* Param() const { return m_Param; } + T ParamVal() const { return *m_Param; } + T Def() const { return m_Def; } + T Min() const { return m_Min; } + T Max() const { return m_Max; } + eParamType Type() const { return m_Type; } + string Name() const { return m_Name; } + bool IsPrecalc() const { return m_IsPrecalc; } + +private: + T* m_Param;//Pointer to the parameter value. + T m_Def;//The default value of the parameter. + T m_Min;//The minimum value the parameter can be. + T m_Max;//The maximum value the parameter can be. + eParamType m_Type;//The type of the parameter. + string m_Name;//Name of the parameter. + bool m_IsPrecalc;//Whether the parameter is actually a precalculated value. +}; + +/// +/// Parametric variations use parameters in addition to weight. +/// These values are stored in members of derived classes, however +/// for easy access, pointers to them are also stored in a vector +/// of ParamWithName in this class. +/// Template argument expected to be float or double. +/// +template +class EMBER_API ParametricVariation : public Variation +{ +public: + /// + /// Constructor which takes arguments and just passes them to the base class. + /// + /// The unique name of the variation + /// The unique ID of the variation + /// The weight. Default: 1. + /// Whether it uses the precalc sum squares value in its calculations. Default: false. + /// Whether it uses the sqrt precalc sum squares value in its calculations. Default: false. + /// Whether it uses the precalc sin and cos values in its calculations. Default: false. + /// Whether it uses the precalc atan XY value in its calculations. Default: false. + /// Whether it uses the precalc atan YX value in its calculations. Default: false. + ParametricVariation(const char* name, eVariationId id, T weight = 1.0, + bool needPrecalcSumSquares = false, + bool needPrecalcSqrtSumSquares = false, + bool needPrecalcAngles = false, + bool needPrecalcAtanXY = false, + bool needPrecalcAtanYX = false) + : Variation(name, id, weight, + needPrecalcSumSquares, + needPrecalcSqrtSumSquares, + needPrecalcAngles, + needPrecalcAtanXY, + needPrecalcAtanYX) + { + m_Params.reserve(5); + } + + /// + /// Default copy constructor. + /// + /// The ParametricVariation object to copy + ParametricVariation(const ParametricVariation& var) + : Variation(var) + { + //Derived classes will have to initialize the m_Params vector + //to the addresses of its members and then assign values from var. + m_Params.reserve(5); + } + + /// + /// Copy constructor to copy a ParametricVariation object of type U. + /// + /// The ParametricVariation object to copy + template + ParametricVariation(const ParametricVariation& var) + : Variation(var) + { + //Derived classes will have to initialize the m_Params vector + //to the addresses of its members and then assign values from var. + m_Params.reserve(5); + } + + /// + /// Determine whether the params vector contains a parameter with the specified name. + /// + /// The name to search for + /// True if found, else false. + bool ContainsParam(const char* name) + { + bool b = false; + + std::for_each(m_Params.begin() , m_Params.end() , [&](ParamWithName& param) + { + if (!strcmp(param.Name().c_str(), name)) + b = true; + }); + + return b; + } + + /// + /// Get a pointer to a parameter value with the specified name. + /// + /// The name to search for + /// A pointer to the parameter value if the name matched, else false. + T* GetParam(const char* name) + { + for (size_t i = 0; i < m_Params.size(); i++) + if (!strcmp(m_Params[i].Name().c_str(), name)) + return m_Params[i].Param(); + + return NULL; + } + + /// + /// Get a parameter value with the specified name. + /// + /// The name to search for + /// A parameter value if the name matched, else 0. + T GetParamVal(const char* name) const + { + for (size_t i = 0; i < m_Params.size(); i++) + if (!strcmp(m_Params[i].Name().c_str(), name)) + return m_Params[i].ParamVal(); + + return 0; + } + + /// + /// Assign a value to the parameter with the specified name and call virtual Precalc() if found. + /// + /// The name of the parameter to assign to + /// The value to assign + /// True if the name matched, else false. + virtual bool SetParamVal(const char* name, T val) + { + bool b = false; + + std::for_each(m_Params.begin(), m_Params.end(), [&](ParamWithName& param) + { + if (!strcmp(param.Name().c_str(), name)) + { + param.Set(val); + b = true; + } + }); + + if (b) + Precalc(); + + return b; + } + + /// + /// Assign a value to the parameter at the specified index and call virtual Precalc() if found. + /// + /// The index of the parameter to assign to + /// The value to assign + /// True if the index was in range, else false. + virtual bool SetParamVal(int index, T val) + { + bool b = false; + + if (index < m_Params.size()) + m_Params[index].Set(val); + + if (b) + Precalc(); + + return b; + } + + /// + /// Place the parametric variation in a random state by setting all of the + /// non-precalc params to values between -1 and 1; + /// + /// The rand. + virtual void Random(QTIsaac& rand) + { + Variation::Random(rand); + std::for_each(m_Params.begin(), m_Params.end(), [&](ParamWithName& param) { param.Set(rand.Frand11()); }); + Precalc(); + } + + /// + /// Assign all 0 to all parameters and call virtual Precalc(). + /// + void Clear() + { + std::for_each(m_Params.begin(), m_Params.end(), [&](ParamWithName& param) { *(param.Param()) = 0; }); + Precalc(); + } + + /// + /// Return the name, weight and parameters of the variation as a string. + /// + /// The name, weight and parameters of the variation + virtual string ToString() const + { + ostringstream ss; + + ss << Variation::ToString() << endl; + std::for_each(m_Params.begin(), m_Params.end(), [&](const ParamWithName& param) { ss << param.ToString() << endl; }); + + return ss.str(); + } + + /// + /// Accessors. + /// + ParamWithName* Params() { return &m_Params[0]; } + unsigned int ParamCount() { return (unsigned int)m_Params.size(); } + const vector>& ParamsVec() const { return m_Params; } + +protected: + /// + /// Copy the non-precalc parameter values of type U to the pointer locations stored in the params vector of type T, + /// where T is usually the same type as U. + /// This will copy the values to the members of derived classes. + /// + /// The vector of parameters whose values will be copied + template + void CopyParamVals(const vector>& params) + { + if (m_Params.size() == params.size()) + { + for (size_t i = 0; i < m_Params.size(); i++) + if (!m_Params[i].IsPrecalc()) + m_Params[i].Set(T(params[i].ParamVal())); + + Precalc(); + } + } + + vector> m_Params;//The params pointer vector which stores pointer to parameter members of derived classes. +}; + +/// +/// Macro to define a default copy constructor, a copy constructor for a different template type, and a virtual Copy() function +/// for classes derived directly from Variation. +/// Defining assignment operators isn't really needed because Variations are always held as pointers. +/// + +#ifdef DO_DOUBLE +#define VARCOPY(name) \ + public: \ + name(const name& var) \ + : Variation(var) \ + { \ + } \ + \ + template \ + name(const name& var) \ + : Variation(var) \ + { \ + } \ + \ + virtual Variation* Copy() \ + { \ + return new name(*this); \ + } \ + \ + virtual void Copy(Variation*& var) const \ + { \ + if (var != NULL) \ + delete var; \ + \ + var = new name(*this); \ + } \ + \ + virtual void Copy(Variation*& var) const \ + { \ + if (var != NULL) \ + delete var; \ + \ + var = new name(*this); \ + } + +#define PREPOSTVARCOPY(name, base) \ + name(const name& var) \ + : base(var) \ + { \ + } \ + \ + template \ + name(const name& var) \ + : base(var) \ + { \ + } \ + \ + virtual Variation* Copy() \ + { \ + return new name(*this); \ + } \ + \ + virtual void Copy(Variation*& var) const \ + { \ + if (var != NULL) \ + delete var; \ + \ + var = new name(*this); \ + } \ + \ + virtual void Copy(Variation*& var) const \ + { \ + if (var != NULL) \ + delete var; \ + \ + var = new name(*this); \ + } + +#else + +#define VARCOPY(name) \ + public: \ + name(const name& var) \ + : Variation(var) \ + { \ + } \ + \ + template \ + name(const name& var) \ + : Variation(var) \ + { \ + } \ + \ + virtual Variation* Copy() \ + { \ + return new name(*this); \ + } \ + \ + virtual void Copy(Variation*& var) const \ + { \ + if (var != NULL) \ + delete var; \ + \ + var = new name(*this); \ + } + +#define PREPOSTVARCOPY(name, base) \ + name(const name& var) \ + : base(var) \ + { \ + } \ + \ + template \ + name(const name& var) \ + : base(var) \ + { \ + } \ + \ + virtual Variation* Copy() \ + { \ + return new name(*this); \ + } \ + \ + virtual void Copy(Variation*& var) const \ + { \ + if (var != NULL) \ + delete var; \ + \ + var = new name(*this); \ + } +#endif + +/// +/// Macro to create pre and post counterparts to a variation. +/// Assign type defaults to set. +/// + +#define MAKEPREPOSTVAR(varName, stringName, enumName) MAKEPREPOSTVARASSIGN(varName, stringName, enumName, ASSIGNTYPE_SET) +#define MAKEPREPOSTVARASSIGN(varName, stringName, enumName, assignType) \ + template \ + class EMBER_API Pre##varName##Variation : public varName##Variation \ + { \ + public: \ + Pre##varName##Variation(T weight = 1.0) : varName##Variation(weight) \ + { \ + m_VariationId = VAR_PRE_##enumName; \ + m_Name = "pre_"#stringName; \ + m_AssignType = assignType; \ + SetType(); \ + } \ + \ + PREPOSTVARCOPY(Pre##varName##Variation, varName##Variation) \ + }; \ + \ + template \ + class EMBER_API Post##varName##Variation : public varName##Variation \ + { \ + public:\ + Post##varName##Variation(T weight = 1.0) : varName##Variation(weight) \ + { \ + m_VariationId = VAR_POST_##enumName; \ + m_Name = "post_"#stringName; \ + m_AssignType = assignType; \ + SetType(); \ + } \ + \ + PREPOSTVARCOPY(Post##varName##Variation, varName##Variation) \ + }; + +/// +/// Macro to define a copy constructor, a copy constructor for a different template type, and a virtual Copy() function +/// for classes derived from ParametricVariation. +/// Another major shortcoming of C++: Ideally, Init() should be a virtual function defined in ParametricVariation. +/// It would be called in that constructor, and defined in each derived class. However, that can't be done because the vtable +/// is not setup during construction. +/// Instead, every class must define it as a non-virtual function and explicitly call it in its constructor. +/// + +#ifdef DO_DOUBLE + +#define PARVARCOPY(name) \ + public: \ + name(const name& var) \ + : ParametricVariation(var) \ + { \ + Init(); /* Assign the addresses of the members to the vector. */ \ + CopyParamVals(var.ParamsVec()); /* Copy values from var's vector and precalc. */ \ + } \ + \ + template \ + name(const name& var) \ + : ParametricVariation(var) \ + { \ + Init(); /* Assign the addresses of the members to the vector. */ \ + CopyParamVals(var.ParamsVec()); /* Copy values from var's vector and precalc. */ \ + } \ + \ + virtual Variation* Copy() \ + { \ + return new name(*this); \ + } \ + \ + virtual void Copy(Variation*& var) const \ + { \ + if (var != NULL) \ + delete var; \ + \ + var = new name(*this); \ + } \ + \ + virtual void Copy(Variation*& var) const \ + { \ + if (var != NULL) \ + delete var; \ + \ + var = new name(*this); \ + } + +#define PREPOSTPARVARCOPY(name, base) \ + name(const name& var) \ + : base(var) \ + { \ + Init(); /* Assign the addresses of the members to the vector. */ \ + CopyParamVals(var.ParamsVec()); /* Copy values from var's vector and precalc. */ \ + } \ + \ + template \ + name(const name& var) \ + : base(var) \ + { \ + Init(); /* Assign the addresses of the members to the vector. */ \ + CopyParamVals(var.ParamsVec()); /* Copy values from var's vector and precalc. */ \ + } \ + \ + virtual Variation* Copy() \ + { \ + return new name(*this); \ + } \ + \ + virtual void Copy(Variation*& var) const \ + { \ + if (var != NULL) \ + delete var; \ + \ + var = new name(*this); \ + } \ + \ + virtual void Copy(Variation*& var) const \ + { \ + if (var != NULL) \ + delete var; \ + \ + var = new name(*this); \ + } +#else + +#define PARVARCOPY(name) \ + public: \ + name(const name& var) \ + : ParametricVariation(var) \ + { \ + Init(); /* Assign the addresses of the members to the vector. */ \ + CopyParamVals(var.ParamsVec()); /* Copy values from var's vector and precalc. */ \ + } \ + \ + template \ + name(const name& var) \ + : ParametricVariation(var) \ + { \ + Init(); /* Assign the addresses of the members to the vector. */ \ + CopyParamVals(var.ParamsVec()); /* Copy values from var's vector and precalc. */ \ + } \ + \ + virtual Variation* Copy() \ + { \ + return new name(*this); \ + } \ + \ + virtual void Copy(Variation*& var) const \ + { \ + if (var != NULL) \ + delete var; \ + \ + var = new name(*this); \ + } + +#define PREPOSTPARVARCOPY(name, base) \ + name(const name& var) \ + : base(var) \ + { \ + Init(); /* Assign the addresses of the members to the vector. */ \ + CopyParamVals(var.ParamsVec()); /* Copy values from var's vector and precalc. */ \ + } \ + \ + template \ + name(const name& var) \ + : base(var) \ + { \ + Init(); /* Assign the addresses of the members to the vector. */ \ + CopyParamVals(var.ParamsVec()); /* Copy values from var's vector and precalc. */ \ + } \ + \ + virtual Variation* Copy() \ + { \ + return new name(*this); \ + } \ + \ + virtual void Copy(Variation*& var) const \ + { \ + if (var != NULL) \ + delete var; \ + \ + var = new name(*this); \ + } +#endif + +/// +/// Macro to create pre and post counterparts to a parametric variation. +/// Assign type defaults to set. +/// This uses the severe hack of calling Init() again after the type has been set +/// avoid having to change the constructor arguments for about 300 variations. +/// + +#define MAKEPREPOSTPARVAR(varName, stringName, enumName) MAKEPREPOSTPARVARASSIGN(varName, stringName, enumName, ASSIGNTYPE_SET) +#define MAKEPREPOSTPARVARASSIGN(varName, stringName, enumName, assignType) \ + template \ + class EMBER_API Pre##varName##Variation : public varName##Variation \ + { \ + public:\ + Pre##varName##Variation(T weight = 1.0) : varName##Variation(weight) \ + { \ + m_VariationId = VAR_PRE_##enumName; \ + m_Name = "pre_"#stringName; \ + m_AssignType = assignType; \ + SetType(); \ + Init(); \ + } \ + \ + PREPOSTPARVARCOPY(Pre##varName##Variation, varName##Variation) \ + }; \ + \ + template \ + class EMBER_API Post##varName##Variation : public varName##Variation \ + { \ + public:\ + Post##varName##Variation(T weight = 1.0) : varName##Variation(weight) \ + { \ + m_VariationId = VAR_POST_##enumName; \ + m_Name = "post_"#stringName; \ + m_AssignType = assignType; \ + SetType(); \ + Init(); \ + } \ + \ + PREPOSTPARVARCOPY(Post##varName##Variation, varName##Variation) \ + }; +} \ No newline at end of file diff --git a/Source/Ember/VariationList.h b/Source/Ember/VariationList.h new file mode 100644 index 0000000..36e408e --- /dev/null +++ b/Source/Ember/VariationList.h @@ -0,0 +1,537 @@ +#pragma once + +#include "Variations01.h" +#include "Variations02.h" +#include "Variations03.h" +#include "Variations04.h" +#include "Variations05.h" +#include "VariationsDC.h" + +/// +/// VariationList class. +/// + +namespace EmberNs +{ +/// +/// Since the list of variations is numerous, it's convenient to be able to make copies +/// of specific ones. This class holds a list of pointers to variation objects for every +/// variation available. Similar to the PaletteList class, a caller can look up a variation +/// by name or ID and retrieve a copy of it. +/// All variations are deleted upon destruction. +/// Template argument expected to be float or double. +/// +template +class EMBER_API VariationList +{ +#define ADDPREPOSTREGVAR(varName) \ + m_Variations.push_back(new varName##Variation()); \ + m_Variations.push_back(new Pre##varName##Variation()); \ + m_Variations.push_back(new Post##varName##Variation()); + +public: + /// + /// Constructor which initializes all of the variation objects and stores them in the list. + /// + VariationList() + { + m_Variations.reserve(900);//Change this as the list grows. + ADDPREPOSTREGVAR(Linear) + ADDPREPOSTREGVAR(Sinusoidal) + ADDPREPOSTREGVAR(Spherical) + ADDPREPOSTREGVAR(Swirl) + ADDPREPOSTREGVAR(Horseshoe) + ADDPREPOSTREGVAR(Polar) + ADDPREPOSTREGVAR(Handkerchief) + ADDPREPOSTREGVAR(Heart) + ADDPREPOSTREGVAR(Disc) + ADDPREPOSTREGVAR(Spiral) + ADDPREPOSTREGVAR(Hyperbolic) + ADDPREPOSTREGVAR(Diamond) + ADDPREPOSTREGVAR(Ex) + ADDPREPOSTREGVAR(Julia) + ADDPREPOSTREGVAR(Bent) + ADDPREPOSTREGVAR(Waves) + ADDPREPOSTREGVAR(Fisheye) + ADDPREPOSTREGVAR(Popcorn) + ADDPREPOSTREGVAR(Exponential) + ADDPREPOSTREGVAR(Power) + ADDPREPOSTREGVAR(Cosine) + ADDPREPOSTREGVAR(Rings) + ADDPREPOSTREGVAR(Fan) + ADDPREPOSTREGVAR(Blob) + ADDPREPOSTREGVAR(Pdj) + ADDPREPOSTREGVAR(Fan2) + ADDPREPOSTREGVAR(Rings2) + ADDPREPOSTREGVAR(Eyefish) + ADDPREPOSTREGVAR(Bubble) + ADDPREPOSTREGVAR(Cylinder) + ADDPREPOSTREGVAR(Perspective) + ADDPREPOSTREGVAR(Noise) + ADDPREPOSTREGVAR(JuliaNGeneric) + ADDPREPOSTREGVAR(JuliaScope) + ADDPREPOSTREGVAR(Blur) + ADDPREPOSTREGVAR(GaussianBlur) + ADDPREPOSTREGVAR(RadialBlur) + ADDPREPOSTREGVAR(Pie) + ADDPREPOSTREGVAR(Ngon) + ADDPREPOSTREGVAR(Curl) + ADDPREPOSTREGVAR(Rectangles) + ADDPREPOSTREGVAR(Arch) + ADDPREPOSTREGVAR(Tangent) + ADDPREPOSTREGVAR(Square) + ADDPREPOSTREGVAR(Rays) + ADDPREPOSTREGVAR(Blade) + ADDPREPOSTREGVAR(Secant2) + ADDPREPOSTREGVAR(TwinTrian) + ADDPREPOSTREGVAR(Cross) + ADDPREPOSTREGVAR(Disc2) + ADDPREPOSTREGVAR(SuperShape) + ADDPREPOSTREGVAR(Flower) + ADDPREPOSTREGVAR(Conic) + ADDPREPOSTREGVAR(Parabola) + ADDPREPOSTREGVAR(Bent2) + ADDPREPOSTREGVAR(Bipolar) + ADDPREPOSTREGVAR(Boarders) + ADDPREPOSTREGVAR(Butterfly) + ADDPREPOSTREGVAR(Cell) + ADDPREPOSTREGVAR(Cpow) + ADDPREPOSTREGVAR(Curve) + ADDPREPOSTREGVAR(Edisc) + ADDPREPOSTREGVAR(Elliptic) + ADDPREPOSTREGVAR(Escher) + ADDPREPOSTREGVAR(Foci) + ADDPREPOSTREGVAR(LazySusan) + ADDPREPOSTREGVAR(Loonie) + ADDPREPOSTREGVAR(Modulus) + ADDPREPOSTREGVAR(Oscilloscope) + ADDPREPOSTREGVAR(Polar2) + ADDPREPOSTREGVAR(Popcorn2) + ADDPREPOSTREGVAR(Scry) + ADDPREPOSTREGVAR(Separation) + ADDPREPOSTREGVAR(Split) + ADDPREPOSTREGVAR(Splits) + ADDPREPOSTREGVAR(Stripes) + ADDPREPOSTREGVAR(Wedge) + ADDPREPOSTREGVAR(WedgeJulia) + ADDPREPOSTREGVAR(WedgeSph) + ADDPREPOSTREGVAR(Whorl) + ADDPREPOSTREGVAR(Waves2) + ADDPREPOSTREGVAR(Exp) + ADDPREPOSTREGVAR(Log) + ADDPREPOSTREGVAR(Sin) + ADDPREPOSTREGVAR(Cos) + ADDPREPOSTREGVAR(Tan) + ADDPREPOSTREGVAR(Sec) + ADDPREPOSTREGVAR(Csc) + ADDPREPOSTREGVAR(Cot) + ADDPREPOSTREGVAR(Sinh) + ADDPREPOSTREGVAR(Cosh) + ADDPREPOSTREGVAR(Tanh) + ADDPREPOSTREGVAR(Sech) + ADDPREPOSTREGVAR(Csch) + ADDPREPOSTREGVAR(Coth) + ADDPREPOSTREGVAR(Auger) + ADDPREPOSTREGVAR(Flux) + ADDPREPOSTREGVAR(Hemisphere) + ADDPREPOSTREGVAR(Epispiral) + ADDPREPOSTREGVAR(Bwraps) + ADDPREPOSTREGVAR(BlurCircle) + ADDPREPOSTREGVAR(BlurZoom) + ADDPREPOSTREGVAR(BlurPixelize) + ADDPREPOSTREGVAR(Crop) + ADDPREPOSTREGVAR(BCircle) + ADDPREPOSTREGVAR(BlurLinear) + ADDPREPOSTREGVAR(BlurSquare) + ADDPREPOSTREGVAR(Boarders2) + ADDPREPOSTREGVAR(Cardioid) + ADDPREPOSTREGVAR(Checks) + ADDPREPOSTREGVAR(Circlize) + ADDPREPOSTREGVAR(Circlize2) + ADDPREPOSTREGVAR(CosWrap) + ADDPREPOSTREGVAR(DeltaA) + ADDPREPOSTREGVAR(Expo) + ADDPREPOSTREGVAR(Extrude) + ADDPREPOSTREGVAR(FDisc) + ADDPREPOSTREGVAR(Fibonacci) + ADDPREPOSTREGVAR(Fibonacci2) + ADDPREPOSTREGVAR(Glynnia) + ADDPREPOSTREGVAR(GridOut) + ADDPREPOSTREGVAR(Hole) + ADDPREPOSTREGVAR(Hypertile) + ADDPREPOSTREGVAR(Hypertile1) + ADDPREPOSTREGVAR(Hypertile2) + ADDPREPOSTREGVAR(Hypertile3D) + ADDPREPOSTREGVAR(Hypertile3D1) + ADDPREPOSTREGVAR(Hypertile3D2) + ADDPREPOSTREGVAR(IDisc) + ADDPREPOSTREGVAR(Julian2) + ADDPREPOSTREGVAR(JuliaQ) + ADDPREPOSTREGVAR(Murl) + ADDPREPOSTREGVAR(Murl2) + ADDPREPOSTREGVAR(NPolar) + ADDPREPOSTREGVAR(Ortho) + ADDPREPOSTREGVAR(Poincare) + ADDPREPOSTREGVAR(Poincare3D) + ADDPREPOSTREGVAR(Polynomial) + ADDPREPOSTREGVAR(PSphere) + ADDPREPOSTREGVAR(Rational3) + ADDPREPOSTREGVAR(Ripple) + ADDPREPOSTREGVAR(Sigmoid) + ADDPREPOSTREGVAR(SinusGrid) + ADDPREPOSTREGVAR(Stwin) + ADDPREPOSTREGVAR(TwoFace) + ADDPREPOSTREGVAR(Unpolar) + ADDPREPOSTREGVAR(WavesN) + ADDPREPOSTREGVAR(XHeart) + ADDPREPOSTREGVAR(Barycentroid) + ADDPREPOSTREGVAR(BiSplit) + ADDPREPOSTREGVAR(Crescents) + ADDPREPOSTREGVAR(Mask) + ADDPREPOSTREGVAR(Cpow2) + ADDPREPOSTREGVAR(Curl3D) + ADDPREPOSTREGVAR(Disc3D) + ADDPREPOSTREGVAR(Funnel) + ADDPREPOSTREGVAR(Linear3D) + ADDPREPOSTREGVAR(PowBlock) + ADDPREPOSTREGVAR(Squirrel) + ADDPREPOSTREGVAR(Ennepers) + ADDPREPOSTREGVAR(SphericalN) + ADDPREPOSTREGVAR(Kaleidoscope) + ADDPREPOSTREGVAR(GlynnSim1) + ADDPREPOSTREGVAR(GlynnSim2) + ADDPREPOSTREGVAR(GlynnSim3) + ADDPREPOSTREGVAR(Starblur) + ADDPREPOSTREGVAR(Sineblur) + ADDPREPOSTREGVAR(Circleblur) + ADDPREPOSTREGVAR(CropN) + ADDPREPOSTREGVAR(ShredRad) + ADDPREPOSTREGVAR(Blob2) + ADDPREPOSTREGVAR(Julia3D) + ADDPREPOSTREGVAR(Julia3Dz) + ADDPREPOSTREGVAR(LinearT) + ADDPREPOSTREGVAR(LinearT3D) + ADDPREPOSTREGVAR(Ovoid) + ADDPREPOSTREGVAR(Ovoid3D) + ADDPREPOSTREGVAR(Spirograph) + ADDPREPOSTREGVAR(Petal) + ADDPREPOSTREGVAR(RoundSpher) + ADDPREPOSTREGVAR(RoundSpher3D) + ADDPREPOSTREGVAR(SpiralWing) + ADDPREPOSTREGVAR(Squarize) + ADDPREPOSTREGVAR(Sschecks) + ADDPREPOSTREGVAR(PhoenixJulia) + ADDPREPOSTREGVAR(Mobius) + ADDPREPOSTREGVAR(MobiusN) + ADDPREPOSTREGVAR(MobiusStrip) + ADDPREPOSTREGVAR(Lissajous) + ADDPREPOSTREGVAR(Svf) + ADDPREPOSTREGVAR(Target) + ADDPREPOSTREGVAR(Taurus) + ADDPREPOSTREGVAR(Collideoscope) + ADDPREPOSTREGVAR(BMod) + ADDPREPOSTREGVAR(BSwirl) + ADDPREPOSTREGVAR(BTransform) + ADDPREPOSTREGVAR(BCollide) + ADDPREPOSTREGVAR(Eclipse) + ADDPREPOSTREGVAR(FlipCircle) + ADDPREPOSTREGVAR(FlipY) + ADDPREPOSTREGVAR(ECollide) + ADDPREPOSTREGVAR(EJulia) + ADDPREPOSTREGVAR(EMod) + ADDPREPOSTREGVAR(EMotion) + ADDPREPOSTREGVAR(EPush) + ADDPREPOSTREGVAR(ERotate) + ADDPREPOSTREGVAR(EScale) + ADDPREPOSTREGVAR(ESwirl) + ADDPREPOSTREGVAR(LazyTravis) + ADDPREPOSTREGVAR(Squish) + ADDPREPOSTREGVAR(Circus) + ADDPREPOSTREGVAR(Tancos) + ADDPREPOSTREGVAR(Rippled) + ADDPREPOSTREGVAR(RotateX) + ADDPREPOSTREGVAR(RotateY) + ADDPREPOSTREGVAR(RotateZ) + ADDPREPOSTREGVAR(Flatten) + ADDPREPOSTREGVAR(Zblur) + ADDPREPOSTREGVAR(Blur3D) + ADDPREPOSTREGVAR(ZScale) + ADDPREPOSTREGVAR(ZTranslate) + ADDPREPOSTREGVAR(ZCone) + ADDPREPOSTREGVAR(MirrorX) + ADDPREPOSTREGVAR(MirrorY) + ADDPREPOSTREGVAR(MirrorZ) + ADDPREPOSTREGVAR(Depth) + ADDPREPOSTREGVAR(Spherical3D) + ADDPREPOSTREGVAR(RBlur) + ADDPREPOSTREGVAR(JuliaNab) + ADDPREPOSTREGVAR(Sintrange) + ADDPREPOSTREGVAR(Voron) + ADDPREPOSTREGVAR(Waffle) + ADDPREPOSTREGVAR(Square3D) + ADDPREPOSTREGVAR(SuperShape3D) + ADDPREPOSTREGVAR(Sphyp3D) + ADDPREPOSTREGVAR(Circlecrop) + ADDPREPOSTREGVAR(Julian3Dx) + ADDPREPOSTREGVAR(Fourth) + ADDPREPOSTREGVAR(Mobiq) + ADDPREPOSTREGVAR(Spherivoid) + ADDPREPOSTREGVAR(Farblur) + ADDPREPOSTREGVAR(CurlSP) + ADDPREPOSTREGVAR(Heat) + ADDPREPOSTREGVAR(Interference2) + ADDPREPOSTREGVAR(Sinq) + ADDPREPOSTREGVAR(Sinhq) + ADDPREPOSTREGVAR(Secq) + ADDPREPOSTREGVAR(Sechq) + ADDPREPOSTREGVAR(Tanq) + ADDPREPOSTREGVAR(Tanhq) + ADDPREPOSTREGVAR(Cosq) + ADDPREPOSTREGVAR(Coshq) + ADDPREPOSTREGVAR(Cotq) + ADDPREPOSTREGVAR(Cothq) + ADDPREPOSTREGVAR(Cscq) + ADDPREPOSTREGVAR(Cschq) + ADDPREPOSTREGVAR(Estiq) + ADDPREPOSTREGVAR(Loq) + ADDPREPOSTREGVAR(Curvature) + ADDPREPOSTREGVAR(Qode) + ADDPREPOSTREGVAR(BlurHeart) + ADDPREPOSTREGVAR(Truchet) + ADDPREPOSTREGVAR(Gdoffs) + ADDPREPOSTREGVAR(Octagon) + ADDPREPOSTREGVAR(Trade) + ADDPREPOSTREGVAR(Juliac) + ADDPREPOSTREGVAR(Blade3D) + ADDPREPOSTREGVAR(Blob3D) + ADDPREPOSTREGVAR(Blocky) + ADDPREPOSTREGVAR(Bubble2) + ADDPREPOSTREGVAR(CircleLinear) + ADDPREPOSTREGVAR(CircleRand) + ADDPREPOSTREGVAR(CircleTrans1) + ADDPREPOSTREGVAR(Cubic3D) + ADDPREPOSTREGVAR(CubicLattice3D) + ADDPREPOSTREGVAR(Foci3D) + ADDPREPOSTREGVAR(Ho) + ADDPREPOSTREGVAR(Julia3Dq) + ADDPREPOSTREGVAR(Line) + ADDPREPOSTREGVAR(Loonie3D) + ADDPREPOSTREGVAR(Mcarpet) + ADDPREPOSTREGVAR(Waves23D) + ADDPREPOSTREGVAR(Pie3D) + ADDPREPOSTREGVAR(Popcorn23D) + ADDPREPOSTREGVAR(Sinusoidal3D) + ADDPREPOSTREGVAR(Scry3D) + ADDPREPOSTREGVAR(Shredlin) + ADDPREPOSTREGVAR(SplitBrdr) + ADDPREPOSTREGVAR(Wdisc) + ADDPREPOSTREGVAR(Falloff) + ADDPREPOSTREGVAR(Falloff2) + ADDPREPOSTREGVAR(Falloff3) + ADDPREPOSTREGVAR(Xtrb) + //ADDPREPOSTREGVAR(LinearXZ) + //ADDPREPOSTREGVAR(LinearYZ) + + //DC are special. + m_Variations.push_back(new DCBubbleVariation()); + ADDPREPOSTREGVAR(DCCarpet) + ADDPREPOSTREGVAR(DCCube) + m_Variations.push_back(new DCCylinderVariation()); + ADDPREPOSTREGVAR(DCGridOut) + m_Variations.push_back(new DCLinearVariation()); + ADDPREPOSTREGVAR(DCTriangle) + ADDPREPOSTREGVAR(DCZTransl) + + std::for_each(m_Variations.begin(), m_Variations.end(), [&](Variation* var) { var->Precalc(); }); + std::sort(m_Variations.begin(), m_Variations.end(), [&](const Variation* var1, const Variation* var2) { return var1->VariationId() < var2->VariationId(); }); + + m_RegVariations.reserve(m_Variations.size() / 3); + m_PreVariations.reserve(m_Variations.size() / 3); + m_PostVariations.reserve(m_Variations.size() / 3); + + std::for_each(m_Variations.begin(), m_Variations.end(), [&](Variation* var) { if (var->VarType() == VARTYPE_REG) m_RegVariations.push_back(var); }); + std::for_each(m_Variations.begin(), m_Variations.end(), [&](Variation* var) { if (var->VarType() == VARTYPE_PRE) m_PreVariations.push_back(var); }); + std::for_each(m_Variations.begin(), m_Variations.end(), [&](Variation* var) { if (var->VarType() == VARTYPE_POST) m_PostVariations.push_back(var); }); + + //Keep a list of which variations derive from ParametricVariation. + //Note that these are not new copies, rather just pointers to the original instances in m_Variations. + for (unsigned int i = 0; i < m_Variations.size(); i++) + { + if (ParametricVariation* parVar = dynamic_cast*>(m_Variations[i])) + m_ParametricVariations.push_back(parVar); + } + } + + /// + /// Delete each element of the list. + /// + ~VariationList() + { + ClearVec(m_Variations);//No need to delete parametric because they point to the entries in original vector. + } + + /// + /// Get a pointer to the variation at the specified index. + /// + /// The index in the list to retrieve + /// A pointer to the variation at the index if in range, else NULL. + Variation* GetVariation(size_t index) { return index < m_Variations.size() ? m_Variations[index] : NULL; } + + /// + /// Get a pointer to the variation of a specified type at the specified index. + /// + /// The index in the list to retrieve + /// The type of variation to retrieve + /// A pointer to the variation of the specified type at the index if in range, else NULL. + Variation* GetVariation(size_t index, eVariationType varType) + { + if (varType == VARTYPE_REG) + return index < m_RegVariations.size() ? m_RegVariations[index] : NULL; + else if (varType == VARTYPE_PRE) + return index < m_PreVariations.size() ? m_PreVariations[index] : NULL; + else if (varType == VARTYPE_POST) + return index < m_PostVariations.size() ? m_PostVariations[index] : NULL; + else + return NULL; + } + + /// + /// Gets a pointer to a copy of the variation at the specified index. + /// Optionally specify a weight to assign the new copy. + /// + /// The index in the list to make a copy of + /// The weight to assign the new copy. Default: 1 + /// A pointer to the variation at the index if in range, else NULL. + Variation* GetVariationCopy(size_t index, T weight = 1) { return MakeCopyWithWeight(GetVariation(index), weight); } + Variation* GetVariationCopy(size_t index, eVariationType varType, T weight = 1) { return MakeCopyWithWeight(GetVariation(index, varType), weight); } + + /// + /// Get a pointer to the variation with the specified ID. + /// + /// The ID to search for + /// A pointer to the variation if found, else NULL. + Variation* GetVariation(eVariationId id) + { + for (unsigned int i = 0; i < m_Variations.size() && m_Variations[i] != NULL; i++) + if (id == m_Variations[i]->VariationId()) + return m_Variations[i]; + + return NULL; + } + + /// + /// Gets a pointer to a copy of the variation with the specified ID. + /// Optionally specify a weight to assign the new copy. + /// + /// The id of the variation in the list to make a copy of + /// The weight to assign the new copy. Default: 1 + /// A pointer to the variation with a matching ID, else NULL. + Variation* GetVariationCopy(eVariationId id, T weight = 1) { return MakeCopyWithWeight(GetVariation(id), weight); } + + /// + /// Get a pointer to the variation with the specified name. + /// + /// The name to search for + /// A pointer to the variation if found, else NULL. + Variation* GetVariation(string name) + { + for (unsigned int i = 0; i < m_Variations.size() && m_Variations[i] != NULL; i++) + if (name == m_Variations[i]->Name()) + return m_Variations[i]; + + return NULL; + } + + /// + /// Gets a pointer to a copy of the variation with the specified name. + /// Optionally specify a weight to assign the new copy. + /// + /// The name of the variation in the list to make a copy of + /// The weight to assign the new copy. Default: 1 + /// A pointer to the variation with a matching name, else NULL. + Variation* GetVariationCopy(string name, T weight = 1) { return MakeCopyWithWeight(GetVariation(name), weight); } + + /// + /// Get a parametric variation at the specified index. + /// Note this is the index in the parametric variations list, not in the master list. + /// + /// The index in the parametric variations list to retrieve + /// The parametric variation at the index specified if in range, else NULL. + ParametricVariation* GetParametricVariation(size_t index) { return index < m_ParametricVariations.size() ? m_ParametricVariations[index] : NULL; } + + /// + /// Get a parametric variation with the specified name. + /// + /// The name of the variation in the parametric variations list to retrieve + /// The parametric variation with a matching name, else NULL. + ParametricVariation* GetParametricVariation(string name) + { + for (unsigned int i = 0; i < m_ParametricVariations.size() && m_ParametricVariations[i] != NULL; i++) + if (name == m_ParametricVariations[i]->Name()) + return m_ParametricVariations[i]; + + return NULL; + } + + /// + /// Get the index of the variation with the specified name. + /// + /// The name of the variation whose index is returned + /// The index of the variation with the matching name, else -1 + int GetVariationIndex(string name) + { + for (unsigned int i = 0; i < m_Variations.size() && m_Variations[i] != NULL; i++) + if (name == m_Variations[i]->Name()) + return i; + + return -1; + } + + /// + /// Accessors. + /// + size_t Size() { return m_Variations.size(); } + size_t RegSize() { return m_RegVariations.size(); } + size_t PreSize() { return m_PreVariations.size(); } + size_t PostSize() { return m_PostVariations.size(); } + size_t ParametricSize() { return m_ParametricVariations.size(); } + +private: + /// + /// Make a dyncamically allocated copy of a variation and assign it a specified weight. + /// Return a pointer to the new copy. + /// + /// The variation to copy + /// The weight to assign it + /// A pointer to the new variation copy if success, else NULL. + Variation* MakeCopyWithWeight(Variation* var, T weight) + { + if (var) + { + Variation* var2 = var->Copy(); + + var2->m_Weight = weight; + return var2; + } + + return NULL; + } + + /// + /// Assignment operator which does nothing since these are non-copyable. + /// Do not provide a copy constructor and ensure the assignment operator does nothing. + /// + /// The VariationList object which won't be copied + /// Reference to unchanged self + VariationList& operator = (const VariationList& varList) + { + return *this; + } + + vector*> m_Variations;//A list of pointers to dynamically allocated variation objects. + vector*> m_RegVariations; + vector*> m_PreVariations; + vector*> m_PostVariations; + vector*> m_ParametricVariations;//A list of pointers to elements in m_Variations which are derived from ParametricVariation. +}; +} \ No newline at end of file diff --git a/Source/Ember/Variations01.h b/Source/Ember/Variations01.h new file mode 100644 index 0000000..22da9d3 --- /dev/null +++ b/Source/Ember/Variations01.h @@ -0,0 +1,6296 @@ +#pragma once + +#include "Variation.h" + +namespace EmberNs +{ +/// +/// Linear: +/// nx = tx; +/// ny = ty; +/// p[0] += weight * nx; +/// p[1] += weight * ny; +/// +template +class EMBER_API LinearVariation : public Variation +{ +public: + LinearVariation(T weight = 1.0) : Variation("linear", VAR_LINEAR, weight) { } + + VARCOPY(LinearVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + helper.Out.x = m_Weight * helper.In.x; + helper.Out.y = m_Weight * helper.In.y; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Sinusoidal: +/// nx = sin(tx); +/// ny = sin(ty); +/// p[0] += weight * nx; +/// p[1] += weight * ny; +/// +template +class EMBER_API SinusoidalVariation : public Variation +{ +public: + SinusoidalVariation(T weight = 1.0) : Variation("sinusoidal", VAR_SINUSOIDAL, weight) { } + + VARCOPY(SinusoidalVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + helper.Out.x = m_Weight * sin(helper.In.x); + helper.Out.y = m_Weight * sin(helper.In.y); + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * sin(vIn.x);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * sin(vIn.y);\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Spherical: +/// T r2 = tx * tx + ty * ty + 1e-6; +/// nx = tx / r2; +/// ny = ty / r2; +/// p[0] += weight * nx; +/// p[1] += weight * ny; +/// +template +class EMBER_API SphericalVariation : public Variation +{ +public: + SphericalVariation(T weight = 1.0) : Variation("spherical", VAR_SPHERICAL, weight, true) { } + + VARCOPY(SphericalVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r2 = m_Weight / (helper.m_PrecalcSumSquares + EPS6); + + helper.Out.x = r2 * helper.In.x; + helper.Out.y = r2 * helper.In.y; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t r2 = xform->m_VariationWeights[" << varIndex << "] / (precalcSumSquares + EPS6);\n" + << "\n" + << "\t\tvOut.x = r2 * vIn.x;\n" + << "\t\tvOut.y = r2 * vIn.y;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Swirl: +/// double r2 = tx * tx + ty * ty; +/// double c1 = sin(r2); +/// double c2 = cos(r2); +/// nx = c1 * tx - c2 * ty; +/// ny = c2 * tx + c1 * ty; +/// p[0] += weight * nx; +/// p[1] += weight * ny; +/// +template +class EMBER_API SwirlVariation : public Variation +{ +public: + SwirlVariation(T weight = 1.0) : Variation("swirl", VAR_SWIRL, weight, true) { } + + VARCOPY(SwirlVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T c1, c2; + + sincos(helper.m_PrecalcSumSquares, &c1, &c2); + helper.Out.x = m_Weight * (c1 * helper.In.x - c2 * helper.In.y); + helper.Out.y = m_Weight * (c2 * helper.In.x + c1 * helper.In.y); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t c1 = sin(precalcSumSquares);\n" + << "\t\treal_t c2 = cos(precalcSumSquares);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (c1 * vIn.x - c2 * vIn.y);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (c2 * vIn.x + c1 * vIn.y);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Horseshoe: +/// a = atan2(tx, ty); +/// c1 = sin(a); +/// c2 = cos(a); +/// nx = c1 * tx - c2 * ty; +/// ny = c2 * tx + c1 * ty; +/// p[0] += weight * nx; +/// p[1] += weight * ny; +/// +template +class EMBER_API HorseshoeVariation : public Variation +{ +public: + HorseshoeVariation(T weight = 1.0) : Variation("horseshoe", VAR_HORSESHOE, weight, true, true) { } + + VARCOPY(HorseshoeVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r = m_Weight / (helper.m_PrecalcSqrtSumSquares + T(EPS)); + + helper.Out.x = (helper.In.x - helper.In.y) * (helper.In.x + helper.In.y) * r; + helper.Out.y = 2 * helper.In.x * helper.In.y * r; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] / (precalcSqrtSumSquares + EPS);\n" + << "\n" + << "\t\tvOut.x = (vIn.x - vIn.y) * (vIn.x + vIn.y) * r;\n" + << "\t\tvOut.y = 2.0 * vIn.x * vIn.y * r;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Polar: +/// nx = atan2(tx, ty) / M_PI; +/// ny = sqrt(tx * tx + ty * ty) - 1.0; +/// p[0] += weight * nx; +/// p[1] += weight * ny; +/// +template +class EMBER_API PolarVariation : public Variation +{ +public: + PolarVariation(T weight = 1.0) : Variation("polar", VAR_POLAR, weight, true, true, false, true, false) { } + + VARCOPY(PolarVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T nx = helper.m_PrecalcAtanxy * T(M_1_PI); + T ny = helper.m_PrecalcSqrtSumSquares - 1; + + helper.Out.x = m_Weight * nx; + helper.Out.y = m_Weight * ny; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\tvOut.x = (xform->m_VariationWeights[" << varIndex << "] * (precalcAtanxy * M_1_PI));\n" + << "\t\tvOut.y = (xform->m_VariationWeights[" << varIndex << "] * (precalcSqrtSumSquares - 1.0));\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Handkerchief: +/// a = atan2(tx, ty); +/// r = sqrt(tx * tx + ty * ty); +/// p[0] += weight * sin(a + r) * r; +/// p[1] += weight * cos(a - r) * r; +/// +template +class EMBER_API HandkerchiefVariation : public Variation +{ +public: + HandkerchiefVariation(T weight = 1.0) : Variation("handkerchief", VAR_HANDKERCHIEF, weight, true, true, false, true) { } + + VARCOPY(HandkerchiefVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + helper.Out.x = m_Weight * helper.m_PrecalcSqrtSumSquares * sin(helper.m_PrecalcAtanxy + helper.m_PrecalcSqrtSumSquares); + helper.Out.y = m_Weight * helper.m_PrecalcSqrtSumSquares * cos(helper.m_PrecalcAtanxy - helper.m_PrecalcSqrtSumSquares); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * precalcSqrtSumSquares * sin(precalcAtanxy + precalcSqrtSumSquares);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * precalcSqrtSumSquares * cos(precalcAtanxy - precalcSqrtSumSquares);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Heart: +/// a = atan2(tx, ty); +/// r = sqrt(tx * tx + ty * ty); +/// a *= r; +/// p[0] += weight * sin(a) * r; +/// p[1] += weight * cos(a) * -r; +/// +template +class EMBER_API HeartVariation : public Variation +{ +public: + HeartVariation(T weight = 1.0) : Variation("heart", VAR_HEART, weight, true, true, false, true) { } + + VARCOPY(HeartVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T a = helper.m_PrecalcSqrtSumSquares * helper.m_PrecalcAtanxy; + T r = m_Weight * helper.m_PrecalcSqrtSumSquares; + + helper.Out.x = r * sin(a); + helper.Out.y = (-r) * cos(a); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t a = precalcSqrtSumSquares * precalcAtanxy;\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * precalcSqrtSumSquares;\n" + << "\n" + << "\t\tvOut.x = r * sin(a);\n" + << "\t\tvOut.y = (-r) * cos(a);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Disc: +/// nx = tx * M_PI; +/// ny = ty * M_PI; +/// a = atan2(nx, ny); +/// r = sqrt(nx * nx + ny * ny); +/// p[0] += weight * sin(r) * a / M_PI; +/// p[1] += weight * cos(r) * a / M_PI; +/// +template +class EMBER_API DiscVariation : public ParametricVariation +{ +public: + DiscVariation(T weight = 1.0) : ParametricVariation("disc", VAR_DISC, weight, true, true, false, true) + { + Init(); + } + + PARVARCOPY(DiscVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T val = T(M_PI) * helper.m_PrecalcSqrtSumSquares; + T r = m_WeightByPI * helper.m_PrecalcAtanxy; + + helper.Out.x = sin(val) * r; + helper.Out.y = cos(val) * r; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string weightByPI = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Precalcs only, no params. + + ss << "\t{\n" + << "\t\treal_t val = M_PI * precalcSqrtSumSquares;\n" + << "\t\treal_t r = " << weightByPI << " * precalcAtanxy;\n" + << "\n" + << "\t\tvOut.x = sin(val) * r;\n" + << "\t\tvOut.y = cos(val) * r;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_WeightByPI = m_Weight * T(M_1_PI); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(true, &m_WeightByPI, prefix + "disc_weight_by_pi"));//Precalcs only, no params. + } + +private: + T m_WeightByPI;//Precalcs only, no params. +}; + +/// +/// Spiral: +/// a = atan2(tx, ty); +/// r = sqrt(tx * tx + ty * ty) + 1e-6; +/// p[0] += weight * (cos(a) + sin(r)) / r; +/// p[1] += weight * (sin(a) - cos(r)) / r; +/// +template +class EMBER_API SpiralVariation : public Variation +{ +public: + SpiralVariation(T weight = 1.0) : Variation("spiral", VAR_SPIRAL, weight, true, true, true) { } + + VARCOPY(SpiralVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r = helper.m_PrecalcSqrtSumSquares + T(EPS); + T r1 = m_Weight / r; + + helper.Out.x = r1 * (helper.m_PrecalcCosa + sin(r)); + helper.Out.y = r1 * (helper.m_PrecalcSina - cos(r)); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t r = precalcSqrtSumSquares + EPS;\n" + << "\t\treal_t r1 = xform->m_VariationWeights[" << varIndex << "] / r;\n" + << "\n" + << "\t\tvOut.x = r1 * (precalcCosa + sin(r));\n" + << "\t\tvOut.y = r1 * (precalcSina - cos(r));\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Hyperbolic: +/// a = atan2(tx, ty); +/// r = sqrt(tx * tx + ty * ty) + 1e-6; +/// p[0] += weight * sin(a) / r; +/// p[1] += weight * cos(a) * r; +/// +template +class EMBER_API HyperbolicVariation : public Variation +{ +public: + HyperbolicVariation(T weight = 1.0) : Variation("hyperbolic", VAR_HYPERBOLIC, weight, true, true, true) { } + + VARCOPY(HyperbolicVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r = helper.m_PrecalcSqrtSumSquares + T(EPS); + + helper.Out.x = m_Weight * helper.m_PrecalcSina / r; + helper.Out.y = m_Weight * helper.m_PrecalcCosa * r; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t r = precalcSqrtSumSquares + EPS;\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * precalcSina / r;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * precalcCosa * r;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Diamond: +/// a = atan2(tx, ty); +/// r = sqrt(tx * tx + ty * ty); +/// p[0] += weight * sin(a) * cos(r); +/// p[1] += weight * cos(a) * sin(r); +/// +template +class EMBER_API DiamondVariation : public Variation +{ +public: + DiamondVariation(T weight = 1.0) : Variation("diamond", VAR_DIAMOND, weight, true, true, true) { } + + VARCOPY(DiamondVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + helper.Out.x = m_Weight * helper.m_PrecalcSina * cos(helper.m_PrecalcSqrtSumSquares); + helper.Out.y = m_Weight * helper.m_PrecalcCosa * sin(helper.m_PrecalcSqrtSumSquares); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * precalcSina * cos(precalcSqrtSumSquares);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * precalcCosa * sin(precalcSqrtSumSquares);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Ex: +/// a = atan2(tx, ty); +/// r = sqrt(tx * tx + ty * ty); +/// n0 = sin(a + r); +/// n1 = cos(a - r); +/// m0 = n0 * n0 * n0 * r; +/// m1 = n1 * n1 * n1 * r; +/// p[0] += weight * (m0 + m1); +/// p[1] += weight * (m0 - m1); +/// +template +class EMBER_API ExVariation : public Variation +{ +public: + ExVariation(T weight = 1.0) : Variation("ex", VAR_EX, weight, true, true, false, true) { } + + VARCOPY(ExVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T a = helper.m_PrecalcAtanxy; + T r = helper.m_PrecalcSqrtSumSquares; + T n0 = sin(a + r); + T n1 = cos(a - r); + T m0 = n0 * n0 * n0 * r; + T m1 = n1 * n1 * n1 * r; + + helper.Out.x = m_Weight * (m0 + m1); + helper.Out.y = m_Weight * (m0 - m1); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t a = precalcAtanxy;\n" + << "\t\treal_t r = precalcSqrtSumSquares;\n" + << "\t\treal_t n0 = sin(a + r);\n" + << "\t\treal_t n1 = cos(a - r);\n" + << "\t\treal_t m0 = n0 * n0 * n0 * r;\n" + << "\t\treal_t m1 = n1 * n1 * n1 * r;\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (m0 + m1);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (m0 - m1);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Julia: +/// a = atan2(tx, ty)/2.0; +/// if (random bit()) a += M_PI; +/// r = pow(tx*tx + ty*ty, 0.25); +/// nx = r * cos(a); +/// ny = r * sin(a); +/// p[0] += v * nx; +/// p[1] += v * ny; +/// +template +class EMBER_API JuliaVariation : public Variation +{ +public: + JuliaVariation(T weight = 1.0) : Variation("julia", VAR_JULIA, weight, true, true, false, true) { } + + VARCOPY(JuliaVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r = m_Weight * sqrt(helper.m_PrecalcSqrtSumSquares); + T a = T(0.5) * helper.m_PrecalcAtanxy; + + if (rand.RandBit()) + a += T(M_PI); + + helper.Out.x = r * cos(a); + helper.Out.y = r * sin(a); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * sqrt(precalcSqrtSumSquares);\n" + << "\t\treal_t a = 0.5 * precalcAtanxy;\n" + << "\n" + << "\t\tif (MwcNext(mwc) & 1)\n" + << "\t\t a += M_PI;\n" + << "\n" + << "\t\tvOut.x = r * cos(a);\n" + << "\t\tvOut.y = r * sin(a);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Bent: +/// nx = tx; +/// ny = ty; +/// if (nx < 0.0) nx = nx * 2.0; +/// if (ny < 0.0) ny = ny / 2.0; +/// p[0] += weight * nx; +/// p[1] += weight * ny; +/// +template +class EMBER_API BentVariation : public Variation +{ +public: + BentVariation(T weight = 1.0) : Variation("bent", VAR_BENT, weight) { } + + VARCOPY(BentVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T nx = helper.In.x < T(0.0) ? helper.In.x * 2 : helper.In.x; + T ny = helper.In.y < T(0.0) ? helper.In.y / 2 : helper.In.y; + + helper.Out.x = m_Weight * nx; + helper.Out.y = m_Weight * ny; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t nx = vIn.x < 0.0 ? (vIn.x * 2.0) : vIn.x;\n" + << "\t\treal_t ny = vIn.y < 0.0 ? (vIn.y / 2.0) : vIn.y;\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * nx;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * ny;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Waves: +/// dx = coef[2][0]; +/// dy = coef[2][1]; +/// nx = tx + coef[1][0] * sin(ty / ((dx * dx) + EPS)); +/// ny = ty + coef[1][1] * sin(tx / ((dy * dy) + EPS)); +/// p[0] += weight * nx; +/// p[1] += weight * ny; +/// Special case here, use parametric for precalcs, but no regular params. +/// +template +class EMBER_API WavesVariation : public ParametricVariation +{ +public: + WavesVariation(T weight = 1.0) : ParametricVariation("waves", VAR_WAVES, weight) + { + Init(); + } + + PARVARCOPY(WavesVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T c10 = m_Xform->m_Affine.B(); + T c11 = m_Xform->m_Affine.E(); + T nx = helper.In.x + c10 * sin(helper.In.y * m_Dx2); + T ny = helper.In.y + c11 * sin(helper.In.x * m_Dy2); + + helper.Out.x = m_Weight * nx; + helper.Out.y = m_Weight * ny; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string dx2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Precalcs only, no params. + string dy2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t c10 = xform->m_B;\n" + << "\t\treal_t c11 = xform->m_E;\n" + << "\t\treal_t nx = vIn.x + c10 * sin(vIn.y * " << dx2 << ");\n" + << "\t\treal_t ny = vIn.y + c11 * sin(vIn.x * " << dy2 << ");\n" + << "\n" + << "\t\tvOut.x = (xform->m_VariationWeights[" << varIndex << "] * nx);\n" + << "\t\tvOut.y = (xform->m_VariationWeights[" << varIndex << "] * ny);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + if (m_Xform)//If this variation exists by itself and hasn't been added to an xform yet, m_Xform will be NULL. + { + T dx = m_Xform->m_Affine.C(); + T dy = m_Xform->m_Affine.F(); + + m_Dx2 = 1 / (dx * dx + T(EPS)); + m_Dy2 = 1 / (dy * dy + T(EPS)); + } + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(true, &m_Dx2, prefix + "waves_dx2"));//Precalcs only, no params. + m_Params.push_back(ParamWithName(true, &m_Dy2, prefix + "waves_dy2")); + } + +private: + T m_Dx2;//Precalcs only, no params. + T m_Dy2; +}; + +/// +/// Fisheye: +/// a = atan2(tx, ty); +/// r = sqrt(tx * tx + ty * ty); +/// r = 2 * r / (r + 1); +/// nx = r * cos(a); +/// ny = r * sin(a); +/// p[0] += weight * nx; +/// p[1] += weight * ny; +/// +template +class EMBER_API FisheyeVariation : public Variation +{ +public: + FisheyeVariation(T weight = 1.0) : Variation("fisheye", VAR_FISHEYE, weight, true, true) { } + + VARCOPY(FisheyeVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r = 2 * m_Weight / (helper.m_PrecalcSqrtSumSquares + 1); + + helper.Out.x = r * helper.In.y; + helper.Out.y = r * helper.In.x; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t r = 2 * xform->m_VariationWeights[" << varIndex << "] / (precalcSqrtSumSquares + 1);\n" + << "\n" + << "\t\tvOut.x = r * vIn.y;\n" + << "\t\tvOut.y = r * vIn.x;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Popcorn: +/// dx = tan(3 * ty); +/// dy = tan(3 * tx); +/// nx = tx + coef[2][0] * sin(dx); +/// ny = ty + coef[2][1] * sin(dy); +/// p[0] += weight * nx; +/// p[1] += weight * ny; +/// +template +class EMBER_API PopcornVariation : public Variation +{ +public: + PopcornVariation(T weight = 1.0) : Variation("popcorn", VAR_POPCORN, weight) { } + + VARCOPY(PopcornVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T dx = tan(3 * helper.In.y); + T dy = tan(3 * helper.In.x); + T nx = helper.In.x + m_Xform->m_Affine.C() * sin(dx); + T ny = helper.In.y + m_Xform->m_Affine.F() * sin(dy); + + helper.Out.x = m_Weight * nx; + helper.Out.y = m_Weight * ny; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t dx = tan(3 * vIn.y);\n" + << "\t\treal_t dy = tan(3 * vIn.x);\n" + << "\t\treal_t nx = vIn.x + xform->m_C * sin(dx);\n" + << "\t\treal_t ny = vIn.y + xform->m_F * sin(dy);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * nx;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * ny;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Exponential: +/// dx = exp(tx - 1.0); +/// dy = M_PI * ty; +/// nx = cos(dy) * dx; +/// ny = sin(dy) * dx; +/// p[0] += weight * nx; +/// p[1] += weight * ny; +/// +template +class EMBER_API ExponentialVariation : public Variation +{ +public: + ExponentialVariation(T weight = 1.0) : Variation("exponential", VAR_EXPONENTIAL, weight) { } + + VARCOPY(ExponentialVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T dx = m_Weight * exp(helper.In.x - 1); + T dy = T(M_PI) * helper.In.y; + + helper.Out.x = dx * cos(dy); + helper.Out.y = dx * sin(dy); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t dx = xform->m_VariationWeights[" << varIndex << "] * exp(vIn.x - 1.0);\n" + << "\t\treal_t dy = M_PI * vIn.y;\n" + << "\n" + << "\t\tvOut.x = dx * cos(dy);\n" + << "\t\tvOut.y = dx * sin(dy);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Power: +/// a = atan2(tx, ty); +/// sa = sin(a); +/// r = sqrt(tx * tx + ty * ty); +/// r = pow(r, sa); +/// nx = r * precalc_cosa; +/// ny = r * sa; +/// p[0] += weight * nx; +/// p[1] += weight * ny; +/// +template +class EMBER_API PowerVariation : public Variation +{ +public: + PowerVariation(T weight = 1.0) : Variation("power", VAR_POWER, weight, true, true, true) { } + + VARCOPY(PowerVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r = m_Weight * pow(helper.m_PrecalcSqrtSumSquares, helper.m_PrecalcSina); + + helper.Out.x = r * helper.m_PrecalcCosa; + helper.Out.y = r * helper.m_PrecalcSina; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * pow(precalcSqrtSumSquares, precalcSina);\n" + << "\n" + << "\t\tvOut.x = r * precalcCosa;\n" + << "\t\tvOut.y = r * precalcSina;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Cosine: +/// nx = cos(tx * M_PI) * cosh(ty); +/// ny = -sin(tx * M_PI) * sinh(ty); +/// p[0] += weight * nx; +/// p[1] += weight * ny; +/// +template +class EMBER_API CosineVariation : public Variation +{ +public: + CosineVariation(T weight = 1.0) : Variation("cosine", VAR_COSINE, weight) { } + + VARCOPY(CosineVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T a = helper.In.x * T(M_PI); + T nx = cos(a) * cosh(helper.In.y); + T ny = -sin(a) * sinh(helper.In.y); + + helper.Out.x = m_Weight * nx; + helper.Out.y = m_Weight * ny; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t a = vIn.x * M_PI;\n" + << "\t\treal_t nx = cos(a) * cosh(vIn.y);\n" + << "\t\treal_t ny = -sin(a) * sinh(vIn.y);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * nx;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * ny;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Rings: +/// dx = coef[2][0]; +/// dx = dx * dx + EPS; +/// r = sqrt(tx * tx + ty * ty); +/// r = fmod(r + dx, 2 * dx) - dx + r * (1 - dx); +/// a = atan2(tx, ty); +/// nx = cos(a) * r; +/// ny = sin(a) * r; +/// p[0] += weight * nx; +/// p[1] += weight * ny; +/// +template +class EMBER_API RingsVariation : public Variation +{ +public: + RingsVariation(T weight = 1.0) : Variation("rings", VAR_RINGS, weight, true, true, true) { } + + VARCOPY(RingsVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T dx = m_Xform->m_Affine.C() * m_Xform->m_Affine.C() + T(EPS); + T r = helper.m_PrecalcSqrtSumSquares; + + r = m_Weight * (fmod(r + dx, 2 * dx) - dx + r * (1 - dx)); + helper.Out.x = r * helper.m_PrecalcCosa; + helper.Out.y = r * helper.m_PrecalcSina; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t dx = xform->m_C * xform->m_C + EPS;\n" + << "\t\treal_t r = precalcSqrtSumSquares;\n" + << "\n" + << "\t\tr = xform->m_VariationWeights[" << varIndex << "] * (fmod(r + dx, 2 * dx) - dx + r * (1 - dx));\n" + << "\t\tvOut.x = r * precalcCosa;\n" + << "\t\tvOut.y = r * precalcSina;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Fan: +/// dx = coef[2][0]; +/// dy = coef[2][1]; +/// dx = M_PI * (dx * dx + EPS); +/// dx2 = dx / 2; +/// a = atan(tx, ty); +/// r = sqrt(tx * tx + ty * ty); +/// a += (fmod(a + dy, dx) > dx2) ? -dx2 : dx2; +/// nx = cos(a) * r; +/// ny = sin(a) * r; +/// p[0] += weight * nx; +/// p[1] += weight * ny; +/// +template +class EMBER_API FanVariation : public Variation +{ +public: + FanVariation(T weight = 1.0) : Variation("fan", VAR_FAN, weight, true, true, false, true) { } + + VARCOPY(FanVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T dx = T(M_PI) * (m_Xform->m_Affine.C() * m_Xform->m_Affine.C() + T(EPS)); + T dy = m_Xform->m_Affine.F(); + T dx2 = T(0.5) * dx; + T a = helper.m_PrecalcAtanxy; + T r = m_Weight * helper.m_PrecalcSqrtSumSquares; + + a += (fmod(a + dy, dx) > dx2) ? -dx2 : dx2; + helper.Out.x = r * cos(a); + helper.Out.y = r * sin(a); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t dx = M_PI * (xform->m_C * xform->m_C + EPS);\n" + << "\t\treal_t dy = xform->m_F;\n" + << "\t\treal_t dx2 = 0.5 * dx;\n" + << "\t\treal_t a = precalcAtanxy + ((fmod(precalcAtanxy + dy, dx) > dx2) ? -dx2 : dx2);\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * precalcSqrtSumSquares;\n" + << "\n" + << "\t\tvOut.x = r * cos(a);\n" + << "\t\tvOut.y = r * sin(a);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Blob: +/// a = atan2(tx, ty); +/// r = sqrt(tx * tx + ty * ty); +/// r = r * (bloblow + (blobhigh - bloblow) * (0.5 + 0.5 * sin(blobwaves * a))); +/// nx = sin(a) * r; +/// ny = cos(a) * r; +/// +/// p[0] += weight * nx; +/// p[1] += weight * ny; +/// +template +class EMBER_API BlobVariation : public ParametricVariation +{ +public: + BlobVariation(T weight = 1.0) : ParametricVariation("blob", VAR_BLOB, weight, true, true, true, true) + { + Init(); + } + + PARVARCOPY(BlobVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r = helper.m_PrecalcSqrtSumSquares * (m_BlobLow + m_BlobDiff * (T(0.5) + T(0.5) * sin(m_BlobWaves * helper.m_PrecalcAtanxy))); + + helper.Out.x = m_Weight * helper.m_PrecalcSina * r; + helper.Out.y = m_Weight * helper.m_PrecalcCosa * r; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string blobLow = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string blobHigh = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string blobWaves = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string blobDiff = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t r = precalcSqrtSumSquares * (" << blobLow << " + " << blobDiff << " * (0.5 + 0.5 * sin(" << blobWaves << " * precalcAtanxy)));\n" + << "\n" + << "\t\tvOut.x = (xform->m_VariationWeights[" << varIndex << "] * precalcSina * r);\n" + << "\t\tvOut.y = (xform->m_VariationWeights[" << varIndex << "] * precalcCosa * r);\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_BlobDiff = m_BlobHigh - m_BlobLow; + } + + virtual void Random(QTIsaac& rand) + { + m_BlobLow = T(0.2) + T(0.5) * rand.Frand01(); + m_BlobHigh = T(0.8) + T(0.4) * rand.Frand01(); + m_BlobWaves = (T)(int)(2 + 5 * rand.Frand01()); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_BlobLow, prefix + "blob_low")); + m_Params.push_back(ParamWithName(&m_BlobHigh, prefix + "blob_high", 1)); + m_Params.push_back(ParamWithName(&m_BlobWaves, prefix + "blob_waves", 1)); + m_Params.push_back(ParamWithName(true, &m_BlobDiff, prefix + "blob_diff"));//Precalc. + } + +private: + T m_BlobLow; + T m_BlobHigh; + T m_BlobWaves; + T m_BlobDiff;//Precalc. +}; + +/// +/// Pdj: +/// nx1 = cos(pdjb * tx); +/// nx2 = sin(pdjc * tx); +/// ny1 = sin(pdja * ty); +/// ny2 = cos(pdjd * ty); +/// +/// p[0] += weight * (ny1 - nx1); +/// p[1] += weight * (nx2 - ny2); +/// +template +class EMBER_API PdjVariation : public ParametricVariation +{ +public: + PdjVariation(T weight = 1.0) : ParametricVariation("pdj", VAR_PDJ, weight) + { + Init(); + } + + PARVARCOPY(PdjVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T nx1 = cos(m_PdjB * helper.In.x); + T nx2 = sin(m_PdjC * helper.In.x); + T ny1 = sin(m_PdjA * helper.In.y); + T ny2 = cos(m_PdjD * helper.In.y); + + helper.Out.x = m_Weight * (ny1 - nx1); + helper.Out.y = m_Weight * (nx2 - ny2); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string pdjA = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string pdjB = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string pdjC = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string pdjD = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t nx1 = cos(" << pdjB << " * vIn.x)" << ";\n" + << "\t\treal_t nx2 = sin(" << pdjC << " * vIn.x)" << ";\n" + << "\t\treal_t ny1 = sin(" << pdjA << " * vIn.y)" << ";\n" + << "\t\treal_t ny2 = cos(" << pdjD << " * vIn.y)" << ";\n" + << "\n" + << "\t\tvOut.x = (xform->m_VariationWeights[" << varIndex << "] * (ny1 - nx1));\n" + << "\t\tvOut.y = (xform->m_VariationWeights[" << varIndex << "] * (nx2 - ny2));\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Random(QTIsaac& rand) + { + m_PdjA = 3 * rand.Frand11(); + m_PdjB = 3 * rand.Frand11(); + m_PdjC = 3 * rand.Frand11(); + m_PdjD = 3 * rand.Frand11(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_PdjA, prefix + "pdj_a")); + m_Params.push_back(ParamWithName(&m_PdjB, prefix + "pdj_b")); + m_Params.push_back(ParamWithName(&m_PdjC, prefix + "pdj_c")); + m_Params.push_back(ParamWithName(&m_PdjD, prefix + "pdj_d")); + } + +private: + T m_PdjA; + T m_PdjB; + T m_PdjC; + T m_PdjD; +}; + +/// +/// Fan2: +/// a = precalc_atan; +/// r = precalc_sqrt; +/// +/// dy = fan2y; +/// dx = M_PI * (fan2x * fan2x + EPS); +/// dx2 = dx / 2.0; +/// +/// t = a + dy - dx * (int)((a + dy) / dx); +/// +/// if (t > dx2) +/// a = a - dx2; +/// else +/// a = a + dx2; +/// +/// nx = sin(a) * r; +/// ny = cos(a) * r; +/// +/// p[0] += weight * nx; +/// p[1] += weight * ny; +/// +template +class EMBER_API Fan2Variation : public ParametricVariation +{ +public: + Fan2Variation(T weight = 1.0) : ParametricVariation("fan2", VAR_FAN2, weight, true, true, false, true) + { + Init(); + } + + PARVARCOPY(Fan2Variation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T a = helper.m_PrecalcAtanxy; + T r = m_Weight * helper.m_PrecalcSqrtSumSquares; + T t = a + m_Fan2Y - m_Fan2Dx * (int)((a + m_Fan2Y) / m_Fan2Dx); + + if (t > m_Fan2Dx2) + a = a - m_Fan2Dx2; + else + a = a + m_Fan2Dx2; + + helper.Out.x = r * sin(a); + helper.Out.y = r * cos(a); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string fan2X = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string fan2Y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string dx = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string dx2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t a = precalcAtanxy;\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * precalcSqrtSumSquares;\n" + << "\t\treal_t t = a + " << fan2Y << " - " << dx << " * (int)((a + " << fan2Y << ") / " << dx << ");\n" + << "\n" + << "\t\tif (t > " << dx2 << ")\n" + << "\t\t a = a - " << dx2 << ";\n" + << "\t\telse\n" + << "\t\t a = a + " << dx2 << ";\n" + << "\n" + << "\t\tvOut.x = r * sin(a);\n" + << "\t\tvOut.y = r * cos(a);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Fan2Dx = T(M_PI) * (SQR(m_Fan2X) + EPS); + m_Fan2Dx2 = T(0.5) * m_Fan2Dx; + } + + virtual void Random(QTIsaac& rand) + { + m_Fan2X = rand.Frand11(); + m_Fan2Y = rand.Frand11(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Fan2X, prefix + "fan2_x")); + m_Params.push_back(ParamWithName(&m_Fan2Y, prefix + "fan2_y")); + m_Params.push_back(ParamWithName(true, &m_Fan2Dx, prefix + "fan2_dx"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Fan2Dx2, prefix + "fan2_dx2")); + } + +private: + T m_Fan2X; + T m_Fan2Y; + T m_Fan2Dx;//Precalc. + T m_Fan2Dx2; +}; + +/// +/// Rings2: +/// r = precalc_sqrt; +/// dx = rings2val * rings2val + EPS; +/// r += dx - 2.0 * dx * (int)((r + dx)/(2.0 * dx)) - dx + r * (1.0 - dx); +/// nx = precalc_sina * r; +/// ny = precalc_cosa * r; +/// p[0] += weight * nx; +/// p[1] += weight * ny; +/// +template +class EMBER_API Rings2Variation : public ParametricVariation +{ +public: + Rings2Variation(T weight = 1.0) : ParametricVariation("rings2", VAR_RINGS2, weight, true, true, true) + { + Init(); + } + + PARVARCOPY(Rings2Variation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r = helper.m_PrecalcSqrtSumSquares; + + r += -2 * m_Rings2Val2 * (int)((r + m_Rings2Val2) / (2 * m_Rings2Val2)) + r * (1 - m_Rings2Val2); + helper.Out.x = m_Weight * helper.m_PrecalcSina * r; + helper.Out.y = m_Weight * helper.m_PrecalcCosa * r; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string rings2Val = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string rings2Val2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t r = precalcSqrtSumSquares;\n" + << "\n" + << "\t\tr += -2.0 * " << rings2Val2 << " * (int)((r + " << rings2Val2 << ") / (2.0 * " << rings2Val2 << ")) + r * (1.0 - " << rings2Val2 << ");\n" + << "\t\tvOut.x = (xform->m_VariationWeights[" << varIndex << "] * precalcSina * r);\n" + << "\t\tvOut.y = (xform->m_VariationWeights[" << varIndex << "] * precalcCosa * r);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Rings2Val2 = SQR(m_Rings2Val) + EPS; + } + + virtual void Random(QTIsaac& rand) + { + m_Rings2Val = 2 * rand.Frand01(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Rings2Val, prefix + "rings2_val", 1));//This differs from the original which used zero. Use 1 instead to avoid getting too close to dividing by zero. + m_Params.push_back(ParamWithName(true, &m_Rings2Val2, prefix + "rings2_val2"));//Precalc. + } + +private: + T m_Rings2Val; + T m_Rings2Val2;//Precalc. +}; + +/// +/// Eyefish: +/// r = 2.0 * weight / (precalc_sqrt + 1.0); +/// p[0] += r * tx; +/// p[1] += r * ty; +/// +template +class EMBER_API EyefishVariation : public Variation +{ +public: + EyefishVariation(T weight = 1.0) : Variation("eyefish", VAR_EYEFISH, weight, true, true) { } + + VARCOPY(EyefishVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r = 2 * m_Weight / (helper.m_PrecalcSqrtSumSquares + 1); + + helper.Out.x = r * helper.In.x; + helper.Out.y = r * helper.In.y; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t r = (xform->m_VariationWeights[" << varIndex << "] * 2.0) / (precalcSqrtSumSquares + 1.0);\n" + << "\n" + << "\t\tvOut.x = r * vIn.x;\n" + << "\t\tvOut.y = r * vIn.y;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Bubble. +/// +template +class EMBER_API BubbleVariation : public Variation +{ +public: + BubbleVariation(T weight = 1.0) : Variation("bubble", VAR_BUBBLE, weight, true) { } + + VARCOPY(BubbleVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r = m_Weight / (T(0.25) * helper.m_PrecalcSumSquares + 1); + + helper.Out.x = r * helper.In.x; + helper.Out.y = r * helper.In.y; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] / (0.25 * precalcSumSquares + 1);\n" + << "\n" + << "\t\tvOut.x = r * vIn.x;\n" + << "\t\tvOut.y = r * vIn.y;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Cylinder. +/// +template +class EMBER_API CylinderVariation : public Variation +{ +public: + CylinderVariation(T weight = 1.0) : Variation("cylinder", VAR_CYLINDER, weight) { } + + VARCOPY(CylinderVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + helper.Out.x = m_Weight * sin(helper.In.x); + helper.Out.y = m_Weight * helper.In.y; + helper.Out.z = m_Weight * cos(helper.In.x); + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * sin(vIn.x);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * cos(vIn.x);\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Perspective. +/// +template +class EMBER_API PerspectiveVariation : public ParametricVariation +{ +public: + PerspectiveVariation(T weight = 1.0) : ParametricVariation("perspective", VAR_PERSPECTIVE, weight) + { + Init(); + } + + PARVARCOPY(PerspectiveVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T d = m_Dist - helper.In.y * m_Vsin; + + if (d == 0) + d = EPS6; + + T t = 1 / d; + + helper.Out.x = m_Weight * m_Dist * helper.In.x * t; + helper.Out.y = m_Weight * m_VfCos * helper.In.y * t; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string angle = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Params. + string dist = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string vSin = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Precalc. + string vfCos = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t d = " << dist << " - vIn.y * " << vSin << ";\n" + << "\n" + << "\t\tif (d == 0)\n" + << "\t\t d = EPS6;\n" + << "\n" + << "\t\treal_t t = 1.0 / d;\n" + << "\n" + << "\t\tvOut.x = (xform->m_VariationWeights[" << varIndex << "] * " << dist << " * vIn.x * t);\n" + << "\t\tvOut.y = (xform->m_VariationWeights[" << varIndex << "] * " << vfCos << " * vIn.y * t);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + T angle = m_Angle * T(M_PI) / 2; + + m_Vsin = sin(angle); + m_VfCos = m_Dist * cos(angle); + } + + virtual void Random(QTIsaac& rand) + { + m_Angle = rand.Frand01(); + m_Dist = 2 * rand.Frand01() + 1; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Angle, prefix + "perspective_angle"));//Params. + m_Params.push_back(ParamWithName(&m_Dist, prefix + "perspective_dist")); + m_Params.push_back(ParamWithName(true, &m_Vsin, prefix + "perspective_vsin"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_VfCos, prefix + "perspective_vfcos")); + } + +private: + T m_Angle;//Params. + T m_Dist; + T m_Vsin;//Precalc. + T m_VfCos; +}; + +/// +/// Noise. +/// +template +class EMBER_API NoiseVariation : public Variation +{ +public: + NoiseVariation(T weight = 1.0) : Variation("noise", VAR_NOISE, weight) { } + + VARCOPY(NoiseVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T tempr = rand.Frand01() * M_2PI; + T r = m_Weight * rand.Frand01(); + + helper.Out.x = helper.In.x * r * cos(tempr); + helper.Out.y = helper.In.y * r * sin(tempr); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t tempr = MwcNext01(mwc) * M_2PI;\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * MwcNext01(mwc);\n" + << "\n" + << "\t\tvOut.x = vIn.x * r * cos(tempr);\n" + << "\t\tvOut.y = vIn.y * r * sin(tempr);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// JuliaN. +/// +template +class EMBER_API JuliaNGenericVariation : public ParametricVariation +{ +public: + JuliaNGenericVariation(T weight = 1.0) : ParametricVariation("julian", VAR_JULIAN, weight, true, false, false, false, true) + { + Init(); + } + + PARVARCOPY(JuliaNGenericVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T tempr = (helper.m_PrecalcAtanyx + M_2PI * rand.Rand((ISAAC_INT)m_Rn)) / m_Power; + T r = m_Weight * pow(helper.m_PrecalcSumSquares, m_Cn); + + helper.Out.x = r * cos(tempr); + helper.Out.y = r * sin(tempr); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string dist = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Params. + string power = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string rn = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Precalc. + string cn = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tint tRnd = (int)(" << rn << " * MwcNext01(mwc));\n" + << "\t\treal_t tempr = (precalcAtanyx + M_2PI * tRnd) / " << power << ";\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * pow(precalcSumSquares, " << cn << ");\n" + << "\n" + << "\t\tvOut.x = r * cos(tempr);\n" + << "\t\tvOut.y = r * sin(tempr);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Rn = fabs(m_Power); + m_Cn = m_Dist / m_Power / 2; + } + + virtual void Random(QTIsaac& rand) + { + m_Dist = 1; + m_Power = (T)(int)(5 * rand.Frand01() + 2); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Dist, prefix + "julian_dist", 1));//Params. + m_Params.push_back(ParamWithName(&m_Power, prefix + "julian_power", 1, INTEGER_NONZERO)); + m_Params.push_back(ParamWithName(true, &m_Rn, prefix + "julian_rn"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Cn, prefix + "julian_cn")); + } + +private: + T m_Dist;//Params. + T m_Power; + T m_Rn;//Precalc. + T m_Cn; +}; + +/// +/// JuliaScope. +/// +template +class EMBER_API JuliaScopeVariation : public ParametricVariation +{ +public: + JuliaScopeVariation(T weight = 1.0) : ParametricVariation("juliascope", VAR_JULIASCOPE, weight, true, false, false, false, true) + { + Init(); + } + + PARVARCOPY(JuliaScopeVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + int rnd = (int)(m_Rn * rand.Frand01()); + T tempr, r = m_Weight * pow(helper.m_PrecalcSumSquares, m_Cn); + + if ((rnd & 1) == 0) + tempr = (M_2PI * rnd + helper.m_PrecalcAtanyx) / m_Power; + else + tempr = (M_2PI * rnd - helper.m_PrecalcAtanyx) / m_Power; + + helper.Out.x = r * cos(tempr); + helper.Out.y = r * sin(tempr); + helper.Out.z = m_Weight * helper.In.z; + + //int rnd = (int)(m_Rn * rand.Frand01()); + //T tempr, r; + + //if ((rnd & 1) == 0) + // tempr = (2 * T(M_PI) * (int)(m_Rn * rand.Frand01()) + helper.m_PrecalcAtanyx) / m_Power;//Fixed to get new random rather than use rnd from above.//SMOULDER + //else + // tempr = (2 * T(M_PI) * (int)(m_Rn * rand.Frand01()) - helper.m_PrecalcAtanyx) / m_Power; + + //r = m_Weight * pow(helper.m_PrecalcSumSquares, m_Cn); + + //helper.Out.x = r * cos(tempr); + //helper.Out.y = r * sin(tempr); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string dist = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Params. + string power = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string rn = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Precalc. + string cn = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + + ss << "\t{\n" + << "\t\tint rnd = (int)(" << rn << " * MwcNext01(mwc));\n" + << "\t\treal_t tempr, r;\n" + << "\n" + << "\t\tif ((rnd & 1) == 0)\n" + << "\t\t tempr = (M_2PI * rnd + precalcAtanyx) / " << power << ";\n" + << "\t\telse\n" + << "\t\t tempr = (M_2PI * rnd - precalcAtanyx) / " << power << ";\n" + << "\n" + << "\t\tr = xform->m_VariationWeights[" << varIndex << "] * pow(precalcSumSquares, " << cn << ");\n" + << "\n" + << "\t\tvOut.x = r * cos(tempr);\n" + << "\t\tvOut.y = r * sin(tempr);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + //ss << "\t{\n" + // << "\t\tint rnd = (int)(" << rn << " * MwcNext01(mwc));\n" + // << "\t\treal_t tempr, r;\n" + // << "\n" + // << "\t\tif ((rnd & 1) == 0)\n" + // << "\t\t tempr = (2 * M_PI * (int)(" << rn << " * MwcNext01(mwc)) + precalcAtanyx) / " << power << ";\n"//Fixed to get new random rather than use rnd from above.//SMOULDER + // << "\t\telse\n" + // << "\t\t tempr = (2 * M_PI * (int)(" << rn << " * MwcNext01(mwc)) - precalcAtanyx) / " << power << ";\n" + // << "\n" + // << "\t\tr = xform->m_VariationWeights[" << varIndex << "] * pow(precalcSumSquares, " << cn << ");\n" + // << "\n" + // << "\t\tvOut.x = r * cos(tempr);\n" + // << "\t\tvOut.y = r * sin(tempr);\n" + // << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + // << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Rn = fabs(m_Power); + m_Cn = m_Dist / m_Power / 2; + } + + virtual void Random(QTIsaac& rand) + { + m_Dist = 1; + m_Power = (T)(int)(5 * rand.Frand01() + 2); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Dist, prefix + "juliascope_dist", 1));//Params. + m_Params.push_back(ParamWithName(&m_Power, prefix + "juliascope_power", 1)); + m_Params.push_back(ParamWithName(true, &m_Rn, prefix + "juliascope_rn"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Cn, prefix + "juliascope_cn")); + } + +private: + T m_Dist;//Params. + T m_Power; + T m_Rn;//Precalc. + T m_Cn; +}; + +/// +/// Blur. +/// +template +class EMBER_API BlurVariation : public Variation +{ +public: + BlurVariation(T weight = 1.0) : Variation("blur", VAR_BLUR, weight) { } + + VARCOPY(BlurVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T tempr = rand.Frand01() * M_2PI; + T r = m_Weight * (rand.Frand01() + rand.Frand01() + + rand.Frand01() + rand.Frand01() - 2); + + helper.Out.x = r * cos(tempr); + helper.Out.y = r * sin(tempr); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t tmpr = MwcNext01(mwc) * M_2PI;\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * MwcNext01(mwc);\n" + << "\n" + << "\t\tvOut.x = r * cos(tmpr);\n" + << "\t\tvOut.y = r * sin(tmpr);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Gaussian blur. +/// +template +class EMBER_API GaussianBlurVariation : public Variation +{ +public: + GaussianBlurVariation(T weight = 1.0) : Variation("gaussian_blur", VAR_GAUSSIAN_BLUR, weight) { } + + VARCOPY(GaussianBlurVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T angle = rand.Frand01() * M_2PI; + T r = m_Weight * (rand.Frand01() + rand.Frand01() + rand.Frand01() + rand.Frand01() - 2); + + helper.Out.x = r * cos(angle); + helper.Out.y = r * sin(angle); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t angle = MwcNext01(mwc) * M_2PI;\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * (MwcNext01(mwc) + MwcNext01(mwc) + MwcNext01(mwc) + MwcNext01(mwc) - 2.0);\n" + << "\n" + << "\t\tvOut.x = r * cos(angle);\n" + << "\t\tvOut.y = r * sin(angle);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Radial blur. +/// +template +class EMBER_API RadialBlurVariation : public ParametricVariation +{ +public: + RadialBlurVariation(T weight = 1.0) : ParametricVariation("radial_blur", VAR_RADIAL_BLUR, weight, true, true, false, false, true) + { + Init(); + } + + PARVARCOPY(RadialBlurVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + //Get pseudo-gaussian. + T rndG = m_Weight * (rand.Frand01() + rand.Frand01() + + rand.Frand01() + rand.Frand01() - 2); + + //Calculate angle & zoom. + T ra = helper.m_PrecalcSqrtSumSquares; + T tempa = helper.m_PrecalcAtanyx + m_Spin * rndG; + T rz = m_Zoom * rndG - 1; + + helper.Out.x = ra * cos(tempa) + rz * helper.In.x; + helper.Out.y = ra * sin(tempa) + rz * helper.In.y; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string angle = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Params. + string spin = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Precalc. + string zoom = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t rndG = xform->m_VariationWeights[" << varIndex << "] * (MwcNext01(mwc) + MwcNext01(mwc) + MwcNext01(mwc) + MwcNext01(mwc) - 2.0);\n" + << "\t\treal_t ra = precalcSqrtSumSquares;\n" + << "\t\treal_t tempa = precalcAtanyx + "<< spin << " * rndG;\n" + << "\t\treal_t rz = " << zoom << " * rndG - 1;\n" + << "\n" + << "\t\tvOut.x = ra * cos(tempa) + rz * vIn.x;\n" + << "\t\tvOut.y = ra * sin(tempa) + rz * vIn.y;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + sincos(m_Angle * T(M_PI) / 2, &m_Spin, &m_Zoom); + } + + virtual void Random(QTIsaac& rand) + { + m_Angle = (2 * rand.Frand01() - 1); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Angle, prefix + "radial_blur_angle"));//Params. + m_Params.push_back(ParamWithName(true, &m_Spin, prefix + "radial_blur_spin"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Zoom, prefix + "radial_blur_zoom")); + } + +private: + T m_Angle;//Params. + T m_Spin;//Precalc. + T m_Zoom; +}; + +/// +/// Pie. +/// +template +class EMBER_API PieVariation : public ParametricVariation +{ +public: + PieVariation(T weight = 1.0) : ParametricVariation("pie", VAR_PIE, weight) + { + Init(); + } + + PARVARCOPY(PieVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + int sl = (int)(rand.Frand01() * m_Slices + T(0.5)); + T a = m_Rotation + M_2PI * (sl + rand.Frand01() * m_Thickness) / m_Slices; + T r = m_Weight * rand.Frand01(); + + helper.Out.x = r * cos(a); + helper.Out.y = r * sin(a); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string slices = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string rotation = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string thickness = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tint sl = (int)(MwcNext01(mwc) * " << slices << " + 0.5);\n" + << "\t\treal_t a = " << rotation << " + M_2PI * (sl + MwcNext01(mwc) * " << thickness << ") / " << slices << ";\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * MwcNext01(mwc);\n" + << "\n" + << "\t\tvOut.x = r * cos(a);\n" + << "\t\tvOut.y = r * sin(a);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Random(QTIsaac& rand) + { + m_Params[0].Set(10 * rand.Frand01());//Slices. + m_Params[1].Set(M_2PI * rand.Frand11());//Rotation. + m_Thickness = rand.Frand01(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Slices, prefix + "pie_slices", 6, INTEGER_NONZERO, 1)); + m_Params.push_back(ParamWithName(&m_Rotation, prefix + "pie_rotation", T(0.5), REAL_CYCLIC, 0, M_2PI)); + m_Params.push_back(ParamWithName(&m_Thickness, prefix + "pie_thickness", T(0.5), REAL, 0, 1)); + } + +private: + T m_Slices; + T m_Rotation; + T m_Thickness; +}; + +/// +/// Ngon. +/// +template +class EMBER_API NgonVariation : public ParametricVariation +{ +public: + NgonVariation(T weight = 1.0) : ParametricVariation("ngon", VAR_NGON, weight, true, false, false, false, true) + { + Init(); + } + + PARVARCOPY(NgonVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T rFactor = pow(helper.m_PrecalcSumSquares, m_Power / 2); + T theta = helper.m_PrecalcAtanyx; + T b = M_2PI / m_Sides; + T amp, phi = theta - (b * Floor(theta / b)); + + if (phi > b / 2) + phi -= b; + + amp = m_Corners * (1 / (cos(phi) + EPS) - 1) + m_Circle; + amp /= (rFactor + T(EPS)); + + helper.Out.x = m_Weight * helper.In.x * amp; + helper.Out.y = m_Weight * helper.In.y * amp; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string sides = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string power = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string circle = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string corners = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t rFactor = pow(precalcSumSquares, " << power << " / 2.0);\n" + << "\t\treal_t theta = precalcAtanyx;\n" + << "\t\treal_t b = M_2PI / " << sides << ";\n" + << "\t\treal_t amp, phi = theta - (b * floor(theta / b));\n" + << "\n" + << "\t\tif (phi > b / 2)\n" + << "\t\t phi -= b;\n" + << "\n" + << "\t\tamp = " << corners << " * (1.0 / (cos(phi) + EPS) - 1.0) + " << circle << ";\n" + << "\t\tamp /= (rFactor + EPS);\n" + << "\t\tvOut.x = (xform->m_VariationWeights[" << varIndex << "] * vIn.x * amp);\n" + << "\t\tvOut.y = (xform->m_VariationWeights[" << varIndex << "] * vIn.y * amp);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Random(QTIsaac& rand) + { + m_Sides = (T)(int)(rand.Frand01() * 10 + 3); + m_Power = 3 * rand.Frand01() + 1; + m_Circle = 3 * rand.Frand01(); + m_Corners = 2 * rand.Frand01() * m_Circle; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Sides, prefix + "ngon_sides", 5, INTEGER_NONZERO)); + m_Params.push_back(ParamWithName(&m_Power, prefix + "ngon_power", 3)); + m_Params.push_back(ParamWithName(&m_Circle, prefix + "ngon_circle", 1)); + m_Params.push_back(ParamWithName(&m_Corners, prefix + "ngon_corners", 2)); + } + +private: + T m_Sides; + T m_Power; + T m_Circle; + T m_Corners; +}; + +/// +/// Curl. +/// +template +class EMBER_API CurlVariation : public ParametricVariation +{ +public: + CurlVariation(T weight = 1.0) : ParametricVariation("curl", VAR_CURL, weight) + { + Init(); + } + + PARVARCOPY(CurlVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T re = 1 + m_C1w * helper.In.x + m_C2w * (SQR(helper.In.x) - SQR(helper.In.y));//Optimized from PostCurl. + T im = m_C1w * helper.In.y + m_C22 * helper.In.x * helper.In.y; + T r = SQR(re) + SQR(im); + + if (r == 0) + r = EPS6; + + helper.Out.x = (helper.In.x * re + helper.In.y * im) / r; + helper.Out.y = (helper.In.y * re - helper.In.x * im) / r; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string c1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c1w = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c2w = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c22 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t x = vIn.x;\n" + << "\t\treal_t y = vIn.y;\n" + << "\t\treal_t re = 1 + " << c1w << " * x + " << c2w << " * (SQR(x) - SQR(y));\n" + << "\t\treal_t im = " << c1w << " * y + " << c22 << " * x * y;\n" + << "\t\treal_t r = SQR(re) + SQR(im);\n" + << "\n" + << "\t\tif (r == 0)\n" + << "\t\t r = EPS6;\n" + << "\n" + << "\t\tvOut.x = (x * re + y * im) / r;\n" + << "\t\tvOut.y = (y * re - x * im) / r;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_C1w = m_C1 * m_Weight; + m_C2w = m_C2 * m_Weight; + m_C22 = 2 * m_C2w; + } + + virtual void Random(QTIsaac& rand) + { + m_C1 = rand.Frand01(); + m_C2 = rand.Frand01(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_C1, prefix + "curl_c1", 1)); + m_Params.push_back(ParamWithName(&m_C2, prefix + "curl_c2")); + m_Params.push_back(ParamWithName(true, &m_C1w, prefix + "curl_c1w"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_C2w, prefix + "curl_c2w")); + m_Params.push_back(ParamWithName(true, &m_C22, prefix + "curl_c22")); + } + +private: + T m_C1; + T m_C2; + T m_C1w;//Precalc. + T m_C2w; + T m_C22; +}; + +/// +/// Rectangles. +/// +template +class EMBER_API RectanglesVariation : public ParametricVariation +{ +public: + RectanglesVariation(T weight = 1.0) : ParametricVariation("rectangles", VAR_RECTANGLES, weight) + { + Init(); + } + + PARVARCOPY(RectanglesVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + if (m_X == 0) + helper.Out.x = m_Weight * helper.In.x; + else + helper.Out.x = m_Weight * ((2 * Floor(helper.In.x / m_X) + 1) * m_X - helper.In.x); + + if (m_Y == 0) + helper.Out.y = m_Weight * helper.In.y; + else + helper.Out.y = m_Weight * ((2 * Floor(helper.In.y / m_Y) + 1) * m_Y - helper.In.y); + + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tif (" << x << " == 0)\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\t\telse\n" + << "\t\t vOut.x = (xform->m_VariationWeights[" << varIndex << "] * ((2 * floor(vIn.x / " << x << ") + 1) * " << x << " - vIn.x));\n" + << "\n" + << "\t\tif (" << y << " == 0)\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\t\telse\n" + << "\t\t vOut.y = (xform->m_VariationWeights[" << varIndex << "] * ((2 * floor(vIn.y / " << y << ") + 1) * " << y << " - vIn.y));\n" + << "\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Random(QTIsaac& rand) + { + m_X = rand.Frand01(); + m_Y = rand.Frand01(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_X, prefix + "rectangles_x", 1)); + m_Params.push_back(ParamWithName(&m_Y, prefix + "rectangles_y", 1)); + } + +private: + T m_X; + T m_Y; +}; + +/// +/// Arch. +/// +template +class EMBER_API ArchVariation : public Variation +{ +public: + ArchVariation(T weight = 1.0) : Variation("arch", VAR_ARCH, weight) { } + + VARCOPY(ArchVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T angle = rand.Frand01() * m_Weight * T(M_PI); + T sinr, cosr; + + sincos(angle, &sinr, &cosr); + helper.Out.x = m_Weight * sinr; + helper.Out.y = m_Weight * (sinr * sinr) / cosr; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t angle = MwcNext01(mwc) * xform->m_VariationWeights[" << varIndex << "] * M_PI;\n" + << "\t\treal_t sinr = sin(angle);\n" + << "\t\treal_t cosr = cos(angle);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * sinr;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (sinr * sinr) / cosr;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Tangent. +/// +template +class EMBER_API TangentVariation : public Variation +{ +public: + TangentVariation(T weight = 1.0) : Variation("tangent", VAR_TANGENT, weight) { } + + VARCOPY(TangentVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + helper.Out.x = m_Weight * sin(helper.In.x) / cos(helper.In.y); + helper.Out.y = m_Weight * tan(helper.In.y); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * sin(vIn.x) / cos(vIn.y);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * tan(vIn.y);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Square. +/// +template +class EMBER_API SquareVariation : public Variation +{ +public: + SquareVariation(T weight = 1.0) : Variation("square", VAR_SQUARE, weight) { } + + VARCOPY(SquareVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + helper.Out.x = m_Weight * (rand.Frand01() - T(0.5)); + helper.Out.y = m_Weight * (rand.Frand01() - T(0.5)); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (MwcNext01(mwc) - 0.5);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (MwcNext01(mwc) - 0.5);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Rays. +/// +template +class EMBER_API RaysVariation : public Variation +{ +public: + RaysVariation(T weight = 1.0) : Variation("rays", VAR_RAYS, weight, true) { } + + VARCOPY(RaysVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T ang = m_Weight * rand.Frand01() * T(M_PI); + T r = m_Weight / (helper.m_PrecalcSumSquares + T(EPS)); + T tanr = m_Weight * tan(ang) * r; + + helper.Out.x = tanr * cos(helper.In.x); + helper.Out.y = tanr * sin(helper.In.y); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t ang = xform->m_VariationWeights[" << varIndex << "] * MwcNext01(mwc) * M_PI;\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] / (precalcSumSquares + EPS);\n" + << "\t\treal_t tanr = xform->m_VariationWeights[" << varIndex << "] * tan(ang) * r;\n" + << "\n" + << "\t\tvOut.x = tanr * cos(vIn.x);\n" + << "\t\tvOut.y = tanr * sin(vIn.y);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Blade. +/// +template +class EMBER_API BladeVariation : public Variation +{ +public: + BladeVariation(T weight = 1.0) : Variation("blade", VAR_BLADE, weight, true, true) { } + + VARCOPY(BladeVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r = rand.Frand01() * m_Weight * helper.m_PrecalcSqrtSumSquares; + T sinr, cosr; + + sincos(r, &sinr, &cosr); + helper.Out.x = m_Weight * helper.In.x * (cosr + sinr); + helper.Out.y = m_Weight * helper.In.x * (cosr - sinr); + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t r = MwcNext01(mwc) * xform->m_VariationWeights[" << varIndex << "] * precalcSqrtSumSquares;\n" + << "\t\treal_t sinr = sin(r);\n" + << "\t\treal_t cosr = cos(r);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x * (cosr + sinr);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.x * (cosr - sinr);\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Secant2. +/// +template +class EMBER_API Secant2Variation : public Variation +{ +public: + Secant2Variation(T weight = 1.0) : Variation("secant2", VAR_SECANT2, weight, true, true) { } + + VARCOPY(Secant2Variation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r = m_Weight * helper.m_PrecalcSqrtSumSquares; + T cr = cos(r); + T icr = 1 / cr; + + helper.Out.x = m_Weight * helper.In.x; + + if (cr < 0) + helper.Out.y = m_Weight * (icr + 1); + else + helper.Out.y = m_Weight * (icr - 1); + + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * precalcSqrtSumSquares;\n" + << "\t\treal_t cr = cos(r);\n" + << "\t\treal_t icr = 1.0 / cr;\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\n" + << "\t\tif (cr < 0.0)\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * (icr + 1.0);\n" + << "\t\telse\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * (icr - 1.0);\n" + << "\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// TwinTrian. +/// +template +class EMBER_API TwinTrianVariation : public Variation +{ +public: + TwinTrianVariation(T weight = 1.0) : Variation("TwinTrian", VAR_TWINTRIAN, weight, true, true) { } + + VARCOPY(TwinTrianVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r = rand.Frand01() * m_Weight * helper.m_PrecalcSqrtSumSquares; + T sinr, cosr, diff; + + sincos(r, &sinr, &cosr); + diff = log10(sinr * sinr) + cosr; + + if (BadVal(diff)) + diff = -30.0; + + helper.Out.x = m_Weight * helper.In.x * diff; + helper.Out.y = m_Weight * helper.In.x * (diff - sinr * T(M_PI)); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t r = MwcNext01(mwc) * xform->m_VariationWeights[" << varIndex << "] * precalcSqrtSumSquares;\n" + << "\t\treal_t sinr = sin(r);\n" + << "\t\treal_t cosr = cos(r);\n" + << "\t\treal_t diff = log10(sinr * sinr) + cosr;\n" + << "\n" + << "\t\tif (BadVal(diff))\n" + << "\t\t diff = -30.0;\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x * diff;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.x * (diff - sinr * M_PI);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Cross. +/// +template +class EMBER_API CrossVariation : public Variation +{ +public: + CrossVariation(T weight = 1.0) : Variation("cross", VAR_CROSS, weight) { } + + VARCOPY(CrossVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T s = helper.In.x * helper.In.x - helper.In.y * helper.In.y; + T r = m_Weight * sqrt(1 / (s * s + EPS)); + + helper.Out.x = helper.In.x * r; + helper.Out.y = helper.In.y * r; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t s = vIn.x * vIn.x - vIn.y * vIn.y;\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * sqrt(1.0 / (s * s + EPS));\n" + << "\n" + << "\t\tvOut.x = vIn.x * r;\n" + << "\t\tvOut.y = vIn.y * r;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Disc2. +/// +template +class EMBER_API Disc2Variation : public ParametricVariation +{ +public: + Disc2Variation(T weight = 1.0) : ParametricVariation("disc2", VAR_DISC2, weight, false, false, false, true) + { + Init(); + } + + PARVARCOPY(Disc2Variation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r, t, sinr, cosr; + + t = m_RotTimesPi * (helper.In.x + helper.In.y); + sincos(t, &sinr, &cosr); + r = m_Weight * helper.m_PrecalcAtanxy / T(M_PI); + helper.Out.x = (sinr + m_CosAdd) * r; + helper.Out.y = (cosr + m_SinAdd) * r; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string rot = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Params. + string twist = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string sinAdd = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Precalc. + string cosAdd = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string rotTimesPi = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t t = " << rotTimesPi << " * (vIn.x + vIn.y);\n" + << "\t\treal_t sinr = sin(t);\n" + << "\t\treal_t cosr = cos(t);\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * precalcAtanxy / M_PI;\n" + << "\n" + << "\t\tvOut.x = (sinr + " << cosAdd << ") * r;\n" + << "\t\tvOut.y = (cosr + " << sinAdd << ") * r;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + T k, add = m_Twist; + + m_RotTimesPi = m_Rot * T(M_PI); + sincos(add, &m_SinAdd, &m_CosAdd); + m_CosAdd -= 1; + + if (add > 2 * M_PI) + { + k = (1 + add - 2 * T(M_PI)); + m_CosAdd *= k; + m_SinAdd *= k; + } + + if (add < -2 * M_PI) + { + k = (1 + add + 2 * T(M_PI)); + m_CosAdd *= k; + m_SinAdd *= k; + } + } + + virtual void Random(QTIsaac& rand) + { + m_Rot = T(0.5) * rand.Frand01(); + m_Twist = T(0.5) * rand.Frand01(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Rot, prefix + "disc2_rot"));//Params. + m_Params.push_back(ParamWithName(&m_Twist, prefix + "disc2_twist")); + m_Params.push_back(ParamWithName(true, &m_SinAdd, prefix + "disc2_sin_add"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_CosAdd, prefix + "disc2_cos_add")); + m_Params.push_back(ParamWithName(true, &m_RotTimesPi, prefix + "disc2_rot_times_pi")); + } + +private: + T m_Rot;//Params. + T m_Twist; + T m_SinAdd;//Precalc. + T m_CosAdd; + T m_RotTimesPi; +}; + +/// +/// SuperShape. +/// +template +class EMBER_API SuperShapeVariation : public ParametricVariation +{ +public: + SuperShapeVariation(T weight = 1.0) : ParametricVariation("super_shape", VAR_SUPER_SHAPE, weight, true, true, false, false, true) + { + Init(); + } + + PARVARCOPY(SuperShapeVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T theta = m_Pm4 * helper.m_PrecalcAtanyx + T(M_PI_4); + + T t1 = fabs(cos(theta)); + t1 = pow(t1, m_N2); + + T t2 = fabs(sin(theta)); + t2 = pow(t2, m_N3); + + T r = m_Weight * ((m_Rnd * rand.Frand01() + (1 - m_Rnd) * helper.m_PrecalcSqrtSumSquares) - m_Holes) + * pow(t1 + t2, m_PNeg1N1) / helper.m_PrecalcSqrtSumSquares; + + helper.Out.x = r * helper.In.x; + helper.Out.y = r * helper.In.y; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string m = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Params. + string n1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string n2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string n3 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string rnd = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string holes = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string pm4 = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Precalc. + string pNeg1N1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t theta = " << pm4 << " * precalcAtanyx + M_PI_4;\n" + << "\t\treal_t t1 = fabs(cos(theta));\n" + << "\t\tt1 = pow(t1, " << n2 << ");\n" + << "\t\treal_t t2 = fabs(sin(theta));\n" + << "\t\tt2 = pow(t2, " << n3 << ");\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * ((" << rnd << " * MwcNext01(mwc) + (1.0 - " << rnd << ") * precalcSqrtSumSquares) - " << holes << ") * pow(t1 + t2, " << pNeg1N1 << ") / precalcSqrtSumSquares;\n" + << "\n" + << "\t\tvOut.x = r * vIn.x;\n" + << "\t\tvOut.y = r * vIn.y;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Pm4 = m_M / T(4.0); + m_PNeg1N1 = T(-1.0) / m_N1; + } + + virtual void Random(QTIsaac& rand) + { + m_Rnd = rand.Frand01(); + m_M = (T)(int)(rand.Frand01() * 6); + m_N1 = rand.Frand01() * 40; + m_N2 = rand.Frand01() * 20; + m_N3 = m_N2; + m_Holes = 0.0; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_M, prefix + "super_shape_m"));//Params. + m_Params.push_back(ParamWithName(&m_N1, prefix + "super_shape_n1", 1)); + m_Params.push_back(ParamWithName(&m_N2, prefix + "super_shape_n2", 1)); + m_Params.push_back(ParamWithName(&m_N3, prefix + "super_shape_n3", 1)); + m_Params.push_back(ParamWithName(&m_Rnd, prefix + "super_shape_rnd")); + m_Params.push_back(ParamWithName(&m_Holes, prefix + "super_shape_holes")); + m_Params.push_back(ParamWithName(true, &m_Pm4, prefix + "super_shape_pm4"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_PNeg1N1, prefix + "super_shape_pneg1n1")); + } + +private: + T m_M;//Params. + T m_N1; + T m_N2; + T m_N3; + T m_Rnd; + T m_Holes; + T m_Pm4;//Precalc. + T m_PNeg1N1; +}; + +/// +/// Flower. +/// +template +class EMBER_API FlowerVariation : public ParametricVariation +{ +public: + FlowerVariation(T weight = 1.0) : ParametricVariation("flower", VAR_FLOWER, weight, true, true, false, false, true) + { + Init(); + } + + PARVARCOPY(FlowerVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T theta = helper.m_PrecalcAtanyx; + T r = m_Weight * (rand.Frand01() - m_Holes) * cos(m_Petals * theta) / helper.m_PrecalcSqrtSumSquares; + + helper.Out.x = r * helper.In.x; + helper.Out.y = r * helper.In.y; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string petals = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string holes = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t theta = precalcAtanyx;\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * (MwcNext01(mwc) - " << holes << ") * cos(" << petals << " * theta) / precalcSqrtSumSquares;\n" + << "\n" + << "\t\tvOut.x = r * vIn.x;\n" + << "\t\tvOut.y = r * vIn.y;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Random(QTIsaac& rand) + { + m_Petals = 4 * rand.Frand01(); + m_Holes = rand.Frand01(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Petals, prefix + "flower_petals")); + m_Params.push_back(ParamWithName(&m_Holes, prefix + "flower_holes")); + } + +private: + T m_Petals; + T m_Holes; +}; + +/// +/// Conic. +/// +template +class EMBER_API ConicVariation : public ParametricVariation +{ +public: + ConicVariation(T weight = 1.0) : ParametricVariation("conic", VAR_CONIC, weight, true, true) + { + Init(); + } + + PARVARCOPY(ConicVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T ct = helper.In.x / helper.m_PrecalcSqrtSumSquares; + T r = m_Weight * (rand.Frand01() - m_Holes) * + m_Eccentricity / (1 + m_Eccentricity * ct) / helper.m_PrecalcSqrtSumSquares; + + helper.Out.x = r * helper.In.x; + helper.Out.y = r * helper.In.y; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string eccentricity = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string holes = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t ct = vIn.x / precalcSqrtSumSquares;\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * (MwcNext01(mwc) - " << holes << ") * " << eccentricity << " / (1 + " << eccentricity << " * ct) / precalcSqrtSumSquares;\n" + << "\n" + << "\t\tvOut.x = r * vIn.x;\n" + << "\t\tvOut.y = r * vIn.y;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Random(QTIsaac& rand) + { + m_Eccentricity = rand.Frand01(); + m_Holes = rand.Frand01(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Eccentricity, prefix + "conic_eccentricity", 1)); + m_Params.push_back(ParamWithName(&m_Holes, prefix + "conic_holes")); + } + +private: + T m_Eccentricity; + T m_Holes; +}; + +/// +/// Parabola. +/// +template +class EMBER_API ParabolaVariation : public ParametricVariation +{ +public: + ParabolaVariation(T weight = 1.0) : ParametricVariation("parabola", VAR_PARABOLA, weight, true, true) + { + Init(); + } + + PARVARCOPY(ParabolaVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T sr, cr; + + sincos(helper.m_PrecalcSqrtSumSquares, &sr, &cr); + helper.Out.x = m_Height * m_Weight * sr * sr * rand.Frand01(); + helper.Out.y = m_Width * m_Weight * cr * rand.Frand01(); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string height = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string width = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t sr = sin(precalcSqrtSumSquares);\n" + << "\t\treal_t cr = cos(precalcSqrtSumSquares);\n" + << "\n" + << "\t\tvOut.x = " << height << " * (xform->m_VariationWeights[" << varIndex << "] * sr * sr * MwcNext01(mwc));\n" + << "\t\tvOut.y = " << width << " * (xform->m_VariationWeights[" << varIndex << "] * cr * MwcNext01(mwc));\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Random(QTIsaac& rand) + { + m_Height = T(0.5) * rand.Frand01(); + m_Width = T(0.5) * rand.Frand01(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Height, prefix + "parabola_height")); + m_Params.push_back(ParamWithName(&m_Width, prefix + "parabola_width")); + } + +private: + T m_Height; + T m_Width; +}; + +/// +/// Bent2. +/// +template +class EMBER_API Bent2Variation : public ParametricVariation +{ +public: + Bent2Variation(T weight = 1.0) : ParametricVariation("bent2", VAR_BENT2, weight) + { + Init(); + } + + PARVARCOPY(Bent2Variation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + if (helper.In.x >= 0) + helper.Out.x = m_Weight * helper.In.x; + else + helper.Out.x = m_Vx * helper.In.x; + + if (helper.In.y >= 0) + helper.Out.y = m_Weight * helper.In.y; + else + helper.Out.y = m_Vy * helper.In.y; + + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string vx = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string vy = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tif (vIn.x >= 0)\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\t\telse\n" + << "\t\t vOut.x = " << vx << " * vIn.x;\n" + << "\n" + << "\t\tif (vIn.y >= 0)\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\t\telse\n" + << "\t\tvOut.y = " << vy << " * vIn.y;\n" + << "\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Vx = m_X * m_Weight; + m_Vy = m_Y * m_Weight; + } + + virtual void Random(QTIsaac& rand) + { + m_X = 3 * (T(-0.5) + rand.Frand01()); + m_Y = 3 * (T(-0.5) + rand.Frand01()); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_X, prefix + "bent2_x", 1));//Params. + m_Params.push_back(ParamWithName(&m_Y, prefix + "bent2_y", 1)); + m_Params.push_back(ParamWithName(true, &m_Vx, prefix + "bent2_vx"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Vy, prefix + "bent2_vy")); + } + +private: + T m_X;//Params. + T m_Y; + T m_Vx;//Precalc. + T m_Vy; +}; + +/// +/// Bipolar. +/// +template +class EMBER_API BipolarVariation : public ParametricVariation +{ +public: + BipolarVariation(T weight = 1.0) : ParametricVariation("bipolar", VAR_BIPOLAR, weight, true) + { + Init(); + } + + PARVARCOPY(BipolarVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T x2y2 = helper.m_PrecalcSumSquares; + T t = x2y2 + 1; + T x2 = 2 * helper.In.x; + T y = T(0.5) * atan2(2 * helper.In.y, x2y2 - 1) + m_S; + + if (y > T(M_PI_2)) + y = T(-M_PI_2) + fmod(y + T(M_PI_2), T(M_PI)); + else if (y < T(-M_PI_2)) + y = T(M_PI_2) - fmod(T(M_PI_2) - y, T(M_PI)); + + T f = t + x2; + T g = t - x2; + + if ((g == 0) || (f / g <= 0)) + { + if (m_VarType == VARTYPE_REG) + { + helper.Out.x = 0; + helper.Out.y = 0; + helper.Out.z = 0; + } + else + { + helper.Out.x = helper.In.x; + helper.Out.y = helper.In.y; + helper.Out.z = helper.In.z; + } + } + else + { + helper.Out.x = m_V4 * log((t + x2) / (t - x2)); + helper.Out.y = m_V * y; + helper.Out.z = m_Weight * helper.In.z; + } + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string shift = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string s = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string v = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string v4 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t x2y2 = precalcSumSquares;\n" + << "\t\treal_t t = x2y2 + 1;\n" + << "\t\treal_t x2 = 2 * vIn.x;\n" + << "\t\treal_t ps = " << s << ";\n" + << "\t\treal_t y = 0.5 * atan2(2.0 * vIn.y, x2y2 - 1.0) + ps;\n" + << "\n" + << "\t\tif (y > M_PI_2)\n" + << "\t\t y = -M_PI_2 + fmod(y + M_PI_2, M_PI);\n" + << "\t\telse if (y < -M_PI_2)\n" + << "\t\t y = M_PI_2 - fmod(M_PI_2 - y, M_PI);\n" + << "\n" + << "\t\treal_t f = t + x2;\n" + << "\t\treal_t g = t - x2;\n" + << "\n"; + + if (m_VarType == VARTYPE_REG) + { + ss << "\t\tif ((g == 0) || (f / g <= 0))\n" + << "\t\t{\n" + << "\t\t vOut.x = 0;\n" + << "\t\t vOut.y = 0;\n" + << "\t\t vOut.z = 0;\n" + << "\t\t}\n"; + } + else + { + ss << "\t\tif ((g == 0) || (f / g <= 0))\n" + << "\t\t{\n" + << "\t\t vOut.x = vIn.x;\n" + << "\t\t vOut.y = vIn.y;\n" + << "\t\t vOut.z = vIn.z;\n" + << "\t\t}\n"; + } + + ss << "\t\telse\n" + << "\t\t{\n" + << "\t\t vOut.x = (" << v4 << " * log((t + x2) / (t - x2)));\n" + << "\t\t vOut.y = (" << v << " * y);\n" + << "\t\t vOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t\t}\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_S = T(-M_PI_2) * m_Shift;; + m_V = m_Weight * T(M_2_PI); + m_V4 = m_Weight * T(0.25) * T(M_2_PI); + } + + virtual void Random(QTIsaac& rand) + { + m_Shift = 2 * rand.Frand01() - 1; + } + + virtual bool SetParamVal(const char* name, T val) + { + if (!strcmp(name, "bipolar_shift")) + { + T temp = Fabsmod(T(0.5) * (val + 1)); + + m_Shift = 2 * temp - 1; + Precalc(); + return true; + } + + return ParametricVariation::SetParamVal(name, val); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Shift, prefix + "bipolar_shift"));//Params. + m_Params.push_back(ParamWithName(true, &m_S, prefix + "bipolar_s"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_V, prefix + "bipolar_v")); + m_Params.push_back(ParamWithName(true, &m_V4, prefix + "bipolar_v4")); + } + +private: + T m_Shift;//Params. + T m_S;//Precalc. + T m_V; + T m_V4; +}; + +/// +/// Boarders. +/// +template +class EMBER_API BoardersVariation : public Variation +{ +public: + BoardersVariation(T weight = 1.0) : Variation("boarders", VAR_BOARDERS, weight) { } + + VARCOPY(BoardersVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T roundX = Rint(helper.In.x); + T roundY = Rint(helper.In.y); + T offsetX = helper.In.x - roundX; + T offsetY = helper.In.y - roundY; + + if (rand.Frand01() >= 0.75) + { + helper.Out.x = m_Weight * (offsetX * T(0.5) + roundX); + helper.Out.y = m_Weight * (offsetY * T(0.5) + roundY); + } + else + { + if (fabs(offsetX) >= fabs(offsetY)) + { + if (offsetX >= 0.0) + { + helper.Out.x = m_Weight * (offsetX * T(0.5) + roundX + T(0.25)); + helper.Out.y = m_Weight * (offsetY * T(0.5) + roundY + T(0.25) * offsetY / offsetX); + } + else + { + helper.Out.x = m_Weight * (offsetX * T(0.5) + roundX - T(0.25)); + helper.Out.y = m_Weight * (offsetY * T(0.5) + roundY - T(0.25) * offsetY / offsetX); + } + } + else + { + if (offsetY >= 0.0) + { + helper.Out.y = m_Weight * (offsetY * T(0.5) + roundY + T(0.25)); + helper.Out.x = m_Weight * (offsetX * T(0.5) + roundX + offsetX / offsetY * T(0.25)); + } + else + { + helper.Out.y = m_Weight * (offsetY * T(0.5) + roundY - T(0.25)); + helper.Out.x = m_Weight * (offsetX * T(0.5) + roundX - offsetX / offsetY * T(0.25)); + } + } + } + + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t roundX = Rint(vIn.x);\n" + << "\t\treal_t roundY = Rint(vIn.y);\n" + << "\t\treal_t offsetX = vIn.x - roundX;\n" + << "\t\treal_t offsetY = vIn.y - roundY;\n" + << "\n" + << "\t\tif (MwcNext01(mwc) >= 0.75)\n" + << "\t\t{\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (offsetX * 0.5 + roundX);\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * (offsetY * 0.5 + roundY);\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t if (fabs(offsetX) >= fabs(offsetY))\n" + << "\t\t {\n" + << "\t\t if (offsetX >= 0.0)\n" + << "\t\t {\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (offsetX * 0.5 + roundX + 0.25);\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * (offsetY * 0.5 + roundY + 0.25 * offsetY / offsetX);\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (offsetX * 0.5 + roundX - 0.25);\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * (offsetY * 0.5 + roundY - 0.25 * offsetY / offsetX);\n" + << "\t\t }\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t if (offsetY >= 0.0)\n" + << "\t\t {\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * (offsetY * 0.5 + roundY + 0.25);\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (offsetX * 0.5 + roundX + offsetX / offsetY * 0.25);\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * (offsetY * 0.5 + roundY - 0.25);\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (offsetX * 0.5 + roundX - offsetX / offsetY * 0.25);\n" + << "\t\t }\n" + << "\t\t }\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Butterfly. +/// +template +class EMBER_API ButterflyVariation : public Variation +{ +public: + ButterflyVariation(T weight = 1.0) : Variation("butterfly", VAR_BUTTERFLY, weight) { } + + VARCOPY(ButterflyVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T wx = m_Weight * T(1.3029400317411197908970256609023);//This precision came from the original. + T y2 = helper.In.y * 2; + T r = wx * sqrt(fabs(helper.In.y * helper.In.x) / (T(EPS) + helper.In.x * helper.In.x + y2 * y2)); + + helper.Out.x = r * helper.In.x; + helper.Out.y = r * y2; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t wx = xform->m_VariationWeights[" << varIndex << "] * 1.3029400317411197908970256609023;\n" + << "\t\treal_t y2 = vIn.y * 2.0;\n" + << "\t\treal_t r = wx * sqrt(fabs(vIn.y * vIn.x) / (EPS + vIn.x * vIn.x + y2 * y2));\n" + << "\n" + << "\t\tvOut.x = r * vIn.x;\n" + << "\t\tvOut.y = r * y2;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Cell. +/// +template +class EMBER_API CellVariation : public ParametricVariation +{ +public: + CellVariation(T weight = 1.0) : ParametricVariation("cell", VAR_CELL, weight) + { + Init(); + } + + PARVARCOPY(CellVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T invCellSize = 1 / m_Size; + T x = floor(helper.In.x * invCellSize);//Calculate input cell. Note that int cast is omitted here. See below. + T y = floor(helper.In.y * invCellSize); + T dx = helper.In.x - x * m_Size;//Offset from cell origin. + T dy = helper.In.y - y * m_Size; + + //Interleave cells. + if (y >= 0) + { + if (x >= 0) + { + y *= 2; + x *= 2; + } + else + { + y *= 2; + x = -(2 * x + 1); + } + } + else + { + if (x >= 0) + { + y = -(2 * y + 1); + x *= 2; + } + else + { + y = -(2 * y + 1); + x = -(2 * x + 1); + } + } + + helper.Out.x = m_Weight * (dx + x * m_Size); + helper.Out.y = -(m_Weight * (dy + y * m_Size)); + helper.Out.z = m_Weight * helper.In.z; + } + + /// + /// Cell is very strange and will not run using integers. + /// When using floats, it at least gives some output, however + /// that output is slightly different than the CPU. But not by enough + /// to change the shape of the final image. + /// + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string size = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t invCellSize = 1.0 / " << size << ";\n" + //Float to int, orig. + //<< "\t\tint x = (int)floor(vIn.x * invCellSize);\n" + //<< "\t\tint y = (int)floor(vIn.y * invCellSize);\n" + + //For some reason, OpenCL renders nothing if these are ints, so use floats. + //Note that Cuburn also omits the usage of ints. + << "\t\treal_t x = floor(vIn.x * invCellSize);\n" + << "\t\treal_t y = floor(vIn.y * invCellSize);\n" + << "\t\treal_t dx = vIn.x - x * " << size << ";\n" + << "\t\treal_t dy = vIn.y - y * " << size << ";\n" + << "\n" + << "\t\tif (y >= 0)\n" + << "\t\t{\n" + << "\t\t if (x >= 0)\n" + << "\t\t {\n" + << "\t\t y *= 2;\n" + << "\t\t x *= 2;\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t y *= 2;\n" + << "\t\t x = -(2 * x + 1);\n" + << "\t\t }\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t if (x >= 0)\n" + << "\t\t {\n" + << "\t\t y = -(2 * y + 1);\n" + << "\t\t x *= 2;\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t y = -(2 * y + 1);\n" + << "\t\t x = -(2 * x + 1);\n" + << "\t\t }\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (dx + x * " << size << ");\n" + << "\t\tvOut.y = -(xform->m_VariationWeights[" << varIndex << "] * (dy + y * " << size << "));\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Random(QTIsaac& rand) + { + m_Size = 2 * rand.Frand01() + T(0.5); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Size, prefix + "cell_size", 1)); + } + +private: + T m_Size; +}; + +/// +/// Cpow. +/// +template +class EMBER_API CpowVariation : public ParametricVariation +{ +public: + CpowVariation(T weight = 1.0) : ParametricVariation("cpow", VAR_CPOW, weight, true, false, false, false, true) + { + Init(); + } + + PARVARCOPY(CpowVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T a = helper.m_PrecalcAtanyx; + T lnr = T(0.5) * log(helper.m_PrecalcSumSquares); + T angle = m_C * a + m_D * lnr + m_Ang * Floor(m_Power * rand.Frand01()); + T m = m_Weight * exp(m_C * lnr - m_D * a); + + helper.Out.x = m * cos(angle); + helper.Out.y = m * sin(angle); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string powerR = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string powerI = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string power = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string d = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ang = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t a = precalcAtanyx;\n" + << "\t\treal_t lnr = 0.5 * log(precalcSumSquares);\n" + << "\t\treal_t angle = " << c << " * a + " << d << " * lnr + " << ang << " * floor(" << power << " * MwcNext01(mwc));\n" + << "\t\treal_t m = xform->m_VariationWeights[" << varIndex << "] * exp(" << c << " * lnr - " << d << " * a);\n" + << "\n" + << "\t\tvOut.x = m * cos(angle);\n" + << "\t\tvOut.y = m * sin(angle);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_C = m_PowerR / m_Power; + m_D = m_PowerI / m_Power; + m_Ang = 2 * T(M_PI) / m_Power; + } + + virtual void Random(QTIsaac& rand) + { + m_PowerR = 3 * rand.Frand01(); + m_PowerI = rand.Frand01() - T(0.5); + m_Params[2].Set(5 * rand.Frand01());//Power. + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_PowerR, prefix + "cpow_r", 1));//Params. + m_Params.push_back(ParamWithName(&m_PowerI, prefix + "cpow_i")); + m_Params.push_back(ParamWithName(&m_Power, prefix + "cpow_power", 1, INTEGER_NONZERO)); + m_Params.push_back(ParamWithName(true, &m_C, prefix + "cpow_c"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_D, prefix + "cpow_d")); + m_Params.push_back(ParamWithName(true, &m_Ang, prefix + "cpow_ang")); + } + +private: + T m_PowerR;//Params. + T m_PowerI; + T m_Power; + T m_C;//Precalc. + T m_D; + T m_Ang; +}; + +/// +/// Curve. +/// +template +class EMBER_API CurveVariation : public ParametricVariation +{ +public: + CurveVariation(T weight = 1.0) : ParametricVariation("curve", VAR_CURVE, weight) + { + Init(); + } + + PARVARCOPY(CurveVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + helper.Out.x = m_Weight * helper.In.x + m_XAmpV * exp(-helper.In.y * helper.In.y * m_XLengthV); + helper.Out.y = m_Weight * helper.In.y + m_YAmpV * exp(-helper.In.x * helper.In.x * m_YLengthV); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string xAmp = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string yAmp = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string xLength = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string yLength = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string xAmpV = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string yAmpV = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string xLengthV = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string yLengthV = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x + " << xAmpV << " * exp(-vIn.y * vIn.y * " << xLengthV << ");\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y + " << yAmpV << " * exp(-vIn.x * vIn.x * " << yLengthV << ");\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_XAmpV = m_Weight * m_XAmp; + m_YAmpV = m_Weight * m_YAmp; + m_XLengthV = 1 / max(SQR(m_XLength), T(1e-20)); + m_YLengthV = 1 / max(SQR(m_YLength), T(1e-20)); + } + + virtual void Random(QTIsaac& rand) + { + m_XAmp = 5 * (rand.Frand01() - T(0.5)); + m_YAmp = 4 * (rand.Frand01() - T(0.5)); + m_XLength = 2 * (rand.Frand01() + T(0.5)); + m_YLength = 2 * (rand.Frand01() + T(0.5)); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_XAmp, prefix + "curve_xamp"));//Params. + m_Params.push_back(ParamWithName(&m_YAmp, prefix + "curve_yamp")); + m_Params.push_back(ParamWithName(&m_XLength, prefix + "curve_xlength", 1)); + m_Params.push_back(ParamWithName(&m_YLength, prefix + "curve_ylength", 1)); + m_Params.push_back(ParamWithName(true, &m_XAmpV, prefix + "curve_xampv"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_YAmpV, prefix + "curve_yampv")); + m_Params.push_back(ParamWithName(true, &m_XLengthV, prefix + "curve_xlenv")); + m_Params.push_back(ParamWithName(true, &m_YLengthV, prefix + "curve_ylenv")); + } + +private: + T m_XAmp;//Params. + T m_YAmp; + T m_XLength; + T m_YLength; + T m_XAmpV;//Precalc. + T m_YAmpV; + T m_XLengthV; + T m_YLengthV; +}; + +/// +/// Edisc. +/// +template +class EMBER_API EdiscVariation : public Variation +{ +public: + EdiscVariation(T weight = 1.0) : Variation("edisc", VAR_EDISC, weight, true) { } + + VARCOPY(EdiscVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T tmp = helper.m_PrecalcSumSquares + 1; + T tmp2 = 2 * helper.In.x; + T r1 = sqrt(tmp + tmp2); + T r2 = sqrt(tmp - tmp2); + T xmax = (r1 + r2) * T(0.5); + T a1 = log(xmax + sqrt(xmax - 1)); + T a2 = -acos(Clamp(helper.In.x / xmax, -1, 1)); + T w = m_Weight / T(11.57034632);//This is an interesting magic number. + T snv, csv, snhu, cshu; + + sincos(a1, &snv, &csv); + + snhu = sinh(a2); + cshu = cosh(a2); + + if (helper.In.y > 0.0) + snv = -snv; + + helper.Out.x = w * cshu * csv; + helper.Out.y = w * snhu * snv; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t tmp = precalcSumSquares + 1.0;\n" + << "\t\treal_t tmp2 = 2.0 * vIn.x;\n" + << "\t\treal_t r1 = sqrt(tmp + tmp2);\n" + << "\t\treal_t r2 = sqrt(tmp - tmp2);\n" + << "\t\treal_t xmax = (r1 + r2) * 0.5;\n" + << "\t\treal_t a1 = log(xmax + sqrt(xmax - 1.0));\n" + << "\t\treal_t a2 = -acos(Clamp(vIn.x / xmax, -1.0, 1.0));\n" + << "\t\treal_t w = xform->m_VariationWeights[" << varIndex << "] / 11.57034632;\n" + << "\t\treal_t snv = sin(a1);\n" + << "\t\treal_t csv = cos(a1);\n" + << "\t\treal_t snhu = sinh(a2);\n" + << "\t\treal_t cshu = cosh(a2);\n" + + << "\t\tif (vIn.y > 0)\n" + << "\t\t snv = -snv;\n" + + << "\t\tvOut.x = w * cshu * csv;\n" + << "\t\tvOut.y = w * snhu * snv;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Elliptic. +/// +template +class EMBER_API EllipticVariation : public Variation +{ +public: + EllipticVariation(T weight = 1.0) : Variation("elliptic", VAR_ELLIPTIC, weight, true) { } + + VARCOPY(EllipticVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T tmp = helper.m_PrecalcSumSquares + 1; + T x2 = 2 * helper.In.x; + T xmax = T(0.5) * (sqrt(tmp + x2) + sqrt(tmp - x2)); + T a = helper.In.x / xmax; + T b = 1 - a * a; + T ssx = xmax - 1; + T w = m_Weight / T(M_PI_2); + + if (b < 0) + b = 0; + else + b = sqrt(b); + + if (ssx < 0) + ssx = 0; + else + ssx = sqrt(ssx); + + helper.Out.x = w * atan2(a, b); + + if (helper.In.y > 0) + helper.Out.y = w * log(xmax + ssx); + else + helper.Out.y = -(w * log(xmax + ssx)); + + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t tmp = precalcSumSquares + 1.0;\n" + << "\t\treal_t x2 = 2.0 * vIn.x;\n" + << "\t\treal_t xmax = 0.5 * (sqrt(tmp + x2) + sqrt(tmp - x2));\n" + << "\t\treal_t a = vIn.x / xmax;\n" + << "\t\treal_t b = 1.0 - a * a;\n" + << "\t\treal_t ssx = xmax - 1.0;\n" + << "\t\treal_t w = xform->m_VariationWeights[" << varIndex << "] / M_PI_2;\n" + << "\n" + << "\t\tif (b < 0)\n" + << "\t\t b = 0;\n" + << "\t\telse\n" + << "\t\t b = sqrt(b);\n" + << "\n" + << "\t\tif (ssx < 0)\n" + << "\t\t ssx = 0;\n" + << "\t\telse\n" + << "\t\t ssx = sqrt(ssx);\n" + << "\n" + << "\t\tvOut.x = w * atan2(a, b);\n" + << "\n" + << "\t\tif (vIn.y > 0)\n" + << "\t\t vOut.y = w * log(xmax + ssx);\n" + << "\t\telse\n" + << "\t\t vOut.y = -(w * log(xmax + ssx));\n" + << "\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Escher. +/// +template +class EMBER_API EscherVariation : public ParametricVariation +{ +public: + EscherVariation(T weight = 1.0) : ParametricVariation("escher", VAR_ESCHER, weight, true, false, false, false, true) + { + Init(); + } + + PARVARCOPY(EscherVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T a = helper.m_PrecalcAtanyx; + T lnr = T(0.5) * log(helper.m_PrecalcSumSquares); + T m = m_Weight * exp(m_C * lnr - m_D * a); + T n = m_C * a + m_D * lnr; + + helper.Out.x = m * cos(n); + helper.Out.y = m * sin(n); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string beta = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string d = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t a = precalcAtanyx;\n" + << "\t\treal_t lnr = 0.5 * log(precalcSumSquares);\n" + << "\t\treal_t m = xform->m_VariationWeights[" << varIndex << "] * exp(" << c << " * lnr - " << d << " * a);\n" + << "\t\treal_t n = " << c << " * a + " << d << " * lnr;\n" + << "\n" + << "\t\tvOut.x = m * cos(n);\n" + << "\t\tvOut.y = m * sin(n);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + sincos(m_Beta, &m_D, &m_C); + m_C = T(0.5) * (1 + m_C); + m_D = T(0.5) * m_D; + } + + virtual void Random(QTIsaac& rand) + { + SetParamVal("escher_beta", T(M_PI) * rand.Frand01()); + } + + virtual bool SetParamVal(const char* name, T val) + { + if (!strcmp(name, "escher_beta")) + { + m_Beta = Fabsmod((val + T(M_PI)) / (2 * T(M_PI))) * 2 * T(M_PI) - T(M_PI); + Precalc(); + return true; + } + + return ParametricVariation::SetParamVal(name, val); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Beta, prefix + "escher_beta"));//Params. + m_Params.push_back(ParamWithName(true, &m_C, prefix + "escher_beta_c"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_D, prefix + "escher_beta_d")); + } + +private: + T m_Beta;//Params. + T m_C;//Precalc. + T m_D; +}; + +/// +/// Foci. +/// +template +class EMBER_API FociVariation : public Variation +{ +public: + FociVariation(T weight = 1.0) : Variation("foci", VAR_FOCI, weight) { } + + VARCOPY(FociVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T expx = exp(helper.In.x) * T(0.5); + T expnx = T(0.25) / expx; + T sn, cn, tmp; + + sincos(helper.In.y, &sn, &cn); + + tmp = expx + expnx - cn; + + if (tmp == 0) + tmp = EPS6; + + tmp = m_Weight / tmp; + + helper.Out.x = tmp * (expx - expnx); + helper.Out.y = tmp * sn; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t expx = exp(vIn.x) * 0.5;\n" + << "\t\treal_t expnx = 0.25 / expx;\n" + << "\t\treal_t sn = sin(vIn.y);\n" + << "\t\treal_t cn = cos(vIn.y);\n" + << "\t\treal_t tmp = expx + expnx - cn;\n" + << "\n" + << "\t\tif (tmp == 0)\n" + << "\t\t tmp = EPS6;\n" + << "\n" + << "\t\ttmp = xform->m_VariationWeights[" << varIndex << "] / tmp;\n" + << "\n" + << "\t\tvOut.x = tmp * (expx - expnx);\n" + << "\t\tvOut.y = tmp * sn;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// LazySusan. +/// +template +class EMBER_API LazySusanVariation : public ParametricVariation +{ +public: + LazySusanVariation(T weight = 1.0) : ParametricVariation("lazysusan", VAR_LAZYSUSAN, weight) + { + Init(); + } + + PARVARCOPY(LazySusanVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T x = helper.In.x - m_X; + T y = helper.In.y + m_Y; + T r = sqrt(x * x + y * y); + + if (r < m_Weight) + { + T a = atan2(y, x) + m_Spin + m_Twist * (m_Weight - r); + + helper.Out.x = m_Weight * (r * cos(a) + m_X);//Fix to make it colapse to 0 when weight is 0.//SMOULDER + helper.Out.y = m_Weight * (r * sin(a) - m_Y); + } + else + { + r = 1 + m_Space / (r + EPS6); + + helper.Out.x = m_Weight * (r * x + m_X);//Fix to make it colapse to 0 when weight is 0.//SMOULDER + helper.Out.y = m_Weight * (r * y - m_Y); + } + + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string spin = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string space = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string twist = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t x = vIn.x - " << x << ";\n" + << "\t\treal_t y = vIn.y + " << y << ";\n" + << "\t\treal_t r = sqrt(x * x + y * y);\n" + << "\n" + << "\t\tif (r < xform->m_VariationWeights[" << varIndex << "])\n" + << "\t\t{\n" + << "\t\t real_t a = atan2(y, x) + " << spin << " + " << twist << " * (xform->m_VariationWeights[" << varIndex << "] - r);\n" + << "\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (r * cos(a) + " << x << ");\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * (r * sin(a) - " << y << ");\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t r = 1.0 + " << space << " / (r + EPS6);\n" + << "\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (r * x + " << x << ");\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * (r * y - " << y << ");\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual bool SetParamVal(const char* name, T val) + { + if (!strcmp(name, "lazysusan_spin")) + { + m_Spin = Fabsmod(val / T(M_2PI)) * T(M_2PI); + Precalc(); + return true; + } + + return ParametricVariation::SetParamVal(name, val); + } + + virtual void Random(QTIsaac& rand) + { + m_X = 2 * rand.Frand11(); + m_Y = 2 * rand.Frand11(); + m_Spin = T(M_PI) * rand.Frand11(); + m_Space = 2 * rand.Frand11(); + m_Twist = 2 * rand.Frand11(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Spin, prefix + "lazysusan_spin", T(M_PI))); + m_Params.push_back(ParamWithName(&m_Space, prefix + "lazysusan_space")); + m_Params.push_back(ParamWithName(&m_Twist, prefix + "lazysusan_twist")); + m_Params.push_back(ParamWithName(&m_X, prefix + "lazysusan_x")); + m_Params.push_back(ParamWithName(&m_Y, prefix + "lazysusan_y")); + } + +private: + T m_Spin; + T m_Space; + T m_Twist; + T m_X; + T m_Y; +}; + +/// +/// Loonie. +/// +template +class EMBER_API LoonieVariation : public ParametricVariation +{ +public: + LoonieVariation(T weight = 1.0) : ParametricVariation("loonie", VAR_LOONIE, weight, true) + { + Init(); + } + + PARVARCOPY(LoonieVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + if (helper.m_PrecalcSumSquares < m_W2) + { + T r = m_Weight * sqrt(m_W2 / helper.m_PrecalcSumSquares - 1); + + helper.Out.x = r * helper.In.x; + helper.Out.y = r * helper.In.y; + } + else + { + helper.Out.x = m_Weight * helper.In.x; + helper.Out.y = m_Weight * helper.In.y; + } + + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string w2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tif (precalcSumSquares < " << w2 << ")\n" + << "\t\t{\n" + << "\t\t real_t r = xform->m_VariationWeights[" << varIndex << "] * sqrt(" << w2 << " / precalcSumSquares - 1.0);\n" + << "\t\t vOut.x = r * vIn.x;\n" + << "\t\t vOut.y = r * vIn.y;\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_W2 = SQR(m_Weight); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(true, &m_W2, prefix + "loonie_w2"));//Precalc. + } + +private: + T m_W2;//Precalc. +}; + +/// +/// Modulus. +/// +template +class EMBER_API ModulusVariation : public ParametricVariation +{ +public: + ModulusVariation(T weight = 1.0) : ParametricVariation("modulus", VAR_MODULUS, weight) + { + Init(); + } + + PARVARCOPY(ModulusVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + if (helper.In.x > m_X) + helper.Out.x = m_Weight * (-m_X + fmod(helper.In.x + m_X, m_XRange)); + else if (helper.In.x < -m_X) + helper.Out.x = m_Weight * ( m_X - fmod(m_X - helper.In.x, m_XRange)); + else + helper.Out.x = m_Weight * helper.In.x; + + if (helper.In.y > m_Y) + helper.Out.y = m_Weight * (-m_Y + fmod(helper.In.y + m_Y, m_YRange)); + else if (helper.In.y < -m_Y) + helper.Out.y = m_Weight * ( m_Y - fmod(m_Y - helper.In.y, m_YRange)); + else + helper.Out.y = m_Weight * helper.In.y; + + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string xr = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string yr = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tif (vIn.x > " << x << ")\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (-" << x << " + fmod(vIn.x + " << x << ", " << xr << "));\n" + << "\t\telse if (vIn.x < -" << x << ")\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * ( " << x << " - fmod(" << x << " - vIn.x, " << xr << "));\n" + << "\t\telse\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\n" + << "\t\tif (vIn.y > " << y << ")\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * (-" << y << " + fmod(vIn.y + " << y << ", " << yr << "));\n" + << "\t\telse if (vIn.y < -" << y << ")\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * ( " << y << " - fmod(" << y << " - vIn.y, " << yr << "));\n" + << "\t\telse\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_XRange = 2 * m_X; + m_YRange = 2 * m_Y; + } + + virtual void Random(QTIsaac& rand) + { + m_X = rand.Frand11(); + m_Y = rand.Frand11(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_X, prefix + "modulus_x", 1));//Params. + m_Params.push_back(ParamWithName(&m_Y, prefix + "modulus_y", 1)); + m_Params.push_back(ParamWithName(true, &m_XRange, prefix + "modulus_xrange"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_YRange, prefix + "modulus_yrange")); + } + +private: + T m_X;//Params. + T m_Y; + T m_XRange;//Precalc. + T m_YRange; +}; + +/// +/// Oscilloscope. +/// +template +class EMBER_API OscilloscopeVariation : public ParametricVariation +{ +public: + OscilloscopeVariation(T weight = 1.0) : ParametricVariation("oscilloscope", VAR_OSCILLOSCOPE, weight) + { + Init(); + } + + PARVARCOPY(OscilloscopeVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T t; + + if (m_Damping == 0.0) + t = m_Amplitude * cos(m_2PiFreq * helper.In.x) + m_Separation; + else + t = m_Amplitude * exp(-fabs(helper.In.x) * m_Damping) * cos(m_2PiFreq * helper.In.x) + m_Separation; + + if (fabs(helper.In.y) <= t) + { + helper.Out.x = m_Weight * helper.In.x; + helper.Out.y = -(m_Weight * helper.In.y); + } + else + { + helper.Out.x = m_Weight * helper.In.x; + helper.Out.y = m_Weight * helper.In.y; + } + + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string separation = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string frequency = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string amplitude = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string damping = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string tpf = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t t;\n" + << "\n" + << "\t\tif (" << damping << " == 0.0)\n" + << "\t\t t = " << amplitude << " * cos(" << tpf << " * vIn.x) + " << separation << ";\n" + << "\t\telse\n" + << "\t\t t = " << amplitude << " * exp(-fabs(vIn.x) * " << damping << ") * cos(" << tpf << " * vIn.x) + " << separation << ";\n" + << "\n" + << "\t\tif (fabs(vIn.y) <= t)\n" + << "\t\t{\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\t\t vOut.y = -(xform->m_VariationWeights[" << varIndex << "] * vIn.y);\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_2PiFreq = m_Frequency * T(M_2PI); + } + + virtual void Random(QTIsaac& rand) + { + m_Separation = 1 + rand.Frand11(); + m_Frequency = T(M_PI) * rand.Frand11(); + m_Amplitude = 1 + 2 * rand.Frand01(); + m_Damping = rand.Frand01(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Separation, prefix + "oscilloscope_separation", 1));//Params. + m_Params.push_back(ParamWithName(&m_Frequency, prefix + "oscilloscope_frequency", T(M_PI))); + m_Params.push_back(ParamWithName(&m_Amplitude, prefix + "oscilloscope_amplitude", 1)); + m_Params.push_back(ParamWithName(&m_Damping, prefix + "oscilloscope_damping")); + m_Params.push_back(ParamWithName(true, &m_2PiFreq, prefix + "oscilloscope_2pifreq"));//Precalc. + } + +private: + T m_Separation;//Params. + T m_Frequency; + T m_Amplitude; + T m_Damping; + T m_2PiFreq;//Precalc. +}; + +/// +/// Polar2. +/// +template +class EMBER_API Polar2Variation : public ParametricVariation +{ +public: + Polar2Variation(T weight = 1.0) : ParametricVariation("polar2", VAR_POLAR2, weight, true, false, false, true) + { + Init(); + } + + PARVARCOPY(Polar2Variation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + helper.Out.x = m_Vvar * helper.m_PrecalcAtanxy; + helper.Out.y = m_Vvar2 * log(helper.m_PrecalcSumSquares); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string vvar = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string vvar2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tvOut.x = " << vvar << " * precalcAtanxy;\n" + << "\t\tvOut.y = " << vvar2 << " * log(precalcSumSquares);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Vvar = m_Weight / T(M_PI); + m_Vvar2 = m_Vvar * T(0.5); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(true, &m_Vvar, prefix + "polar2_vvar"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Vvar2, prefix + "polar2_vvar2")); + } + +private: + T m_Vvar; + T m_Vvar2; +}; + +/// +/// Popcorn2. +/// +template +class EMBER_API Popcorn2Variation : public ParametricVariation +{ +public: + Popcorn2Variation(T weight = 1.0) : ParametricVariation("popcorn2", VAR_POPCORN2, weight) + { + Init(); + } + + PARVARCOPY(Popcorn2Variation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + helper.Out.x = m_Weight * (helper.In.x + m_X * sin(tan(helper.In.y * m_C))); + helper.Out.y = m_Weight * (helper.In.y + m_Y * sin(tan(helper.In.x * m_C))); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (vIn.x + " << x << " * sin(tan(vIn.y * " << c << ")));\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (vIn.y + " << y << " * sin(tan(vIn.x * " << c << ")));\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Random(QTIsaac& rand) + { + m_X = T(0.2) + rand.Frand01(); + m_Y = T(0.2) * rand.Frand01(); + m_C = 5 * rand.Frand01(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_X, prefix + "popcorn2_x", T(0.1))); + m_Params.push_back(ParamWithName(&m_Y, prefix + "popcorn2_y", T(0.1))); + m_Params.push_back(ParamWithName(&m_C, prefix + "popcorn2_c", 3)); + } + +private: + T m_X; + T m_Y; + T m_C; +}; + +/// +/// Scry. +/// Note that scry does not multiply by weight, but as the +/// values still approach 0 as the weight approaches 0, it +/// should be ok. +/// +template +class EMBER_API ScryVariation : public ParametricVariation +{ +public: + ScryVariation(T weight = 1.0) : ParametricVariation("scry", VAR_SCRY, weight, true, true) + { + Init(); + } + + PARVARCOPY(ScryVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T t = helper.m_PrecalcSumSquares; + T r = 1 / (helper.m_PrecalcSqrtSumSquares * (t + m_InvWeight)); + + helper.Out.x = helper.In.x * r; + helper.Out.y = helper.In.y * r; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string invWeight = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t t = precalcSumSquares;\n" + << "\t\treal_t r = 1.0 / (precalcSqrtSumSquares * (t + " << invWeight << "));\n" + << "\n" + << "\t\tvOut.x = vIn.x * r;\n" + << "\t\tvOut.y = vIn.y * r;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_InvWeight = 1 / (m_Weight + EPS6); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(true, &m_InvWeight, prefix + "scry_inv_weight"));//Precalcs only, no params. + } + +private: + T m_InvWeight;//Precalcs only, no params. +}; + +/// +/// Separation. +/// +template +class EMBER_API SeparationVariation : public ParametricVariation +{ +public: + SeparationVariation(T weight = 1.0) : ParametricVariation("separation", VAR_SEPARATION, weight) + { + Init(); + } + + PARVARCOPY(SeparationVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + if (helper.In.x > 0.0) + helper.Out.x = m_Weight * (sqrt(SQR(helper.In.x) + m_XX) - helper.In.x * m_XInside); + else + helper.Out.x = -(m_Weight * (sqrt(SQR(helper.In.x) + m_XX) + helper.In.x * m_XInside)); + + if (helper.In.y > 0.0) + helper.Out.y = m_Weight * (sqrt(SQR(helper.In.y) + m_YY) - helper.In.y * m_YInside); + else + helper.Out.y = -(m_Weight * (sqrt(SQR(helper.In.y) + m_YY) + helper.In.y * m_YInside)); + + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string xInside = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string yInside = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string xx = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string yy = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tif (vIn.x > 0.0)\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (sqrt(vIn.x * vIn.x + " << xx << ") - vIn.x * " << xInside << ");\n" + << "\t\telse\n" + << "\t\t vOut.x = -(xform->m_VariationWeights[" << varIndex << "] * (sqrt(vIn.x * vIn.x + " << xx << ") + vIn.x * " << xInside << "));\n" + << "\n" + << "\t\tif (vIn.y > 0.0)\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * (sqrt(vIn.y * vIn.y + " << yy << ") - vIn.y * " << yInside << ");\n" + << "\t\telse\n" + << "\t\t vOut.y = -(xform->m_VariationWeights[" << varIndex << "] * (sqrt(vIn.y * vIn.y + " << yy << ") + vIn.y * " << yInside << "));\n" + << "\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_XX = SQR(m_X); + m_YY = SQR(m_Y); + } + + virtual void Random(QTIsaac& rand) + { + m_X = 1 + rand.Frand11(); + m_XInside = 1 + rand.Frand11(); + m_Y = rand.Frand11(); + m_YInside = rand.Frand11(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_X, prefix + "separation_x", 1));//Params. + m_Params.push_back(ParamWithName(&m_XInside, prefix + "separation_xinside")); + m_Params.push_back(ParamWithName(&m_Y, prefix + "separation_y", 1)); + m_Params.push_back(ParamWithName(&m_YInside, prefix + "separation_yinside")); + m_Params.push_back(ParamWithName(true, &m_XX, prefix + "separation_xx"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_YY, prefix + "separation_yy")); + } + +private: + T m_X;//Params. + T m_XInside; + T m_Y; + T m_YInside; + T m_XX;//Precalc. + T m_YY; +}; + +/// +/// Split. +/// +template +class EMBER_API SplitVariation : public ParametricVariation +{ +public: + SplitVariation(T weight = 1.0) : ParametricVariation("split", VAR_SPLIT, weight) + { + Init(); + } + + PARVARCOPY(SplitVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + if (cos(helper.In.y * m_YAng) >= 0) + helper.Out.x = m_Weight * helper.In.x; + else + helper.Out.x = -(m_Weight * helper.In.x); + + if (cos(helper.In.x * m_XAng) >= 0) + helper.Out.y = m_Weight * helper.In.y; + else + helper.Out.y = -(m_Weight * helper.In.y); + + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string xSize = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ySize = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string xAng = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string yAng = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tif (cos(vIn.y * " << yAng << ") >= 0)\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\t\telse\n" + << "\t\t vOut.x = -(xform->m_VariationWeights[" << varIndex << "] * vIn.x);\n" + << "\n" + << "\t\tif (cos(vIn.x * " << xAng << ") >= 0)\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\t\telse\n" + << "\t\t vOut.y = -(xform->m_VariationWeights[" << varIndex << "] * vIn.y);\n" + << "\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_XAng = T(M_PI) * m_XSize; + m_YAng = T(M_PI) * m_YSize; + } + + virtual void Random(QTIsaac& rand) + { + m_XSize = rand.Frand11(); + m_YSize = rand.Frand11(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_XSize, prefix + "split_xsize", T(0.5)));//Params. + m_Params.push_back(ParamWithName(&m_YSize, prefix + "split_ysize", T(0.5))); + m_Params.push_back(ParamWithName(true, &m_XAng, prefix + "split_xang"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_YAng, prefix + "split_yang")); + } + +private: + T m_XSize;//Params. + T m_YSize; + T m_XAng;//Precalc. + T m_YAng; +}; + +/// +/// Splits. +/// +template +class EMBER_API SplitsVariation : public ParametricVariation +{ +public: + SplitsVariation(T weight = 1.0) : ParametricVariation("splits", VAR_SPLITS, weight) + { + Init(); + } + + PARVARCOPY(SplitsVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + if (helper.In.x >= 0) + helper.Out.x = m_Weight * (helper.In.x + m_X); + else + helper.Out.x = m_Weight * (helper.In.x - m_X); + + if (helper.In.y >= 0) + helper.Out.y = m_Weight * (helper.In.y + m_Y); + else + helper.Out.y = m_Weight * (helper.In.y - m_Y); + + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tif (vIn.x >= 0)\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (vIn.x + " << x << ");\n" + << "\t\telse\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (vIn.x - " << x << ");\n" + << "\n" + << "\t\tif (vIn.y >= 0)\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * (vIn.y + " << y << ");\n" + << "\t\telse\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * (vIn.y - " << y << ");\n" + << "\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Random(QTIsaac& rand) + { + m_X = rand.Frand11(); + m_Y = rand.Frand11(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_X, prefix + "splits_x")); + m_Params.push_back(ParamWithName(&m_Y, prefix + "splits_y")); + } + +private: + T m_X; + T m_Y; +}; + +/// +/// Stripes. +/// +template +class EMBER_API StripesVariation : public ParametricVariation +{ +public: + StripesVariation(T weight = 1.0) : ParametricVariation("stripes", VAR_STRIPES, weight) + { + Init(); + } + + PARVARCOPY(StripesVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T roundx = (T)Floor(helper.In.x + T(0.5)); + T offsetx = helper.In.x - roundx; + + helper.Out.x = m_Weight * (offsetx * (1 - m_Space) + roundx); + helper.Out.y = m_Weight * (helper.In.y + offsetx * offsetx * m_Warp); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string space = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string warp = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t roundx = floor(vIn.x + 0.5);\n" + << "\t\treal_t offsetx = vIn.x - roundx;\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (offsetx * (1.0 - " << space << ") + roundx);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (vIn.y + offsetx * offsetx * " << warp << ");\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Random(QTIsaac& rand) + { + m_Params[0].Set(rand.Frand01());//Space. + m_Params[1].Set(5 * rand.Frand01());//Warp. + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Space, prefix + "stripes_space", T(0.5), REAL, T(0.5), 5)); + m_Params.push_back(ParamWithName(&m_Warp, prefix + "stripes_warp")); + } + +private: + T m_Space; + T m_Warp; +}; + +/// +/// Wedge. +/// +template +class EMBER_API WedgeVariation : public ParametricVariation +{ +public: + WedgeVariation(T weight = 1.0) : ParametricVariation("wedge", VAR_WEDGE, weight, true, true, false, false, true) + { + Init(); + } + + PARVARCOPY(WedgeVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r = helper.m_PrecalcSqrtSumSquares; + T a = helper.m_PrecalcAtanyx + m_Swirl * r; + T c = (T)Floor((m_Count * a + T(M_PI)) * T(M_1_PI) * T(0.5)); + + a = a * m_CompFac + c * m_Angle; + r = m_Weight * (r + m_Hole); + helper.Out.x = r * cos(a); + helper.Out.y = r * sin(a); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string angle = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string hole = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string count = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string swirl = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string compFac = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t r = precalcSqrtSumSquares;\n" + << "\t\treal_t a = precalcAtanyx + " << swirl << " * r;\n" + << "\t\treal_t c = floor((" << count << " * a + M_PI) * M_1_PI * 0.5);\n" + << "\n" + << "\t\ta = a * " << compFac << " + c * " << angle << ";\n" + << "\t\tr = xform->m_VariationWeights[" << varIndex << "] * (r + " << hole << ");\n" + << "\t\tvOut.x = r * cos(a);\n" + << "\t\tvOut.y = r * sin(a);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_CompFac = 1 - m_Angle * m_Count * T(M_1_PI) * T(0.5); + } + + virtual void Random(QTIsaac& rand) + { + m_Angle = T(M_PI) * rand.Frand01(); + m_Hole = T(0.5) * rand.Frand11(); + m_Count = (T)Floor(5 * rand.Frand01()) + 1; + m_Swirl = rand.Frand01(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Angle, prefix + "wedge_angle", T(M_PI_2)));//Params. + m_Params.push_back(ParamWithName(&m_Hole, prefix + "wedge_hole")); + m_Params.push_back(ParamWithName(&m_Count, prefix + "wedge_count", 2, INTEGER, 1)); + m_Params.push_back(ParamWithName(&m_Swirl, prefix + "wedge_swirl")); + m_Params.push_back(ParamWithName(true, &m_CompFac, prefix + "wedge_compfac"));//Precalc. + } + +private: + T m_Angle;//Params. + T m_Hole; + T m_Count; + T m_Swirl; + T m_CompFac;//Precalc. +}; + +/// +/// Wedge julia. +/// +template +class EMBER_API WedgeJuliaVariation : public ParametricVariation +{ +public: + WedgeJuliaVariation(T weight = 1.0) : ParametricVariation("wedge_julia", VAR_WEDGE_JULIA, weight, true, false, false, false, true) + { + Init(); + } + + PARVARCOPY(WedgeJuliaVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r = m_Weight * pow(helper.m_PrecalcSumSquares, m_Cn); + int tRand = (int)(m_Rn * rand.Frand01()); + T a = (helper.m_PrecalcAtanyx + M_2PI * tRand) / m_Power; + T c = (T)Floor((m_Count * a + T(M_PI)) * T(M_1_PI) * T(0.5)); + + a = a * m_Cf + c * m_Angle; + helper.Out.x = r * cos(a); + helper.Out.y = r * sin(a); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string angle = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Params. + string count = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string power = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string dist = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string rn = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Precalc. + string cn = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cf = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * pow(precalcSumSquares, " << cn << ");\n" + << "\t\tint tRand = (int)(" << rn << " * MwcNext01(mwc));\n" + << "\t\treal_t a = (precalcAtanyx + M_2PI * tRand) / " << power << ";\n" + << "\t\treal_t c = floor((" << count << " * a + M_PI) * M_1_PI * 0.5);\n" + << "\n" + << "\t\ta = a * " << cf << " + c * " << angle << ";\n" + << "\t\tvOut.x = r * cos(a);\n" + << "\t\tvOut.y = r * sin(a);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Cf = 1 - m_Angle * m_Count * T(M_1_PI) * T(0.5); + m_Rn = fabs(m_Power); + m_Cn = m_Dist / m_Power / 2; + } + + virtual void Random(QTIsaac& rand) + { + m_Power = (T)(int)(5 * rand.Frand01() + 2); + m_Dist = 1; + m_Count = (T)(int)(3 * rand.Frand01() + 1); + m_Angle = T(M_PI) * rand.Frand01(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Angle, prefix + "wedge_julia_angle"));//Params. + m_Params.push_back(ParamWithName(&m_Count, prefix + "wedge_julia_count", 1)); + m_Params.push_back(ParamWithName(&m_Power, prefix + "wedge_julia_power", 1)); + m_Params.push_back(ParamWithName(&m_Dist, prefix + "wedge_julia_dist")); + m_Params.push_back(ParamWithName(true, &m_Rn, prefix + "wedge_julia_rn"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Cn, prefix + "wedge_julia_cn")); + m_Params.push_back(ParamWithName(true, &m_Cf, prefix + "wedge_julia_cf")); + } + +private: + T m_Angle;//Params. + T m_Count; + T m_Power; + T m_Dist; + T m_Rn;//Precalc. + T m_Cn; + T m_Cf; +}; + +/// +/// Wedge sph. +/// +template +class EMBER_API WedgeSphVariation : public ParametricVariation +{ +public: + WedgeSphVariation(T weight = 1.0) : ParametricVariation("wedge_sph", VAR_WEDGE_SPH, weight, true, true, false, false, true) + { + Init(); + } + + PARVARCOPY(WedgeSphVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r = 1 / (helper.m_PrecalcSqrtSumSquares + T(EPS)); + T a = helper.m_PrecalcAtanyx + m_Swirl * r; + T c = (T)Floor((m_Count * a + T(M_PI)) * T(M_1_PI) * T(0.5)); + T compFac = 1 - m_Angle * m_Count * T(M_1_PI) * T(0.5); + + a = a * compFac + c * m_Angle; + r = m_Weight * (r + m_Hole); + helper.Out.x = r * cos(a); + helper.Out.y = r * sin(a); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string angle = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string count = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string hole = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string swirl = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t r = 1.0 / (precalcSqrtSumSquares + EPS);\n" + << "\t\treal_t a = precalcAtanyx + " << swirl << " * r;\n" + << "\t\treal_t c = floor((" << count << " * a + M_PI) * M_1_PI * 0.5);\n" + << "\t\treal_t compFac = 1 - " << angle << " * " << count << " * M_1_PI * 0.5;\n" + << "\n" + << "\t\ta = a * compFac + c * " << angle << ";\n" + << "\t\tr = xform->m_VariationWeights[" << varIndex << "] * (r + " << hole << ");\n" + << "\t\tvOut.x = r * cos(a);\n" + << "\t\tvOut.y = r * sin(a);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Random(QTIsaac& rand) + { + m_Angle = T(M_PI) * rand.Frand01(); + m_Count = (T)Floor(5 * rand.Frand01()) + 1; + m_Hole = T(0.5) * rand.Frand11(); + m_Swirl = rand.Frand01(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Angle, prefix + "wedge_sph_angle")); + m_Params.push_back(ParamWithName(&m_Count, prefix + "wedge_sph_hole", 1)); + m_Params.push_back(ParamWithName(&m_Hole, prefix + "wedge_sph_count")); + m_Params.push_back(ParamWithName(&m_Swirl, prefix + "wedge_sph_swirl")); + } + +private: + T m_Angle; + T m_Count; + T m_Hole; + T m_Swirl; +}; + +/// +/// Whorl. +/// +template +class EMBER_API WhorlVariation : public ParametricVariation +{ +public: + WhorlVariation(T weight = 1.0) : ParametricVariation("whorl", VAR_WHORL, weight, true, true, false, false, true) + { + Init(); + } + + PARVARCOPY(WhorlVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T a, r = helper.m_PrecalcSqrtSumSquares; + + if (r < m_Weight) + a = helper.m_PrecalcAtanyx + m_Inside / (m_Weight - r); + else + a = helper.m_PrecalcAtanyx + m_Outside / (m_Weight - r); + + helper.Out.x = m_Weight * r * cos(a); + helper.Out.y = m_Weight * r * sin(a); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string inside = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string outside = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t a;\n" + << "\t\treal_t r = precalcSqrtSumSquares;\n" + << "\n" + << "\t\tif (r < xform->m_VariationWeights[" << varIndex << "])\n" + << "\t\t a = precalcAtanyx + " << inside << " / (xform->m_VariationWeights[" << varIndex << "] - r);\n" + << "\t\telse\n" + << "\t\t a = precalcAtanyx + " << outside << " / (xform->m_VariationWeights[" << varIndex << "] - r);\n" + << "\n" + << "\t\tvOut.x = (xform->m_VariationWeights[" << varIndex << "] * r * cos(a));\n" + << "\t\tvOut.y = (xform->m_VariationWeights[" << varIndex << "] * r * sin(a));\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Random(QTIsaac& rand) + { + m_Inside = rand.Frand01(); + m_Outside = rand.Frand01(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Inside, prefix + "whorl_inside", 1)); + m_Params.push_back(ParamWithName(&m_Outside, prefix + "whorl_outside", 1)); + } + +private: + T m_Inside; + T m_Outside; +}; + +/// +/// Waves. +/// +template +class EMBER_API Waves2Variation : public ParametricVariation +{ +public: + Waves2Variation(T weight = 1.0) : ParametricVariation("waves2", VAR_WAVES2, weight, true, true) + { + Init(); + } + + PARVARCOPY(Waves2Variation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + helper.Out.x = m_Weight * (helper.In.x + m_ScaleX * sin(helper.In.y * m_FreqX)); + helper.Out.y = m_Weight * (helper.In.y + m_ScaleY * sin(helper.In.x * m_FreqY)); + helper.Out.z = m_Weight * (helper.In.z + m_ScaleZ * sin(helper.m_PrecalcSqrtSumSquares * m_FreqZ)); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string freqX = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string scaleX = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string freqY = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string scaleY = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string freqZ = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string scaleZ = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (vIn.x + " << scaleX << " * sin(vIn.y * " << freqX << "));\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (vIn.y + " << scaleY << " * sin(vIn.x * " << freqY << "));\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * (vIn.z + " << scaleZ << " * sin(precalcSqrtSumSquares * " << freqZ << "));\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Random(QTIsaac& rand) + { + m_FreqX = 4 * rand.Frand01(); + m_ScaleX = T(0.5) + rand.Frand01(); + m_FreqY = 4 * rand.Frand01(); + m_ScaleY = T(0.5) + rand.Frand01(); + m_FreqZ = 0; + m_ScaleZ = 0; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_FreqX, prefix + "waves2_freqx", 2)); + m_Params.push_back(ParamWithName(&m_ScaleX, prefix + "waves2_scalex")); + m_Params.push_back(ParamWithName(&m_FreqY, prefix + "waves2_freqy", 2)); + m_Params.push_back(ParamWithName(&m_ScaleY, prefix + "waves2_scaley")); + m_Params.push_back(ParamWithName(&m_FreqZ, prefix + "waves2_freqz")); + m_Params.push_back(ParamWithName(&m_ScaleZ, prefix + "waves2_scalez")); + } + +private: + T m_FreqX; + T m_ScaleX; + T m_FreqY; + T m_ScaleY; + T m_FreqZ; + T m_ScaleZ; +}; + +/// +/// Exp. +/// +template +class EMBER_API ExpVariation : public Variation +{ +public: + ExpVariation(T weight = 1.0) : Variation("exp", VAR_EXP, weight) { } + + VARCOPY(ExpVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T expe = m_Weight * exp(helper.In.x); + + helper.Out.x = expe * cos(helper.In.y); + helper.Out.y = expe * sin(helper.In.y); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t expe = xform->m_VariationWeights[" << varIndex << "] * exp(vIn.x);\n" + << "\n" + << "\t\tvOut.x = expe * cos(vIn.y);\n" + << "\t\tvOut.y = expe * sin(vIn.y);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Log. +/// +template +class EMBER_API LogVariation : public ParametricVariation +{ +public: + LogVariation(T weight = 1.0) : ParametricVariation("log", VAR_LOG, weight, true, false, false, false, true) + { + Init(); + } + + PARVARCOPY(LogVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + helper.Out.x = m_Weight * log(helper.m_PrecalcSumSquares) * m_Denom; + helper.Out.y = m_Weight * helper.m_PrecalcAtanyx; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string base = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string denom = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * log(precalcSumSquares) * " << denom << ";\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * precalcAtanyx;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Denom = T(0.5) / log(m_Base); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Base, prefix + "log_base", T(M_E), REAL, EPS6, TMAX)); + m_Params.push_back(ParamWithName(true, &m_Denom, prefix + "log_denom"));//Precalc. + } + +private: + T m_Base; + T m_Denom;//Precalc. +}; + +/// +/// Sine. +/// +template +class EMBER_API SinVariation : public Variation +{ +public: + SinVariation(T weight = 1.0) : Variation("sin", VAR_SIN, weight) { } + + VARCOPY(SinVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + helper.Out.x = m_Weight * sin(helper.In.x) * cosh(helper.In.y); + helper.Out.y = m_Weight * cos(helper.In.x) * sinh(helper.In.y); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * sin(vIn.x) * cosh(vIn.y);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * cos(vIn.x) * sinh(vIn.y);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Cosine. +/// +template +class EMBER_API CosVariation : public Variation +{ +public: + CosVariation(T weight = 1.0) : Variation("cos", VAR_COS, weight) { } + + VARCOPY(CosVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + //clamp fabs x and y to 7.104760e+002 for cosh, and |x| 7.104760e+002 for sinh + helper.Out.x = m_Weight * cos(helper.In.x) * cosh(helper.In.y); + helper.Out.y = -(m_Weight * sin(helper.In.x) * sinh(helper.In.y)); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * cos(vIn.x) * cosh(vIn.y);\n" + << "\t\tvOut.y = -(xform->m_VariationWeights[" << varIndex << "] * sin(vIn.x) * sinh(vIn.y));\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Tangent. +/// +template +class EMBER_API TanVariation : public Variation +{ +public: + TanVariation(T weight = 1.0) : Variation("tan", VAR_TAN, weight) { } + + VARCOPY(TanVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T tansin, tancos, tansinh, tancosh, tanden; + + sincos(2 * helper.In.x, &tansin, &tancos); + tansinh = sinh(2 * helper.In.y); + tancosh = cosh(2 * helper.In.y); + tanden = 1 / (tancos + tancosh); + helper.Out.x = m_Weight * tanden * tansin; + helper.Out.y = m_Weight * tanden * tansinh; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t tansin = sin(2.0 * vIn.x);\n" + << "\t\treal_t tancos = cos(2.0 * vIn.x);\n" + << "\t\treal_t tansinh = sinh(2.0 * vIn.y);\n" + << "\t\treal_t tancosh = cosh(2.0 * vIn.y);\n" + << "\t\treal_t tanden = 1.0 / (tancos + tancosh);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * tanden * tansin;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * tanden * tansinh;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Sec. +/// +template +class EMBER_API SecVariation : public Variation +{ +public: + SecVariation(T weight = 1.0) : Variation("sec", VAR_SEC, weight) { } + + VARCOPY(SecVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T secsin, seccos, secsinh, seccosh, secden; + + sincos(helper.In.x, &secsin, &seccos); + secsinh = sinh(helper.In.y); + seccosh = cosh(helper.In.y); + secden = 2 / (cos(2 * helper.In.x) + cosh(2 * helper.In.y)); + helper.Out.x = m_Weight * secden * seccos * seccosh; + helper.Out.y = m_Weight * secden * secsin * secsinh; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t secsin = sin(vIn.x);\n" + << "\t\treal_t seccos = cos(vIn.x);\n" + << "\t\treal_t secsinh = sinh(vIn.y);\n" + << "\t\treal_t seccosh = cosh(vIn.y);\n" + << "\t\treal_t secden = 2.0 / (cos(2.0 * vIn.x) + cosh(2.0 * vIn.y));\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * secden * seccos * seccosh;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * secden * secsin * secsinh;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Cosecant. +/// +template +class EMBER_API CscVariation : public Variation +{ +public: + CscVariation(T weight = 1.0) : Variation("csc", VAR_CSC, weight) { } + + VARCOPY(CscVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T cscsin, csccos, cscsinh, csccosh, cscden; + + sincos(helper.In.x, &cscsin, &csccos); + cscsinh = sinh(helper.In.y); + csccosh = cosh(helper.In.y); + cscden = 2 / (cosh(2 * helper.In.y) - cos(2 * helper.In.x)); + helper.Out.x = m_Weight * cscden * cscsin * csccosh; + helper.Out.y = -(m_Weight * cscden * csccos * cscsinh); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t cscsin = sin(vIn.x);\n" + << "\t\treal_t csccos = cos(vIn.x);\n" + << "\t\treal_t cscsinh = sinh(vIn.y);\n" + << "\t\treal_t csccosh = cosh(vIn.y);\n" + << "\t\treal_t cscden = 2.0 / (cosh(2.0 * vIn.y) - cos(2.0 * vIn.x));\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * cscden * cscsin * csccosh;\n" + << "\t\tvOut.y = -(xform->m_VariationWeights[" << varIndex << "] * cscden * csccos * cscsinh);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Cotangent. +/// +template +class EMBER_API CotVariation : public Variation +{ +public: + CotVariation(T weight = 1.0) : Variation("cot", VAR_COT, weight) { } + + VARCOPY(CotVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T cotsin, cotcos, cotsinh, cotcosh, cotden; + + sincos(2 * helper.In.x, &cotsin, &cotcos); + cotsinh = sinh(2 * helper.In.y); + cotcosh = cosh(2 * helper.In.y); + cotden = 1 / (cotcosh - cotcos); + helper.Out.x = m_Weight * cotden * cotsin; + helper.Out.y = m_Weight * cotden * -1 * cotsinh; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t cotsin = sin(2.0 * vIn.x);\n" + << "\t\treal_t cotcos = cos(2.0 * vIn.x);\n" + << "\t\treal_t cotsinh = sinh(2.0 * vIn.y);\n" + << "\t\treal_t cotcosh = cosh(2.0 * vIn.y);\n" + << "\t\treal_t cotden = 1.0 / (cotcosh - cotcos);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * cotden * cotsin;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * cotden * -1 * cotsinh;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Sinh. +/// +template +class EMBER_API SinhVariation : public Variation +{ +public: + SinhVariation(T weight = 1.0) : Variation("sinh", VAR_SINH, weight) { } + + VARCOPY(SinhVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T sinhsin, sinhcos, sinhsinh, sinhcosh; + + sincos(helper.In.y, &sinhsin, &sinhcos); + sinhsinh = sinh(helper.In.x); + sinhcosh = cosh(helper.In.x); + helper.Out.x = m_Weight * sinhsinh * sinhcos; + helper.Out.y = m_Weight * sinhcosh * sinhsin; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t sinhsin = sin(vIn.y);\n" + << "\t\treal_t sinhcos = cos(vIn.y);\n" + << "\t\treal_t sinhsinh = sinh(vIn.x);\n" + << "\t\treal_t sinhcosh = cosh(vIn.x);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * sinhsinh * sinhcos;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * sinhcosh * sinhsin;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Cosh. +/// +template +class EMBER_API CoshVariation : public Variation +{ +public: + CoshVariation(T weight = 1.0) : Variation("cosh", VAR_COSH, weight) { } + + VARCOPY(CoshVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T coshsin,coshcos,coshsinh,coshcosh; + + sincos(helper.In.y, &coshsin, &coshcos); + coshsinh = sinh(helper.In.x); + coshcosh = cosh(helper.In.x); + helper.Out.x = m_Weight * coshcosh * coshcos; + helper.Out.y = m_Weight * coshsinh * coshsin; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t coshsin = sin(vIn.y);\n" + << "\t\treal_t coshcos = cos(vIn.y);\n" + << "\t\treal_t coshsinh = sinh(vIn.x);\n" + << "\t\treal_t coshcosh = cosh(vIn.x);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * coshcosh * coshcos;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * coshsinh * coshsin;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Tanh. +/// +template +class EMBER_API TanhVariation : public Variation +{ +public: + TanhVariation(T weight = 1.0) : Variation("tanh", VAR_TANH, weight) { } + + VARCOPY(TanhVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T tanhsin, tanhcos, tanhsinh, tanhcosh, tanhden; + + sincos(2 * helper.In.y, &tanhsin, &tanhcos); + tanhsinh = sinh(2 * helper.In.x); + tanhcosh = cosh(2 * helper.In.x); + tanhden = 1 / (tanhcos + tanhcosh); + helper.Out.x = m_Weight * tanhden * tanhsinh; + helper.Out.y = m_Weight * tanhden * tanhsin; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t tanhsin = sin(2.0 * vIn.y);\n" + << "\t\treal_t tanhcos = cos(2.0 * vIn.y);\n" + << "\t\treal_t tanhsinh = sinh(2.0 * vIn.x);\n" + << "\t\treal_t tanhcosh = cosh(2.0 * vIn.x);\n" + << "\t\treal_t tanhden = 1.0 / (tanhcos + tanhcosh);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * tanhden * tanhsinh;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * tanhden * tanhsin;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Sech +/// +template +class EMBER_API SechVariation : public Variation +{ +public: + SechVariation(T weight = 1.0) : Variation("sech", VAR_SECH, weight) { } + + VARCOPY(SechVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T sechsin, sechcos, sechsinh, sechcosh, sechden; + + sincos(helper.In.y, &sechsin, &sechcos); + sechsinh = sinh(helper.In.x); + sechcosh = cosh(helper.In.x); + sechden = 2 / (cos(2 * helper.In.y) + cosh(2 * helper.In.x)); + helper.Out.x = m_Weight * sechden * sechcos * sechcosh; + helper.Out.y = -(m_Weight * sechden * sechsin * sechsinh); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t sechsin = sin(vIn.y);\n" + << "\t\treal_t sechcos = cos(vIn.y);\n" + << "\t\treal_t sechsinh = sinh(vIn.x);\n" + << "\t\treal_t sechcosh = cosh(vIn.x);\n" + << "\t\treal_t sechden = 2.0 / (cos(2.0 * vIn.y) + cosh(2.0 * vIn.x));\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * sechden * sechcos * sechcosh;\n" + << "\t\tvOut.y = -(xform->m_VariationWeights[" << varIndex << "] * sechden * sechsin * sechsinh);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Csch. +/// +template +class EMBER_API CschVariation : public Variation +{ +public: + CschVariation(T weight = 1.0) : Variation("csch", VAR_CSCH, weight) { } + + VARCOPY(CschVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T cschsin, cschcos, cschsinh, cschcosh, cschden; + + sincos(helper.In.y, &cschsin, &cschcos); + cschsinh = sinh(helper.In.x); + cschcosh = cosh(helper.In.x); + cschden = 2 / (cosh(2 * helper.In.x) - cos(2 * helper.In.y)); + helper.Out.x = m_Weight * cschden * cschsinh * cschcos; + helper.Out.y = -(m_Weight * cschden * cschcosh * cschsin); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t cschsin = sin(vIn.y);\n" + << "\t\treal_t cschcos = cos(vIn.y);\n" + << "\t\treal_t cschsinh = sinh(vIn.x);\n" + << "\t\treal_t cschcosh = cosh(vIn.x);\n" + << "\t\treal_t cschden = 2.0 / (cosh(2.0 * vIn.x) - cos(2.0 * vIn.y));\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * cschden * cschsinh * cschcos;\n" + << "\t\tvOut.y = -(xform->m_VariationWeights[" << varIndex << "] * cschden * cschcosh * cschsin);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Coth. +/// +template +class EMBER_API CothVariation : public Variation +{ +public: + CothVariation(T weight = 1.0) : Variation("coth", VAR_COTH, weight) { } + + VARCOPY(CothVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T cothsin, cothcos, cothsinh, cothcosh, cothden; + + sincos(2 * helper.In.y, &cothsin, &cothcos); + cothsinh = sinh(2 * helper.In.x); + cothcosh = cosh(2 * helper.In.x); + cothden = 1 / (cothcosh - cothcos); + helper.Out.x = m_Weight * cothden * cothsinh; + helper.Out.y = m_Weight * cothden * cothsin; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t cothsin = sin(2.0 * vIn.y);\n" + << "\t\treal_t cothcos = cos(2.0 * vIn.y);\n" + << "\t\treal_t cothsinh = sinh(2.0 * vIn.x);\n" + << "\t\treal_t cothcosh = cosh(2.0 * vIn.x);\n" + << "\t\treal_t cothden = 1.0 / (cothcosh - cothcos);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * cothden * cothsinh;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * cothden * cothsin;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Auger. +/// +template +class EMBER_API AugerVariation : public ParametricVariation +{ +public: + AugerVariation(T weight = 1.0) : ParametricVariation("auger", VAR_AUGER, weight) + { + Init(); + } + + PARVARCOPY(AugerVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T s = sin(m_Freq * helper.In.x); + T t = sin(m_Freq * helper.In.y); + T dy = helper.In.y + m_AugerWeight * (m_Scale * s / 2 + fabs(helper.In.y) * s); + T dx = helper.In.x + m_AugerWeight * (m_Scale * t / 2 + fabs(helper.In.x) * t); + + helper.Out.x = m_Weight * (helper.In.x + m_Symmetry * (dx - helper.In.x)); + helper.Out.y = m_Weight * dy; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string symmetry = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string augerWeight = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string freq = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string scale = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t s = sin(" << freq << " * vIn.x);\n" + << "\t\treal_t t = sin(" << freq << " * vIn.y);\n" + << "\t\treal_t dy = vIn.y + " << augerWeight << " * (" << scale << " * s / 2.0 + fabs(vIn.y) * s);\n" + << "\t\treal_t dx = vIn.x + " << augerWeight << " * (" << scale << " * t / 2.0 + fabs(vIn.x) * t);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (vIn.x + " << symmetry << " * (dx - vIn.x));\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * dy;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Random(QTIsaac& rand) + { + m_Symmetry = 0; + m_AugerWeight = T(0.5) + rand.Frand01() / 2; + m_Freq = (T)Floor(5 * rand.Frand01()) + 1; + m_Scale = rand.Frand01(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Symmetry, prefix + "auger_sym")); + m_Params.push_back(ParamWithName(&m_AugerWeight, prefix + "auger_weight", T(0.5))); + m_Params.push_back(ParamWithName(&m_Freq, prefix + "auger_freq", 5)); + m_Params.push_back(ParamWithName(&m_Scale, prefix + "auger_scale", T(0.1))); + } + +private: + T m_Symmetry; + T m_AugerWeight; + T m_Freq; + T m_Scale; +}; + +/// +/// Flux. +/// +template +class EMBER_API FluxVariation : public ParametricVariation +{ +public: + FluxVariation(T weight = 1.0) : ParametricVariation("flux", VAR_FLUX, weight) + { + Init(); + } + + PARVARCOPY(FluxVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T xpw = helper.In.x + m_Weight; + T xmw = helper.In.x - m_Weight; + T yy = SQR(helper.In.y); + T frac = sqrt(yy + SQR(xmw)); + + if (frac == 0) + frac = 1; + + T avgr = m_Weight * (m_Spr * sqrt(sqrt(yy + SQR(xpw)) / frac)); + T avga = (atan2(helper.In.y, xmw) - atan2(helper.In.y, xpw)) * T(0.5); + + helper.Out.x = avgr * cos(avga); + helper.Out.y = avgr * sin(avga); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string spread = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string spr = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t xpw = vIn.x + xform->m_VariationWeights[" << varIndex << "];\n" + << "\t\treal_t xmw = vIn.x - xform->m_VariationWeights[" << varIndex << "];\n" + << "\t\treal_t yy = SQR(vIn.y);\n" + << "\t\treal_t frac = sqrt(yy + SQR(xmw));\n" + << "\n" + << "\t\tif (frac == 0.0)\n" + << "\t\t frac = 1.0;\n" + << "\n" + << "\t\treal_t avgr = xform->m_VariationWeights[" << varIndex << "] * (" << spr << " * sqrt(sqrt(yy + SQR(xpw)) / frac));\n" + << "\t\treal_t avga = (atan2(vIn.y, xmw) - atan2(vIn.y, xpw)) * 0.5;\n" + << "\n" + << "\t\tvOut.x = avgr * cos(avga);\n" + << "\t\tvOut.y = avgr * sin(avga);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Spr = 2 + m_Spread; + } + + virtual void Random(QTIsaac& rand) + { + m_Spread = T(0.5) + rand.Frand01() / 2; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Spread, prefix + "flux_spread"));//Params. + m_Params.push_back(ParamWithName(true, &m_Spr, prefix + "flux_spr"));//Precalc. + } + +private: + T m_Spread;//Params. + T m_Spr;//Precalc. +}; + +MAKEPREPOSTVAR(Linear, linear, LINEAR) +MAKEPREPOSTVAR(Sinusoidal, sinusoidal, SINUSOIDAL) +MAKEPREPOSTVAR(Spherical, spherical, SPHERICAL) +MAKEPREPOSTVAR(Swirl, swirl, SWIRL) +MAKEPREPOSTVAR(Horseshoe, horseshoe, HORSESHOE) +MAKEPREPOSTVAR(Polar, polar, POLAR) +MAKEPREPOSTVAR(Handkerchief, handkerchief, HANDKERCHIEF) +MAKEPREPOSTVAR(Heart, heart, HEART) +MAKEPREPOSTPARVAR(Disc, disc, DISC) +MAKEPREPOSTVAR(Spiral, spiral, SPIRAL) +MAKEPREPOSTVAR(Hyperbolic, hyperbolic, HYPERBOLIC) +MAKEPREPOSTVAR(Diamond, diamond, DIAMOND) +MAKEPREPOSTVAR(Ex, ex, EX) +MAKEPREPOSTVAR(Julia, julia, JULIA) +MAKEPREPOSTVAR(Bent, bent, BENT) +MAKEPREPOSTPARVAR(Waves, waves, WAVES) +MAKEPREPOSTVAR(Fisheye, fisheye, FISHEYE) +MAKEPREPOSTVAR(Popcorn, popcorn, POPCORN) +MAKEPREPOSTVAR(Exponential, exponential, EXPONENTIAL) +MAKEPREPOSTVAR(Power, power, POWER) +MAKEPREPOSTVAR(Cosine, cosine, COSINE) +MAKEPREPOSTVAR(Rings, rings, RINGS) +MAKEPREPOSTVAR(Fan, fan, FAN) +MAKEPREPOSTPARVAR(Blob, blob, BLOB) +MAKEPREPOSTPARVAR(Pdj, pdj, PDJ) +MAKEPREPOSTPARVAR(Fan2, fan2, FAN2) +MAKEPREPOSTPARVAR(Rings2, rings2, RINGS2) +MAKEPREPOSTVAR(Eyefish, eyefish, EYEFISH) +MAKEPREPOSTVAR(Bubble, bubble, BUBBLE) +MAKEPREPOSTVAR(Cylinder, cylinder, CYLINDER) +MAKEPREPOSTPARVAR(Perspective, perspective, PERSPECTIVE) +MAKEPREPOSTVAR(Noise, noise, NOISE) +MAKEPREPOSTPARVAR(JuliaNGeneric, julian, JULIAN) +MAKEPREPOSTPARVAR(JuliaScope, juliascope, JULIASCOPE) +MAKEPREPOSTVARASSIGN(Blur, blur, BLUR, ASSIGNTYPE_SUM) +MAKEPREPOSTVARASSIGN(GaussianBlur, gaussian_blur, GAUSSIAN_BLUR, ASSIGNTYPE_SUM) +MAKEPREPOSTPARVAR(RadialBlur, radial_blur, RADIAL_BLUR) +MAKEPREPOSTPARVARASSIGN(Pie, pie, PIE, ASSIGNTYPE_SUM) +MAKEPREPOSTPARVAR(Ngon, ngon, NGON) +MAKEPREPOSTPARVAR(Curl, curl, CURL) +MAKEPREPOSTPARVAR(Rectangles, rectangles, RECTANGLES) +MAKEPREPOSTVARASSIGN(Arch, arch, ARCH, ASSIGNTYPE_SUM) +MAKEPREPOSTVAR(Tangent, tangent, TANGENT) +MAKEPREPOSTVARASSIGN(Square, square, SQUARE, ASSIGNTYPE_SUM) +MAKEPREPOSTVAR(Rays, rays, RAYS) +MAKEPREPOSTVAR(Blade, blade, BLADE) +MAKEPREPOSTVAR(Secant2, secant2, SECANT2) +MAKEPREPOSTVAR(TwinTrian, TwinTrian, TWINTRIAN) +MAKEPREPOSTVAR(Cross, cross, CROSS) +MAKEPREPOSTPARVAR(Disc2, disc2, DISC2) +MAKEPREPOSTPARVAR(SuperShape, super_shape, SUPER_SHAPE) +MAKEPREPOSTPARVAR(Flower, flower, FLOWER) +MAKEPREPOSTPARVAR(Conic, conic, CONIC) +MAKEPREPOSTPARVAR(Parabola, parabola, PARABOLA) +MAKEPREPOSTPARVAR(Bent2, bent2, BENT2) +MAKEPREPOSTPARVAR(Bipolar, bipolar, BIPOLAR) +MAKEPREPOSTVAR(Boarders, boarders, BOARDERS) +MAKEPREPOSTVAR(Butterfly, butterfly, BUTTERFLY) +MAKEPREPOSTPARVAR(Cell, cell, CELL) +MAKEPREPOSTPARVAR(Cpow, cpow, CPOW) +MAKEPREPOSTPARVAR(Curve, curve, CURVE) +MAKEPREPOSTVAR(Edisc, edisc, EDISC) +MAKEPREPOSTVAR(Elliptic, elliptic, ELLIPTIC) +MAKEPREPOSTPARVAR(Escher, escher, ESCHER) +MAKEPREPOSTVAR(Foci, foci, FOCI) +MAKEPREPOSTPARVAR(LazySusan, lazysusan, LAZYSUSAN) +MAKEPREPOSTPARVAR(Loonie, loonie, LOONIE) +MAKEPREPOSTPARVAR(Modulus, modulus, MODULUS) +MAKEPREPOSTPARVAR(Oscilloscope, oscilloscope, OSCILLOSCOPE) +MAKEPREPOSTPARVAR(Polar2, polar2, POLAR2) +MAKEPREPOSTPARVAR(Popcorn2, popcorn2, POPCORN2) +MAKEPREPOSTPARVAR(Scry, scry, SCRY) +MAKEPREPOSTPARVAR(Separation, separation, SEPARATION) +MAKEPREPOSTPARVAR(Split, split, SPLIT) +MAKEPREPOSTPARVAR(Splits, splits, SPLITS) +MAKEPREPOSTPARVAR(Stripes, stripes, STRIPES) +MAKEPREPOSTPARVAR(Wedge, wedge, WEDGE) +MAKEPREPOSTPARVAR(WedgeJulia, wedge_julia, WEDGE_JULIA) +MAKEPREPOSTPARVAR(WedgeSph, wedge_sph, WEDGE_SPH) +MAKEPREPOSTPARVAR(Whorl, whorl, WHORL) +MAKEPREPOSTPARVAR(Waves2, waves2, WAVES2) +MAKEPREPOSTVAR(Exp, exp, EXP) +MAKEPREPOSTPARVAR(Log, log, LOG) +MAKEPREPOSTVAR(Sin, sin, SIN) +MAKEPREPOSTVAR(Cos, cos, COS) +MAKEPREPOSTVAR(Tan, tan, TAN) +MAKEPREPOSTVAR(Sec, sec, SEC) +MAKEPREPOSTVAR(Csc, csc, CSC) +MAKEPREPOSTVAR(Cot, cot, COT) +MAKEPREPOSTVAR(Sinh, sinh, SINH) +MAKEPREPOSTVAR(Cosh, cosh, COSH) +MAKEPREPOSTVAR(Tanh, tanh, TANH) +MAKEPREPOSTVAR(Sech, sech, SECH) +MAKEPREPOSTVAR(Csch, csch, CSCH) +MAKEPREPOSTVAR(Coth, coth, COTH) +MAKEPREPOSTPARVAR(Auger, auger, AUGER) +MAKEPREPOSTPARVAR(Flux, flux, FLUX) +} \ No newline at end of file diff --git a/Source/Ember/Variations02.h b/Source/Ember/Variations02.h new file mode 100644 index 0000000..048b476 --- /dev/null +++ b/Source/Ember/Variations02.h @@ -0,0 +1,5756 @@ +#pragma once + +#include "Variation.h" + +namespace EmberNs +{ +/// +/// Hemisphere. +/// +template +class EMBER_API HemisphereVariation : public Variation +{ +public: + HemisphereVariation(T weight = 1.0) : Variation("hemisphere", VAR_HEMISPHERE, weight, true) { } + + VARCOPY(HemisphereVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T t = m_Weight / sqrt(helper.m_PrecalcSumSquares + 1); + + helper.Out.x = helper.In.x * t; + helper.Out.y = helper.In.y * t; + helper.Out.z = t; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t t = xform->m_VariationWeights[" << varIndex << "] / sqrt(precalcSumSquares + 1.0);\n" + << "\n" + << "\t\tvOut.x = vIn.x * t;\n" + << "\t\tvOut.y = vIn.y * t;\n" + << "\t\tvOut.z = t;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Epispiral. +/// +template +class EMBER_API EpispiralVariation : public ParametricVariation +{ +public: + EpispiralVariation(T weight = 1.0) : ParametricVariation("epispiral", VAR_EPISPIRAL, weight, false, false, false, false, true) + { + Init(); + } + + PARVARCOPY(EpispiralVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T theta = helper.m_PrecalcAtanyx; + T t = (rand.Frand01() * m_Thickness) * (1 / cos(m_N * theta)) - m_Holes; + + if (fabs(t) != 0) + { + helper.Out.x = m_Weight * t * cos(theta); + helper.Out.y = m_Weight * t * sin(theta); + helper.Out.z = m_Weight * helper.In.z; + } + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string n = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string thickness = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string holes = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t theta = precalcAtanyx;\n" + << "\t\treal_t t = (MwcNext01(mwc) * " << thickness << ") * (1 / cos(" << n << " * theta)) - " << holes << ";\n" + << "\n" + << "\t\tif (fabs(t) != 0)\n" + << "\t\t{\n" + << "\t\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * t * cos(theta);\n" + << "\t\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * t * sin(theta);\n" + << "\t\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t\t}\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_N, prefix + "epispiral_n", 6)); + m_Params.push_back(ParamWithName(&m_Thickness, prefix + "epispiral_thickness")); + m_Params.push_back(ParamWithName(&m_Holes, prefix + "epispiral_holes", 1)); + } + +private: + T m_N; + T m_Thickness; + T m_Holes; +}; + +/// +/// Bwraps. +/// Note, this is the same as bwraps2. +/// +template +class EMBER_API BwrapsVariation : public ParametricVariation +{ +public: + BwrapsVariation(T weight = 1.0) : ParametricVariation("bwraps", VAR_BWRAPS, weight) + { + Init(); + } + + PARVARCOPY(BwrapsVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + if (m_BwrapsCellsize == 0) + { + helper.Out.x = m_Weight * helper.In.x; + helper.Out.y = m_Weight * helper.In.y; + } + else + { + T vx = helper.In.x; + T vy = helper.In.y; + T cx = (Floor(vx / m_BwrapsCellsize) + T(0.5)) * m_BwrapsCellsize; + T cy = (Floor(vy / m_BwrapsCellsize) + T(0.5)) * m_BwrapsCellsize; + T lx = vx - cx; + T ly = vy - cy; + + if ((SQR(lx) + SQR(ly)) > m_R2) + { + helper.Out.x = m_Weight * helper.In.x; + helper.Out.y = m_Weight * helper.In.y; + } + else + { + lx *= m_G2; + ly *= m_G2; + + T r = m_Rfactor / ((SQR(lx) + SQR(ly)) / 4 + 1); + + lx *= r; + ly *= r; + r = (SQR(lx) + SQR(ly)) / m_R2; + + T theta = m_BwrapsInnerTwist * (1 - r) + m_BwrapsOuterTwist * r; + T s = sin(theta); + T c = cos(theta); + + vx = cx + c * lx + s * ly; + vy = cy - s * lx + c * ly; + + helper.Out.x = m_Weight * vx; + helper.Out.y = m_Weight * vy; + } + } + + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string bwrapsCellsize = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string bwrapsSpace = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string bwrapsGain = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string bwrapsInnerTwist = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string bwrapsOuterTwist = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string g2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string r2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string rfactor = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tif (" << bwrapsCellsize << " == 0)\n" + << "\t\t{\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t real_t vx = vIn.x;\n" + << "\t\t real_t vy = vIn.y;\n" + << "\t\t real_t cx = (floor(vx / " << bwrapsCellsize << ") + 0.5) * " << bwrapsCellsize << ";\n" + << "\t\t real_t cy = (floor(vy / " << bwrapsCellsize << ") + 0.5) * " << bwrapsCellsize << ";\n" + << "\t\t real_t lx = vx - cx;\n" + << "\t\t real_t ly = vy - cy;\n" + << "\n" + << "\t\t if ((SQR(lx) + SQR(ly)) > " << r2 << ")\n" + << "\t\t {\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t lx *= " << g2 << ";\n" + << "\t\t ly *= " << g2 << ";\n" + << "\n" + << "\t\t real_t r = " << rfactor << " / ((SQR(lx) + SQR(ly)) / 4 + 1);\n" + << "\n" + << "\t\t lx *= r;\n" + << "\t\t ly *= r;\n" + << "\t\t r = (SQR(lx) + SQR(ly)) / " << r2 << ";\n" + << "\n" + << "\t\t real_t theta = " << bwrapsInnerTwist << " * (1 - r) + " << bwrapsOuterTwist << " * r;\n" + << "\t\t real_t s = sin(theta);\n" + << "\t\t real_t c = cos(theta);\n" + << "\n" + << "\t\t vx = cx + c * lx + s * ly;\n" + << "\t\t vy = cy - s * lx + c * ly;\n" + << "\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * vx;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * vy;\n" + << "\t\t }\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + T radius = T(0.5) * (m_BwrapsCellsize / (1 + SQR(m_BwrapsSpace))); + m_G2 = SQR(m_BwrapsGain) / (radius + EPS6) + EPS6; + T maxBubble = m_G2 * radius; + + if (maxBubble > 2) + maxBubble = 1; + else + maxBubble *= (1 / (SQR(maxBubble) / 4 + 1)); + + m_R2 = SQR(radius); + m_Rfactor = radius / maxBubble; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_BwrapsCellsize, prefix + "bwraps_cellsize", 1)); + m_Params.push_back(ParamWithName(&m_BwrapsSpace, prefix + "bwraps_space")); + m_Params.push_back(ParamWithName(&m_BwrapsGain, prefix + "bwraps_gain", 1)); + m_Params.push_back(ParamWithName(&m_BwrapsInnerTwist, prefix + "bwraps_inner_twist")); + m_Params.push_back(ParamWithName(&m_BwrapsOuterTwist, prefix + "bwraps_outer_twist")); + m_Params.push_back(ParamWithName(true, &m_G2, prefix + "bwraps_g2"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_R2, prefix + "bwraps_r2")); + m_Params.push_back(ParamWithName(true, &m_Rfactor, prefix + "bwraps_rfactor")); + } + +private: + T m_BwrapsCellsize; + T m_BwrapsSpace; + T m_BwrapsGain; + T m_BwrapsInnerTwist; + T m_BwrapsOuterTwist; + T m_G2;//Precalc. + T m_R2; + T m_Rfactor; +}; + +/// +/// BlurCircle. +/// +template +class EMBER_API BlurCircleVariation : public Variation +{ +public: + BlurCircleVariation(T weight = 1.0) : Variation("blur_circle", VAR_BLUR_CIRCLE, weight) { } + + VARCOPY(BlurCircleVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T x = 2 * rand.Frand01() - 1; + T y = 2 * rand.Frand01() - 1; + T absx = x; + T absy = y; + T side, perimeter; + + if (absx < 0) + absx = absx * -1; + + if (absy < 0) + absy = absy * -1; + + if (absx >= absy) + { + if (x >= absy) + perimeter = absx + y; + else + perimeter = 5 * absx - y; + + side = absx; + } + else + { + if (y >= absx) + perimeter = 3 * absy - x; + else + perimeter = 7 * absy + x; + + side = absy; + } + + T r = m_Weight * side; + T val = T(M_PI_4) * perimeter / side - T(M_PI_4); + T sina = sin(val); + T cosa = cos(val); + + helper.Out.x = r * cosa; + helper.Out.y = r * sina; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + + ss << "\t{\n" + << "\t\treal_t x = 2 * MwcNext01(mwc) - 1;\n" + << "\t\treal_t y = 2 * MwcNext01(mwc) - 1;\n" + << "\t\treal_t absx = x;\n" + << "\t\treal_t absy = y;\n" + << "\t\treal_t side, perimeter;\n" + << "\t\t\n" + << "\t\tif (absx < 0)\n" + << "\t\t absx = absx * -1;\n" + << "\n" + << "\t\tif (absy < 0)\n" + << "\t\t absy = absy * -1;\n" + << "\n" + << "\t\tif (absx >= absy)\n" + << "\t\t{\n" + << "\t\t if (x >= absy)\n" + << "\t\t perimeter = absx + y;\n" + << "\t\t else\n" + << "\t\t perimeter = 5 * absx - y;\n" + << "\n" + << "\t\t side = absx;\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t if (y >= absx)\n" + << "\t\t perimeter = 3 * absy - x;\n" + << "\t\t else\n" + << "\t\t perimeter = 7 * absy + x;\n" + << "\n" + << "\t\t side = absy;\n" + << "\t\t}\n" + << "\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * side;\n" + << "\t\treal_t val = M_PI_4 * perimeter / side - M_PI_4;\n" + << "\t\treal_t sina = sin(val);\n" + << "\t\treal_t cosa = cos(val);\n" + << "\n" + << "\t\tvOut.x = r * cosa;\n" + << "\t\tvOut.y = r * sina;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// BlurZoom. +/// +template +class EMBER_API BlurZoomVariation : public ParametricVariation +{ +public: + BlurZoomVariation(T weight = 1.0) : ParametricVariation("blur_zoom", VAR_BLUR_ZOOM, weight) + { + Init(); + } + + PARVARCOPY(BlurZoomVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T z = 1 + m_BlurZoomLength * rand.Frand01(); + + helper.Out.x = m_Weight * ((helper.In.x - m_BlurZoomX) * z + m_BlurZoomX); + helper.Out.y = m_Weight * ((helper.In.y - m_BlurZoomY) * z - m_BlurZoomY); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string blurZoomLength = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string blurZoomX = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string blurZoomY = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t z = 1 + " << blurZoomLength << " * MwcNext01(mwc);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * ((vIn.x - " << blurZoomX << ") * z + " << blurZoomX << ");\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * ((vIn.y - " << blurZoomY << ") * z - " << blurZoomY << ");\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_BlurZoomLength, prefix + "blur_zoom_length")); + m_Params.push_back(ParamWithName(&m_BlurZoomX, prefix + "blur_zoom_x")); + m_Params.push_back(ParamWithName(&m_BlurZoomY, prefix + "blur_zoom_y")); + } + +private: + T m_BlurZoomLength; + T m_BlurZoomX; + T m_BlurZoomY; +}; + +/// +/// BlurPixelize. +/// +template +class EMBER_API BlurPixelizeVariation : public ParametricVariation +{ +public: + BlurPixelizeVariation(T weight = 1.0) : ParametricVariation("blur_pixelize", VAR_BLUR_PIXELIZE, weight) + { + Init(); + } + + PARVARCOPY(BlurPixelizeVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T x = (T)Floor(helper.In.x * m_InvSize); + T y = (T)Floor(helper.In.y * m_InvSize); + + helper.Out.x = m_V * (x + m_BlurPixelizeScale * (rand.Frand01() - T(0.5)) + T(0.5)); + helper.Out.y = m_V * (y + m_BlurPixelizeScale * (rand.Frand01() - T(0.5)) + T(0.5)); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string blurPixelizeSize = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string blurPixelizeScale = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string v = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string invSize = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t x = floor(vIn.x * " << invSize << ");\n" + << "\t\treal_t y = floor(vIn.y * " << invSize << ");\n" + << "\n" + << "\t\tvOut.x = " << v << " * (x + " << blurPixelizeScale << " * (MwcNext01(mwc) - 0.5) + 0.5);\n" + << "\t\tvOut.y = " << v << " * (y + " << blurPixelizeScale << " * (MwcNext01(mwc) - 0.5) + 0.5);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_V = m_Weight * m_BlurPixelizeSize; + m_InvSize = 1 / m_BlurPixelizeSize; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_BlurPixelizeSize, prefix + "blur_pixelize_size", T(0.1), REAL, EPS6)); + m_Params.push_back(ParamWithName(&m_BlurPixelizeScale, prefix + "blur_pixelize_scale", 1)); + m_Params.push_back(ParamWithName(true, &m_V, prefix + "blur_pixelize_v"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_InvSize, prefix + "blur_pixelize_inv_size")); + } + +private: + T m_BlurPixelizeSize; + T m_BlurPixelizeScale; + T m_V;//Precalc. + T m_InvSize; +}; + +/// +/// Crop. +/// +template +class EMBER_API CropVariation : public ParametricVariation +{ +public: + CropVariation(T weight = 1.0) : ParametricVariation("crop", VAR_CROP, weight) + { + Init(); + } + + PARVARCOPY(CropVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T x = helper.In.x; + T y = helper.In.y; + + if (((x < m_X0_) || (x > m_X1_) || (y < m_Y0_) || (y > m_Y1_)) && m_Z != 0) + { + x = 0; + y = 0; + } + else + { + if (x < m_X0_) + x = m_X0_ + rand.Frand01() * m_W; + else if (x > m_X1_) + x = m_X1_ - rand.Frand01() * m_W; + + if (y < m_Y0_) + y = m_Y0_ + rand.Frand01() * m_H; + else if (y > m_Y1_) + y = m_Y1_ - rand.Frand01() * m_H; + } + + helper.Out.x = m_Weight * x; + helper.Out.y = m_Weight * y; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string x0 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string y0 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string x1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string y1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string s = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string z = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string x0_ = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string y0_ = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string x1_ = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string y1_ = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string w = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string h = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t x = vIn.x;\n" + << "\t\treal_t y = vIn.y;\n" + << "\n" + << "\t\tif (((x < " << x0_ << ") || (x > " << x1_ << ") || (y < " << y0_ << ") || (y > " << y1_ << ")) && " << z << " != 0)\n" + << "\t\t{\n" + << "\t\t x = 0;\n" + << "\t\t y = 0;\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t if (x < " << x0_ << ")\n" + << "\t\t x = " << x0_ << " + MwcNext01(mwc) * " << w << ";\n" + << "\t\t else if (x > " << x1_ << ")\n" + << "\t\t x = " << x1_ << " - MwcNext01(mwc) * " << w << ";\n" + << "\t\t\n" + << "\t\t if (y < " << y0_ << ")\n" + << "\t\t y = " << y0_ << " + MwcNext01(mwc) * " << h << ";\n" + << "\t\t else if (y > " << y1_ << ")\n" + << "\t\t y = " << y1_ << " - MwcNext01(mwc) * " << h << ";\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * x;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * y;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + if (m_X0 < m_X1) + { + m_X0_ = m_X0; + m_X1_ = m_X1; + } + else + { + m_X0_ = m_X1; + m_X1_ = m_X0; + } + + if (m_Y0 < m_Y1) + { + m_Y0_ = m_Y0; + m_Y1_ = m_Y1; + } + else + { + m_Y0_ = m_Y1; + m_Y1_ = m_Y0; + } + + m_W = (m_X1_ - m_X0_) * T(0.5) * m_S; + m_H = (m_Y1_ - m_Y0_) * T(0.5) * m_S; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_X0, prefix + "crop_left", -1)); + m_Params.push_back(ParamWithName(&m_Y0, prefix + "crop_top", -1)); + m_Params.push_back(ParamWithName(&m_X1, prefix + "crop_right", 1)); + m_Params.push_back(ParamWithName(&m_Y1, prefix + "crop_bottom", 1)); + m_Params.push_back(ParamWithName(&m_S, prefix + "crop_scatter_area", 0, REAL, -1, 1)); + m_Params.push_back(ParamWithName(&m_Z, prefix + "crop_zero", 0, INTEGER, 0, 1)); + m_Params.push_back(ParamWithName(true, &m_X0_, prefix + "crop_x0_"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Y0_, prefix + "crop_y0_")); + m_Params.push_back(ParamWithName(true, &m_X1_, prefix + "crop_x1_")); + m_Params.push_back(ParamWithName(true, &m_Y1_, prefix + "crop_y1_")); + m_Params.push_back(ParamWithName(true, &m_W, prefix + "crop_w")); + m_Params.push_back(ParamWithName(true, &m_H, prefix + "crop_h")); + } + +private: + T m_X0; + T m_Y0; + T m_X1; + T m_Y1; + T m_S; + T m_Z; + T m_X0_;//Precalc. + T m_Y0_; + T m_X1_; + T m_Y1_; + T m_W; + T m_H; +}; + +/// +/// BCircle. +/// +template +class EMBER_API BCircleVariation : public ParametricVariation +{ +public: + BCircleVariation(T weight = 1.0) : ParametricVariation("bcircle", VAR_BCIRCLE, weight) + { + Init(); + } + + PARVARCOPY(BCircleVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + if ((helper.In.x == 0) && (helper.In.y == 0)) + return; + + T x = helper.In.x * m_Scale; + T y = helper.In.y * m_Scale; + T r = sqrt(SQR(x) + SQR(y)); + + if (r <= 1) + { + helper.Out.x = m_Weight * x; + helper.Out.y = m_Weight * y; + } + else + { + if (m_Bcbw != 0) + { + T ang = atan2(y, x); + T omega = (T(0.2) * m_Bcbw * rand.Frand01()) + 1; + T px = omega * cos(ang); + T py = omega * sin(ang); + helper.Out.x = m_Weight * px; + helper.Out.y = m_Weight * py; + } + } + + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string scale = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string borderWidth = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string bcbw = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tif ((vIn.x == 0) && (vIn.y == 0))\n" + << "\t\t return;\n" + << "\n" + << "\t\treal_t x = vIn.x * " << scale << ";\n" + << "\t\treal_t y = vIn.y * " << scale << ";\n" + << "\t\treal_t r = sqrt(SQR(x) + SQR(y));\n" + << "\n" + << "\t\tif (r <= 1)\n" + << "\t\t{\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * x;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * y;\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t if (" << bcbw << " != 0)\n" + << "\t\t {\n" + << "\t\t real_t ang = atan2(y, x);\n" + << "\t\t real_t omega = (0.2 * " << bcbw << " * MwcNext01(mwc)) + 1;\n" + << "\t\t real_t px = omega * cos(ang);\n" + << "\t\t real_t py = omega * sin(ang);\n" + << "\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * px;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * py;\n" + << "\t\t }\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Bcbw = fabs(m_BorderWidth); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Scale, prefix + "bcircle_scale", 1)); + m_Params.push_back(ParamWithName(&m_BorderWidth, prefix + "bcircle_borderwidth")); + m_Params.push_back(ParamWithName(true, &m_Bcbw, prefix + "bcircle_bcbw"));//Precalc. + } + +private: + T m_Scale; + T m_BorderWidth; + T m_Bcbw;//Precalc. +}; + +/// +/// BlurLinear. +/// +template +class EMBER_API BlurLinearVariation : public ParametricVariation +{ +public: + BlurLinearVariation(T weight = 1.0) : ParametricVariation("blur_linear", VAR_BLUR_LINEAR, weight) + { + Init(); + } + + PARVARCOPY(BlurLinearVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r = m_BlurLinearLength * rand.Frand01(); + + helper.Out.x = m_Weight * (helper.In.x + r * m_C); + helper.Out.y = m_Weight * (helper.In.y + r * m_S); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string blurLinearLength = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string blurLinearAngle = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string s = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t r = " << blurLinearLength << " * MwcNext01(mwc);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (vIn.x + r * " << c << ");\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (vIn.y + r * " << s << ");\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + sincos(m_BlurLinearAngle, &m_S, &m_C); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_BlurLinearLength, prefix + "blur_linear_length")); + m_Params.push_back(ParamWithName(&m_BlurLinearAngle, prefix + "blur_linear_angle", 0, REAL_CYCLIC, 0, T(M_2PI))); + m_Params.push_back(ParamWithName(true, &m_S, prefix + "blur_linear_s"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_C, prefix + "blur_linear_c")); + } + +private: + T m_BlurLinearLength; + T m_BlurLinearAngle; + T m_S;//Precalc. + T m_C; +}; + +/// +/// BlurSquare. +/// +template +class EMBER_API BlurSquareVariation : public ParametricVariation +{ +public: + BlurSquareVariation(T weight = 1.0) : ParametricVariation("blur_square", VAR_BLUR_SQUARE, weight) + { + Init(); + } + + PARVARCOPY(BlurSquareVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + helper.Out.x = m_V * (rand.Frand01() - T(0.5)); + helper.Out.y = m_V * (rand.Frand01() - T(0.5)); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string v = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Precalcs only, no params. + + ss << "\t{\n" + << "\t\tvOut.x = " << v << " * (MwcNext01(mwc) - 0.5);\n" + << "\t\tvOut.y = " << v << " * (MwcNext01(mwc) - 0.5);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_V = m_Weight * 2; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(true, &m_V, prefix + "blur_square_v"));//Precalcs only, no params. + } + +private: + T m_V; +}; + +/// +/// Flatten. +/// This uses in/out in a rare and different way. +/// +template +class EMBER_API FlattenVariation : public Variation +{ +public: + FlattenVariation(T weight = 1.0) : Variation("flatten", VAR_FLATTEN, weight) { } + + VARCOPY(FlattenVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + if (m_VarType == VARTYPE_REG) + { + helper.Out.x = helper.Out.y = helper.Out.z = 0; + outPoint.m_Z = 0; + } + else + { + helper.Out.x = helper.In.x; + helper.Out.y = helper.In.y; + helper.Out.z = 0; + } + } + + virtual string OpenCLString() + { + ostringstream ss; + + if (m_VarType == VARTYPE_REG)//Rare and different usage of in/out. + { + ss << "\t{\n" + << "\t\tvOut.x = vOut.y = vOut.z = 0;\n" + << "\t\toutPoint->m_Z = 0;\n" + << "\t}\n"; + } + else + { + ss << "\t{\n" + << "\t\tvOut.x = vIn.x;\n" + << "\t\tvOut.y = vIn.y;\n" + << "\t\tvOut.z = 0;\n" + << "\t}\n"; + } + + return ss.str(); + } +}; + +/// +/// Zblur. +/// This uses in/out in a rare and different way. +/// +template +class EMBER_API ZblurVariation : public Variation +{ +public: + ZblurVariation(T weight = 1.0) : Variation("zblur", VAR_ZBLUR, weight) { } + + VARCOPY(ZblurVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + helper.Out.x = helper.Out.y = 0; + helper.Out.z = m_Weight * (rand.Frand01() + rand.Frand01() + rand.Frand01() + rand.Frand01() - 2); + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\tvOut.x = vOut.y = 0;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * (MwcNext01(mwc) + MwcNext01(mwc) + MwcNext01(mwc) + MwcNext01(mwc) - 2.0);\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// ZScale. +/// +template +class EMBER_API ZScaleVariation : public Variation +{ +public: + ZScaleVariation(T weight = 1.0) : Variation("zscale", VAR_ZSCALE, weight) { } + + VARCOPY(ZScaleVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + helper.Out.x = helper.Out.y = 0; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\tvOut.x = vOut.y = 0;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// ZTranslate. +/// +template +class EMBER_API ZTranslateVariation : public Variation +{ +public: + ZTranslateVariation(T weight = 1.0) : Variation("ztranslate", VAR_ZTRANSLATE, weight) { } + + VARCOPY(ZTranslateVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + helper.Out.x = helper.Out.y = 0; + helper.Out.z = m_Weight; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\tvOut.x = vOut.y = 0;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "];\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// zcone. +/// +template +class EMBER_API ZConeVariation : public Variation +{ +public: + ZConeVariation(T weight = 1.0) : Variation("zcone", VAR_ZCONE, weight, true, true) { } + + VARCOPY(ZConeVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + if (m_VarType == VARTYPE_REG) + { + helper.Out.x = helper.Out.y = 0; + } + else + { + helper.Out.x = helper.In.x; + helper.Out.y = helper.In.y; + } + + helper.Out.z = m_Weight * helper.m_PrecalcSqrtSumSquares; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + if (m_VarType == VARTYPE_REG) + { + ss << "\t{\n" + << "\t\tvOut.x = vOut.y = 0;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * precalcSqrtSumSquares;\n" + << "\t}\n"; + } + else + { + ss << "\t{\n" + << "\t\tvOut.x = vIn.x;\n" + << "\t\tvOut.y = vIn.y;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * precalcSqrtSumSquares;\n" + << "\t}\n"; + } + + return ss.str(); + } +}; + +/// +/// Blur3D. +/// +template +class EMBER_API Blur3DVariation : public Variation +{ +public: + Blur3DVariation(T weight = 1.0) : Variation("blur_3d", VAR_BLUR3D, weight) { } + + VARCOPY(Blur3DVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T angle = rand.Frand01() * M_2PI; + T r = m_Weight * (rand.Frand01() + rand.Frand01() + rand.Frand01() + rand.Frand01() - 2); + T angle2 = rand.Frand01() * T(M_PI); + T sina = sin(angle); + T cosa = cos(angle); + T sinb = sin(angle2); + T cosb = cos(angle2); + + helper.Out.x = r * sinb * cosa; + helper.Out.y = r * sinb * sina; + helper.Out.z = r * cosb; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t angle = MwcNext01(mwc) * M_2PI;\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * (MwcNext01(mwc) + MwcNext01(mwc) + MwcNext01(mwc) + MwcNext01(mwc) - 2.0);\n" + << "\t\treal_t angle2 = MwcNext01(mwc) * M_PI;\n" + << "\t\treal_t sina = sin(angle);\n" + << "\t\treal_t cosa = cos(angle);\n" + << "\t\treal_t sinb = sin(angle2);\n" + << "\t\treal_t cosb = cos(angle2);\n" + << "\n" + << "\t\tvOut.x = r * sinb * cosa;\n" + << "\t\tvOut.y = r * sinb * sina;\n" + << "\t\tvOut.z = r * cosb;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Spherical3D. +/// +template +class EMBER_API Spherical3DVariation : public Variation +{ +public: + Spherical3DVariation(T weight = 1.0) : Variation("Spherical3D", VAR_SPHERICAL3D, weight, true) { } + + VARCOPY(Spherical3DVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r2 = m_Weight / (helper.m_PrecalcSumSquares + helper.In.z + EPS6); + + helper.Out.x = r2 * helper.In.x; + helper.Out.y = r2 * helper.In.y; + helper.Out.z = r2 * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t r2 = xform->m_VariationWeights[" << varIndex << "] / (precalcSumSquares + vIn.z + EPS6);\n" + << "\n" + << "\t\tvOut.x = r2 * vIn.x;\n" + << "\t\tvOut.y = r2 * vIn.y;\n" + << "\t\tvOut.z = r2 * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Curl3D. +/// +template +class EMBER_API Curl3DVariation : public ParametricVariation +{ +public: + Curl3DVariation(T weight = 1.0) : ParametricVariation("curl3D", VAR_CURL3D, weight, true) + { + Init(); + } + + PARVARCOPY(Curl3DVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r2 = helper.m_PrecalcSumSquares + SQR(helper.In.z); + T r = m_Weight / ((r2 * m_C2) + (m_C2x * helper.In.x) - (m_C2y * helper.In.y) + (m_C2z * helper.In.z) + 1); + + helper.Out.x = r * (helper.In.x + m_Cx * r2); + helper.Out.y = r * (helper.In.y - m_Cy * r2); + helper.Out.z = r * (helper.In.z + m_Cz * r2); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string cx = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cy = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cz = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c2x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c2y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c2z = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t r2 = precalcSumSquares + SQR(vIn.z);\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] / ((r2 * " << c2 << ") + (" << c2x << " * vIn.x) - (" << c2y << " * vIn.y) + (" << c2z << " * vIn.z) + 1.0);\n" + << "\n" + << "\t\tvOut.x = r * (vIn.x + " << cx << " * r2);\n" + << "\t\tvOut.y = r * (vIn.y - " << cy << " * r2);\n" + << "\t\tvOut.z = r * (vIn.z + " << cz << " * r2);\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_C2x = 2 * m_Cx; + m_C2y = 2 * m_Cy; + m_C2z = 2 * m_Cz; + m_C2 = SQR(m_C2x) + SQR(m_C2y) + SQR(m_C2z); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Cx, prefix + "curl3D_cx")); + m_Params.push_back(ParamWithName(&m_Cy, prefix + "curl3D_cy")); + m_Params.push_back(ParamWithName(&m_Cz, prefix + "curl3D_cz")); + m_Params.push_back(ParamWithName(true, &m_C2, prefix + "curl3D_c2"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_C2x, prefix + "curl3D_c2x")); + m_Params.push_back(ParamWithName(true, &m_C2y, prefix + "curl3D_c2y")); + m_Params.push_back(ParamWithName(true, &m_C2z, prefix + "curl3D_c2z")); + } + +private: + T m_Cx; + T m_Cy; + T m_Cz; + T m_C2;//Precalc. + T m_C2x; + T m_C2y; + T m_C2z; +}; + +/// +/// Disc3D. +/// +template +class EMBER_API Disc3DVariation : public ParametricVariation +{ +public: + Disc3DVariation(T weight = 1.0) : ParametricVariation("disc3d", VAR_DISC3D, weight, true, true, false, true, false) + { + Init(); + } + + PARVARCOPY(Disc3DVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r = helper.m_PrecalcSqrtSumSquares; + T temp = r * m_Pi; + T sr = sin(temp); + T cr = cos(temp); + T vv = m_Weight * helper.m_PrecalcAtanxy / (m_Pi + EPS6); + + helper.Out.x = vv * sr; + helper.Out.y = vv * cr; + helper.Out.z = vv * (r * cos(helper.In.z)); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string pi = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t r = precalcSqrtSumSquares;\n" + << "\t\treal_t temp = r * " << pi << ";\n" + << "\t\treal_t sr = sin(temp);\n" + << "\t\treal_t cr = cos(temp);\n" + << "\t\treal_t vv = xform->m_VariationWeights[" << varIndex << "] * precalcAtanxy / (" << pi << " + EPS6);\n" + << "\n" + << "\t\tvOut.x = vv * sr;\n" + << "\t\tvOut.y = vv * cr;\n" + << "\t\tvOut.z = vv * (r * cos(vIn.z));\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Pi, prefix + "disc3d_pi", T(M_PI))); + } + +private: + T m_Pi; +}; + +/// +/// Boarders2. +/// +template +class EMBER_API Boarders2Variation : public ParametricVariation +{ +public: + Boarders2Variation(T weight = 1.0) : ParametricVariation("boarders2", VAR_BOARDERS2, weight) + { + Init(); + } + + PARVARCOPY(Boarders2Variation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T roundX = Rint(helper.In.x); + T roundY = Rint(helper.In.y); + T offsetX = helper.In.x - roundX; + T offsetY = helper.In.y - roundY; + + if (rand.Frand01() >= m_Cr) + { + helper.Out.x = m_Weight * (offsetX * m_AbsC + roundX); + helper.Out.y = m_Weight * (offsetY * m_AbsC + roundY); + } + else + { + if (fabs(offsetX) >= fabs(offsetY)) + { + if (offsetX >= 0) + { + helper.Out.x = m_Weight * (offsetX * m_AbsC + roundX + m_Cl); + helper.Out.y = m_Weight * (offsetY * m_AbsC + roundY + m_Cl * offsetY / offsetX); + } + else + { + helper.Out.x = m_Weight * (offsetX * m_AbsC + roundX - m_Cl); + helper.Out.y = m_Weight * (offsetY * m_AbsC + roundY - m_Cl * offsetY / offsetX); + } + } + else + { + if(offsetY >= 0) + { + helper.Out.y = m_Weight * (offsetY * m_AbsC + roundY + m_Cl); + helper.Out.x = m_Weight * (offsetX * m_AbsC + roundX + offsetX / offsetY * m_Cl); + } + else + { + helper.Out.y = m_Weight * (offsetY * m_AbsC + roundY - m_Cl); + helper.Out.x = m_Weight * (offsetX * m_AbsC + roundX - offsetX / offsetY * m_Cl); + } + } + } + + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string c = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string l = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string r = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string absc = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cl = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cr = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t roundX = Rint(vIn.x);\n" + << "\t\treal_t roundY = Rint(vIn.y);\n" + << "\t\treal_t offsetX = vIn.x - roundX;\n" + << "\t\treal_t offsetY = vIn.y - roundY;\n" + << "\n" + << "\t\tif (MwcNext01(mwc) >= " << cr << ")\n" + << "\t\t{\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (offsetX * " << absc << " + roundX);\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * (offsetY * " << absc << " + roundY);\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t if (fabs(offsetX) >= fabs(offsetY))\n" + << "\t\t {\n" + << "\t\t if (offsetX >= 0)\n" + << "\t\t {\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (offsetX * " << absc << " + roundX + " << cl << ");\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * (offsetY * " << absc << " + roundY + " << cl << " * offsetY / offsetX);\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (offsetX * " << absc << " + roundX - " << cl << ");\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * (offsetY * " << absc << " + roundY - " << cl << " * offsetY / offsetX);\n" + << "\t\t }\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t if(offsetY >= 0)\n" + << "\t\t {\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * (offsetY * " << absc << " + roundY + " << cl << ");\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (offsetX * " << absc << " + roundX + offsetX / offsetY * " << cl << ");\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * (offsetY * " << absc << " + roundY - " << cl << ");\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (offsetX * " << absc << " + roundX - offsetX / offsetY * " << cl << ");\n" + << "\t\t }\n" + << "\t\t }\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + T c = fabs(m_C); + T cl = fabs(m_Left); + T cr = fabs(m_Right); + + c = c == 0 ? EPS : c; + cl = cl == 0 ? EPS : cl; + cr = cr == 0 ? EPS : cr; + + m_AbsC = c; + m_Cl = c * cl; + m_Cr = c + (c * cr); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_C, prefix + "boarders2_c", T(0.5))); + m_Params.push_back(ParamWithName(&m_Left, prefix + "boarders2_left", T(0.5))); + m_Params.push_back(ParamWithName(&m_Right, prefix + "boarders2_right", T(0.5))); + m_Params.push_back(ParamWithName(true, &m_AbsC, prefix + "boarders2_cabs"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Cl, prefix + "boarders2_cl")); + m_Params.push_back(ParamWithName(true, &m_Cr, prefix + "boarders2_cr")); + } + +private: + T m_C; + T m_Left; + T m_Right; + T m_AbsC;//Precalc. + T m_Cl; + T m_Cr; +}; + +/// +/// Cardioid. +/// +template +class EMBER_API CardioidVariation : public ParametricVariation +{ +public: + CardioidVariation(T weight = 1.0) : ParametricVariation("cardioid", VAR_CARDIOID, weight, true, true, true, false, true) + { + Init(); + } + + PARVARCOPY(CardioidVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r = m_Weight * sqrt(helper.m_PrecalcSumSquares + sin(helper.m_PrecalcAtanyx * m_A) + 1); + + helper.Out.x = r * helper.m_PrecalcCosa; + helper.Out.y = r * helper.m_PrecalcSina; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string a = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * sqrt(precalcSumSquares + sin(precalcAtanyx * " << a << ") + 1);\n" + << "\n" + << "\t\tvOut.x = r * precalcCosa;\n" + << "\t\tvOut.y = r * precalcSina;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_A, prefix + "cardioid_a", 1)); + } + +private: + T m_A; +}; + +/// +/// Checks. +/// +template +class EMBER_API ChecksVariation : public ParametricVariation +{ +public: + ChecksVariation(T weight = 1.0) : ParametricVariation("checks", VAR_CHECKS, weight) + { + Init(); + } + + PARVARCOPY(ChecksVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T dx, dy; + T rnx = m_Rnd * rand.Frand01(); + T rny = m_Rnd * rand.Frand01(); + + int isXY = (int)(LRint(helper.In.x * m_Cs) + LRint(helper.In.y * m_Cs)); + + if (isXY % 2) + { + dx = m_Ncx + rnx; + dy = m_Ncy; + } + else + { + dx = m_Cx; + dy = m_Cy + rny; + } + + helper.Out.x = m_Weight * (helper.In.x + dx); + helper.Out.y = m_Weight * (helper.In.y + dy); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string size = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string rnd = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cs = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cx = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cy = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ncx = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ncy = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t dx, dy;\n" + << "\t\treal_t rnx = " << rnd << " * MwcNext01(mwc);\n" + << "\t\treal_t rny = " << rnd << " * MwcNext01(mwc);\n" + << "\n" + << "\t\tint isXY = (int)(LRint(vIn.x * " << cs << ") + LRint(vIn.y * " << cs << "));\n" + << "\n" + << "\t\tif (isXY % 2)\n" + << "\t\t{\n" + << "\t\t dx = " << ncx << " + rnx;\n" + << "\t\t dy = " << ncy << ";\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t dx = " << cx << ";\n" + << "\t\t dy = " << cy << " + rny;\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (vIn.x + dx);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (vIn.y + dy);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Cs = 1 / (m_Size + EPS); + m_Cx = m_X; + m_Cy = m_Y; + m_Ncx = -m_X; + m_Ncy = -m_Y; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_X, prefix + "checks_x", T(0.5))); + m_Params.push_back(ParamWithName(&m_Y, prefix + "checks_y", T(0.5))); + m_Params.push_back(ParamWithName(&m_Size, prefix + "checks_size", T(0.5))); + m_Params.push_back(ParamWithName(&m_Rnd, prefix + "checks_rnd")); + m_Params.push_back(ParamWithName(true, &m_Cs, prefix + "checks_cs"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Cx, prefix + "checks_cx")); + m_Params.push_back(ParamWithName(true, &m_Cy, prefix + "checks_cy")); + m_Params.push_back(ParamWithName(true, &m_Ncx, prefix + "checks_ncx")); + m_Params.push_back(ParamWithName(true, &m_Ncy, prefix + "checks_ncy")); + } + +private: + T m_X; + T m_Y; + T m_Size; + T m_Rnd; + T m_Cs;//Precalc. + T m_Cx; + T m_Cy; + T m_Ncx; + T m_Ncy; +}; + +/// +/// Circlize. +/// +template +class EMBER_API CirclizeVariation : public ParametricVariation +{ +public: + CirclizeVariation(T weight = 1.0) : ParametricVariation("circlize", VAR_CIRCLIZE, weight) + { + Init(); + } + + PARVARCOPY(CirclizeVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T side; + T perimeter; + T r, val; + T absx = fabs(helper.In.x); + T absy = fabs(helper.In.y); + + if (absx >= absy) + { + if (helper.In.x >= absy) + perimeter = absx + helper.In.y; + else + perimeter = 5 * absx - helper.In.y; + + side = absx; + } + else + { + if (helper.In.y >= absx) + perimeter = 3 * absy - helper.In.x; + else + perimeter = 7 * absy + helper.In.x; + + side = absy; + } + + r = m_Vvar4Pi * side + m_Hole; + val = T(M_PI_4) * perimeter / side - T(M_PI_4); + helper.Out.x = r * cos(val); + helper.Out.y = r * sin(val); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string hole = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string vvar4pi = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t side;\n" + << "\t\treal_t perimeter;\n" + << "\t\treal_t absx = fabs(vIn.x);\n" + << "\t\treal_t absy = fabs(vIn.y);\n" + << "\n" + << "\t\tif (absx >= absy)\n" + << "\t\t{\n" + << "\t\t if (vIn.x >= absy)\n" + << "\t\t perimeter = absx + vIn.y;\n" + << "\t\t else\n" + << "\t\t perimeter = 5 * absx - vIn.y;\n" + << "\n" + << "\t\t side = absx;\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t if (vIn.y >= absx)\n" + << "\t\t perimeter = 3 * absy - vIn.x;\n" + << "\t\t else\n" + << "\t\t perimeter = 7 * absy + vIn.x;\n" + << "\n" + << "\t\t side = absy;\n" + << "\t\t}\n" + << "\n" + << "\t\treal_t r = " << vvar4pi << " * side + " << hole << ";\n" + << "\t\treal_t val = M_PI_4 * perimeter / side - M_PI_4;\n" + << "\n" + << "\t\tvOut.x = r * cos(val);\n" + << "\t\tvOut.y = r * sin(val);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Vvar4Pi = m_Weight / T(M_PI_4); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Hole, prefix + "circlize_hole")); + m_Params.push_back(ParamWithName(true, &m_Vvar4Pi, prefix + "circlize_vvar4pi"));//Precalc. + } + +private: + T m_Hole; + T m_Vvar4Pi;//Precalc. +}; + +/// +/// Circlize2. +/// +template +class EMBER_API Circlize2Variation : public ParametricVariation +{ +public: + Circlize2Variation(T weight = 1.0) : ParametricVariation("circlize2", VAR_CIRCLIZE2, weight) + { + Init(); + } + + PARVARCOPY(Circlize2Variation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T side; + T perimeter; + T absx = fabs(helper.In.x); + T absy = fabs(helper.In.y); + + if (absx >= absy) + { + if (helper.In.x >= absy) + perimeter = absx + helper.In.y; + else + perimeter = 5 * absx - helper.In.y; + + side = absx; + } + else + { + if (helper.In.y >= absx) + perimeter = 3 * absy - helper.In.x; + else + perimeter = 7 * absy + helper.In.x; + + side = absy; + } + + T r = m_Weight * (side + m_Hole); + T val = T(M_PI_4) * perimeter / side - T(M_PI_4); + + helper.Out.x = r * cos(val); + helper.Out.y = r * sin(val); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string hole = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t side;\n" + << "\t\treal_t perimeter;\n" + << "\t\treal_t absx = fabs(vIn.x);\n" + << "\t\treal_t absy = fabs(vIn.y);\n" + << "\n" + << "\t\tif (absx >= absy)\n" + << "\t\t{\n" + << "\t\t if (vIn.x >= absy)\n" + << "\t\t perimeter = absx + vIn.y;\n" + << "\t\t else\n" + << "\t\t perimeter = 5 * absx - vIn.y;\n" + << "\n" + << "\t\t side = absx;\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t if (vIn.y >= absx)\n" + << "\t\t perimeter = 3 * absy - vIn.x;\n" + << "\t\t else\n" + << "\t\t perimeter = 7 * absy + vIn.x;\n" + << "\n" + << "\t\t side = absy;\n" + << "\t\t}\n" + << "\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * (side + " << hole << ");\n" + << "\t\treal_t val = M_PI_4 * perimeter / side - M_PI_4;\n" + << "\n" + << "\t\tvOut.x = r * cos(val);\n" + << "\t\tvOut.y = r * sin(val);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Hole, prefix + "circlize2_hole")); + } + +private: + T m_Hole; +}; + +/// +/// CosWrap. +/// +template +class EMBER_API CosWrapVariation : public ParametricVariation +{ +public: + CosWrapVariation(T weight = 1.0) : ParametricVariation("coswrap", VAR_COS_WRAP, weight) + { + Init(); + } + + PARVARCOPY(CosWrapVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T x = T(0.5) * helper.In.x + T(0.5); + T y = T(0.5) * helper.In.y + T(0.5); + T bx = Fabsmod(m_Fr * x); + T by = Fabsmod(m_Fr * y); + T oscnapx = Foscn(m_AmountX, m_Px); + T oscnapy = Foscn(m_AmountY, m_Py); + + helper.Out.x = -1 + m_Vv2 * Lerp(Lerp(x, Fosc(x, T(4), m_Px), oscnapx), Fosc(bx, T(4), m_Px), oscnapx);//Original did a direct assignment to outPoint, which is incompatible with Ember's design. + helper.Out.y = -1 + m_Vv2 * Lerp(Lerp(y, Fosc(y, T(4), m_Py), oscnapy), Fosc(by, T(4), m_Py), oscnapy); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string repeat = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string amountX = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string amountY = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string phaseX = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string phaseY = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ax = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ay = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string px = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string py = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string fr = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string vv2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t x = 0.5 * vIn.x + 0.5;\n" + << "\t\treal_t y = 0.5 * vIn.y + 0.5;\n" + << "\t\treal_t bx = Fabsmod(" << fr << " * x);\n" + << "\t\treal_t by = Fabsmod(" << fr << " * y);\n" + << "\t\treal_t oscnapx = Foscn(" << amountX << ", " << px << ");\n" + << "\t\treal_t oscnapy = Foscn(" << amountY << ", " << py << ");\n" + << "\n" + << "\t\tvOut.x = -1 + " << vv2 << " * Lerp(Lerp(x, Fosc(x, 4, " << px << "), oscnapx), Fosc(bx, 4, " << px << "), oscnapx);\n" + << "\t\tvOut.y = -1 + " << vv2 << " * Lerp(Lerp(y, Fosc(y, 4, " << py << "), oscnapy), Fosc(by, 4, " << py << "), oscnapy);\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Ax = M_2PI * fabs(m_AmountX); + m_Ay = M_2PI * fabs(m_AmountY); + m_Px = T(M_PI) * m_PhaseX; + m_Py = T(M_PI) * m_PhaseY; + m_Fr = fabs(m_Repeat); + m_Vv2 = 2 * m_Weight; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Repeat, prefix + "coswrap_repeat", 1, INTEGER_NONZERO)); + m_Params.push_back(ParamWithName(&m_AmountX, prefix + "coswrap_amount_x")); + m_Params.push_back(ParamWithName(&m_AmountY, prefix + "coswrap_amount_y")); + m_Params.push_back(ParamWithName(&m_PhaseX, prefix + "coswrap_phase_x", 0, REAL_CYCLIC, -1, 1)); + m_Params.push_back(ParamWithName(&m_PhaseY, prefix + "coswrap_phase_y", 0, REAL_CYCLIC, -1, 1)); + m_Params.push_back(ParamWithName(true, &m_Ax, prefix + "coswrap_ax"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Ay, prefix + "coswrap_ay")); + m_Params.push_back(ParamWithName(true, &m_Px, prefix + "coswrap_px")); + m_Params.push_back(ParamWithName(true, &m_Py, prefix + "coswrap_py")); + m_Params.push_back(ParamWithName(true, &m_Fr, prefix + "coswrap_fr")); + m_Params.push_back(ParamWithName(true, &m_Vv2, prefix + "coswrap_vv2")); + } + +private: + T m_Repeat; + T m_AmountX; + T m_AmountY; + T m_PhaseX; + T m_PhaseY; + T m_Ax;//Precalc. + T m_Ay; + T m_Px; + T m_Py; + T m_Fr; + T m_Vv2; +}; + +/// +/// DeltaA. +/// The original in deltaA.c in Apophysis used a precalc variable named v, but +/// was unused in the calculation. So this remains a non-parametric variation with +/// that precalc variable omitted. +/// +template +class EMBER_API DeltaAVariation : public Variation +{ +public: + DeltaAVariation(T weight = 1.0) : Variation("deltaa", VAR_DELTA_A, weight) { } + + VARCOPY(DeltaAVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T s, c; + T avgr = m_Weight * (sqrt(SQR(helper.In.y) + SQR(helper.In.x + 1)) / sqrt(SQR(helper.In.y) + SQR(helper.In.x - 1))); + T avga = (atan2(helper.In.y, helper.In.x - 1) - atan2(helper.In.y, helper.In.x + 1)) / 2; + + sincos(avga, &s, &c); + helper.Out.x = avgr * c; + helper.Out.y = avgr * s; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t avgr = xform->m_VariationWeights[" << varIndex << "] * (sqrt(SQR(vIn.y) + SQR(vIn.x + 1)) / sqrt(SQR(vIn.y) + SQR(vIn.x - 1)));\n" + << "\t\treal_t avga = (atan2(vIn.y, vIn.x - 1) - atan2(vIn.y, vIn.x + 1)) / 2;\n" + << "\t\treal_t s = sin(avga);\n" + << "\t\treal_t c = cos(avga);\n" + << "\n" + << "\t\tvOut.x = avgr * c;\n" + << "\t\tvOut.y = avgr * s;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Expo. +/// +template +class EMBER_API ExpoVariation : public ParametricVariation +{ +public: + ExpoVariation(T weight = 1.0) : ParametricVariation("expo", VAR_EXPO, weight) + { + Init(); + } + + PARVARCOPY(ExpoVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T expor = exp(helper.In.x * m_K - helper.In.y * m_T); + T temp = helper.In.x * m_T + helper.In.y * m_K; + T snv = sin(temp); + T csv = cos(temp); + + helper.Out.x = m_Weight * expor * csv; + helper.Out.y = m_Weight * expor * snv; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string real = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string imag = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string k = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string t = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t expor = exp(vIn.x * " << k << " - vIn.y * " << t << ");\n" + << "\t\treal_t temp = vIn.x * " << t << " + vIn.y * " << k << ";\n" + << "\t\treal_t snv = sin(temp);\n" + << "\t\treal_t csv = cos(temp);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * expor * csv;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * expor * snv;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_K = T(0.5) * log(SQR(m_Real) + SQR(m_Imag) + EPS);//Original used 1e-300, which isn't representable with a float. + m_T = atan2(m_Imag, m_Real); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Real, prefix + "expo_real", -1)); + m_Params.push_back(ParamWithName(&m_Imag, prefix + "expo_imaginary", 1)); + m_Params.push_back(ParamWithName(true, &m_K, prefix + "expo_k"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_T, prefix + "expo_t")); + } + +private: + T m_Real; + T m_Imag; + T m_K;//Precalc. + T m_T; +}; + +/// +/// Extrude. +/// +template +class EMBER_API ExtrudeVariation : public ParametricVariation +{ +public: + ExtrudeVariation(T weight = 1.0) : ParametricVariation("extrude", VAR_EXTRUDE, weight) + { + Init(); + } + + PARVARCOPY(ExtrudeVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + if (m_VarType == VARTYPE_REG) + { + helper.Out.x = helper.Out.y = helper.Out.z = 0; + + if (rand.Frand01() < m_RootFace) + outPoint.m_Z = ClampGte0(m_Weight); + else + outPoint.m_Z = m_Weight * rand.Frand01(); + } + else + { + helper.Out.x = helper.In.x; + helper.Out.y = helper.In.y; + + if (rand.Frand01() < m_RootFace) + helper.Out.z = ClampGte0(m_Weight); + else + helper.Out.z = m_Weight * rand.Frand01(); + } + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string rootFace = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + if (m_VarType == VARTYPE_REG) + { + ss << "\t{\n" + << "\t\tvOut.x = vOut.y = vOut.z = 0;\n" + << "\n" + << "\t\tif (MwcNext01(mwc) < " << rootFace << ")\n" + << "\t\t outPoint->m_Z = max(xform->m_VariationWeights[" << varIndex << "], 0.0);\n" + << "\t\telse\n" + << "\t\t outPoint->m_Z = xform->m_VariationWeights[" << varIndex << "] * MwcNext01(mwc);\n" + << "\t}\n"; + } + else + { + ss << "\t{\n" + << "\t\tvOut.x = vIn.x;\n" + << "\t\tvOut.y = vIn.y;\n" + << "\n" + << "\t\tif (MwcNext01(mwc) < " << rootFace << ")\n" + << "\t\t vOut.z = max(xform->m_VariationWeights[" << varIndex << "], 0.0);\n" + << "\t\telse\n" + << "\t\t vOut.z = xform->m_VariationWeights[" << varIndex << "] * MwcNext01(mwc);\n" + << "\t}\n"; + } + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_RootFace, prefix + "extrude_root_face", T(0.5))); + } + +private: + T m_RootFace; +}; + +/// +/// fdisc. +/// +template +class EMBER_API FDiscVariation : public Variation +{ +public: + FDiscVariation(T weight = 1.0) : Variation("fdisc", VAR_FDISC, weight, true, true, false, false, true) { } + + VARCOPY(FDiscVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T c, s; + T a = M_2PI / (helper.m_PrecalcSqrtSumSquares + 1); + T r = (helper.m_PrecalcAtanyx * T(M_1_PI) + 1) * T(0.5); + + sincos(a, &s, &c); + helper.Out.x = m_Weight * r * c; + helper.Out.y = m_Weight * r * s; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t a = M_2PI / (precalcSqrtSumSquares + 1);\n" + << "\t\treal_t r = (precalcAtanyx * M_1_PI + 1) * 0.5;\n" + << "\t\treal_t s = sin(a);\n" + << "\t\treal_t c = cos(a);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * r * c;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * r * s;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Fibonacci. +/// +template +class EMBER_API FibonacciVariation : public ParametricVariation +{ +public: + FibonacciVariation(T weight = 1.0) : ParametricVariation("fibonacci", VAR_FIBONACCI, weight) + { + Init(); + } + + PARVARCOPY(FibonacciVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T snum1, cnum1, snum2, cnum2; + T temp = helper.In.y * m_NatLog; + + sincos(temp, &snum1, &cnum1); + temp = (helper.In.x * T(M_PI) + helper.In.y * m_NatLog) * -1; + sincos(temp, &snum2, &cnum2); + + T eradius1 = exp(helper.In.x * m_NatLog); + T eradius2 = exp((helper.In.x * m_NatLog - helper.In.y * T(M_PI)) * -1); + + helper.Out.x = m_Weight * (eradius1 * cnum1 - eradius2 * cnum2) * m_Five; + helper.Out.y = m_Weight * (eradius1 * snum1 - eradius2 * snum2) * m_Five; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string five = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Precalcs only, no params. + string natLog = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t temp = vIn.y * " << natLog << ";\n" + << "\t\treal_t snum1 = sin(temp);\n" + << "\t\treal_t cnum1 = cos(temp);\n" + << "\t\ttemp = (vIn.x * M_PI + vIn.y * " << natLog << ") * -1.0;\n" + << "\t\treal_t snum2 = sin(temp);\n" + << "\t\treal_t cnum2 = cos(temp);\n" + << "\t\treal_t eradius1 = exp(vIn.x * " << natLog << ");\n" + << "\t\treal_t eradius2 = exp((vIn.x * " << natLog << " - vIn.y * M_PI) * -1.0);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (eradius1 * cnum1 - eradius2 * cnum2) * " << five << ";\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (eradius1 * snum1 - eradius2 * snum2) * " << five << ";\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Five = 1 / SQRT5; + m_NatLog = log(M_PHI); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(true, &m_Five, prefix + "fibonacci_five"));//Precalcs only, no params. + m_Params.push_back(ParamWithName(true, &m_NatLog, prefix + "fibonacci_nat_log")); + } + +private: + T m_Five;//Precalcs only, no params. + T m_NatLog; +}; + +/// +/// Fibonacci2. +/// +template +class EMBER_API Fibonacci2Variation : public ParametricVariation +{ +public: + Fibonacci2Variation(T weight = 1.0) : ParametricVariation("fibonacci2", VAR_FIBONACCI2, weight) + { + Init(); + } + + PARVARCOPY(Fibonacci2Variation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T snum1, cnum1, snum2, cnum2; + T temp = helper.In.y * m_NatLog; + + sincos(temp, &snum1, &cnum1); + temp = (helper.In.x * T(M_PI) + helper.In.y * m_NatLog) * -1; + sincos(temp, &snum2, &cnum2); + + T eradius1 = m_Sc * exp(m_Sc2 * (helper.In.x * m_NatLog)); + T eradius2 = m_Sc * exp(m_Sc2 * ((helper.In.x * m_NatLog - helper.In.y * T(M_PI)) * -1)); + + helper.Out.x = m_Weight * (eradius1 * cnum1 - eradius2 * cnum2) * m_Five; + helper.Out.y = m_Weight * (eradius1 * snum1 - eradius2 * snum2) * m_Five; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string sc = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string sc2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string five = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string natLog = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t temp = vIn.y * " << natLog << ";\n" + << "\t\treal_t snum1 = sin(temp);\n" + << "\t\treal_t cnum1 = cos(temp);\n" + << "\t\ttemp = (vIn.x * M_PI + vIn.y * " << natLog << ") * -1;\n" + << "\t\treal_t snum2 = sin(temp);\n" + << "\t\treal_t cnum2 = cos(temp);\n" + << "\t\treal_t eradius1 = " << sc << " * exp(" << sc2 << " * (vIn.x * " << natLog << "));\n" + << "\t\treal_t eradius2 = " << sc << " * exp(" << sc2 << " * ((vIn.x * " << natLog << " - vIn.y * M_PI) * -1));\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (eradius1 * cnum1 - eradius2 * cnum2) * " << five << ";\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (eradius1 * snum1 - eradius2 * snum2) * " << five << ";\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Five = 1 / SQRT5; + m_NatLog = log(M_PHI); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Sc, prefix + "fibonacci2_sc", 1)); + m_Params.push_back(ParamWithName(&m_Sc2, prefix + "fibonacci2_sc2", 1)); + m_Params.push_back(ParamWithName(true, &m_Five, prefix + "fibonacci2_five"));//Precalcs. + m_Params.push_back(ParamWithName(true, &m_NatLog, prefix + "fibonacci2_nat_log")); + } + +private: + T m_Sc; + T m_Sc2; + T m_Five;//Precalcs. + T m_NatLog; +}; + +/// +/// Glynnia. +/// +template +class EMBER_API GlynniaVariation : public ParametricVariation +{ +public: + GlynniaVariation(T weight = 1.0) : ParametricVariation("glynnia", VAR_GLYNNIA, weight, true, true) + { + Init(); + } + + PARVARCOPY(GlynniaVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T d, r = helper.m_PrecalcSqrtSumSquares; + + if (r > 1) + { + if (rand.Frand01() > T(0.5)) + { + d = sqrt(r + helper.In.x); + helper.Out.x = m_V2 * d; + helper.Out.y = -(m_V2 / d * helper.In.y); + } + else + { + d = r + helper.In.x; + r = m_Weight / sqrt(r * (SQR(helper.In.y) + SQR(d))); + helper.Out.x = r * d; + helper.Out.y = r * helper.In.y; + } + } + else + { + if (rand.Frand01() > T(0.5)) + { + d = sqrt(r + helper.In.x); + helper.Out.x = -(m_V2 * d); + helper.Out.y = -(m_V2 / d * helper.In.y); + } + else + { + d = r + helper.In.x; + r = m_Weight / sqrt(r * (SQR(helper.In.y) + SQR(d))); + helper.Out.x = -(r * d); + helper.Out.y = r * helper.In.y; + } + } + + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string v2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Precalcs only, no params. + + ss << "\t{\n" + << "\t\treal_t d, r = precalcSqrtSumSquares;\n" + << "\n" + << "\t\tif (r > 1)\n" + << "\t\t{\n" + << "\t\t if (MwcNext01(mwc) > 0.5)\n" + << "\t\t {\n" + << "\t\t d = sqrt(r + vIn.x);\n" + << "\t\t vOut.x = " << v2 << " * d;\n" + << "\t\t vOut.y = -(" << v2 << " / d * vIn.y);\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t d = r + vIn.x;\n" + << "\t\t r = xform->m_VariationWeights[" << varIndex << "] / sqrt(r * (SQR(vIn.y) + SQR(d)));\n" + << "\t\t vOut.x = r * d;\n" + << "\t\t vOut.y = r * vIn.y;\n" + << "\t\t }\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t if (MwcNext01(mwc) > 0.5)\n" + << "\t\t {\n" + << "\t\t d = sqrt(r + vIn.x);\n" + << "\t\t vOut.x = -(" << v2 << " * d);\n" + << "\t\t vOut.Y = -(" << v2 << " / d * vIn.y);\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t d = r + vIn.x;\n" + << "\t\t r = xform->m_VariationWeights[" << varIndex << "] / sqrt(r * (SQR(vIn.y) + SQR(d)));\n" + << "\t\t vOut.x = -(r * d);\n" + << "\t\t vOut.y = r * vIn.y;\n" + << "\t\t }\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_V2 = m_Weight * sqrt(T(2)) / 2; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(true, &m_V2, prefix + "glynnia_v2"));//Precalcs only, no params. + } + +private: + T m_V2;//Precalcs only, no params. +}; + +/// +/// GridOut. +/// +template +class EMBER_API GridOutVariation : public Variation +{ +public: + GridOutVariation(T weight = 1.0) : Variation("gridout", VAR_GRIDOUT, weight) { } + + VARCOPY(GridOutVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T x = LRint(helper.In.x); + T y = LRint(helper.In.y); + + if (y <= 0) + { + if (x > 0) + { + if (-y >= x) + { + helper.Out.x = m_Weight * (helper.In.x + 1); + helper.Out.y = m_Weight * helper.In.y; + } + else + { + helper.Out.x = m_Weight * helper.In.x; + helper.Out.y = m_Weight * (helper.In.y + 1); + } + } + else + { + if (y <= x) + { + helper.Out.x = m_Weight * (helper.In.x + 1); + helper.Out.y = m_Weight * helper.In.y; + } + else + { + helper.Out.x = m_Weight * helper.In.x; + helper.Out.y = m_Weight * (helper.In.y - 1); + } + } + } + else + { + if (x > 0) + { + if (y >= x) + { + helper.Out.x = m_Weight * (helper.In.x - 1); + helper.Out.y = m_Weight * helper.In.y; + } + else + { + helper.Out.x = m_Weight * helper.In.x; + helper.Out.y = m_Weight * (helper.In.y + 1); + } + } + else + { + if (y > -x) + { + helper.Out.x = m_Weight * (helper.In.x - 1); + helper.Out.y = m_Weight * helper.In.y; + } + else + { + helper.Out.x = m_Weight * helper.In.x; + helper.Out.y = m_Weight * (helper.In.y - 1); + } + } + } + + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t x = LRint(vIn.x);\n" + << "\t\treal_t y = LRint(vIn.y);\n" + << "\n" + << "\t\tif (y <= 0)\n" + << "\t\t{\n" + << "\t\t if (x > 0)\n" + << "\t\t {\n" + << "\t\t if (-y >= x)\n" + << "\t\t {\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (vIn.x + 1);\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * (vIn.y + 1);\n" + << "\t\t }\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t if (y <= x)\n" + << "\t\t {\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (vIn.x + 1);\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * (vIn.y - 1);\n" + << "\t\t }\n" + << "\t\t }\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t if (x > 0)\n" + << "\t\t {\n" + << "\t\t if (y >= x)\n" + << "\t\t {\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (vIn.x - 1);\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * (vIn.y + 1);\n" + << "\t\t }\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t if (y > -x)\n" + << "\t\t {\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (vIn.x - 1);\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * (vIn.y - 1);\n" + << "\t\t }\n" + << "\t\t }\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Hole. +/// +template +class EMBER_API HoleVariation : public ParametricVariation +{ +public: + HoleVariation(T weight = 1.0) : ParametricVariation("hole", VAR_HOLE, weight, true, true, true, false, true) + { + Init(); + } + + PARVARCOPY(HoleVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r, delta = pow(helper.m_PrecalcAtanyx / T(M_PI) + 1, m_A); + + if (m_Inside != 0) + r = m_Weight * delta / (helper.m_PrecalcSqrtSumSquares + delta); + else + r = m_Weight * helper.m_PrecalcSqrtSumSquares + delta; + + helper.Out.x = r * helper.m_PrecalcCosa; + helper.Out.y = r * helper.m_PrecalcSina; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string a = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string inside = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t r, delta = pow(precalcAtanyx / M_PI + 1, " << a << ");\n" + << "\n" + << "\t\tif (" << inside << " != 0)\n" + << "\t\t r = xform->m_VariationWeights[" << varIndex << "] * delta / (precalcSqrtSumSquares + delta);\n" + << "\t\telse\n" + << "\t\t r = xform->m_VariationWeights[" << varIndex << "] * precalcSqrtSumSquares + delta;\n" + << "\n" + << "\t\tvOut.x = r * precalcCosa;\n" + << "\t\tvOut.y = r * precalcSina;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_A, prefix + "hole_a", 1)); + m_Params.push_back(ParamWithName(&m_Inside, prefix + "hole_inside", 0, INTEGER, 0, 1)); + } + +private: + T m_A; + T m_Inside; +}; + +/// +/// Hypertile. +/// +template +class EMBER_API HypertileVariation : public ParametricVariation +{ +public: + HypertileVariation(T weight = 1.0) : ParametricVariation("hypertile", VAR_HYPERTILE, weight) + { + Init(); + } + + PARVARCOPY(HypertileVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T a = helper.In.x + m_Real; + T b = helper.In.y - m_Imag; + T c = m_Real * helper.In.x - m_Imag * helper.In.y + 1; + T d = m_Real * helper.In.y + m_Imag * helper.In.x; + T vr = m_Weight / (SQR(c) + SQR(d)); + + helper.Out.x = vr * (a * c + b * d); + helper.Out.y = vr * (b * c - a * d); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string p = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string q = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string n = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string real = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string imag = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t a = vIn.x + " << real << ";\n" + << "\t\treal_t b = vIn.y - " << imag << ";\n" + << "\t\treal_t c = " << real << " * vIn.x - " << imag << " * vIn.y + 1;\n" + << "\t\treal_t d = " << real << " * vIn.y + " << imag << " * vIn.x;\n" + << "\t\treal_t vr = xform->m_VariationWeights[" << varIndex << "] / (SQR(c) + SQR(d));\n" + << "\n" + << "\t\tvOut.x = vr * (a * c + b * d);\n" + << "\t\tvOut.y = vr * (b * c - a * d);\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + T pa = 2 * T(M_PI) / m_P; + T qa = 2 * T(M_PI) / m_Q; + T r = (1 - cos(pa)) / (cos(pa) + cos(qa)) + 1; + T a = m_N * pa; + + if (r > 0) + r = 1 / sqrt(r); + else + r = 1; + + m_Real = r * cos(a); + m_Imag = r * sin(a); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_P, prefix + "hypertile_p", 3, INTEGER, 3, T(0x7fffffff))); + m_Params.push_back(ParamWithName(&m_Q, prefix + "hypertile_q", 7, INTEGER, 3, T(0x7fffffff))); + m_Params.push_back(ParamWithName(&m_N, prefix + "hypertile_n", 0, INTEGER)); + m_Params.push_back(ParamWithName(true, &m_Real, prefix + "hypertile_real"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Imag, prefix + "hypertile_imag")); + } + +private: + T m_P; + T m_Q; + T m_N; + T m_Real;//Precalc. + T m_Imag; +}; + +/// +/// Hypertile1. +/// +template +class EMBER_API Hypertile1Variation : public ParametricVariation +{ +public: + Hypertile1Variation(T weight = 1.0) : ParametricVariation("hypertile1", VAR_HYPERTILE1, weight) + { + Init(); + } + + PARVARCOPY(Hypertile1Variation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T temp = rand.Rand() * m_Pa; + T sina = sin(temp); + T cosa = cos(temp); + T re = m_R * cosa; + T im = m_R * sina; + T a = helper.In.x + re; + T b = helper.In.y - im; + T c = re * helper.In.x - im * helper.In.y + 1; + T d = re * helper.In.y + im * helper.In.x; + T vr = m_Weight / (SQR(c) + SQR(d)); + + helper.Out.x = vr * (a * c + b * d); + helper.Out.y = vr * (b * c - a * d); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string p = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string q = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string pa = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string r = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t temp = MwcNext(mwc) * " << pa << ";\n" + << "\t\treal_t sina = sin(temp);\n" + << "\t\treal_t cosa = cos(temp);\n" + << "\t\treal_t re = " << r << " * cosa;\n" + << "\t\treal_t im = " << r << " * sina;\n" + << "\t\treal_t a = vIn.x + re;\n" + << "\t\treal_t b = vIn.y - im;\n" + << "\t\treal_t c = re * vIn.x - im * vIn.y + 1;\n" + << "\t\treal_t d = re * vIn.y + im * vIn.x;\n" + << "\t\treal_t vr = xform->m_VariationWeights[" << varIndex << "] / (SQR(c) + SQR(d));\n" + << "\n" + << "\t\tvOut.x = vr * (a * c + b * d);\n" + << "\t\tvOut.y = vr * (b * c - a * d);\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + T r2 = 1 - (cos(2 * T(M_PI) / m_P) - 1) / + (cos(2 * T(M_PI) / m_P) + cos(2 * T(M_PI) / m_Q)); + + if (r2 > 0) + m_R = 1 / sqrt(r2); + else + m_R = 1; + + m_Pa = 2 * T(M_PI) / m_P; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_P, prefix + "hypertile1_p", 3, INTEGER, 3, T(0x7fffffff))); + m_Params.push_back(ParamWithName(&m_Q, prefix + "hypertile1_q", 7, INTEGER, 3, T(0x7fffffff))); + m_Params.push_back(ParamWithName(true, &m_Pa, prefix + "hypertile1_pa"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_R, prefix + "hypertile1_r")); + } + +private: + T m_P; + T m_Q; + T m_Pa;//Precalc. + T m_R; +}; + +/// +/// Hypertile2. +/// +template +class EMBER_API Hypertile2Variation : public ParametricVariation +{ +public: + Hypertile2Variation(T weight = 1.0) : ParametricVariation("hypertile2", VAR_HYPERTILE2, weight) + { + Init(); + } + + PARVARCOPY(Hypertile2Variation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T a = helper.In.x + m_R; + T b = helper.In.y; + T c = m_R * helper.In.x + 1; + T d = m_R * helper.In.y; + T x = (a * c + b * d); + T y = (b * c - a * d); + T vr = m_Weight / (SQR(c) + SQR(d)); + T temp = rand.Rand() * m_Pa; + T sina = sin(temp); + T cosa = cos(temp); + + helper.Out.x = vr * (x * cosa + y * sina); + helper.Out.y = vr * (y * cosa - x * sina); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string p = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string q = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string pa = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string r = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t a = vIn.x + " << r << ";\n" + << "\t\treal_t b = vIn.y;\n" + << "\t\treal_t c = " << r << " * vIn.x + 1;\n" + << "\t\treal_t d = " << r << " * vIn.y;\n" + << "\t\treal_t x = (a * c + b * d);\n" + << "\t\treal_t y = (b * c - a * d);\n" + << "\t\treal_t vr = xform->m_VariationWeights[" << varIndex << "] / (SQR(c) + SQR(d));\n" + << "\t\treal_t temp = MwcNext(mwc) * " << pa << ";\n" + << "\t\treal_t sina = sin(temp);\n" + << "\t\treal_t cosa = cos(temp);\n" + << "\n" + << "\t\tvOut.x = vr * (x * cosa + y * sina);\n" + << "\t\tvOut.y = vr * (y * cosa - x * sina);\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + T r2 = 1 - (cos(2 * T(M_PI) / m_P) - 1) / + (cos(2 * T(M_PI) / m_P) + cos(2 * T(M_PI) / m_Q)); + + if (r2 > 0) + m_R = 1 / sqrt(r2); + else + m_R = 1; + + m_Pa = 2 * T(M_PI) / m_P; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_P, prefix + "hypertile2_p", 3, INTEGER, 3, T(0x7fffffff))); + m_Params.push_back(ParamWithName(&m_Q, prefix + "hypertile2_q", 7, INTEGER, 3, T(0x7fffffff))); + m_Params.push_back(ParamWithName(true, &m_Pa, prefix + "hypertile2_pa"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_R, prefix + "hypertile2_r")); + } + +private: + T m_P; + T m_Q; + T m_Pa;//Precalc. + T m_R; +}; + +/// +/// Hypertile3D. +/// +template +class EMBER_API Hypertile3DVariation : public ParametricVariation +{ +public: + Hypertile3DVariation(T weight = 1.0) : ParametricVariation("hypertile3D", VAR_HYPERTILE3D, weight, true) + { + Init(); + } + + PARVARCOPY(Hypertile3DVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r2 = helper.m_PrecalcSumSquares + helper.In.z; + T x2cx = m_C2x * helper.In.x; + T y2cy = m_C2y * helper.In.y; + T d = m_Weight / (m_C2 * r2 + x2cx - y2cy + 1); + + helper.Out.x = d * (helper.In.x * m_S2x - m_Cx * ( y2cy - r2 - 1)); + helper.Out.y = d * (helper.In.y * m_S2y + m_Cy * (-x2cx - r2 - 1)); + helper.Out.z = d * (helper.In.z * m_S2z); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string p = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string q = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string n = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cx = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cy = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cz = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string s2x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string s2y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string s2z = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c2x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c2y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c2z = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t r2 = precalcSumSquares + vIn.z;\n" + << "\t\treal_t x2cx = " << c2x << " * vIn.x;\n" + << "\t\treal_t y2cy = " << c2y << " * vIn.y;\n" + << "\t\treal_t d = xform->m_VariationWeights[" << varIndex << "] / (" << c2 << " * r2 + x2cx - y2cy + 1);\n" + << "\n" + << "\t\tvOut.x = d * (vIn.x * " << s2x << " - " << cx << "* ( y2cy - r2 - 1));\n" + << "\t\tvOut.y = d * (vIn.y * " << s2y << " + " << cy << "* (-x2cx - r2 - 1));\n" + << "\t\tvOut.z = d * (vIn.z * " << s2z << ");\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + T pa = 2 * T(M_PI) / m_P; + T qa = 2 * T(M_PI) / m_Q; + T r = -(cos(pa) - 1) / (cos(pa) + cos(qa)); + T na = m_N * pa; + + if (r > 0) + r = 1 / sqrt(1 + r); + else + r = 1; + + m_Cx = r * cos(na); + m_Cy = r * sin(na); + m_C2 = SQR(m_Cx) + SQR(m_Cy); + m_C2x = 2 * m_Cx; + m_C2y = 2 * m_Cy; + m_S2x = 1 + SQR(m_Cx) - SQR(m_Cy); + m_S2y = 1 + SQR(m_Cy) - SQR(m_Cx); + m_S2z = 1 - SQR(m_Cy) - SQR(m_Cx); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_P, prefix + "hypertile3D_p", 3, INTEGER, 3, T(0x7fffffff))); + m_Params.push_back(ParamWithName(&m_Q, prefix + "hypertile3D_q", 7, INTEGER, 3, T(0x7fffffff))); + m_Params.push_back(ParamWithName(&m_N, prefix + "hypertile3D_n", 0, INTEGER)); + m_Params.push_back(ParamWithName(true, &m_Cx, prefix + "hypertile3D_cx"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Cy, prefix + "hypertile3D_cy")); + m_Params.push_back(ParamWithName(true, &m_Cz, prefix + "hypertile3D_cz")); + m_Params.push_back(ParamWithName(true, &m_S2x, prefix + "hypertile3D_s2x")); + m_Params.push_back(ParamWithName(true, &m_S2y, prefix + "hypertile3D_s2y")); + m_Params.push_back(ParamWithName(true, &m_S2z, prefix + "hypertile3D_s2z")); + m_Params.push_back(ParamWithName(true, &m_C2x, prefix + "hypertile3D_c2x")); + m_Params.push_back(ParamWithName(true, &m_C2y, prefix + "hypertile3D_c2y")); + m_Params.push_back(ParamWithName(true, &m_C2z, prefix + "hypertile3D_c2z")); + m_Params.push_back(ParamWithName(true, &m_C2, prefix + "hypertile3D_c2")); + } + +private: + T m_P; + T m_Q; + T m_N; + T m_Cx;//Precalc. + T m_Cy; + T m_Cz; + T m_S2x; + T m_S2y; + T m_S2z; + T m_C2x; + T m_C2y; + T m_C2z; + T m_C2; +}; + +/// +/// Hypertile3D1. +/// +template +class EMBER_API Hypertile3D1Variation : public ParametricVariation +{ +public: + Hypertile3D1Variation(T weight = 1.0) : ParametricVariation("hypertile3D1", VAR_HYPERTILE3D1, weight, true) + { + Init(); + } + + PARVARCOPY(Hypertile3D1Variation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T temp = rand.Rand() * m_Pa; + T cx = m_R * cos(temp); + T cy = m_R * sin(temp); + T s2x = 1 + SQR(cx) - SQR(cy); + T s2y = 1 + SQR(cy) - SQR(cx); + T r2 = helper.m_PrecalcSumSquares + SQR(helper.In.z); + T x2cx = 2 * cx * helper.In.x; + T y2cy = 2 * cy * helper.In.x; + T d = m_Weight / (m_C2 * r2 + x2cx - y2cy + 1); + + helper.Out.x = d * (helper.In.x * s2x - cx * ( y2cy - r2 - 1)); + helper.Out.y = d * (helper.In.y * s2y + cy * (-x2cx - r2 - 1)); + helper.Out.z = d * (helper.In.z * m_S2z); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string p = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string q = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string pa = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string r = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string s2z = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t temp = MwcNext(mwc) * " << pa << ";\n" + << "\t\treal_t cx = " << r << " * cos(temp);\n" + << "\t\treal_t cy = " << r << " * sin(temp);\n" + << "\t\treal_t s2x = 1 + SQR(cx) - SQR(cy);\n" + << "\t\treal_t s2y = 1 + SQR(cy) - SQR(cx);\n" + << "\t\treal_t r2 = precalcSumSquares + SQR(vIn.z);\n" + << "\t\treal_t x2cx = 2 * cx * vIn.x;\n" + << "\t\treal_t y2cy = 2 * cy * vIn.x;\n" + << "\t\treal_t d = xform->m_VariationWeights[" << varIndex << "] / (" << c2 << " * r2 + x2cx - y2cy + 1);\n" + << "\n" + << "\t\tvOut.x = d * (vIn.x * s2x - cx * ( y2cy - r2 - 1));\n" + << "\t\tvOut.y = d * (vIn.y * s2y + cy * (-x2cx - r2 - 1));\n" + << "\t\tvOut.z = d * (vIn.z * " << s2z << ");\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + T pa = M_2PI / m_P; + T qa = M_2PI / m_Q; + T r = -(cos(pa) - 1) / (cos(pa) + cos(qa)); + + if (r > 0) + r = 1 / sqrt(1 + r); + else + r = 1; + + m_Pa = pa; + m_R = r; + m_C2 = SQR(r); + m_S2z = 1 - m_C2; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_P, prefix + "hypertile3D1_p", 3, INTEGER, 3, T(0x7fffffff))); + m_Params.push_back(ParamWithName(&m_Q, prefix + "hypertile3D1_q", 7, INTEGER, 3, T(0x7fffffff))); + m_Params.push_back(ParamWithName(true, &m_Pa, prefix + "hypertile3D1_pa"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_R, prefix + "hypertile3D1_r")); + m_Params.push_back(ParamWithName(true, &m_C2, prefix + "hypertile3D1_c2")); + m_Params.push_back(ParamWithName(true, &m_S2z, prefix + "hypertile3D1_s2z")); + } + +private: + T m_P; + T m_Q; + T m_Pa;//Precalc. + T m_R; + T m_C2; + T m_S2z; +}; + +/// +/// Hypertile3D2. +/// +template +class EMBER_API Hypertile3D2Variation : public ParametricVariation +{ +public: + Hypertile3D2Variation(T weight = 1.0) : ParametricVariation("hypertile3D2", VAR_HYPERTILE3D2, weight, true) + { + Init(); + } + + PARVARCOPY(Hypertile3D2Variation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r2 = helper.m_PrecalcSumSquares + SQR(helper.In.z); + T x2cx = m_C2x * helper.In.x; + T x = helper.In.x * m_S2x - m_Cx * (-r2 - 1); + T y = helper.In.y * m_S2y; + T vr = m_Weight / (m_C2 * r2 + x2cx + 1); + T temp = rand.Rand() * m_Pa; + T sina = sin(temp); + T cosa = cos(temp); + + helper.Out.x = vr * (x * cosa + y * sina); + helper.Out.y = vr * (y * cosa - x * sina); + helper.Out.z = vr * (helper.In.z * m_S2z); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string p = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string q = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string pa = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cx = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c2x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string s2x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string s2y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string s2z = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t r2 = precalcSumSquares + SQR(vIn.z);\n" + << "\t\treal_t x2cx = " << c2x << " * vIn.x;\n" + << "\t\treal_t x = vIn.x * " << s2x << " - " << cx << " * (-r2 - 1);\n" + << "\t\treal_t y = vIn.y * " << s2y << ";\n" + << "\t\treal_t vr = xform->m_VariationWeights[" << varIndex << "] / (" << c2 << " * r2 + x2cx + 1);\n" + << "\t\treal_t temp = MwcNext(mwc) * " << pa << ";\n" + << "\t\treal_t sina = sin(temp);\n" + << "\t\treal_t cosa = cos(temp);\n" + << "\n" + << "\t\tvOut.x = vr * (x * cosa + y * sina);\n" + << "\t\tvOut.y = vr * (y * cosa - x * sina);\n" + << "\t\tvOut.z = vr * (vIn.z * " << s2z << ");\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + T pa = M_2PI / m_P; + T qa = M_2PI / m_Q; + T r = -(cos(pa) - 1) / (cos(pa) + cos(qa)); + + if (r > 0) + r = 1 / sqrt(1 + r); + else + r = 1; + + m_Pa = pa; + m_Cx = r; + m_C2 = SQR(m_Cx); + m_C2x = 2 * m_Cx; + m_S2x = 1 + SQR(m_Cx); + m_S2y = 1 - SQR(m_Cx); + m_S2z = 1 - SQR(m_Cx); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_P, prefix + "hypertile3D2_p", 3, INTEGER, 3, T(0x7fffffff))); + m_Params.push_back(ParamWithName(&m_Q, prefix + "hypertile3D2_q", 7, INTEGER, 3, T(0x7fffffff))); + m_Params.push_back(ParamWithName(true, &m_Pa, prefix + "hypertile3D2_pa"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Cx, prefix + "hypertile3D2_cx")); + m_Params.push_back(ParamWithName(true, &m_C2, prefix + "hypertile3D2_c2")); + m_Params.push_back(ParamWithName(true, &m_C2x, prefix + "hypertile3D2_c2x")); + m_Params.push_back(ParamWithName(true, &m_S2x, prefix + "hypertile3D2_s2x")); + m_Params.push_back(ParamWithName(true, &m_S2y, prefix + "hypertile3D2_s2y")); + m_Params.push_back(ParamWithName(true, &m_S2z, prefix + "hypertile3D2_s2z")); + } + +private: + T m_P; + T m_Q; + T m_Pa;//Precalc. + T m_Cx; + T m_C2; + T m_C2x; + T m_S2x; + T m_S2y; + T m_S2z; +}; + +/// +/// IDisc. +/// +template +class EMBER_API IDiscVariation : public ParametricVariation +{ +public: + IDiscVariation(T weight = 1.0) : ParametricVariation("idisc", VAR_IDISC, weight, true, true, false, false, true) + { + Init(); + } + + PARVARCOPY(IDiscVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T a = T(M_PI) / (helper.m_PrecalcSqrtSumSquares + 1); + T s = sin(a); + T c = cos(a); + T r = helper.m_PrecalcAtanyx * m_V; + + helper.Out.x = r * c; + helper.Out.y = r * s; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string v = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Precalcs only, no params. + + ss << "\t{\n" + << "\t\treal_t a = M_PI / (precalcSqrtSumSquares + 1);\n" + << "\t\treal_t s = sin(a);\n" + << "\t\treal_t c = cos(a);\n" + << "\t\treal_t r = precalcAtanyx * " << v << ";\n" + << "\n" + << "\t\tvOut.x = r * c;\n" + << "\t\tvOut.y = r * s;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_V = m_Weight * T(M_1_PI); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(true, &m_V, prefix + "idisc_v"));//Precalcs only, no params. + } + +private: + T m_V;//Precalcs only, no params. +}; + +/// +/// Julian2. +/// +template +class EMBER_API Julian2Variation : public ParametricVariation +{ +public: + Julian2Variation(T weight = 1.0) : ParametricVariation("julian2", VAR_JULIAN2, weight) + { + Init(); + } + + PARVARCOPY(Julian2Variation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T x = m_A * helper.In.x + m_B * helper.In.y + m_E; + T y = m_C * helper.In.x + m_D * helper.In.y + m_F; + T angle = (atan2(y, x) + M_2PI * rand.Rand((int)m_AbsN)) / m_Power; + T sina = sin(angle); + T cosa = cos(angle); + T r = m_Weight * pow(SQR(x) + SQR(y), m_Cn); + + helper.Out.x = r * cosa; + helper.Out.y = r * sina; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string a = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string b = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string d = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string e = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string f = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string power = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string dist = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string absn = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cn = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t x = " << a << " * vIn.x + " << b << " * vIn.y + " << e << ";\n" + << "\t\treal_t y = " << c << " * vIn.x + " << d << " * vIn.y + " << f << ";\n" + << "\t\treal_t angle = (atan2(y, x) + M_2PI * MwcNextRange(mwc, (uint)" << absn << ")) / " << power << ";\n" + << "\t\treal_t sina = sin(angle);\n" + << "\t\treal_t cosa = cos(angle);\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * pow(SQR(x) + SQR(y), " << cn << ");\n" + << "\n" + << "\t\tvOut.x = r * cosa;\n" + << "\t\tvOut.y = r * sina;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + if (m_Power == 0) + m_Power = 2; + + m_AbsN = T((int)abs(m_Power)); + m_Cn = m_Dist / m_Power / 2; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_A, prefix + "julian2_a", 1)); + m_Params.push_back(ParamWithName(&m_B, prefix + "julian2_b")); + m_Params.push_back(ParamWithName(&m_C, prefix + "julian2_c")); + m_Params.push_back(ParamWithName(&m_D, prefix + "julian2_d", 1)); + m_Params.push_back(ParamWithName(&m_E, prefix + "julian2_e")); + m_Params.push_back(ParamWithName(&m_F, prefix + "julian2_f")); + m_Params.push_back(ParamWithName(&m_Power, prefix + "julian2_power", 2, INTEGER_NONZERO)); + m_Params.push_back(ParamWithName(&m_Dist, prefix + "julian2_dist", 1)); + m_Params.push_back(ParamWithName(true, &m_AbsN, prefix + "julian2_absn"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Cn, prefix + "julian2_cn")); + } + +private: + T m_A; + T m_B; + T m_C; + T m_D; + T m_E; + T m_F; + T m_Power; + T m_Dist; + T m_AbsN;//Precalc. + T m_Cn; +}; + +/// +/// JuliaQ. +/// +template +class EMBER_API JuliaQVariation : public ParametricVariation +{ +public: + JuliaQVariation(T weight = 1.0) : ParametricVariation("juliaq", VAR_JULIAQ, weight, true, false, false, false, true) + { + Init(); + } + + PARVARCOPY(JuliaQVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T a = helper.m_PrecalcAtanyx * m_InvPower + rand.Rand() * m_InvPower2pi; + T sina = sin(a); + T cosa = cos(a); + T r = m_Weight * pow(helper.m_PrecalcSumSquares, m_HalfInvPower); + + helper.Out.x = r * cosa; + helper.Out.y = r * sina; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string power = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string divisor = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string halfInvPower = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string invPower = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string invPower2Pi = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t a = precalcAtanyx * " << invPower << " + MwcNext(mwc) * " << invPower2Pi << ";\n" + << "\t\treal_t sina = sin(a);\n" + << "\t\treal_t cosa = cos(a);\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * pow(precalcSumSquares, " << halfInvPower << ");\n" + << "\n" + << "\t\tvOut.x = r * cosa;\n" + << "\t\tvOut.y = r * sina;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_HalfInvPower = T(0.5) * m_Divisor / m_Power; + m_InvPower = m_Divisor / m_Power; + m_InvPower2pi = M_2PI / m_Power; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Power, prefix + "juliaq_power", 3, INTEGER_NONZERO)); + m_Params.push_back(ParamWithName(&m_Divisor, prefix + "juliaq_divisor", 2, INTEGER_NONZERO)); + m_Params.push_back(ParamWithName(true, &m_HalfInvPower, prefix + "juliaq_half_inv_power"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_InvPower, prefix + "juliaq_inv_power")); + m_Params.push_back(ParamWithName(true, &m_InvPower2pi, prefix + "juliaq_inv_power_2pi")); + } + +private: + T m_Power; + T m_Divisor; + T m_HalfInvPower;//Precalc. + T m_InvPower; + T m_InvPower2pi; +}; + +/// +/// Murl. +/// +template +class EMBER_API MurlVariation : public ParametricVariation +{ +public: + MurlVariation(T weight = 1.0) : ParametricVariation("murl", VAR_MURL, weight, true, false, false, false, true) + { + Init(); + } + + PARVARCOPY(MurlVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T angle = helper.m_PrecalcAtanyx * m_Power; + T sina = sin(angle); + T cosa = cos(angle); + T r = m_Cp * pow(helper.m_PrecalcSumSquares, m_P2); + T re = r * cosa + 1; + T im = r * sina; + T r1 = m_Vp / (SQR(re) + SQR(im)); + + helper.Out.x = r1 * (helper.In.x * re + helper.In.y * im); + helper.Out.y = r1 * (helper.In.y * re - helper.In.x * im); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string c = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string power = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cp = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string p2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string vp = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t angle = precalcAtanyx * " << power << ";\n" + << "\t\treal_t sina = sin(angle);\n" + << "\t\treal_t cosa = cos(angle);\n" + << "\t\treal_t r = " << cp << " * pow(precalcSumSquares, " << p2 << ");\n" + << "\t\treal_t re = r * cosa + 1;\n" + << "\t\treal_t im = r * sina;\n" + << "\t\treal_t r1 = " << vp << " / (SQR(re) + SQR(im));\n" + << "\n" + << "\t\tvOut.x = r1 * (vIn.x * re + vIn.y * im);\n" + << "\t\tvOut.y = r1 * (vIn.y * re - vIn.x * im);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + if (m_Power != 1) + m_Cp = m_C / (m_Power - 1); + else + m_Cp = m_C; + + m_P2 = m_Power / 2; + m_Vp = m_Weight * (m_Cp + 1); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_C, prefix + "murl_c")); + m_Params.push_back(ParamWithName(&m_Power, prefix + "murl_power", 2, INTEGER, 2, T(0x7fffffff))); + m_Params.push_back(ParamWithName(true, &m_Cp, prefix + "murl_cp"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_P2, prefix + "murl_p2")); + m_Params.push_back(ParamWithName(true, &m_Vp, prefix + "murl_vp")); + } + +private: + T m_C; + T m_Power; + T m_Cp;//Precalc. + T m_P2; + T m_Vp; +}; + +/// +/// Murl2. +/// +template +class EMBER_API Murl2Variation : public ParametricVariation +{ +public: + Murl2Variation(T weight = 1.0) : ParametricVariation("murl2", VAR_MURL2, weight, true, false, false, false, true) + { + Init(); + } + + PARVARCOPY(Murl2Variation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T angle = helper.m_PrecalcAtanyx * m_Power; + T sina = sin(angle); + T cosa = cos(angle); + T r = m_C * pow(helper.m_PrecalcSumSquares, m_P2); + T re = r * cosa + 1; + T im = r * sina; + + r = pow(SQR(re) + SQR(im), m_InvP); + angle = atan2(im, re) * m_InvP2; + sina = sin(angle); + cosa = cos(angle); + re = r * cosa; + im = r * sina; + + T r1 = m_Vp / SQR(r); + + helper.Out.x = r1 * (helper.In.x * re + helper.In.y * im); + helper.Out.y = r1 * (helper.In.y * re - helper.In.x * im); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string c = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string power = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string p2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string invp = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string invp2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string vp = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t angle = precalcAtanyx * " << power << ";\n" + << "\t\treal_t sina = sin(angle);\n" + << "\t\treal_t cosa = cos(angle);\n" + << "\t\treal_t r = " << c << " * pow(precalcSumSquares, " << p2 << ");\n" + << "\t\treal_t re = r * cosa + 1;\n" + << "\t\treal_t im = r * sina;\n" + << "\n" + << "\t\tr = pow(SQR(re) + SQR(im), " << invp << ");\n" + << "\t\tangle = atan2(im, re) * " << invp2 << ";\n" + << "\t\tsina = sin(angle);\n" + << "\t\tcosa = cos(angle);\n" + << "\t\tre = r * cosa;\n" + << "\t\tim = r * sina;\n" + << "\n" + << "\t\treal_t r1 = " << vp << " / SQR(r);\n" + << "\n" + << "\t\tvOut.x = r1 * (vIn.x * re + vIn.y * im);\n" + << "\t\tvOut.y = r1 * (vIn.y * re - vIn.x * im);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_P2 = m_Power / 2; + m_InvP = 1 / m_Power; + m_InvP2 = 2 / m_Power; + + if (m_C == -1) + m_Vp = 0; + else + m_Vp = m_Weight * pow(m_C + 1, 2 / m_Power); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_C, prefix + "murl2_c", 0, REAL, -1, 1)); + m_Params.push_back(ParamWithName(&m_Power, prefix + "murl2_power", 1, INTEGER_NONZERO)); + m_Params.push_back(ParamWithName(true, &m_P2, prefix + "murl2_p2"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_InvP, prefix + "murl2_invp")); + m_Params.push_back(ParamWithName(true, &m_InvP2, prefix + "murl2_invp2")); + m_Params.push_back(ParamWithName(true, &m_Vp, prefix + "murl2_vp")); + } + +private: + T m_C; + T m_Power; + T m_P2;//Precalc. + T m_InvP; + T m_InvP2; + T m_Vp; +}; + +/// +/// NPolar. +/// +template +class EMBER_API NPolarVariation : public ParametricVariation +{ +public: + NPolarVariation(T weight = 1.0) : ParametricVariation("npolar", VAR_NPOLAR, weight, true, false, false, true, false) + { + Init(); + } + + PARVARCOPY(NPolarVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T x = (m_IsOdd != 0) ? helper.In.x : m_Vvar * helper.m_PrecalcAtanxy; + T y = (m_IsOdd != 0) ? helper.In.y : m_Vvar2 * log(helper.m_PrecalcSumSquares); + T angle = (atan2(y, x) + M_2PI * rand.Rand((int)m_AbsN)) / m_Nnz; + T r = m_Weight * pow(SQR(x) + SQR(y), m_Cn) * ((m_IsOdd == 0) ? 1 : m_Parity); + T sina = sin(angle) * r; + T cosa = cos(angle) * r; + + x = (m_IsOdd != 0) ? cosa : (m_Vvar2 * log(SQR(cosa) + SQR(sina))); + y = (m_IsOdd != 0) ? sina : (m_Vvar * atan2(cosa, sina)); + helper.Out.x = x; + helper.Out.y = y; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string parity = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string n = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string nnz = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string vvar = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string vvar2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string absn = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cn = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string isOdd = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t x = (" << isOdd << " != 0) ? vIn.x : " << vvar << " * precalcAtanxy;\n" + << "\t\treal_t y = (" << isOdd << " != 0) ? vIn.y : " << vvar2 << " * log(precalcSumSquares);\n" + << "\t\treal_t angle = (atan2(y, x) + M_2PI * MwcNextRange(mwc, (uint)" << absn << ")) / " << nnz << ";\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * pow(SQR(x) + SQR(y), " << cn << ") * ((" << isOdd << " == 0) ? 1 : " << parity << ");\n" + << "\t\treal_t sina = sin(angle) * r;\n" + << "\t\treal_t cosa = cos(angle) * r;\n" + << "\n" + << "\t\tx = (" << isOdd << " != 0) ? cosa : (" << vvar2 << " * log(SQR(cosa) + SQR(sina)));\n" + << "\t\ty = (" << isOdd << " != 0) ? sina : (" << vvar << " * atan2(cosa, sina));\n" + << "\t\tvOut.x = x;\n" + << "\t\tvOut.y = y;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Nnz = (m_N == 0) ? 1 : m_N; + m_Vvar = m_Weight / T(M_PI); + m_Vvar2 = m_Vvar * T(0.5); + m_AbsN = abs(m_Nnz); + m_Cn = 1 / m_Nnz / 2; + m_IsOdd = T(abs((int)m_Parity) % 2); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Parity, prefix + "npolar_parity", 0, INTEGER)); + m_Params.push_back(ParamWithName(&m_N, prefix + "npolar_n", 1, INTEGER)); + m_Params.push_back(ParamWithName(true, &m_Nnz, prefix + "npolar_nnz"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Vvar, prefix + "npolar_vvar")); + m_Params.push_back(ParamWithName(true, &m_Vvar2, prefix + "npolar_vvar_2")); + m_Params.push_back(ParamWithName(true, &m_AbsN, prefix + "npolar_absn")); + m_Params.push_back(ParamWithName(true, &m_Cn, prefix + "npolar_cn")); + m_Params.push_back(ParamWithName(true, &m_IsOdd, prefix + "npolar_isodd")); + } + +private: + T m_Parity; + T m_N; + T m_Nnz;//Precalc. + T m_Vvar; + T m_Vvar2; + T m_AbsN; + T m_Cn; + T m_IsOdd; +}; + +/// +/// Ortho. +/// +template +class EMBER_API OrthoVariation : public ParametricVariation +{ +public: + OrthoVariation(T weight = 1.0) : ParametricVariation("ortho", VAR_ORTHO, weight, true, false, false, false, true) + { + Init(); + } + + PARVARCOPY(OrthoVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r, a; + T xo; + T ro; + T c,s; + T x, y, tc, ts; + T theta; + + r = helper.m_PrecalcSumSquares; + + if (r < 1) + { + if (helper.In.x >= 0) + { + xo = (r + 1) / (2 * helper.In.x); + ro = sqrt(SQR(helper.In.x - xo) + SQR(helper.In.y)); + theta = atan2(1, ro); + a = fmod(m_In * theta + atan2(helper.In.y, xo - helper.In.x) + theta, 2 * theta) - theta; + sincos(a, &s, &c); + + helper.Out.x = m_Weight * (xo - c * ro); + helper.Out.y = m_Weight * s * ro; + } + else + { + xo = - (r + 1) / (2 * helper.In.x); + ro = sqrt(SQR(-helper.In.x - xo) + SQR(helper.In.y)); + theta = atan2(1 , ro); + a = fmod(m_In * theta + atan2(helper.In.y, xo + helper.In.x) + theta, 2 * theta) - theta; + sincos(a, &s, &c); + + helper.Out.x = -(m_Weight * (xo - c * ro)); + helper.Out.y = m_Weight * s * ro; + } + } + else + { + r = 1 / sqrt(r); + ts = sin(helper.m_PrecalcAtanyx); + tc = cos(helper.m_PrecalcAtanyx); + x = r * tc; + y = r * ts; + + if (x >= 0) + { + xo = (SQR(x) + SQR(y) + 1) / (2 * x); + ro = sqrt(SQR(x - xo) + SQR(y)); + theta = atan2(1 , ro); + a = fmod(m_Out * theta + atan2(y, xo - x) + theta, 2 * theta) - theta; + sincos(a, &s, &c); + + x = (xo - c * ro); + y = s * ro; + theta = atan2(y, x); + sincos(theta, &ts, &tc); + r = 1 / sqrt(SQR(x) + SQR(y)); + + helper.Out.x = m_Weight * r * tc; + helper.Out.y = m_Weight * r * ts; + } + else + { + xo = - (SQR(x) + SQR(y) + 1) / (2 * x); + ro = sqrt(SQR(-x - xo) + SQR(y)); + theta = atan2(1 , ro); + a = fmod(m_Out * theta + atan2(y, xo + x) + theta, 2 * theta) - theta; + sincos(a, &s, &c); + + x = (xo - c * ro); + y = s * ro; + theta = atan2(y, x); + sincos(theta, &ts, &tc); + r = 1 / sqrt(SQR(x) + SQR(y)); + + helper.Out.x = -(m_Weight * r * tc); + helper.Out.y = m_Weight * r * ts; + } + } + + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string in = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string out = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t r, a;\n" + << "\t\treal_t xo;\n" + << "\t\treal_t ro;\n" + << "\t\treal_t c,s;\n" + << "\t\treal_t x, y, tc, ts;\n" + << "\t\treal_t theta;\n" + << "\n" + << "\t\tr = precalcSumSquares;\n" + << "\n" + << "\t\tif (r < 1)\n" + << "\t\t{\n" + << "\t\t if (vIn.x >= 0)\n" + << "\t\t {\n" + << "\t\t xo = (r + 1) / (2 * vIn.x);\n" + << "\t\t ro = sqrt(SQR(vIn.x - xo) + SQR(vIn.y));\n" + << "\t\t theta = atan2(1, ro);\n" + << "\t\t a = fmod(" << in << " * theta + atan2(vIn.y, xo - vIn.x) + theta, 2 * theta) - theta;\n" + << "\t\t s = sin(a);\n" + << "\t\t c = cos(a);\n" + << "\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (xo - c * ro);\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * s * ro;\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t xo = - (r + 1) / (2 * vIn.x);\n" + << "\t\t ro = sqrt(SQR(-vIn.x - xo) + SQR(vIn.y));\n" + << "\t\t theta = atan2(1 , ro);\n" + << "\t\t a = fmod(" << in << " * theta + atan2(vIn.y, xo + vIn.x) + theta, 2 * theta) - theta;\n" + << "\t\t s = sin(a);\n" + << "\t\t c = cos(a);\n" + << "\n" + << "\t\t vOut.x = -(xform->m_VariationWeights[" << varIndex << "] * (xo - c * ro));\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * s * ro;\n" + << "\t\t }\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t r = 1 / sqrt(r);\n" + << "\t\t ts = sin(precalcAtanyx);\n" + << "\t\t tc = cos(precalcAtanyx);\n" + << "\t\t x = r * tc;\n" + << "\t\t y = r * ts;\n" + << "\n" + << "\t\t if (x >= 0)\n" + << "\t\t {\n" + << "\t\t xo = (SQR(x) + SQR(y) + 1) / (2 * x);\n" + << "\t\t ro = sqrt(SQR(x - xo) + SQR(y));\n" + << "\t\t theta = atan2(1 , ro);\n" + << "\t\t a = fmod(" << out << " * theta + atan2(y, xo - x) + theta, 2 * theta) - theta;\n" + << "\t\t s = sin(a);\n" + << "\t\t c = cos(a);\n" + << "\n" + << "\t\t x = (xo - c * ro);\n" + << "\t\t y = s * ro;\n" + << "\t\t theta = atan2(y, x);\n" + << "\t\t ts = sin(theta);\n" + << "\t\t tc = cos(theta);\n" + << "\t\t r = 1 / sqrt(SQR(x) + SQR(y));\n" + << "\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * r * tc;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * r * ts;\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t xo = - (SQR(x) + SQR(y) + 1) / (2 * x);\n" + << "\t\t ro = sqrt(SQR(-x - xo) + SQR(y));\n" + << "\t\t theta = atan2(1 , ro);\n" + << "\t\t a = fmod(" << out << " * theta + atan2(y, xo + x) + theta, 2 * theta) - theta;\n" + << "\t\t s = sin(a);\n" + << "\t\t c = cos(a);\n" + << "\n" + << "\t\t x = (xo - c * ro);\n" + << "\t\t y = s * ro;\n" + << "\t\t theta = atan2(y, x);\n" + << "\t\t ts = sin(theta);\n" + << "\t\t tc = cos(theta);\n" + << "\t\t r = 1 / sqrt(SQR(x) + SQR(y));\n" + << "\n" + << "\t\t vOut.x = -(xform->m_VariationWeights[" << varIndex << "] * r * tc);\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * r * ts;\n" + << "\t\t }\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_In, prefix + "ortho_in", 0, REAL_CYCLIC, T(-M_PI), T(M_PI))); + m_Params.push_back(ParamWithName(&m_Out, prefix + "ortho_out", 0, REAL_CYCLIC, T(-M_PI), T(M_PI))); + } + +private: + T m_In; + T m_Out; +}; + +/// +/// Poincare. +/// +template +class EMBER_API PoincareVariation : public ParametricVariation +{ +public: + PoincareVariation(T weight = 1.0) : ParametricVariation("poincare", VAR_POINCARE, weight) + { + Init(); + } + + PARVARCOPY(PoincareVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T x = m_C1x + (SQR(m_C1r) * (helper.In.x - m_C1x)) / (SQR(helper.In.x - m_C1x) + SQR(helper.In.y - m_C1y)); + T y = m_C1y + (SQR(m_C1r) * (helper.In.y - m_C1y)) / (SQR(helper.In.x - m_C1x) + SQR(helper.In.y - m_C1y)); + + helper.Out.x = m_C2x + (SQR(m_C2r) * (x - m_C2x)) / (SQR(x - m_C2x) + SQR(y - m_C2y)); + helper.Out.y = m_C2y + (SQR(m_C2r) * (y - m_C2y)) / (SQR(x - m_C2x) + SQR(y - m_C2y)); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string c1r = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c1a = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c2r = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c2a = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c1x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c1y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c2x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c2y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c1d = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c2d = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t x = " << c1x << " + (SQR(" << c1r << ") * (vIn.x - " << c1x << ")) / (SQR(vIn.x - " << c1x << ") + SQR(vIn.y - " << c1y << "));\n" + << "\t\treal_t y = " << c1y << " + (SQR(" << c1r << ") * (vIn.y - " << c1y << ")) / (SQR(vIn.x - " << c1x << ") + SQR(vIn.y - " << c1y << "));\n" + << "\n" + << "\t\tvOut.x = " << c2x << " + (SQR(" << c2r << ") * (x - " << c2x << ")) / (SQR(x - " << c2x << ") + SQR(y - " << c2y << "));\n" + << "\t\tvOut.y = " << c2y << " + (SQR(" << c2r << ") * (y - " << c2y << ")) / (SQR(x - " << c2x << ") + SQR(y - " << c2y << "));\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_C1d = sqrt(1 + SQR(m_C1r)); + m_C2d = sqrt(1 + SQR(m_C2r)); + + m_C1x = m_C1d * cos(fmod(m_C1a, T(M_PI))); + m_C1y = m_C1d * sin(fmod(m_C1a, T(M_PI))); + m_C2x = m_C2d * cos(fmod(m_C2a, T(M_PI))); + m_C2y = m_C2d * sin(fmod(m_C2a, T(M_PI))); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_C1r, prefix + "poincare_c1r", 1)); + m_Params.push_back(ParamWithName(&m_C1a, prefix + "poincare_c1a", -1, REAL_CYCLIC, T(-M_PI), T(M_PI))); + m_Params.push_back(ParamWithName(&m_C2r, prefix + "poincare_c2r", 1)); + m_Params.push_back(ParamWithName(&m_C2a, prefix + "poincare_c2a", 1, REAL_CYCLIC, T(-M_PI), T(M_PI))); + m_Params.push_back(ParamWithName(true, &m_C1x, prefix + "poincare_c1x"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_C1y, prefix + "poincare_c1y")); + m_Params.push_back(ParamWithName(true, &m_C2x, prefix + "poincare_c2x")); + m_Params.push_back(ParamWithName(true, &m_C2y, prefix + "poincare_c2y")); + m_Params.push_back(ParamWithName(true, &m_C1d, prefix + "poincare_c1d")); + m_Params.push_back(ParamWithName(true, &m_C2d, prefix + "poincare_c2d")); + } + +private: + T m_C1r; + T m_C1a; + T m_C2r; + T m_C2a; + T m_C1x;//Precalc. + T m_C1y; + T m_C2x; + T m_C2y; + T m_C1d; + T m_C2d; +}; + +/// +/// Poincare3D. +/// +template +class EMBER_API Poincare3DVariation : public ParametricVariation +{ +public: + Poincare3DVariation(T weight = 1.0) : ParametricVariation("poincare3D", VAR_POINCARE3D, weight, true) + { + Init(); + } + + PARVARCOPY(Poincare3DVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r2 = helper.m_PrecalcSumSquares + SQR(helper.In.z); + T x2cx = m_C2x * helper.In.x; + T y2cy = m_C2y * helper.In.y; + T z2cz = m_C2z * helper.In.z; + T val = m_C2 * r2 - x2cx - y2cy - z2cz + 1; + + if (val == 0) + val += EPS6; + + T d = m_Weight / val; + + helper.Out.x = d * (helper.In.x * m_S2x + m_Cx * (y2cy + z2cz - r2 - 1)); + helper.Out.y = d * (helper.In.y * m_S2y + m_Cy * (x2cx + z2cz - r2 - 1)); + helper.Out.z = d * (helper.In.z * m_S2z + m_Cz * (y2cy + x2cx - r2 - 1)); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string r = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string a = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string b = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cx = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cy = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cz = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c2x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c2y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c2z = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string s2x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string s2y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string s2z = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t r2 = precalcSumSquares + SQR(vIn.z);\n" + << "\t\treal_t x2cx = " << c2x << " * vIn.x;\n" + << "\t\treal_t y2cy = " << c2y << " * vIn.y;\n" + << "\t\treal_t z2cz = " << c2z << " * vIn.z;\n" + << "\t\treal_t val = " << c2 << " * r2 - x2cx - y2cy - z2cz + 1.0;\n" + << "\n" + << "\t\tif (val == 0.0)\n" + << "\t\t val += EPS6;\n" + << "\n" + << "\t\treal_t d = xform->m_VariationWeights[" << varIndex << "] / val;\n" + << "\n" + << "\t\tvOut.x = d * (vIn.x * " << s2x << " + " << cx << " * (y2cy + z2cz - r2 - 1.0));\n" + << "\t\tvOut.y = d * (vIn.y * " << s2y << " + " << cy << " * (x2cx + z2cz - r2 - 1.0));\n" + << "\t\tvOut.z = d * (vIn.z * " << s2z << " + " << cz << " * (y2cy + x2cx - r2 - 1.0));\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Cx = -m_R * cos(m_A * T(M_PI_2)) * cos(m_B * T(M_PI_2)); + m_Cy = m_R * sin(m_A * T(M_PI_2)) * cos(m_B * T(M_PI_2)); + m_Cz = -m_R * sin(m_B * T(M_PI_2)); + + m_C2 = SQR(m_Cx) + SQR(m_Cy) + SQR(m_Cz); + + m_C2x = 2 * m_Cx; + m_C2y = 2 * m_Cy; + m_C2z = 2 * m_Cz; + + m_S2x = SQR(m_Cx) - SQR(m_Cy) - SQR(m_Cz) + 1; + m_S2y = SQR(m_Cy) - SQR(m_Cx) - SQR(m_Cz) + 1; + m_S2z = SQR(m_Cz) - SQR(m_Cy) - SQR(m_Cx) + 1; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_R, prefix + "poincare3D_r")); + m_Params.push_back(ParamWithName(&m_A, prefix + "poincare3D_a")); + m_Params.push_back(ParamWithName(&m_B, prefix + "poincare3D_b")); + m_Params.push_back(ParamWithName(true, &m_Cx, prefix + "poincare3D_cx"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Cy, prefix + "poincare3D_cy")); + m_Params.push_back(ParamWithName(true, &m_Cz, prefix + "poincare3D_cz")); + m_Params.push_back(ParamWithName(true, &m_C2, prefix + "poincare3D_c2")); + m_Params.push_back(ParamWithName(true, &m_C2x, prefix + "poincare3D_c2x")); + m_Params.push_back(ParamWithName(true, &m_C2y, prefix + "poincare3D_c2y")); + m_Params.push_back(ParamWithName(true, &m_C2z, prefix + "poincare3D_c2z")); + m_Params.push_back(ParamWithName(true, &m_S2x, prefix + "poincare3D_s2x")); + m_Params.push_back(ParamWithName(true, &m_S2y, prefix + "poincare3D_s2y")); + m_Params.push_back(ParamWithName(true, &m_S2z, prefix + "poincare3D_s2z")); + } + +private: + T m_R; + T m_A; + T m_B; + T m_Cx;//Precalc. + T m_Cy; + T m_Cz; + T m_C2; + T m_C2x; + T m_C2y; + T m_C2z; + T m_S2x; + T m_S2y; + T m_S2z; +}; + +/// +/// Polynomial. +/// +template +class EMBER_API PolynomialVariation : public ParametricVariation +{ +public: + PolynomialVariation(T weight = 1.0) : ParametricVariation("polynomial", VAR_POLYNOMIAL, weight) + { + Init(); + } + + PARVARCOPY(PolynomialVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T xp = pow(fabs(m_Weight) * fabs(helper.In.x), m_Powx);//Original did not fabs. + T yp = pow(fabs(m_Weight) * fabs(helper.In.y), m_Powy); + + helper.Out.x = xp * Sign(helper.In.x) + m_Lcx * helper.In.x + m_Scx; + helper.Out.y = yp * Sign(helper.In.y) + m_Lcy * helper.In.y + m_Scy; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string powx = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string powy = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string lcx = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string lcy = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string scx = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string scy = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t xp = pow(fabs(xform->m_VariationWeights[" << varIndex << "]) * fabs(vIn.x), " << powx << ");\n" + << "\t\treal_t yp = pow(fabs(xform->m_VariationWeights[" << varIndex << "]) * fabs(vIn.y), " << powy << ");\n" + << "\t\treal_t zp = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\n" + << "\t\tvOut.x = xp * Sign(vIn.x) + " << lcx << " * vIn.x + " << scx << ";\n" + << "\t\tvOut.y = yp * Sign(vIn.y) + " << lcy << " * vIn.y + " << scy << ";\n" + << "\t\tvOut.z = zp;\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Powx, prefix + "polynomial_powx", 1)); + m_Params.push_back(ParamWithName(&m_Powy, prefix + "polynomial_powy", 1)); + m_Params.push_back(ParamWithName(&m_Lcx, prefix + "polynomial_lcx")); + m_Params.push_back(ParamWithName(&m_Lcy, prefix + "polynomial_lcy")); + m_Params.push_back(ParamWithName(&m_Scx, prefix + "polynomial_scx")); + m_Params.push_back(ParamWithName(&m_Scy, prefix + "polynomial_scy")); + } + +private: + T m_Powx; + T m_Powy; + T m_Lcx; + T m_Lcy; + T m_Scx; + T m_Scy; +}; + +/// +/// PSphere. +/// +template +class EMBER_API PSphereVariation : public ParametricVariation +{ +public: + PSphereVariation(T weight = 1.0) : ParametricVariation("psphere", VAR_PSPHERE, weight) + { + Init(); + } + + PARVARCOPY(PSphereVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T c0 = helper.In.x * m_Vpi; + T c1 = helper.In.y * m_Vpi; + T c2 = helper.In.z * m_Vpi; + T sinc0, cosc0, sinc1, cosc1; + + sincos(c0, &sinc0, &cosc0); + sincos(c1, &sinc1, &cosc1); + + helper.Out.x = cosc0 * -sinc1; + helper.Out.y = sinc0 * cosc1; + helper.Out.z = cosc1 * m_ZScale; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string zscale = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string vpi = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t c0 = vIn.x * " << vpi << ";\n" + << "\t\treal_t c1 = vIn.y * " << vpi << ";\n" + << "\t\treal_t c2 = vIn.z * " << vpi << ";\n" + << "\n" + << "\t\treal_t sinc0 = sin(c0);\n" + << "\t\treal_t cosc0 = cos(c0);\n" + << "\t\treal_t sinc1 = sin(c1);\n" + << "\t\treal_t cosc1 = cos(c1);\n" + << "\n" + << "\t\tvOut.x = cosc0 * -sinc1;\n" + << "\t\tvOut.y = sinc0 * cosc1;\n" + << "\t\tvOut.z = cosc1 * " << zscale << ";\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Vpi = m_Weight * T(M_PI); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_ZScale, prefix + "psphere_zscale")); + m_Params.push_back(ParamWithName(true, &m_Vpi, prefix + "psphere_vpi"));//Precalc. + } + +private: + T m_ZScale; + T m_Vpi;//Precalc. +}; + +/// +/// Rational3. +/// +template +class EMBER_API Rational3Variation : public ParametricVariation +{ +public: + Rational3Variation(T weight = 1.0) : ParametricVariation("rational3", VAR_RATIONAL3, weight) + { + Init(); + } + + PARVARCOPY(Rational3Variation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T xsqr = helper.In.x * helper.In.x; + T ysqr = helper.In.y * helper.In.y; + T xcb = helper.In.x * helper.In.x * helper.In.x; + T ycb = helper.In.y * helper.In.y * helper.In.y; + + T tr = m_T3 * (xcb - 3 * helper.In.x * ysqr) + m_T2 * (xsqr - ysqr) + m_T1 * helper.In.x + m_Tc; + T ti = m_T3 * (3 * xsqr * helper.In.y - ycb) + m_T2 * 2 * helper.In.x * helper.In.y + m_T1 * helper.In.y; + + T br = m_B3 * (xcb - 3 * helper.In.x * ysqr) + m_B2 * (xsqr - ysqr) + m_B1 * helper.In.x + m_Bc; + T bi = m_B3 * (3 * xsqr * helper.In.y - ycb) + m_B2 * 2 * helper.In.x * helper.In.y + m_B1 * helper.In.y; + + T r3den = 1 / (br * br + bi * bi); + + helper.Out.x = m_Weight * (tr * br + ti * bi) * r3den; + helper.Out.y = m_Weight * (ti * br - tr * bi) * r3den; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string t3 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string t2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string t1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string tc = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string b3 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string b2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string b1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string bc = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t xsqr = vIn.x * vIn.x;\n" + << "\t\treal_t ysqr = vIn.y * vIn.y;\n" + << "\t\treal_t xcb = vIn.x * vIn.x * vIn.x;\n" + << "\t\treal_t ycb = vIn.y * vIn.y * vIn.y;\n" + << "\n" + << "\t\treal_t tr = " << t3 << " * (xcb - 3 * vIn.x * ysqr) + " << t2 << " * (xsqr - ysqr) + " << t1 << " * vIn.x + " << tc << ";\n" + << "\t\treal_t ti = " << t3 << " * (3 * xsqr * vIn.y - ycb) + " << t2 << " * 2 * vIn.x * vIn.y + " << t1 << " * vIn.y;\n" + << "\n" + << "\t\treal_t br = " << b3 << " * (xcb - 3 * vIn.x * ysqr) + " << b2 << " * (xsqr - ysqr) + " << b1 << " * vIn.x + " << bc << ";\n" + << "\t\treal_t bi = " << b3 << " * (3 * xsqr * vIn.y - ycb) + " << b2 << " * 2 * vIn.x * vIn.y + " << b1 << " * vIn.y;\n" + << "\n" + << "\t\treal_t r3den = 1 / (br * br + bi * bi);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (tr * br + ti * bi) * r3den;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (ti * br - tr * bi) * r3den;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_T3, prefix + "rational3_t3", 1)); + m_Params.push_back(ParamWithName(&m_T2, prefix + "rational3_t2")); + m_Params.push_back(ParamWithName(&m_T1, prefix + "rational3_t1")); + m_Params.push_back(ParamWithName(&m_Tc, prefix + "rational3_tc", 1)); + m_Params.push_back(ParamWithName(&m_B3, prefix + "rational3_b3")); + m_Params.push_back(ParamWithName(&m_B2, prefix + "rational3_b2", 1)); + m_Params.push_back(ParamWithName(&m_B1, prefix + "rational3_b1")); + m_Params.push_back(ParamWithName(&m_Bc, prefix + "rational3_bc", 1)); + } + +private: + T m_T3; + T m_T2; + T m_T1; + T m_Tc; + T m_B3; + T m_B2; + T m_B1; + T m_Bc; +}; + +/// +/// Ripple. +/// +template +class EMBER_API RippleVariation : public ParametricVariation +{ +public: + RippleVariation(T weight = 1.0) : ParametricVariation("ripple", VAR_RIPPLE, weight) + { + Init(); + } + + PARVARCOPY(RippleVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + //Align input x, y to given center and multiply with scale. + T x = (helper.In.x * m_S) - m_CenterX; + T y = (helper.In.y * m_S) + m_CenterY; + + //Calculate distance from center but constrain it to EPS. + T d = max(EPS, sqrt(SQR(x) * SQR(y))); + + //Normalize x and y. + T nx = x / d; + T ny = y / d; + + //Calculate cosine wave with given frequency, velocity + //and phase based on the distance to center. + T wave = cos(m_F * d - m_Vxp); + + //Calculate the wave offsets + T d1 = wave * m_Pxa + d; + T d2 = wave * m_Pixa + d; + + //We got two offsets, so we also got two new positions (u,v). + T u1 = m_CenterX + nx * d1; + T v1 = -m_CenterY + ny * d1; + T u2 = m_CenterX + nx * d2; + T v2 = -m_CenterY + ny * d2; + + //Interpolate the two positions by the given phase and + //invert the multiplication with scale from before. + helper.Out.x = m_Weight * Lerp(u1, u2, m_P) * m_Is;//Original did a direct assignment to outPoint, which is incompatible with Ember's design. + helper.Out.y = m_Weight * Lerp(v1, v2, m_P) * m_Is; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string frequency = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string velocity = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string amplitude = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string centerx = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string centery = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string phase = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string scale = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string f = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string a = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string p = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string s = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string is = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string vxp = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string pxa = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string pixa = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t x = (vIn.x * " << s << ") - " << centerx << ";\n" + << "\t\treal_t y = (vIn.y * " << s << ") + " << centery << ";\n" + << "\n" + << "\t\treal_t d = max(EPS, sqrt(SQR(x) * SQR(y)));\n" + << "\n" + << "\t\treal_t nx = x / d;\n" + << "\t\treal_t ny = y / d;\n" + << "\n" + << "\t\treal_t wave = cos(" << f << " * d - " << vxp << ");\n" + << "\n" + << "\t\treal_t d1 = wave * " << pxa << " + d;\n" + << "\t\treal_t d2 = wave * " << pixa << " + d;\n" + << "\n" + << "\t\treal_t u1 = " << centerx << " + nx * d1;\n" + << "\t\treal_t v1 = -" << centery << " + ny * d1;\n" + << "\t\treal_t u2 = " << centerx << " + nx * d2;\n" + << "\t\treal_t v2 = -" << centery << " + ny * d2;\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * Lerp(u1, u2, " << p << ") * " << is << ";\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * Lerp(v1, v2, " << p << ") * " << is << ";\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_F = m_Frequency * 5; + m_A = m_Amplitude * T(0.01); + m_P = m_Phase * M_2PI - T(M_PI); + m_S = m_Scale == 0 ? EPS6 : m_Scale;//Scale must not be zero. + m_Is = 1 / m_S;//Need the inverse scale. + + //Pre-multiply velocity + phase, phase + amplitude and (PI - phase) + amplitude. + m_Vxp = m_Velocity * m_P; + m_Pxa = m_P * m_A; + m_Pixa = (T(M_PI) - m_P) * m_A; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Frequency, prefix + "ripple_frequency", 2)); + m_Params.push_back(ParamWithName(&m_Velocity, prefix + "ripple_velocity", 1)); + m_Params.push_back(ParamWithName(&m_Amplitude, prefix + "ripple_amplitude", T(0.5))); + m_Params.push_back(ParamWithName(&m_CenterX, prefix + "ripple_centerx")); + m_Params.push_back(ParamWithName(&m_CenterY, prefix + "ripple_centery")); + m_Params.push_back(ParamWithName(&m_Phase, prefix + "ripple_phase")); + m_Params.push_back(ParamWithName(&m_Scale, prefix + "ripple_scale", 1)); + m_Params.push_back(ParamWithName(true, &m_F, prefix + "ripple_f"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_A, prefix + "ripple_a")); + m_Params.push_back(ParamWithName(true, &m_P, prefix + "ripple_p")); + m_Params.push_back(ParamWithName(true, &m_S, prefix + "ripple_s")); + m_Params.push_back(ParamWithName(true, &m_Is, prefix + "ripple_is")); + m_Params.push_back(ParamWithName(true, &m_Vxp, prefix + "ripple_vxp")); + m_Params.push_back(ParamWithName(true, &m_Pxa , prefix + "ripple_pxa")); + m_Params.push_back(ParamWithName(true, &m_Pixa, prefix + "ripple_pixa")); + } + +private: + T m_Frequency; + T m_Velocity; + T m_Amplitude; + T m_CenterX; + T m_CenterY; + T m_Phase; + T m_Scale; + T m_F;//Precalc. + T m_A; + T m_P; + T m_S; + T m_Is; + T m_Vxp; + T m_Pxa; + T m_Pixa; +}; + +/// +/// Sigmoid. +/// +template +class EMBER_API SigmoidVariation : public ParametricVariation +{ +public: + SigmoidVariation(T weight = 1.0) : ParametricVariation("sigmoid", VAR_SIGMOID, weight) + { + Init(); + } + + PARVARCOPY(SigmoidVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T c0 = m_Ax / (1 + exp(m_Sx * helper.In.x)); + T c1 = m_Ay / (1 + exp(m_Sy * helper.In.y)); + T x = (2 * (c0 - T(0.5))); + T y = (2 * (c1 - T(0.5))); + + helper.Out.x = m_Vv * x; + helper.Out.y = m_Vv * y; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string shiftX = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string shiftY = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string sx = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string sy = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ax = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ay = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string vv = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t c0 = " << ax << " / (1 + exp(" << sx << " * vIn.x));\n" + << "\t\treal_t c1 = " << ay << " / (1 + exp(" << sy << " * vIn.y));\n" + << "\t\treal_t x = (2 * (c0 - 0.5));\n" + << "\t\treal_t y = (2 * (c1 - 0.5));\n" + << "\n" + << "\t\tvOut.x = " << vv << " * x;\n" + << "\t\tvOut.y = " << vv << " * y;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Sx = m_ShiftX; + m_Sy = m_ShiftY; + m_Ax = 1; + m_Ay = 1; + + if (m_Sx < 1 && m_Sx > -1) + { + if (m_Sx == 0) + { + m_Sx = EPS6; + m_Ax = 1; + } + else + { + m_Ax = T(m_Sx < 0 ? -1 : 1); + m_Sx = 1 / m_Sx; + } + } + + if (m_Sy < 1 && m_Sy > -1) + { + if (m_Sy == 0) + { + m_Sy = EPS; + m_Ay = 1; + } + else + { + m_Ay = T(m_Sy < 0 ? -1 : 1); + m_Sy = 1 / m_Sy; + } + } + + m_Sx *= -5; + m_Sy *= -5; + + m_Vv = fabs(m_Weight); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_ShiftX, prefix + "sigmoid_shiftx", 1)); + m_Params.push_back(ParamWithName(&m_ShiftY, prefix + "sigmoid_shifty", 1)); + m_Params.push_back(ParamWithName(true, &m_Sx, prefix + "sigmoid_sx"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Sy, prefix + "sigmoid_sy")); + m_Params.push_back(ParamWithName(true, &m_Ax, prefix + "sigmoid_ax")); + m_Params.push_back(ParamWithName(true, &m_Ay, prefix + "sigmoid_ay")); + m_Params.push_back(ParamWithName(true, &m_Vv, prefix + "sigmoid_vv")); + } + +private: + T m_ShiftX; + T m_ShiftY; + T m_Sx;//Precalc. + T m_Sy; + T m_Ax; + T m_Ay; + T m_Vv; +}; + +/// +/// SinusGrid. +/// +template +class EMBER_API SinusGridVariation : public ParametricVariation +{ +public: + SinusGridVariation(T weight = 1.0) : ParametricVariation("sinusgrid", VAR_SINUS_GRID, weight) + { + Init(); + } + + PARVARCOPY(SinusGridVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T x = helper.In.x; + T y = helper.In.y; + T sx = -1 * cos(x * m_Fx); + T sy = -1 * cos(y * m_Fy); + T tx = Lerp(helper.In.x, sx, m_Ax); + T ty = Lerp(helper.In.y, sy, m_Ay); + + helper.Out.x = m_Weight * tx; + helper.Out.y = m_Weight * ty; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string ampX = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ampY = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string freqX = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string freqY = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string fx = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string fy = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ax = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ay = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t x = vIn.x;\n" + << "\t\treal_t y = vIn.y;\n" + << "\t\treal_t sx = -1 * cos(x * " << fx << ");\n" + << "\t\treal_t sy = -1 * cos(y * " << fy << ");\n" + << "\t\treal_t tx = Lerp(vIn.x, sx, " << ax << ");\n" + << "\t\treal_t ty = Lerp(vIn.y, sy, " << ay << ");\n" + << "\t\treal_t tz = vIn.z;\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * tx;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * ty;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * tz;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Ax = m_AmpX; + m_Ay = m_AmpY; + m_Fx = m_FreqX * M_2PI; + m_Fy = m_FreqY * M_2PI; + + if (m_Fx == 0) m_Fx = EPS6; + if (m_Fy == 0) m_Fy = EPS6; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_AmpX, prefix + "sinusgrid_ampx", T(0.5))); + m_Params.push_back(ParamWithName(&m_AmpY, prefix + "sinusgrid_ampy", T(0.5))); + m_Params.push_back(ParamWithName(&m_FreqX, prefix + "sinusgrid_freqx", 1)); + m_Params.push_back(ParamWithName(&m_FreqY, prefix + "sinusgrid_freqy", 1)); + m_Params.push_back(ParamWithName(true, &m_Fx, prefix + "sinusgrid_fx"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Fy, prefix + "sinusgrid_fy")); + m_Params.push_back(ParamWithName(true, &m_Ax, prefix + "sinusgrid_ax")); + m_Params.push_back(ParamWithName(true, &m_Ay, prefix + "sinusgrid_ay")); + } + +private: + T m_AmpX; + T m_AmpY; + T m_FreqX; + T m_FreqY; + T m_Fx;//Precalc. + T m_Fy; + T m_Ax; + T m_Ay; +}; + +/// +/// Stwin. +/// +template +class EMBER_API StwinVariation : public ParametricVariation +{ +public: + StwinVariation(T weight = 1.0) : ParametricVariation("stwin", VAR_STWIN, weight) + { + Init(); + } + + PARVARCOPY(StwinVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + const T multiplier = T(0.05); + T x = helper.In.x * m_Weight * multiplier; + T y = helper.In.y * m_Weight * multiplier; + T x2 = SQR(x); + T y2 = SQR(y); + T xPlusy = x + y; + T x2Minusy2 = x2 - y2; + T x2Plusy2 = x2 + y2; + T result = x2Minusy2 * sin(M_2PI * m_Distort * xPlusy); + T divident = 1; + + if (x2Plusy2 != 0) + divident = x2Plusy2; + + result /= divident; + + helper.Out.x = m_Weight * helper.In.x + result; + helper.Out.y = m_Weight * helper.In.y + result; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string distort = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t x = vIn.x * xform->m_VariationWeights[" << varIndex << "] * 0.05;\n" + << "\t\treal_t y = vIn.y * xform->m_VariationWeights[" << varIndex << "] * 0.05;\n" + << "\t\treal_t x2 = SQR(x);\n" + << "\t\treal_t y2 = SQR(y);\n" + << "\t\treal_t xPlusy = x + y;\n" + << "\t\treal_t x2Minusy2 = x2 - y2;\n" + << "\t\treal_t x2Plusy2 = x2 + y2;\n" + << "\t\treal_t result = x2Minusy2 * sin(M_2PI * " << distort << " * xPlusy);\n" + << "\t\treal_t divident = 1;\n" + << "\n" + << "\t\tif (x2Plusy2 != 0)\n" + << "\t\t divident = x2Plusy2;\n" + << "\n" + << "\t\tresult /= divident;\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x + result;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y + result;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Distort, prefix + "stwin_distort", 1));//Original had a misspelling of swtin, which is incompatible with Ember's design. + } + +private: + T m_Distort; +}; + +/// +/// TwoFace. +/// +template +class EMBER_API TwoFaceVariation : public Variation +{ +public: + TwoFaceVariation(T weight = 1.0) : Variation("twoface", VAR_TWO_FACE, weight, true) { } + + VARCOPY(TwoFaceVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r = m_Weight; + + if (helper.In.x > 0) + r /= helper.m_PrecalcSumSquares; + + helper.Out.x = r * helper.In.x; + helper.Out.y = r * helper.In.y; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "];\n" + << "\n" + << "\t\tif (vIn.x > 0)\n" + << "\t\t r /= precalcSumSquares;\n" + << "\n" + << "\t\tvOut.x = r * vIn.x;\n" + << "\t\tvOut.y = r * vIn.y;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Unpolar. +/// +template +class EMBER_API UnpolarVariation : public ParametricVariation +{ +public: + UnpolarVariation(T weight = 1.0) : ParametricVariation("unpolar", VAR_UNPOLAR, weight) + { + Init(); + } + + PARVARCOPY(UnpolarVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r = exp(helper.In.y); + T s = sin(helper.In.x); + T c = cos(helper.In.x); + + helper.Out.x = m_Vvar2 * r * s; + helper.Out.y = m_Vvar2 * r * c; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string vvar2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Precalcs only, no params. + + ss << "\t{\n" + << "\t\treal_t r = exp(vIn.y);\n" + << "\t\treal_t s = sin(vIn.x);\n" + << "\t\treal_t c = cos(vIn.x);\n" + << "\n" + << "\t\tvOut.x = " << vvar2 << " * r * s;\n" + << "\t\tvOut.y = " << vvar2 << " * r * c;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Vvar2 = (m_Weight / T(M_PI)) * T(0.5); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(true, &m_Vvar2, prefix + "unpolar_vvar_2"));//Precalcs only, no params. + } + +private: + T m_Vvar2;//Precalcs only, no params. +}; + +/// +/// WavesN. +/// +template +class EMBER_API WavesNVariation : public ParametricVariation +{ +public: + WavesNVariation(T weight = 1.0) : ParametricVariation("wavesn", VAR_WAVESN, weight, true, false, false, false, true) + { + Init(); + } + + PARVARCOPY(WavesNVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T angle = (helper.m_PrecalcAtanyx + M_2PI * rand.Rand((int)m_AbsN)) / m_Power; + T r = m_Weight * pow(helper.m_PrecalcSumSquares, m_Cn); + T sina = sin(angle); + T cosa = cos(angle); + T xn = r * cosa; + T yn = r * sina; + T siny = sin(m_FreqX * yn); + T sinx = sin(m_FreqY * xn); + T dx = xn + T(0.5) * (m_ScaleX * siny + fabs(xn) * m_IncX * siny); + T dy = yn + T(0.5) * (m_ScaleY * sinx + fabs(yn) * m_IncY * sinx); + + helper.Out.x = m_Weight * dx; + helper.Out.y = m_Weight * dy; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string freqX = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string freqY = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string scaleX = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string scaleY = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string incX = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string incY = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string power = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string absn = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cn = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t angle = (precalcAtanyx + M_2PI * MwcNextRange(mwc, (uint)" << absn << ")) / " << power << ";\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * pow(precalcSumSquares, " << cn << ");\n" + << "\t\treal_t sina = sin(angle);\n" + << "\t\treal_t cosa = cos(angle);\n" + << "\t\treal_t xn = r * cosa;\n" + << "\t\treal_t yn = r * sina;\n" + << "\t\treal_t siny = sin(" << freqX << " * yn);\n" + << "\t\treal_t sinx = sin(" << freqY << " * xn);\n" + << "\t\treal_t dx = xn + 0.5 * (" << scaleX << " * siny + fabs(xn) * " << incX << " * siny);\n" + << "\t\treal_t dy = yn + 0.5 * (" << scaleY << " * sinx + fabs(yn) * " << incY << " * sinx);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * dx;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * dy;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + if (m_Power == 0) + m_Power = 2; + + m_AbsN = T((int)fabs(m_Power)); + + m_Cn = 1 / m_Power / 2; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_FreqX, prefix + "wavesn_freqx", 2)); + m_Params.push_back(ParamWithName(&m_FreqY, prefix + "wavesn_freqy", 2)); + m_Params.push_back(ParamWithName(&m_ScaleX, prefix + "wavesn_scalex", 1)); + m_Params.push_back(ParamWithName(&m_ScaleY, prefix + "wavesn_scaley", 1)); + m_Params.push_back(ParamWithName(&m_IncX, prefix + "wavesn_incx")); + m_Params.push_back(ParamWithName(&m_IncY, prefix + "wavesn_incy")); + m_Params.push_back(ParamWithName(&m_Power, prefix + "wavesn_power", 1, INTEGER_NONZERO)); + m_Params.push_back(ParamWithName(true, &m_AbsN, prefix + "wavesn_absn"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Cn, prefix + "wavesn_cn")); + } + +private: + T m_FreqX; + T m_FreqY; + T m_ScaleX; + T m_ScaleY; + T m_IncX; + T m_IncY; + T m_Power; + T m_AbsN;//Precalc. + T m_Cn; +}; + +/// +/// XHeart. +/// +template +class EMBER_API XHeartVariation : public ParametricVariation +{ +public: + XHeartVariation(T weight = 1.0) : ParametricVariation("xheart", VAR_XHEART, weight, true) + { + Init(); + } + + PARVARCOPY(XHeartVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r2_4 = helper.m_PrecalcSumSquares + 4; + + if (r2_4 == 0) + r2_4 = 1; + + T bx = 4 / r2_4; + T by = m_Rat / r2_4; + T x = m_Cosa * (bx * helper.In.x) - m_Sina * (by * helper.In.y); + T y = m_Sina * (bx * helper.In.x) + m_Cosa * (by *helper.In.y); + + if (x > 0) + { + helper.Out.x = m_Weight * x; + helper.Out.y = m_Weight * y; + } + else + { + helper.Out.x = m_Weight * x; + helper.Out.y = -m_Weight * y; + } + + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string angle = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ratio = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cosa = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string sina = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string rat = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t r2_4 = precalcSumSquares + 4;\n" + << "\n" + << "\t\tif (r2_4 == 0)\n" + << "\t\t r2_4 = 1;\n" + << "\n" + << "\t\treal_t bx = 4 / r2_4;\n" + << "\t\treal_t by = " << rat << " / r2_4;\n" + << "\t\treal_t x = " << cosa << " * (bx * vIn.x) - " << sina << " * (by * vIn.y);\n" + << "\t\treal_t y = " << sina << " * (bx * vIn.x) + " << cosa << " * (by * vIn.y);\n" + << "\n" + << "\t\tif (x > 0)\n" + << "\t\t{\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * x;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * y;\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * x;\n" + << "\t\t vOut.y = -xform->m_VariationWeights[" << varIndex << "] * y;\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + T ang = T(M_PI_4) + (T(0.5) * T(M_PI_4) * m_Angle); + + sincos(ang, &m_Sina, &m_Cosa); + m_Rat = 6 + 2 * m_Ratio; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Angle, prefix + "xheart_angle")); + m_Params.push_back(ParamWithName(&m_Ratio, prefix + "xheart_ratio")); + m_Params.push_back(ParamWithName(true, &m_Cosa, prefix + "xheart_cosa"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Sina, prefix + "xheart_sina")); + m_Params.push_back(ParamWithName(true, &m_Rat, prefix + "xheart_rat")); + } + +private: + T m_Angle; + T m_Ratio; + T m_Cosa;//Precalc. + T m_Sina; + T m_Rat; +}; + +/// +/// Barycentroid. +/// +template +class EMBER_API BarycentroidVariation : public ParametricVariation +{ +public: + BarycentroidVariation(T weight = 1.0) : ParametricVariation("barycentroid", VAR_BARYCENTROID, weight) + { + Init(); + } + + PARVARCOPY(BarycentroidVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + //Compute dot products. + T dot00 = SQR(m_A) + SQR(m_B);//v0 * v0. + T dot01 = m_A * m_C + m_B * m_D;//v0 * v1. + T dot02 = m_A * helper.In.x + m_B * helper.In.y;//v0 * v2. + T dot11 = SQR(m_C) + SQR(m_D);//v1 * v1. + T dot12 = m_C * helper.In.x + m_D * helper.In.y;//v1 * v2. + + //Compute inverse denomiator. + T invDenom = 1 / (dot00 * dot11 - dot01 * dot01); + + //Now we can pull [u,v] as the barycentric coordinates of the point + //P in the triangle [A, B, C]. + T u = (dot11 * dot02 - dot01 * dot12) * invDenom; + T v = (dot00 * dot12 - dot01 * dot02) * invDenom; + + // now combine with input + T um = sqrt(SQR(u) + SQR(helper.In.x)) * Sign(u); + T vm = sqrt(SQR(v) + SQR(helper.In.y)) * Sign(v); + + helper.Out.x = m_Weight * um; + helper.Out.y = m_Weight * vm; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string a = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string b = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string d = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t dot00 = SQR(" << a << ") + SQR(" << b << ");\n" + << "\t\treal_t dot01 = " << a << " * " << c << " + " << b << " * " << d << ";\n" + << "\t\treal_t dot02 = " << a << " * vIn.x + " << b << " * vIn.y;\n" + << "\t\treal_t dot11 = SQR(" << c << ") + SQR(" << d << ");\n" + << "\t\treal_t dot12 = " << c << " * vIn.x + " << d << " * vIn.y;\n" + << "\t\treal_t invDenom = 1.0 / (dot00 * dot11 - dot01 * dot01);\n" + << "\t\treal_t u = (dot11 * dot02 - dot01 * dot12) * invDenom;\n" + << "\t\treal_t v = (dot00 * dot12 - dot01 * dot02) * invDenom;\n" + << "\t\treal_t um = sqrt(SQR(u) + SQR(vIn.x)) * Sign(u);\n" + << "\t\treal_t vm = sqrt(SQR(v) + SQR(vIn.y)) * Sign(v);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * um;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * vm;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_A, prefix + "barycentroid_a", 1)); + m_Params.push_back(ParamWithName(&m_B, prefix + "barycentroid_b")); + m_Params.push_back(ParamWithName(&m_C, prefix + "barycentroid_c")); + m_Params.push_back(ParamWithName(&m_D, prefix + "barycentroid_d", 1)); + } + +private: + T m_A; + T m_B; + T m_C; + T m_D; +}; + +/// +/// BiSplit. +/// +template +class EMBER_API BiSplitVariation : public ParametricVariation +{ +public: + BiSplitVariation(T weight = 1.0) : ParametricVariation("bisplit", VAR_BISPLIT, weight) + { + Init(); + } + + PARVARCOPY(BiSplitVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + helper.Out.x = m_Weight01 / tan(helper.In.x) * cos(helper.In.y); + helper.Out.y = m_Weight01 / sin(helper.In.x) * (-helper.In.y); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string weight01 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tvOut.x = " << weight01 << " / tan(vIn.x) * cos(vIn.y);\n" + << "\t\tvOut.y = " << weight01 << " / sin(vIn.x) * (-vIn.y);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Weight01 = m_Weight * T(0.1); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(true, &m_Weight01, prefix + "bisplit_weight01"));//Precalc only. + } + +private: + T m_Weight01; +}; + +/// +/// Crescents. +/// +template +class EMBER_API CrescentsVariation : public Variation +{ +public: + CrescentsVariation(T weight = 1.0) : Variation("crescents", VAR_CRESCENTS, weight) { } + + VARCOPY(CrescentsVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + helper.Out.x = m_Weight * sin(helper.In.x) * (cosh(helper.In.y) + 1) * Sqr(sin(helper.In.x)); + helper.Out.y = m_Weight * cos(helper.In.x) * (cosh(helper.In.y) + 1) * Sqr(sin(helper.In.x)); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * sin(vIn.x) * (cosh(vIn.y) + 1.0) * Sqr(sin(vIn.x));\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * cos(vIn.x) * (cosh(vIn.y) + 1.0) * Sqr(sin(vIn.x));\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Mask. +/// +template +class EMBER_API MaskVariation : public Variation +{ +public: + MaskVariation(T weight = 1.0) : Variation("mask", VAR_MASK, weight, true) { } + + VARCOPY(MaskVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T d = m_Weight / helper.m_PrecalcSumSquares; + + helper.Out.x = d * sin(helper.In.x) * (cosh(helper.In.y) + 1) * Sqr(sin(helper.In.x)); + helper.Out.y = d * cos(helper.In.x) * (cosh(helper.In.y) + 1) * Sqr(sin(helper.In.x)); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t d = xform->m_VariationWeights[" << varIndex << "] / precalcSumSquares;\n" + << "\n" + << "\t\tvOut.x = d * sin(vIn.x) * (cosh(vIn.y) + 1.0) * Sqr(sin(vIn.x));\n" + << "\t\tvOut.y = d * cos(vIn.x) * (cosh(vIn.y) + 1.0) * Sqr(sin(vIn.x));\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Cpow2. +/// +template +class EMBER_API Cpow2Variation : public ParametricVariation +{ +public: + Cpow2Variation(T weight = 1.0) : ParametricVariation("cpow2", VAR_CPOW2, weight, true, false, false, false, true) + { + Init(); + } + + PARVARCOPY(Cpow2Variation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T a = helper.m_PrecalcAtanyx; + int n = rand.Rand((unsigned int)m_Spread); + + if (a < 0) + n++; + + a += M_2PI * n; + + if (cos(a * m_InvSpread) < rand.Rand() * 2 / 0xFFFFFFFF - 1)//Rand max. + a -= m_FullSpread; + + T lnr2 = log(helper.m_PrecalcSumSquares); + T r = m_Weight * exp(m_HalfC * lnr2 - m_D * a); + T temp = m_C * a + m_HalfD * lnr2 + m_Ang * rand.Rand(); + + helper.Out.x = r * cos(temp); + helper.Out.y = r * sin(temp); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string r = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string a = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string divisor = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string spread = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string halfC = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string d = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string halfD = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ang = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string invSpread = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string fullSpread = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t a = precalcAtanyx;\n" + << "\t\tint n = MwcNextRange(mwc, (uint)" << spread << ");\n" + << "\n" + << "\t\tif (a < 0)\n" + << "\t\t n++;\n" + << "\n" + << "\t\ta += M_2PI * n;\n" + << "\n" + << "\t\tif (cos(a * " << invSpread << ") < MwcNext(mwc) * 2 / 0xFFFFFFFF - 1)\n" + << "\t\t a -= " << fullSpread << ";\n" + << "\n" + << "\t\treal_t lnr2 = log(precalcSumSquares);\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * exp(" << halfC << " * lnr2 - " << d << " * a);\n" + << "\t\treal_t temp = " << c << " * a + " << halfD << " * lnr2 + " << ang << " * MwcNext(mwc);\n" + << "\n" + << "\t\tvOut.x = r * cos(temp);\n" + << "\t\tvOut.y = r * sin(temp);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Ang = M_2PI / m_Divisor; + m_C = m_R * cos(T(M_PI) / 2 * m_A) / m_Divisor; + m_D = m_R * sin(T(M_PI) / 2 * m_A) / m_Divisor; + m_HalfC = m_C / 2; + m_HalfD = m_D / 2; + m_InvSpread = T(0.5) / m_Spread; + m_FullSpread = M_2PI * m_Spread; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_R, prefix + "cpow2_r", 1)); + m_Params.push_back(ParamWithName(&m_A, prefix + "cpow2_a")); + m_Params.push_back(ParamWithName(&m_Divisor, prefix + "cpow2_divisor", 1, INTEGER_NONZERO)); + m_Params.push_back(ParamWithName(&m_Spread, prefix + "cpow2_spread", 1, INTEGER, 1, T(0x7FFFFFFF))); + m_Params.push_back(ParamWithName(true, &m_C, prefix + "cpow2_c"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_HalfC, prefix + "cpow2_halfc")); + m_Params.push_back(ParamWithName(true, &m_D, prefix + "cpow2_d")); + m_Params.push_back(ParamWithName(true, &m_HalfD, prefix + "cpow2_halfd")); + m_Params.push_back(ParamWithName(true, &m_Ang, prefix + "cpow2_ang")); + m_Params.push_back(ParamWithName(true, &m_InvSpread, prefix + "cpow2_inv_spread")); + m_Params.push_back(ParamWithName(true, &m_FullSpread, prefix + "cpow2_full_spread")); + } + +private: + T m_R; + T m_A; + T m_Divisor; + T m_Spread; + T m_C;//Precalc. + T m_HalfC; + T m_D; + T m_HalfD; + T m_Ang; + T m_InvSpread; + T m_FullSpread; +}; + +MAKEPREPOSTVAR(Hemisphere, hemisphere, HEMISPHERE) +MAKEPREPOSTPARVAR(Epispiral, epispiral, EPISPIRAL) +MAKEPREPOSTPARVAR(Bwraps, bwraps, BWRAPS) +MAKEPREPOSTVARASSIGN(BlurCircle, blur_circle, BLUR_CIRCLE, ASSIGNTYPE_SUM) +MAKEPREPOSTPARVAR(BlurZoom, blur_zoom, BLUR_ZOOM) +MAKEPREPOSTPARVAR(BlurPixelize, blur_pixelize, BLUR_PIXELIZE) +MAKEPREPOSTPARVAR(Crop, crop, CROP) +MAKEPREPOSTPARVAR(BCircle, bcircle, BCIRCLE) +MAKEPREPOSTPARVAR(BlurLinear, blur_linear, BLUR_LINEAR) +MAKEPREPOSTPARVARASSIGN(BlurSquare, blur_square, BLUR_SQUARE, ASSIGNTYPE_SUM) +MAKEPREPOSTVAR(Flatten, flatten, FLATTEN) +MAKEPREPOSTVARASSIGN(Zblur, zblur, ZBLUR, ASSIGNTYPE_SUM) +MAKEPREPOSTVARASSIGN(Blur3D, blur_3d, BLUR3D, ASSIGNTYPE_SUM) +MAKEPREPOSTVARASSIGN(ZScale, zscale, ZSCALE, ASSIGNTYPE_SUM) +MAKEPREPOSTVARASSIGN(ZTranslate, ztranslate, ZTRANSLATE, ASSIGNTYPE_SUM) +MAKEPREPOSTVAR(ZCone, zcone, ZCONE) +MAKEPREPOSTVAR(Spherical3D, Spherical3D, SPHERICAL3D) +MAKEPREPOSTPARVAR(Curl3D, curl3D, CURL3D) +MAKEPREPOSTPARVAR(Disc3D, disc3d, DISC3D) +MAKEPREPOSTPARVAR(Boarders2, boarders2, BOARDERS2) +MAKEPREPOSTPARVAR(Cardioid, cardioid, CARDIOID) +MAKEPREPOSTPARVAR(Checks, checks, CHECKS) +MAKEPREPOSTPARVAR(Circlize, circlize, CIRCLIZE) +MAKEPREPOSTPARVAR(Circlize2, circlize2, CIRCLIZE2) +MAKEPREPOSTPARVAR(CosWrap, coswrap, COS_WRAP) +MAKEPREPOSTVAR(DeltaA, deltaa, DELTA_A) +MAKEPREPOSTPARVAR(Expo, expo, EXPO) +MAKEPREPOSTPARVAR(Extrude, extrude, EXTRUDE) +MAKEPREPOSTVAR(FDisc, fdisc, FDISC) +MAKEPREPOSTPARVAR(Fibonacci, fibonacci, FIBONACCI) +MAKEPREPOSTPARVAR(Fibonacci2, fibonacci2, FIBONACCI2) +MAKEPREPOSTPARVAR(Glynnia, glynnia, GLYNNIA) +MAKEPREPOSTVAR(GridOut, gridout, GRIDOUT) +MAKEPREPOSTPARVAR(Hole, hole, HOLE) +MAKEPREPOSTPARVAR(Hypertile, hypertile, HYPERTILE) +MAKEPREPOSTPARVAR(Hypertile1, hypertile1, HYPERTILE1) +MAKEPREPOSTPARVAR(Hypertile2, hypertile2, HYPERTILE2) +MAKEPREPOSTPARVAR(Hypertile3D, hypertile3D, HYPERTILE3D) +MAKEPREPOSTPARVAR(Hypertile3D1, hypertile3D1, HYPERTILE3D1) +MAKEPREPOSTPARVAR(Hypertile3D2, hypertile3D2, HYPERTILE3D2) +MAKEPREPOSTPARVAR(IDisc, idisc, IDISC) +MAKEPREPOSTPARVAR(Julian2, julian2, JULIAN2) +MAKEPREPOSTPARVAR(JuliaQ, juliaq, JULIAQ) +MAKEPREPOSTPARVAR(Murl, murl, MURL) +MAKEPREPOSTPARVAR(Murl2, murl2, MURL2) +MAKEPREPOSTPARVAR(NPolar, npolar, NPOLAR) +MAKEPREPOSTPARVAR(Ortho, ortho, ORTHO) +MAKEPREPOSTPARVAR(Poincare, poincare, POINCARE) +MAKEPREPOSTPARVAR(Poincare3D, poincare3D, POINCARE3D) +MAKEPREPOSTPARVAR(Polynomial, polynomial, POLYNOMIAL) +MAKEPREPOSTPARVAR(PSphere, psphere, PSPHERE) +MAKEPREPOSTPARVAR(Rational3, rational3, RATIONAL3) +MAKEPREPOSTPARVAR(Ripple, ripple, RIPPLE) +MAKEPREPOSTPARVAR(Sigmoid, sigmoid, SIGMOID) +MAKEPREPOSTPARVAR(SinusGrid, sinusgrid, SINUS_GRID) +MAKEPREPOSTPARVAR(Stwin, stwin, STWIN) +MAKEPREPOSTVAR(TwoFace, twoface, TWO_FACE) +MAKEPREPOSTPARVAR(Unpolar, unpolar, UNPOLAR) +MAKEPREPOSTPARVAR(WavesN, wavesn, WAVESN) +MAKEPREPOSTPARVAR(XHeart, xheart, XHEART) +MAKEPREPOSTPARVAR(Barycentroid, barycentroid, BARYCENTROID) +MAKEPREPOSTPARVAR(BiSplit, bisplit, BISPLIT) +MAKEPREPOSTVAR(Crescents, crescents, CRESCENTS) +MAKEPREPOSTVAR(Mask, mask, MASK) +MAKEPREPOSTPARVAR(Cpow2, cpow2, CPOW2) +} \ No newline at end of file diff --git a/Source/Ember/Variations03.h b/Source/Ember/Variations03.h new file mode 100644 index 0000000..8d21653 --- /dev/null +++ b/Source/Ember/Variations03.h @@ -0,0 +1,4596 @@ +#pragma once + +#include "Variation.h" + +namespace EmberNs +{ +/// +/// Funnel. +/// +template +class EMBER_API FunnelVariation : public ParametricVariation +{ +public: + FunnelVariation(T weight = 1.0) : ParametricVariation("funnel", VAR_FUNNEL, weight) + { + Init(); + } + + PARVARCOPY(FunnelVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T temp = 1 / cos(helper.In.y) + m_Effect * T(M_PI); + + helper.Out.x = m_Weight * (tanh(helper.In.x) * temp); + helper.Out.y = m_Weight * (tanh(helper.In.y) * temp); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string effect = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t temp = 1 / cos(vIn.y) + " << effect << " * M_PI;\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (tanh(vIn.x) * temp);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (tanh(vIn.y) * temp);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Effect, prefix + "funnel_effect", 8, INTEGER)); + } + +private: + T m_Effect; +}; + +/// +/// Linear3D. +/// +template +class EMBER_API Linear3DVariation : public Variation +{ +public: + Linear3DVariation(T weight = 1.0) : Variation("linear3D", VAR_LINEAR3D, weight) { } + + VARCOPY(Linear3DVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + helper.Out.x = m_Weight * helper.In.x; + helper.Out.y = m_Weight * helper.In.y; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// PowBlock. +/// +template +class EMBER_API PowBlockVariation : public ParametricVariation +{ +public: + PowBlockVariation(T weight = 1.0) : ParametricVariation("pow_block", VAR_POW_BLOCK, weight, true, false, false, false, true) + { + Init(); + } + + PARVARCOPY(PowBlockVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r2 = pow(helper.m_PrecalcSumSquares, m_Power * T(0.5)) * m_Weight; + T ran = (helper.m_PrecalcAtanyx / (m_Denominator + EPS6) + (m_Root * M_2PI * Floor(rand.Frand01() * m_Denominator) / (m_Denominator + EPS))) * m_Numerator; + + helper.Out.x = r2 * cos(ran); + helper.Out.y = r2 * sin(ran); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string numerator = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string denominator = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string root = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string correctN = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string correctD = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string power = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t r2 = pow(precalcSumSquares, " << power << " * 0.5) * xform->m_VariationWeights[" << varIndex << "];\n" + << "\t\treal_t ran = (precalcAtanyx / (" << denominator << " + EPS6) + (" << root << " * M_2PI * floor(MwcNext01(mwc) * " << denominator << ") / (" << denominator << " + EPS))) * " << numerator << ";\n" + << "\n" + << "\t\tvOut.x = r2 * cos(ran);\n" + << "\t\tvOut.y = r2 * sin(ran);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Power = m_Numerator / (m_Denominator * m_Correctn * (1 / m_Correctd) + EPS6); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Numerator, prefix + "pow_block_numerator", 3));//Original used a prefix of pow_, which is incompatible with Ember's design. + m_Params.push_back(ParamWithName(&m_Denominator, prefix + "pow_block_denominator", 2)); + m_Params.push_back(ParamWithName(&m_Root, prefix + "pow_block_root", 1)); + m_Params.push_back(ParamWithName(&m_Correctn, prefix + "pow_block_correctn", 1)); + m_Params.push_back(ParamWithName(&m_Correctd, prefix + "pow_block_correctd", 1)); + m_Params.push_back(ParamWithName(true, &m_Power, prefix + "pow_block_power"));//Precalc. + } + +private: + T m_Numerator; + T m_Denominator; + T m_Root; + T m_Correctn; + T m_Correctd; + T m_Power;//Precalc. +}; + +/// +/// Squirrel. +/// +template +class EMBER_API SquirrelVariation : public ParametricVariation +{ +public: + SquirrelVariation(T weight = 1.0) : ParametricVariation("squirrel", VAR_SQUIRREL, weight) + { + Init(); + } + + PARVARCOPY(SquirrelVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T u = sqrt(ClampGte0((m_A + EPS6) * SQR(helper.In.x) + (m_B + EPS6) * SQR(helper.In.y)));//Original did not clamp. + + helper.Out.x = cos(u) * tan(helper.In.x) * m_Weight; + helper.Out.y = sin(u) * tan(helper.In.y) * m_Weight; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string a = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string b = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t u = sqrt(ClampGte((" << a << " + EPS6) * SQR(vIn.x) + (" << b << " + EPS6) * SQR(vIn.y), 0.0));\n" + << "\n" + << "\t\tvOut.x = cos(u) * tan(vIn.x) * xform->m_VariationWeights[" << varIndex << "];\n" + << "\t\tvOut.y = sin(u) * tan(vIn.y) * xform->m_VariationWeights[" << varIndex << "];\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_A, prefix + "squirrel_a", 1)); + m_Params.push_back(ParamWithName(&m_B, prefix + "squirrel_b", 1)); + } + +private: + T m_A; + T m_B; +}; + +/// +/// Ennepers. +/// +template +class EMBER_API EnnepersVariation : public Variation +{ +public: + EnnepersVariation(T weight = 1.0) : Variation("ennepers", VAR_ENNEPERS, weight) { } + + VARCOPY(EnnepersVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + helper.Out.x = m_Weight * (helper.In.x - ((SQR(helper.In.x) * helper.In.x) / 3)) + helper.In.x * SQR(helper.In.y); + helper.Out.y = m_Weight * (helper.In.y - ((SQR(helper.In.y) * helper.In.y) / 3)) + helper.In.y * SQR(helper.In.x); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (vIn.x - ((SQR(vIn.x) * vIn.x) / 3)) + vIn.x * SQR(vIn.y);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (vIn.y - ((SQR(vIn.y) * vIn.y) / 3)) + vIn.y * SQR(vIn.x);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// SphericalN. +/// +template +class EMBER_API SphericalNVariation : public ParametricVariation +{ +public: + SphericalNVariation(T weight = 1.0) : ParametricVariation("SphericalN", VAR_SPHERICALN, weight, true, true, false, false, true) + { + Init(); + } + + PARVARCOPY(SphericalNVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r = pow(helper.m_PrecalcSqrtSumSquares, m_Dist); + int n = Floor(m_Power * rand.Frand01()); + T alpha = helper.m_PrecalcAtanyx + n * M_2PI / Floor(m_Power); + //int n = (int)floor(m_Power * rand.Frand01()); + //T alpha = helper.m_PrecalcAtanyx + n * M_2PI / floor(m_Power); + T sina = sin(alpha); + T cosa = cos(alpha); + + helper.Out.x = m_Weight * cosa / r; + helper.Out.y = m_Weight * sina / r; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string power = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string dist = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t r = pow(precalcSqrtSumSquares, " << dist << ");\n" + << "\t\tint n = floor(" << power << " * MwcNext01(mwc));\n" + << "\t\treal_t alpha = precalcAtanyx + n * M_2PI / floor(" << power << ");\n" + << "\t\treal_t sina = sin(alpha);\n" + << "\t\treal_t cosa = cos(alpha);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * cosa / r;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * sina / r;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Power, prefix + "SphericalN_Power", 1)); + m_Params.push_back(ParamWithName(&m_Dist, prefix + "SphericalN_Dist", 1)); + } + +private: + T m_Power; + T m_Dist; +}; + +/// +/// Kaleidoscope. +/// +template +class EMBER_API KaleidoscopeVariation : public ParametricVariation +{ +public: + KaleidoscopeVariation(T weight = 1.0) : ParametricVariation("Kaleidoscope", VAR_KALEIDOSCOPE, weight) + { + Init(); + } + + PARVARCOPY(KaleidoscopeVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T sin45 = sin(45 * DEG_2_RAD_T);//Was 45 radians? They probably meant to convert this from degrees. + T cos45 = cos(45 * DEG_2_RAD_T); + + helper.Out.x = ((m_Rotate * helper.In.x) * cos45 - helper.In.y * sin45 + m_LineUp) + m_X; + + //The if function splits the plugin in two. + if (helper.In.y > 0) + helper.Out.y = ((m_Rotate * helper.In.y) * cos45 + helper.In.x * sin45 + m_Pull + m_LineUp) + m_Y; + else + helper.Out.y = (m_Rotate * helper.In.y) * cos45 + helper.In.x * sin45 - m_Pull - m_LineUp; + + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string pull = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string rotate = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string lineUp = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t sin45 = sin(45 * DEG_2_RAD);\n" + << "\t\treal_t cos45 = cos(45 * DEG_2_RAD);\n" + << "\n" + << "\t\tvOut.x = ((" << rotate << " * vIn.x) * cos45 - vIn.y * sin45 + " << lineUp << ") + " << x << ";\n" + << "\n" + << "\t\tif (vIn.y > 0)\n" + << "\t\t vOut.y = ((" << rotate << " * vIn.y) * cos45 + vIn.x * sin45 + " << pull << " + " << lineUp << ") + " << y << ";\n" + << "\t\telse\n" + << "\t\t vOut.y = (" << rotate << " * vIn.y) * cos45 + vIn.x * sin45 - " << pull << " - " << lineUp << ";\n" + << "\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Pull, prefix + "Kaleidoscope_pull")); + m_Params.push_back(ParamWithName(&m_Rotate, prefix + "Kaleidoscope_rotate", 1)); + m_Params.push_back(ParamWithName(&m_LineUp, prefix + "Kaleidoscope_line_up", 1)); + m_Params.push_back(ParamWithName(&m_X, prefix + "Kaleidoscope_x")); + m_Params.push_back(ParamWithName(&m_Y, prefix + "Kaleidoscope_y")); + } + +private: + T m_Pull;//Pulls apart the 2 sections of the plugin. + T m_Rotate;//Rotates both halves of the plugin. + T m_LineUp; + T m_X;//Changes x co-ordinates. + T m_Y;//Changes y co-ordinates for 1 part of the plugin. +}; + +/// +/// GlynnSim1. +/// +template +class EMBER_API GlynnSim1Variation : public ParametricVariation +{ +public: + GlynnSim1Variation(T weight = 1.0) : ParametricVariation("GlynnSim1", VAR_GLYNNSIM1, weight, true, true) + { + Init(); + } + + PARVARCOPY(GlynnSim1Variation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T x, y, z, alpha = m_Radius / helper.m_PrecalcSqrtSumSquares; + + if (helper.m_PrecalcSqrtSumSquares < m_Radius)//Object generation. + { + Circle(rand, &x, &y); + helper.Out.x = m_Weight * x; + helper.Out.y = m_Weight * y; + } + else + { + if (rand.Frand01() > m_Contrast * pow(alpha, m_Pow)) + { + x = helper.In.x; + y = helper.In.y; + } + else + { + x = SQR(alpha) * helper.In.x; + y = SQR(alpha) * helper.In.y; + } + + z = Sqr(x - m_X1) + Sqr(y - m_Y1); + + if (z < SQR(m_Radius1))//Object generation. + { + Circle(rand, &x, &y); + helper.Out.x = m_Weight * x; + helper.Out.y = m_Weight * y; + } + else + { + helper.Out.x = m_Weight * x; + helper.Out.y = m_Weight * y; + } + } + + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string radius = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string radius1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string phi1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string thickness = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string contrast = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string pow = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string x1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string y1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t x, y, z, alpha = " << radius << " / precalcSqrtSumSquares;\n" + << "\n" + << "\t\tif (precalcSqrtSumSquares < " << radius << ")\n" + << "\t\t{\n" + << "\t\t GlynnSim1Circle(&" << radius1 << ", &" << thickness << ", &" << x1 << ", &" << y1 << ", mwc, &x, &y);\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * x;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * y;\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t if (MwcNext01(mwc) > " << contrast << " * pow(alpha, " << pow << "))\n" + << "\t\t {\n" + << "\t\t x = vIn.x;\n" + << "\t\t y = vIn.y;\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t x = SQR(alpha) * vIn.x;\n" + << "\t\t y = SQR(alpha) * vIn.y;\n" + << "\t\t }\n" + << "\n" + << "\t\t z = Sqr(x - " << x1 << ") + Sqr(y - " << y1 << ");\n" + << "\n" + << "\t\t if (z < SQR(" << radius1 << "))\n" + << "\t\t {\n" + << "\t\t GlynnSim1Circle(&" << radius1 << ", &" << thickness << ", &" << x1 << ", &" << y1 << ", mwc, &x, &y);\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * x;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * y;\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * x;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * y;\n" + << "\t\t }\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual string OpenCLFuncsString() + { + return + "void GlynnSim1Circle(__constant real_t* radius1, __constant real_t* thickness, __constant real_t* x1, __constant real_t* y1, uint2* mwc, real_t* x, real_t* y)\n" + "{\n" + " real_t r = *radius1 * (*thickness + (1.0 - *thickness) * MwcNext01(mwc));\n" + " real_t phi = M_2PI * MwcNext01(mwc);\n" + " real_t sinPhi = sin(phi);\n" + " real_t cosPhi = cos(phi);\n" + "\n" + " *x = r * cosPhi + *x1;\n" + " *y = r * sinPhi + *y1;\n" + "}\n" + "\n"; + } + + virtual void Precalc() + { + T val = DEG_2_RAD_T * m_Phi1; + T sinPhi1 = sin(val); + T cosPhi1 = cos(val); + + m_Pow = fabs(m_Pow); + m_X1 = m_Radius * cosPhi1; + m_Y1 = m_Radius * sinPhi1; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Radius, prefix + "GlynnSim1_radius", 1)); + m_Params.push_back(ParamWithName(&m_Radius1, prefix + "GlynnSim1_radius1", T(0.1))); + m_Params.push_back(ParamWithName(&m_Phi1, prefix + "GlynnSim1_phi1")); + m_Params.push_back(ParamWithName(&m_Thickness, prefix + "GlynnSim1_thickness", T(0.1), REAL, 0, 1)); + m_Params.push_back(ParamWithName(&m_Contrast, prefix + "GlynnSim1_contrast", T(1.5))); + m_Params.push_back(ParamWithName(&m_Pow, prefix + "GlynnSim1_pow", T(0.5), REAL, 0, 1)); + m_Params.push_back(ParamWithName(true, &m_X1, prefix + "GlynnSim1_x1"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Y1, prefix + "GlynnSim1_y1")); + } + +private: + void Circle(QTIsaac& rand, T* x, T* y) + { + T r = m_Radius1 * (m_Thickness + (1 - m_Thickness) * rand.Frand01()); + T phi = M_2PI * rand.Frand01(); + T sinPhi = sin(phi); + T cosPhi = cos(phi); + + *x = r * cosPhi + m_X1; + *y = r * sinPhi + m_Y1; + } + + T m_Radius;//Params. + T m_Radius1; + T m_Phi1; + T m_Thickness; + T m_Contrast; + T m_Pow; + T m_X1;//Precalc. + T m_Y1; +}; + +/// +/// GlynnSim2. +/// +template +class EMBER_API GlynnSim2Variation : public ParametricVariation +{ +public: + GlynnSim2Variation(T weight = 1.0) : ParametricVariation("GlynnSim2", VAR_GLYNNSIM2, weight, true, true) + { + Init(); + } + + PARVARCOPY(GlynnSim2Variation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T x, y, alpha = m_Radius / helper.m_PrecalcSqrtSumSquares; + + if (helper.m_PrecalcSqrtSumSquares < m_Radius) + { + Circle(rand, &x,&y); + helper.Out.x = m_Weight * x; + helper.Out.y = m_Weight * y; + } + else + { + if (rand.Frand01() > m_Contrast * pow(alpha, m_Pow)) + { + helper.Out.x = m_Weight * helper.In.x; + helper.Out.y = m_Weight * helper.In.y; + } + else + { + helper.Out.x = m_Weight * SQR(alpha) * helper.In.x; + helper.Out.y = m_Weight * SQR(alpha) * helper.In.y; + } + } + + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string radius = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string thickness = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string contrast = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string pow = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string phi1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string phi2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string phi10 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string phi20 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string gamma = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string delta = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t x, y, alpha = " << radius << " / precalcSqrtSumSquares;\n" + << "\n" + << "\t\tif (precalcSqrtSumSquares < " << radius << ")\n" + << "\t\t{\n" + << "\t\t GlynnSim2Circle(&" << radius << ", &" << thickness << ", &" << phi10 << ", &" << delta << ", &" << gamma << ", mwc, &x,&y);\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * x;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * y;\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t if (MwcNext01(mwc) > " << contrast << " * pow(alpha, " << pow << "))\n" + << "\t\t {\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * SQR(alpha) * vIn.x;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * SQR(alpha) * vIn.y;\n" + << "\t\t }\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual string OpenCLFuncsString() + { + return + "void GlynnSim2Circle(__constant real_t* radius, __constant real_t* thickness, __constant real_t* phi10, __constant real_t* delta, __constant real_t* gamma, uint2* mwc, real_t* x, real_t* y)\n" + "{\n" + " real_t r = *radius + *thickness - *gamma * MwcNext01(mwc);\n" + " real_t phi = *phi10 + *delta * MwcNext01(mwc);\n" + " real_t sinPhi = sin(phi);\n" + " real_t cosPhi = cos(phi);\n" + "\n" + " *x = r * cosPhi;\n" + " *y = r * sinPhi;\n" + "}\n" + "\n"; + } + + virtual void Precalc() + { + m_Pow = fabs(m_Pow); + m_Phi10 = M_2PI * m_Phi1; + m_Phi20 = M_2PI * m_Phi2; + m_Gamma = m_Thickness * (2 * m_Radius + m_Thickness) / (m_Radius + m_Thickness); + m_Delta = m_Phi20 - m_Phi10; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Radius, prefix + "GlynnSim2_radius", 1)); + m_Params.push_back(ParamWithName(&m_Thickness, prefix + "GlynnSim2_thickness", T(0.1), REAL, 0, 1)); + m_Params.push_back(ParamWithName(&m_Contrast, prefix + "GlynnSim2_contrast", T(0.5), REAL, 0, 1)); + m_Params.push_back(ParamWithName(&m_Pow, prefix + "GlynnSim2_pow", T(1.5))); + m_Params.push_back(ParamWithName(&m_Phi1, prefix + "GlynnSim2_Phi1")); + m_Params.push_back(ParamWithName(&m_Phi2, prefix + "GlynnSim2_Phi2", 360)); + m_Params.push_back(ParamWithName(true, &m_Phi10, prefix + "GlynnSim2_Phi10"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Phi20, prefix + "GlynnSim2_Phi20")); + m_Params.push_back(ParamWithName(true, &m_Gamma, prefix + "GlynnSim2_Gamma")); + m_Params.push_back(ParamWithName(true, &m_Delta, prefix + "GlynnSim2_Delta")); + } + +private: + void Circle(QTIsaac& rand, T* x, T* y) + { + T r = m_Radius + m_Thickness - m_Gamma * rand.Frand01(); + T phi = m_Phi10 + m_Delta * rand.Frand01(); + T sinPhi = sin(phi); + T cosPhi = cos(phi); + + *x = r * cosPhi; + *y = r * sinPhi; + } + + T m_Radius;//Params. + T m_Thickness; + T m_Contrast; + T m_Pow; + T m_Phi1; + T m_Phi2; + T m_Phi10;//Precalc. + T m_Phi20; + T m_Gamma; + T m_Delta; +}; + +/// +/// GlynnSim3. +/// +template +class EMBER_API GlynnSim3Variation : public ParametricVariation +{ +public: + GlynnSim3Variation(T weight = 1.0) : ParametricVariation("GlynnSim3", VAR_GLYNNSIM3, weight, true, true) + { + Init(); + } + + PARVARCOPY(GlynnSim3Variation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T x, y, alpha = m_Radius / helper.m_PrecalcSqrtSumSquares; + + if (helper.m_PrecalcSqrtSumSquares < m_Radius1) + { + Circle(rand, &x,&y); + helper.Out.x = m_Weight * x; + helper.Out.y = m_Weight * y; + } + else + { + if (rand.Frand01() > m_Contrast * pow(alpha, m_Pow)) + { + helper.Out.x = m_Weight * helper.In.x; + helper.Out.y = m_Weight * helper.In.y; + } + else + { + helper.Out.x = m_Weight * SQR(alpha) * helper.In.x; + helper.Out.y = m_Weight * SQR(alpha) * helper.In.y; + } + } + + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string radius = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string thickness = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string thickness2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string contrast = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string pow = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string radius1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string radius2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string gamma = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t x, y, alpha = " << radius << " / precalcSqrtSumSquares;\n" + << "\n" + << "\t\tif (precalcSqrtSumSquares < " << radius1 << ")\n" + << "\t\t{\n" + << "\t\t GlynnSim3Circle(&" << radius << ", &" << radius1 << ", &" << radius2 << ", &" << thickness << ", &" << gamma << ", mwc, &x,&y);\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * x;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * y;\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t if (MwcNext01(mwc) > " << contrast << " * pow(alpha, " << pow << "))\n" + << "\t\t {\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * SQR(alpha) * vIn.x;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * SQR(alpha) * vIn.y;\n" + << "\t\t }\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual string OpenCLFuncsString() + { + return + "void GlynnSim3Circle(__constant real_t* radius, __constant real_t* radius1, __constant real_t* radius2, __constant real_t* thickness, __constant real_t* gamma, uint2* mwc, real_t* x, real_t* y)\n" + "{\n" + " real_t r = *radius + *thickness - *gamma * MwcNext01(mwc);\n" + " real_t phi = M_2PI * MwcNext01(mwc);\n" + " real_t sinPhi = sin(phi);\n" + " real_t cosPhi = cos(phi);\n" + "\n" + " if (MwcNext01(mwc) < *gamma)\n" + " r = *radius1;\n" + " else\n" + " r = *radius2;\n" + "\n" + " *x = r * cosPhi;\n" + " *y = r * sinPhi;\n" + "}\n" + "\n"; + } + + virtual void Precalc() + { + m_Radius1 = m_Radius + m_Thickness; + m_Radius2 = SQR(m_Radius) / m_Radius1; + m_Gamma = m_Radius1 / (m_Radius1 + m_Radius2); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Radius, prefix + "GlynnSim3_radius", 1)); + m_Params.push_back(ParamWithName(&m_Thickness, prefix + "GlynnSim3_thickness", T(0.1))); + m_Params.push_back(ParamWithName(&m_Thickness2, prefix + "GlynnSim3_thickness2", T(0.1))); + m_Params.push_back(ParamWithName(&m_Contrast, prefix + "GlynnSim3_contrast", T(0.5), REAL, 0, 1)); + m_Params.push_back(ParamWithName(&m_Pow, prefix + "GlynnSim3_pow", T(1.5))); + m_Params.push_back(ParamWithName(true, &m_Radius1, prefix + "GlynnSim3_radius1"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Radius2, prefix + "GlynnSim3_radius2")); + m_Params.push_back(ParamWithName(true, &m_Gamma, prefix + "GlynnSim3_Gamma")); + } + +private: + void Circle(QTIsaac& rand, T* x, T* y) + { + T r = m_Radius + m_Thickness - m_Gamma * rand.Frand01(); + T phi = M_2PI * rand.Frand01(); + T sinPhi = sin(phi); + T cosPhi = cos(phi); + + if (rand.Frand01() < m_Gamma) + r = m_Radius1; + else + r = m_Radius2; + + *x = r * cosPhi; + *y = r * sinPhi; + } + + T m_Radius;//Params. + T m_Thickness; + T m_Thickness2; + T m_Contrast; + T m_Pow; + T m_Radius1;//Precalc. + T m_Radius2; + T m_Gamma; +}; + +/// +/// Starblur. +/// +template +class EMBER_API StarblurVariation : public ParametricVariation +{ +public: + StarblurVariation(T weight = 1.0) : ParametricVariation("starblur", VAR_STARBLUR, weight) + { + Init(); + } + + PARVARCOPY(StarblurVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T f = rand.Frand01() * m_Power * 2; + T angle = (T)(int)(f); + + f -= angle; + + T x = f * m_Length; + T z = sqrt(1 + SQR(x) - 2 * x * cos(m_Alpha)); + + if (((int)angle) % 2) + angle = M_2PI / m_Power * (((int)angle) / 2) + asin(sin(m_Alpha) * x / z); + else + angle = M_2PI / m_Power * (((int)angle) / 2) - asin(sin(m_Alpha) * x / z); + + z *= sqrt(rand.Frand01()); + + T temp = angle - T(M_PI_2); + + helper.Out.x = m_Weight * z * cos(temp); + helper.Out.y = m_Weight * z * sin(temp); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string power = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string range = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string length = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string alpha = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t f = MwcNext01(mwc) * " << power << " * 2;\n" + << "\t\treal_t angle = (real_t)(int)(f);\n" + << "\n" + << "\t\tf -= angle;\n" + << "\n" + << "\t\treal_t x = f * " << length << ";\n" + << "\t\treal_t z = sqrt(1 + SQR(x) - 2 * x * cos(" << alpha << "));\n" + << "\n" + << "\t\tif (((int)angle) % 2)\n" + << "\t\t angle = M_2PI / " << power << " * (((int)angle) / 2) + asin(sin(" << alpha << ") * x / z);\n" + << "\t\telse\n" + << "\t\t angle = M_2PI / " << power << " * (((int)angle) / 2) - asin(sin(" << alpha << ") * x / z);\n" + << "\n" + << "\t\tz *= sqrt(MwcNext01(mwc));\n" + << "\n" + << "\t\treal_t temp = angle - M_PI_2;\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * z * cos(temp);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * z * sin(temp);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Alpha = T(M_PI) / m_Power; + m_Length = sqrt(1 + SQR(m_Range) - 2 * m_Range * cos(m_Alpha)); + m_Alpha = asin(sin(m_Alpha) * m_Range / m_Length); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Power, prefix + "starblur_power", 5, INTEGER_NONZERO)); + m_Params.push_back(ParamWithName(&m_Range, prefix + "starblur_range", T(0.4016228317))); + m_Params.push_back(ParamWithName(true, &m_Length, prefix + "starblur_length"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Alpha, prefix + "starblur_alpha")); + } + +private: + T m_Power; + T m_Range; + T m_Length;//Precalc. + T m_Alpha; +}; + +/// +/// Sineblur. +/// +template +class EMBER_API SineblurVariation : public ParametricVariation +{ +public: + SineblurVariation(T weight = 1.0) : ParametricVariation("sineblur", VAR_SINEBLUR, weight) + { + Init(); + } + + PARVARCOPY(SineblurVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T ang = rand.Frand01() * M_2PI; + T s = sin(ang); + T c = cos(ang); + T r = m_Weight * (m_Power == 1 ? acos(rand.Frand01() * 2 - 1) / T(M_PI) : acos(exp(log(rand.Frand01()) * m_Power) * 2 - 1) / T(M_PI)); + + helper.Out.x = r * c; + helper.Out.y = r * s; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string power = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t ang = MwcNext01(mwc) * M_2PI;\n" + << "\t\treal_t s = sin(ang);\n" + << "\t\treal_t c = cos(ang);\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * (" << power << " == 1 ? acos(MwcNext01(mwc) * 2 - 1) / M_PI : acos(exp(log(MwcNext01(mwc)) * " << power << ") * 2 - 1) / M_PI);\n" + << "\n" + << "\t\tvOut.x = r * c;\n" + << "\t\tvOut.y = r * s;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + ClampGte0Ref(m_Power); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Power, prefix + "sineblur_power", 1)); + } + +private: + T m_Power; +}; + +/// +/// Circleblur. +/// +template +class EMBER_API CircleblurVariation : public Variation +{ +public: + CircleblurVariation(T weight = 1.0) : Variation("circleblur", VAR_CIRCLEBLUR, weight) { } + + VARCOPY(CircleblurVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T rad = sqrt(rand.Frand01()); + T temp = rand.Frand01() * M_2PI; + + helper.Out.x = m_Weight * cos(temp) * rad; + helper.Out.y = m_Weight * sin(temp) * rad; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t rad = sqrt(MwcNext01(mwc));\n" + << "\t\treal_t temp = MwcNext01(mwc) * M_2PI;\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * cos(temp) * rad;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * sin(temp) * rad;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Depth. +/// +template +class EMBER_API DepthVariation : public ParametricVariation +{ +public: + DepthVariation(T weight = 1.0) : ParametricVariation("depth", VAR_DEPTH, weight) + { + Init(); + } + + PARVARCOPY(DepthVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T coeff = fabs(helper.In.z); + + if (coeff != 0 && m_Power != 1) + coeff = exp(log(coeff) * m_Power); + + helper.Out.x = m_Weight * (helper.m_TransX + helper.In.x * coeff); + helper.Out.y = m_Weight * (helper.m_TransY + helper.In.y * coeff); + helper.Out.z = m_Weight * (helper.m_TransZ + helper.In.z * coeff); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string power = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t coeff = fabs(vIn.z);\n" + << "\n" + << "\t\tif (coeff != 0 && " << power << " != 1)\n" + << "\t\t coeff = exp(log(coeff) * " << power << ");\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (transX + vIn.x * coeff);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (transY + vIn.y * coeff);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * (transZ + vIn.z * coeff);\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Power, prefix + "depth_power", 1)); + } + +private: + T m_Power; +}; + +/// +/// CropN. +/// +template +class EMBER_API CropNVariation : public ParametricVariation +{ +public: + CropNVariation(T weight = 1.0) : ParametricVariation("cropn", VAR_CROPN, weight, true, true, false, false, true) + { + Init(); + } + + PARVARCOPY(CropNVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T xang = (helper.m_PrecalcAtanyx + T(M_PI)) / m_Alpha; + + xang = (xang - (int) xang) * m_Alpha; + xang = cos((xang < m_Alpha / 2) ? xang : m_Alpha - xang); + + T xr = xang > 0 ? m_Radius / xang : 1; + + if ((helper.m_PrecalcSqrtSumSquares > xr) == (m_Power > 0)) + { + if (m_Zero == 1) + { + helper.Out.x = helper.Out.y = 0; + } + else + { + T rdc = xr + (rand.Frand01() * T(0.5) * m_ScatterDist); + + helper.Out.x = m_Weight * rdc * cos(helper.m_PrecalcAtanyx); + helper.Out.y = m_Weight * rdc * sin(helper.m_PrecalcAtanyx); + } + } + else + { + helper.Out.x = m_Weight * helper.In.x; + helper.Out.y = m_Weight * helper.In.y; + } + + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string power = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string radius = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string scatterDist = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string zero = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string workPower = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string alpha = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t xang = (precalcAtanyx + M_PI) / " << alpha << ";\n" + << "\n" + << "\t\txang = (xang - (int) xang) * " << alpha << ";\n" + << "\t\txang = cos((xang < " << alpha << " / 2) ? xang : " << alpha << " - xang);\n" + << "\n" + << "\t\treal_t xr = xang > 0 ? " << radius << " / xang : 1;\n" + << "\n" + << "\t\tif ((precalcSqrtSumSquares > xr) == (" << power << " > 0))\n" + << "\t\t{\n" + << "\t\t if (" << zero << " == 1)\n" + << "\t\t {\n" + << "\t\t vOut.x = vOut.y = 0;\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t real_t rdc = xr + (MwcNext01(mwc) * 0.5 * " << scatterDist << ");\n" + << "\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * rdc * cos(precalcAtanyx);\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * rdc * sin(precalcAtanyx);\n" + << "\t\t }\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + bool mode = m_Power > 0; + + m_WorkPower = mode ? m_Power : -m_Power; + ClampLteRef(m_WorkPower, 2); + m_Alpha = M_2PI / m_WorkPower; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Power, prefix + "cropn_power", -5)); + m_Params.push_back(ParamWithName(&m_Radius, prefix + "cropn_radius", 1)); + m_Params.push_back(ParamWithName(&m_ScatterDist, prefix + "cropn_scatterdist")); + m_Params.push_back(ParamWithName(&m_Zero, prefix + "cropn_zero", 0, INTEGER, 0, 1)); + m_Params.push_back(ParamWithName(true, &m_WorkPower, prefix + "cropn_workpower"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Alpha, prefix + "cropn_alpha")); + } + +private: + T m_Power; + T m_Radius; + T m_ScatterDist; + T m_Zero; + T m_WorkPower;//Precalc. + T m_Alpha; +}; + +/// +/// ShredRad. +/// +template +class EMBER_API ShredRadVariation : public ParametricVariation +{ +public: + ShredRadVariation(T weight = 1.0) : ParametricVariation("shredrad", VAR_SHRED_RAD, weight, true, true, false, false, true) + { + Init(); + } + + PARVARCOPY(ShredRadVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T xang = (helper.m_PrecalcAtanyx + M_3PI + m_Alpha / 2) / m_Alpha; + T zang = ((xang - (int)xang) * m_Width + (int)xang) * m_Alpha - T(M_PI) - m_Alpha / 2 * m_Width; + + helper.Out.x = m_Weight * helper.m_PrecalcSqrtSumSquares * cos(zang); + helper.Out.y = m_Weight * helper.m_PrecalcSqrtSumSquares * sin(zang); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string n = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string width = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string alpha = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t xang = (precalcAtanyx + M_3PI + " << alpha << " / 2) / " << alpha << ";\n" + << "\t\treal_t zang = ((xang - (int)xang) * " << width << " + (int)xang) * " << alpha << " - M_PI - " << alpha << " / 2 * " << width << ";\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * precalcSqrtSumSquares * cos(zang);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * precalcSqrtSumSquares * sin(zang);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Alpha = M_2PI / m_N; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_N, prefix + "shredrad_n", 4, REAL_NONZERO)); + m_Params.push_back(ParamWithName(&m_Width, prefix + "shredrad_width", T(0.5), REAL, -1, 1)); + m_Params.push_back(ParamWithName(true, &m_Alpha, prefix + "shredrad_alpha"));//Precalc. + } + +private: + T m_N; + T m_Width; + T m_Alpha;//Precalc. +}; + +/// +/// Blob2. +/// +template +class EMBER_API Blob2Variation : public ParametricVariation +{ +public: + Blob2Variation(T weight = 1.0) : ParametricVariation("blob2", VAR_BLOB2, weight, true, true, false, false, true) + { + Init(); + } + + PARVARCOPY(Blob2Variation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + if (helper.m_PrecalcSqrtSumSquares < m_Radius) + { + helper.Out.x = m_Weight * helper.In.x; + helper.Out.y = m_Weight * helper.In.y; + } + else + { + T delta = (sin(helper.m_PrecalcAtanyx * m_N) + m_Symmetry) / m_DeltaHelp; + T positive = 1 - T(delta < 0 ? 1 : 0) * 2; + + if (m_Mode != 0) + delta = exp(m_Prescale * log(delta * positive)) * m_Postscale * m_Mode; + else + delta = exp(m_Prescale * log(delta * positive)) * m_Postscale * positive; + + T rad = m_Radius + (helper.m_PrecalcSqrtSumSquares - m_Radius) * delta; + + helper.Out.x = m_Weight * rad * cos(helper.m_PrecalcAtanyx); + helper.Out.y = m_Weight * rad * sin(helper.m_PrecalcAtanyx); + helper.Out.z = m_Weight * helper.In.z; + //helper.m_TransZ += m_Weight * outPoint.m_Z;//Original had this which is probably wrong. + } + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string mode = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string n = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string radius = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string prescale = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string postscale = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string symmetry = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string comp = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string dataHelp = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tif (precalcSqrtSumSquares < " << radius << ")\n" + << "\t\t{\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t real_t delta = (sin(precalcAtanyx * " << n << ") + " << symmetry << ") / " << dataHelp << ";\n" + << "\t\t real_t positive = 1 - (real_t)(delta < 0 ? 1 : 0) * 2;\n" + << "\n" + << "\t\t if (" << mode << " != 0)\n" + << "\t\t delta = exp(" << prescale << " * log(delta * positive)) * " << postscale << " * " << mode << ";\n" + << "\t\t else\n" + << "\t\t delta = exp(" << prescale << " * log(delta * positive)) * " << postscale << " * positive;\n" + << "\n" + << "\t\t real_t rad = " << radius << " + (precalcSqrtSumSquares - " << radius << ") * delta;\n" + << "\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * rad * cos(precalcAtanyx);\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * rad * sin(precalcAtanyx);\n" + << "\t\t vOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + //<< "\t\t transZ += xform->m_VariationWeights[" << varIndex << "] * outPoint->m_Z;\n"//Original had this which is probably wrong. + << "\t\t}\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_DeltaHelp = 1 + m_Compensation * m_Symmetry * (1 - (m_Symmetry < 0) * 2); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Mode, prefix + "blob2_mode", 0, INTEGER, -1, 1)); + m_Params.push_back(ParamWithName(&m_N, prefix + "blob2_n", 5, INTEGER)); + m_Params.push_back(ParamWithName(&m_Radius, prefix + "blob2_radius")); + m_Params.push_back(ParamWithName(&m_Prescale, prefix + "blob2_prescale", 1)); + m_Params.push_back(ParamWithName(&m_Postscale, prefix + "blob2_postscale", T(0.5))); + m_Params.push_back(ParamWithName(&m_Symmetry, prefix + "blob2_symmetry", 0, REAL, -1, 1)); + m_Params.push_back(ParamWithName(&m_Compensation, prefix + "blob2_compensation", 0, REAL, 0, 1)); + m_Params.push_back(ParamWithName(true, &m_DeltaHelp, prefix + "blob2_deltahelp"));//Precalc. + } + +private: + T m_Mode; + T m_N; + T m_Radius; + T m_Prescale; + T m_Postscale; + T m_Symmetry; + T m_Compensation; + T m_DeltaHelp;//Precalc. +}; + +/// +/// Julia3D. +/// +template +class EMBER_API Julia3DVariation : public ParametricVariation +{ +public: + Julia3DVariation(T weight = 1.0) : ParametricVariation("julia3D", VAR_JULIA3D, weight, true, true, false, false, true) + { + Init(); + } + + PARVARCOPY(Julia3DVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T z = helper.In.z / m_AbsN; + T r = m_Weight * pow(helper.m_PrecalcSumSquares + SQR(z), m_Cn); + T tmp = r * helper.m_PrecalcSqrtSumSquares; + T ang = helper.m_PrecalcAtanyx + M_2PI * rand.Rand((unsigned int)m_AbsN); + + helper.Out.x = tmp * cos(ang); + helper.Out.y = tmp * sin(ang); + helper.Out.z = r * z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string n = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string absn = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cn = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t z = vIn.z / " << absn << ";\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * pow(precalcSumSquares + SQR(z), " << cn << ");\n" + << "\t\treal_t tmp = r * precalcSqrtSumSquares;\n" + << "\t\treal_t ang = precalcAtanyx + M_2PI * MwcNextRange(mwc, (uint)" << absn << ");\n" + << "\n" + << "\t\tvOut.x = tmp * cos(ang);\n" + << "\t\tvOut.y = tmp * sin(ang);\n" + << "\t\tvOut.z = r * z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_AbsN = fabs(m_N); + m_Cn = (1 / m_N - 1) / 2; + } + + virtual void Random(QTIsaac& rand) + { + m_N = T(rand.Rand(5) + 2); + + if (rand.Rand(2) == 0) + m_N = -m_N; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_N, prefix + "julia3D_power", 2, INTEGER_NONZERO)); + m_Params.push_back(ParamWithName(true, &m_AbsN, prefix + "julia3D_absn"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Cn, prefix + "julia3D_cn")); + } + +private: + T m_N; + T m_AbsN;//Precalc. + T m_Cn; +}; + +/// +/// Julia3Dz. +/// +template +class EMBER_API Julia3DzVariation : public ParametricVariation +{ +public: + Julia3DzVariation(T weight = 1.0) : ParametricVariation("julia3Dz", VAR_JULIA3DZ, weight, true, true, false, false, true) + { + Init(); + } + + PARVARCOPY(Julia3DzVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r = m_Weight * pow(helper.m_PrecalcSumSquares, m_Cn); + T temp = helper.m_PrecalcAtanyx + M_2PI * rand.Rand((unsigned int)m_AbsN); + + helper.Out.x = r * cos(temp); + helper.Out.y = r * sin(temp); + helper.Out.z = r * helper.In.z / (helper.m_PrecalcSqrtSumSquares * m_AbsN); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string n = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string absn = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cn = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * pow(precalcSumSquares, " << cn << ");\n" + << "\t\treal_t temp = precalcAtanyx + M_2PI * MwcNextRange(mwc, (uint)" << absn << ");\n" + << "\n" + << "\t\tvOut.x = r * cos(temp);\n" + << "\t\tvOut.y = r * sin(temp);\n" + << "\t\tvOut.z = r * vIn.z / (precalcSqrtSumSquares * " << absn << ");\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_AbsN = fabs(m_N); + m_Cn = 1 / m_N / 2; + } + + virtual void Random(QTIsaac& rand) + { + m_N = T(rand.Rand(5) + 2); + + if (rand.Rand(2) == 0) + m_N = -m_N; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_N, prefix + "julia3Dz_power", 2, INTEGER_NONZERO)); + m_Params.push_back(ParamWithName(true, &m_AbsN, prefix + "julia3Dz_absn"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Cn, prefix + "julia3Dz_cn")); + } + +private: + T m_N; + T m_AbsN;//Precalc. + T m_Cn; +}; + +/// +/// LinearT. +/// +template +class EMBER_API LinearTVariation : public ParametricVariation +{ +public: + LinearTVariation(T weight = 1.0) : ParametricVariation("linearT", VAR_LINEAR_T, weight) + { + Init(); + } + + PARVARCOPY(LinearTVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + helper.Out.x = SignNz(helper.In.x) * pow(fabs(helper.In.x), m_PowX) * m_Weight; + helper.Out.y = SignNz(helper.In.y) * pow(fabs(helper.In.y), m_PowY) * m_Weight; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string powx = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string powy = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tvOut.x = (real_t)(vIn.x < 0 ? -1 : 1) * pow(fabs(vIn.x), " << powx << ") * xform->m_VariationWeights[" << varIndex << "];\n" + << "\t\tvOut.y = (real_t)(vIn.y < 0 ? -1 : 1) * pow(fabs(vIn.y), " << powy << ") * xform->m_VariationWeights[" << varIndex << "];\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_PowX, prefix + "linearT_powX", 1));//Original used a prefix of lT, which is incompatible with Ember's design. + m_Params.push_back(ParamWithName(&m_PowY, prefix + "linearT_powY", 1)); + } + +private: + T m_PowX; + T m_PowY; +}; + +/// +/// LinearT3D. +/// +template +class EMBER_API LinearT3DVariation : public ParametricVariation +{ +public: + LinearT3DVariation(T weight = 1.0) : ParametricVariation("linearT3D", VAR_LINEAR_T3D, weight) + { + Init(); + } + + PARVARCOPY(LinearT3DVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + helper.Out.x = T(helper.In.x < 0 ? -1 : 1) * pow(fabs(helper.In.x), m_PowX) * m_Weight; + helper.Out.y = T(helper.In.y < 0 ? -1 : 1) * pow(fabs(helper.In.y), m_PowY) * m_Weight; + helper.Out.z = T(helper.In.z < 0 ? -1 : 1) * pow(fabs(helper.In.z), m_PowZ) * m_Weight; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string powx = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string powy = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string powz = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tvOut.x = (real_t)(vIn.x < 0 ? -1 : 1) * pow(fabs(vIn.x), " << powx << ") * xform->m_VariationWeights[" << varIndex << "];\n" + << "\t\tvOut.y = (real_t)(vIn.y < 0 ? -1 : 1) * pow(fabs(vIn.y), " << powy << ") * xform->m_VariationWeights[" << varIndex << "];\n" + << "\t\tvOut.z = (real_t)(vIn.z < 0 ? -1 : 1) * pow(fabs(vIn.z), " << powz << ") * xform->m_VariationWeights[" << varIndex << "];\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_PowX, prefix + "linearT3D_powX", 1)); + m_Params.push_back(ParamWithName(&m_PowY, prefix + "linearT3D_powY", 1)); + m_Params.push_back(ParamWithName(&m_PowZ, prefix + "linearT3D_powZ", 1)); + } + +private: + T m_PowX; + T m_PowY; + T m_PowZ; +}; + +/// +/// Ovoid. +/// +template +class EMBER_API OvoidVariation : public ParametricVariation +{ +public: + OvoidVariation(T weight = 1.0) : ParametricVariation("ovoid", VAR_OVOID, weight, true) + { + Init(); + } + + PARVARCOPY(OvoidVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r = m_Weight / (helper.m_PrecalcSumSquares + EPS6); + + helper.Out.x = helper.In.x * r * m_X; + helper.Out.y = helper.In.y * r * m_Y; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] / (precalcSumSquares + EPS6);\n" + << "\n" + << "\t\tvOut.x = vIn.x * r * " << x << ";\n" + << "\t\tvOut.y = vIn.y * r * " << y << ";\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_X, prefix + "ovoid_x", 1)); + m_Params.push_back(ParamWithName(&m_Y, prefix + "ovoid_y", 1)); + } + +private: + T m_X; + T m_Y; +}; + +/// +/// Ovoid3D. +/// +template +class EMBER_API Ovoid3DVariation : public ParametricVariation +{ +public: + Ovoid3DVariation(T weight = 1.0) : ParametricVariation("ovoid3d", VAR_OVOID3D, weight, true) + { + Init(); + } + + PARVARCOPY(Ovoid3DVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r = m_Weight / (helper.m_PrecalcSumSquares + SQR(helper.In.z) + EPS6); + + helper.Out.x = helper.In.x * r * m_X; + helper.Out.y = helper.In.y * r * m_Y; + helper.Out.z = helper.In.z * r * m_Z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string z = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] / (precalcSumSquares + SQR(vIn.z) + EPS6);\n" + << "\n" + << "\t\tvOut.x = vIn.x * r * " << x << ";\n" + << "\t\tvOut.y = vIn.y * r * " << y << ";\n" + << "\t\tvOut.z = vIn.z * r * " << z << ";\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_X, prefix + "ovoid3d_x", 1)); + m_Params.push_back(ParamWithName(&m_Y, prefix + "ovoid3d_y", 1)); + m_Params.push_back(ParamWithName(&m_Z, prefix + "ovoid3d_z", 1)); + } + +private: + T m_X; + T m_Y; + T m_Z; +}; + +/// +/// Spirograph. +/// +template +class EMBER_API SpirographVariation : public ParametricVariation +{ +public: + SpirographVariation(T weight = 1.0) : ParametricVariation("Spirograph", VAR_SPIROGRAPH, weight) + { + Init(); + } + + PARVARCOPY(SpirographVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T t = (m_TMax - m_TMin) * rand.Frand01() + m_TMin; + T y = (m_YMax - m_YMin) * rand.Frand01() + m_YMin; + T x1 = (m_A + m_B) * cos(t) - m_C1 * cos((m_A + m_B) / m_B * t); + T y1 = (m_A + m_B) * sin(t) - m_C2 * sin((m_A + m_B) / m_B * t); + + helper.Out.x = m_Weight * (x1 + m_D * cos(t) + y); + helper.Out.y = m_Weight * (y1 + m_D * sin(t) + y); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string a = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string b = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string d = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string tmin = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ymin = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string tmax = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ymax = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t t = (" << tmax << " - " << tmin << ") * MwcNext01(mwc) + " << tmin << ";\n" + << "\t\treal_t y = (" << ymax << " - " << ymin << ") * MwcNext01(mwc) + " << ymin << ";\n" + << "\t\treal_t x1 = (" << a << " + " << b << ") * cos(t) - " << c1 << " * cos((" << a << " + " << b << ") / " << b << " * t);\n" + << "\t\treal_t y1 = (" << a << " + " << b << ") * sin(t) - " << c2 << " * sin((" << a << " + " << b << ") / " << b << " * t);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (x1 + " << d << " * cos(t) + y);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (y1 + " << d << " * sin(t) + y);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_A, prefix + "Spirograph_a", 3)); + m_Params.push_back(ParamWithName(&m_B, prefix + "Spirograph_b", 2)); + m_Params.push_back(ParamWithName(&m_D, prefix + "Spirograph_d", 1)); + m_Params.push_back(ParamWithName(&m_TMin, prefix + "Spirograph_tmin", -1)); + m_Params.push_back(ParamWithName(&m_YMin, prefix + "Spirograph_ymin", -1)); + m_Params.push_back(ParamWithName(&m_TMax, prefix + "Spirograph_tmax", 1)); + m_Params.push_back(ParamWithName(&m_YMax, prefix + "Spirograph_ymax", 1)); + m_Params.push_back(ParamWithName(&m_C1, prefix + "Spirograph_c1", 0)); + m_Params.push_back(ParamWithName(&m_C2, prefix + "Spirograph_c2", 0)); + } + +private: + T m_A; + T m_B; + T m_D; + T m_TMin; + T m_YMin; + T m_TMax; + T m_YMax; + T m_C1; + T m_C2; +}; + +/// +/// Petal. +/// +template +class EMBER_API PetalVariation : public Variation +{ +public: + PetalVariation(T weight = 1.0) : Variation("petal", VAR_PETAL, weight) { } + + VARCOPY(PetalVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T sinX = sin(helper.In.x); + T cosX = cos(helper.In.x); + T sinY = sin(helper.In.y); + T cosY = cos(helper.In.y); + T bx = Cube(cosX*cosY); + T by = Cube(sinX*cosY); + + helper.Out.x = m_Weight * cosX * bx; + helper.Out.y = m_Weight * cosX * by; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t sinX = sin(vIn.x);\n" + << "\t\treal_t cosX = cos(vIn.x);\n" + << "\t\treal_t sinY = sin(vIn.y);\n" + << "\t\treal_t cosY = cos(vIn.y);\n" + << "\t\treal_t bx = Cube(cosX*cosY);\n" + << "\t\treal_t by = Cube(sinX*cosY);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * cosX * bx;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * cosX * by;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// RoundSpher. +/// +template +class EMBER_API RoundSpherVariation : public Variation +{ +public: + RoundSpherVariation(T weight = 1.0) : Variation("roundspher", VAR_ROUNDSPHER, weight, true) { } + + VARCOPY(RoundSpherVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T e = 1 / helper.m_PrecalcSumSquares + SQR(T(M_2_PI)); + + helper.Out.x = m_Weight * (m_Weight / helper.m_PrecalcSumSquares * helper.In.x / e); + helper.Out.y = m_Weight * (m_Weight / helper.m_PrecalcSumSquares * helper.In.y / e); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t e = 1 / precalcSumSquares + SQR(M_2_PI);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (xform->m_VariationWeights[" << varIndex << "] / precalcSumSquares * vIn.x / e);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (xform->m_VariationWeights[" << varIndex << "] / precalcSumSquares * vIn.y / e);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// roundSpher3D. +/// +template +class EMBER_API RoundSpher3DVariation : public Variation +{ +public: + RoundSpher3DVariation(T weight = 1.0) : Variation("roundspher3D", VAR_ROUNDSPHER3D, weight, true, true) { } + + VARCOPY(RoundSpher3DVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T inZ, otherZ, tempTz, tempPz; + inZ = helper.In.z; + + if (m_VarType == VARTYPE_PRE) + otherZ = helper.m_TransZ; + else + otherZ = outPoint.m_Z; + + if (inZ == 0) + tempTz = cos(helper.m_PrecalcSqrtSumSquares); + else + tempTz = helper.In.z; + + if (otherZ == 0) + { + tempPz = cos(helper.m_PrecalcSqrtSumSquares); + + if (m_VarType == VARTYPE_PRE) + helper.m_TransZ = 0; + else + outPoint.m_Z = 0; + } + else + { + if (m_VarType == VARTYPE_PRE) + { + tempPz = helper.m_TransZ; + helper.m_TransZ = 0; + } + else + { + tempPz = outPoint.m_Z; + outPoint.m_Z = 0; + } + } + + T d = helper.m_PrecalcSumSquares + SQR(tempTz); + T e = 1 / d + SQR(T(M_2_PI)); + + helper.Out.x = m_Weight * (m_Weight / d * helper.In.x / e); + helper.Out.y = m_Weight * (m_Weight / d * helper.In.y / e); + helper.Out.z = tempPz + m_Weight * (m_Weight / d * tempTz / e); + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t inZ, otherZ, tempTz, tempPz;\n" + << "\t\tinZ = vIn.z;\n" + << "\n"; + + if (m_VarType == VARTYPE_PRE) + ss << "\t\totherZ = transZ;\n"; + else + ss << "\t\totherZ = outPoint->m_Z;\n"; + ss + << "\n" + << "\t\tif (inZ == 0)\n" + << "\t\t tempTz = cos(precalcSqrtSumSquares);\n" + << "\t\telse\n" + << "\t\t tempTz = vIn.z;\n" + << "\n" + << "\t\tif (otherZ == 0)\n" + << "\t\t{\n" + << "\t\t tempPz = cos(precalcSqrtSumSquares);\n" + << "\n"; + + if (m_VarType == VARTYPE_PRE) + ss << "\t\t transZ = 0;\n"; + else + ss << "\t\t outPoint->m_Z = 0;\n"; + + ss + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n"; + + if (m_VarType == VARTYPE_PRE) + { + ss + << "\t\t tempPz = transZ;\n" + << "\t\t transZ = 0;\n"; + } + else + { + ss + << "\t\t tempPz = outPoint->m_Z;\n" + << "\t\t outPoint->m_Z = 0;\n"; + } + + ss + << "\t\t}\n" + << "\n" + << "\t\treal_t d = precalcSumSquares + SQR(tempTz);\n" + << "\t\treal_t e = 1 / d + SQR(M_2_PI);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (xform->m_VariationWeights[" << varIndex << "] / d * vIn.x / e);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (xform->m_VariationWeights[" << varIndex << "] / d * vIn.y / e);\n" + << "\t\tvOut.z = tempPz + xform->m_VariationWeights[" << varIndex << "] * (xform->m_VariationWeights[" << varIndex << "] / d * tempTz / e);\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// SpiralWing. +/// +template +class EMBER_API SpiralWingVariation : public Variation +{ +public: + SpiralWingVariation(T weight = 1.0) : Variation("spiralwing", VAR_SPIRAL_WING, weight, true) { } + + VARCOPY(SpiralWingVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T c1 = SQR(helper.In.x); + T c2 = SQR(helper.In.y); + + helper.Out.x = m_Weight * ((1 / helper.m_PrecalcSumSquares + EPS6) * cos(c1 + EPS6) * sin(c2 + EPS6)); + helper.Out.y = m_Weight * ((1 / helper.m_PrecalcSumSquares + EPS6) * sin(c1 + EPS6) * sin(c2 + EPS6)); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t c1 = SQR(vIn.x);\n" + << "\t\treal_t c2 = SQR(vIn.y);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * ((1.0 / precalcSumSquares + EPS6) * cos(c1 + EPS6) * sin(c2 + EPS6));\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * ((1.0 / precalcSumSquares + EPS6) * sin(c1 + EPS6) * sin(c2 + EPS6));\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Squarize. +/// +template +class EMBER_API SquarizeVariation : public Variation +{ +public: + SquarizeVariation(T weight = 1.0) : Variation("squarize", VAR_SQUARIZE, weight, true, true, false, false, true) { } + + VARCOPY(SquarizeVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T a = helper.m_PrecalcAtanyx; + + if (a < 0) + a += M_2PI; + + T p = 4 * helper.m_PrecalcSqrtSumSquares * a * T(M_1_PI); + + if (p <= 1 * helper.m_PrecalcSqrtSumSquares) + { + helper.Out.x = m_Weight * helper.m_PrecalcSqrtSumSquares; + helper.Out.y = m_Weight * p; + } + else if (p <= 3 * helper.m_PrecalcSqrtSumSquares) + { + helper.Out.x = m_Weight * (2 * helper.m_PrecalcSqrtSumSquares - p); + helper.Out.y = m_Weight * helper.m_PrecalcSqrtSumSquares; + } + else if (p <= 5 * helper.m_PrecalcSqrtSumSquares) + { + helper.Out.x = -(m_Weight * helper.m_PrecalcSqrtSumSquares); + helper.Out.y = m_Weight * (4 * helper.m_PrecalcSqrtSumSquares - p); + } + else if (p <= 7 * helper.m_PrecalcSqrtSumSquares) + { + helper.Out.x = -(m_Weight * (6 * helper.m_PrecalcSqrtSumSquares - p)); + helper.Out.y = -(m_Weight * helper.m_PrecalcSqrtSumSquares); + } + else + { + helper.Out.x = m_Weight * helper.m_PrecalcSqrtSumSquares; + helper.Out.y = -(m_Weight * (8 * helper.m_PrecalcSqrtSumSquares - p)); + } + + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t a = precalcAtanyx;\n" + << "\n" + << "\t\tif (a < 0)\n" + << "\t\t a += M_2PI;\n" + << "\n" + << "\t\treal_t p = 4 * precalcSqrtSumSquares * a * M_1_PI;\n" + << "\n" + << "\t\tif (p <= 1 * precalcSqrtSumSquares)\n" + << "\t\t{\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * precalcSqrtSumSquares;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * p;\n" + << "\t\t}\n" + << "\t\telse if (p <= 3 * precalcSqrtSumSquares)\n" + << "\t\t{\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (2 * precalcSqrtSumSquares - p);\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * precalcSqrtSumSquares;\n" + << "\t\t}\n" + << "\t\telse if (p <= 5 * precalcSqrtSumSquares)\n" + << "\t\t{\n" + << "\t\t vOut.x = -(xform->m_VariationWeights[" << varIndex << "] * precalcSqrtSumSquares);\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * (4 * precalcSqrtSumSquares - p);\n" + << "\t\t}\n" + << "\t\telse if (p <= 7 * precalcSqrtSumSquares)\n" + << "\t\t{\n" + << "\t\t vOut.x = -(xform->m_VariationWeights[" << varIndex << "] * (6 * precalcSqrtSumSquares - p));\n" + << "\t\t vOut.y = -(xform->m_VariationWeights[" << varIndex << "] * precalcSqrtSumSquares);\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * precalcSqrtSumSquares;\n" + << "\t\t vOut.y = -(xform->m_VariationWeights[" << varIndex << "] * (8 * precalcSqrtSumSquares - p));\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Sschecks. +/// +template +class EMBER_API SschecksVariation : public ParametricVariation +{ +public: + SschecksVariation(T weight = 1.0) : ParametricVariation("sschecks", VAR_SSCHECKS, weight, true) + { + Init(); + } + + PARVARCOPY(SschecksVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T dx, dy, r = m_Weight / (helper.m_PrecalcSumSquares + EPS6); + int isXY = int(LRint(helper.In.x * m_InvSize) + LRint(helper.In.y * m_InvSize)); + + if (isXY % 2) + { + dx = -m_X + m_Rand * rand.Frand01(); + dy = -m_Y; + } + else + { + dx = m_X; + dy = m_Y + m_Rand * rand.Frand01(); + } + + helper.Out.x = m_Weight * (sin(helper.In.x) * r + dx); + helper.Out.y = m_Weight * (sin(helper.In.y) * r + dy); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string size = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string rand = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string invSize = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Precalc. + + ss << "\t{\n" + << "\t\treal_t dx, dy, r = xform->m_VariationWeights[" << varIndex << "] / (precalcSumSquares + EPS6);\n" + << "\t\tint isXY = LRint(vIn.x * " << invSize << ") + LRint(vIn.y * " << invSize << ");\n" + << "\n" + << "\t\tif (isXY % 2)\n" + << "\t\t{\n" + << "\t\t dx = -" << x << " + " << rand << " * MwcNext01(mwc);\n" + << "\t\t dy = -" << y << ";\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t dx = " << x << ";\n" + << "\t\t dy = " << y << " + " << rand << " * MwcNext01(mwc);\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (sin(vIn.x) * r + dx);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (sin(vIn.y) * r + dy);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_InvSize = 1 / (m_Size + EPS6); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_X, prefix + "sschecks_x", T(0.5))); + m_Params.push_back(ParamWithName(&m_Y, prefix + "sschecks_y", T(0.5))); + m_Params.push_back(ParamWithName(&m_Size, prefix + "sschecks_size", T(0.5))); + m_Params.push_back(ParamWithName(&m_Rand, prefix + "sschecks_rnd")); + m_Params.push_back(ParamWithName(true, &m_InvSize, prefix + "sschecks_inv_size"));//Precalc. + } + +private: + T m_X; + T m_Y; + T m_Size; + T m_Rand; + T m_InvSize;//Precalc. +}; + +/// +/// PhoenixJulia. +/// +template +class EMBER_API PhoenixJuliaVariation : public ParametricVariation +{ +public: + PhoenixJuliaVariation(T weight = 1.0) : ParametricVariation("phoenix_julia", VAR_PHOENIX_JULIA, weight, true) + { + Init(); + } + + PARVARCOPY(PhoenixJuliaVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T preX = helper.In.x * (m_XDistort + 1); + T preY = helper.In.y * (m_YDistort + 1); + T temp = atan2(preY, preX) * m_InvN + rand.Rand() * m_Inv2PiN; + T r = m_Weight * pow(helper.m_PrecalcSumSquares, m_Cn); + + helper.Out.x = r * cos(temp); + helper.Out.y = r * sin(temp); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string power = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string dist = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string xDistort = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string yDistort = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cN = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string invN = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string inv2PiN = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t preX = vIn.x * (" << xDistort << " + 1);\n" + << "\t\treal_t preY = vIn.y * (" << yDistort << " + 1);\n" + << "\t\treal_t temp = atan2(preY, preX) * " << invN << " + MwcNext(mwc) * " << inv2PiN << ";\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * pow(precalcSumSquares, " << cN << ");\n" + << "\n" + << "\t\tvOut.x = r * cos(temp);\n" + << "\t\tvOut.y = r * sin(temp);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_InvN = m_Dist / m_Power; + m_Inv2PiN = M_2PI / m_Power; + m_Cn = m_Dist / m_Power / 2; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Power, prefix + "phoenix_julia_power", 2)); + m_Params.push_back(ParamWithName(&m_Dist, prefix + "phoenix_julia_dist", 1)); + m_Params.push_back(ParamWithName(&m_XDistort, prefix + "phoenix_julia_x_distort", T(-0.5)));//Original omitted phoenix_ prefix. + m_Params.push_back(ParamWithName(&m_YDistort, prefix + "phoenix_julia_y_distort")); + m_Params.push_back(ParamWithName(true, &m_Cn, prefix + "phoenix_julia_cn"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_InvN, prefix + "phoenix_julia_invn")); + m_Params.push_back(ParamWithName(true, &m_Inv2PiN, prefix + "phoenix_julia_inv2pin")); + } + +private: + T m_Power; + T m_Dist; + T m_XDistort; + T m_YDistort; + T m_Cn;//Precalc. + T m_InvN; + T m_Inv2PiN; +}; + +/// +/// Mobius. +/// +template +class EMBER_API MobiusVariation : public ParametricVariation +{ +public: + MobiusVariation(T weight = 1.0) : ParametricVariation("Mobius", VAR_MOBIUS, weight) + { + Init(); + } + + PARVARCOPY(MobiusVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T uRe = m_Re_A * helper.In.x - m_Im_A * helper.In.y + m_Re_B; + T uIm = m_Re_A * helper.In.y + m_Im_A * helper.In.x + m_Im_B; + T vRe = m_Re_C * helper.In.x - m_Im_C * helper.In.y + m_Re_D; + T vIm = m_Re_C * helper.In.y + m_Im_C * helper.In.x + m_Im_D; + T vDenom = vRe * vRe + vIm * vIm; + + helper.Out.x = m_Weight * (uRe * vRe + uIm * vIm) / vDenom; + helper.Out.y = m_Weight * (uIm * vRe - uRe * vIm) / vDenom; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string reA = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string imA = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string reB = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string imB = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string reC = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string imC = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string reD = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string imD = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t uRe = " << reA << " * vIn.x - " << imA << " * vIn.y + " << reB << ";\n" + << "\t\treal_t uIm = " << reA << " * vIn.y + " << imA << " * vIn.x + " << imB << ";\n" + << "\t\treal_t vRe = " << reC << " * vIn.x - " << imC << " * vIn.y + " << reD << ";\n" + << "\t\treal_t vIm = " << reC << " * vIn.y + " << imC << " * vIn.x + " << imD << ";\n" + << "\t\treal_t vDenom = vRe * vRe + vIm * vIm;\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (uRe * vRe + uIm * vIm) / vDenom;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (uIm * vRe - uRe * vIm) / vDenom;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Re_A, prefix + "Mobius_Re_A", 1));//Original omitted Mobius_ prefix, which is incompatible with Ember's design. + m_Params.push_back(ParamWithName(&m_Im_A, prefix + "Mobius_Im_A")); + m_Params.push_back(ParamWithName(&m_Re_B, prefix + "Mobius_Re_B")); + m_Params.push_back(ParamWithName(&m_Im_B, prefix + "Mobius_Im_B")); + m_Params.push_back(ParamWithName(&m_Re_C, prefix + "Mobius_Re_C")); + m_Params.push_back(ParamWithName(&m_Im_C, prefix + "Mobius_Im_C")); + m_Params.push_back(ParamWithName(&m_Re_D, prefix + "Mobius_Re_D", 1)); + m_Params.push_back(ParamWithName(&m_Im_D, prefix + "Mobius_Im_D")); + } + +private: + T m_Re_A; + T m_Im_A; + T m_Re_B; + T m_Im_B; + T m_Re_C; + T m_Im_C; + T m_Re_D; + T m_Im_D; +}; + +/// +/// MobiusN. +/// +template +class EMBER_API MobiusNVariation : public ParametricVariation +{ +public: + MobiusNVariation(T weight = 1.0) : ParametricVariation("MobiusN", VAR_MOBIUSN, weight, true, true, false, false, true) + { + Init(); + } + + PARVARCOPY(MobiusNVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + int n; + + T z = 4 * m_Dist / m_Power; + T r = pow(helper.m_PrecalcSqrtSumSquares, z); + T alpha = helper.m_PrecalcAtanyx * m_Power; + T x = r * cos(alpha); + T y = r * sin(alpha); + T reU = m_Re_A * x - m_Im_A * y + m_Re_B; + T imU = m_Re_A * y + m_Im_A * x + m_Im_B; + T reV = m_Re_C * x - m_Im_C * y + m_Re_D; + T imV = m_Re_C * y + m_Im_C * x + m_Im_D; + T radV = reV * reV + imV * imV; + + x = (reU * reV + imU * imV) / radV; + y = (imU * reV - reU * imV) / radV; + + z = 1 / z; + r = pow(sqrt(SQR(x) + SQR(y)), z); + n = Floor(m_Power * rand.Frand01()); + alpha = (atan2(y, x) + n * M_2PI) / Floor(m_Power); + + helper.Out.x = m_Weight * r * cos(alpha); + helper.Out.y = m_Weight * r * sin(alpha); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string reA = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string imA = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string reB = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string imB = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string reC = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string imC = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string reD = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string imD = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string power = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string dist = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tint n;\n" + << "\n" + << "\t\treal_t z = 4.0 * " << dist << " / " << power << ";\n" + << "\t\treal_t r = pow(precalcSqrtSumSquares, z);\n" + << "\t\treal_t alpha = precalcAtanyx * " << power << ";\n" + << "\t\treal_t x = r * cos(alpha);\n" + << "\t\treal_t y = r * sin(alpha);\n" + << "\t\treal_t reU = " << reA << " * x - " << imA << " * y + " << reB << ";\n" + << "\t\treal_t imU = " << reA << " * y + " << imA << " * x + " << imB << ";\n" + << "\t\treal_t reV = " << reC << " * x - " << imC << " * y + " << reD << ";\n" + << "\t\treal_t imV = " << reC << " * y + " << imC << " * x + " << imD << ";\n" + << "\t\treal_t radV = reV * reV + imV * imV;\n" + << "\n" + << "\t\tx = (reU * reV + imU * imV) / radV;\n" + << "\t\ty = (imU * reV - reU * imV) / radV;\n" + << "\n" + << "\t\tz = 1.0 / z;\n" + << "\t\tr = pow(sqrt(SQR(x) + SQR(y)), z);\n" + << "\t\tn = (int)floor(" << power << " * MwcNext01(mwc));\n" + << "\t\talpha = (atan2(y, x) + n * M_2PI) / floor(" << power << ");\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * r * cos(alpha);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * r * sin(alpha);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + if (fabs(m_Power) < 1) + m_Power = 1; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Re_A, prefix + "MobiusNRe_A", 1)); + m_Params.push_back(ParamWithName(&m_Im_A, prefix + "MobiusNIm_A")); + m_Params.push_back(ParamWithName(&m_Re_B, prefix + "MobiusNRe_B")); + m_Params.push_back(ParamWithName(&m_Im_B, prefix + "MobiusNIm_B")); + m_Params.push_back(ParamWithName(&m_Re_C, prefix + "MobiusNRe_C")); + m_Params.push_back(ParamWithName(&m_Im_C, prefix + "MobiusNIm_C")); + m_Params.push_back(ParamWithName(&m_Re_D, prefix + "MobiusNRe_D", 1)); + m_Params.push_back(ParamWithName(&m_Im_D, prefix + "MobiusNIm_D")); + m_Params.push_back(ParamWithName(&m_Power, prefix + "MobiusN_Power", 2)); + m_Params.push_back(ParamWithName(&m_Dist, prefix + "MobiusN_Dist", 1)); + } + +private: + T m_Re_A; + T m_Im_A; + T m_Re_B; + T m_Im_B; + T m_Re_C; + T m_Im_C; + T m_Re_D; + T m_Im_D; + T m_Power; + T m_Dist; +}; + +/// +/// mobius_strip. +/// Original was "mobius", which conflicts with the other mobius variation. +/// Rename this mobius_strip for deconfliction, which breaks backward compatibility. +/// +template +class EMBER_API MobiusStripVariation : public ParametricVariation +{ +public: + MobiusStripVariation(T weight = 1.0) : ParametricVariation("mobius_strip", VAR_MOBIUS_STRIP, weight) + { + Init(); + } + + PARVARCOPY(MobiusStripVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T s, t, mx, my, mz, rx, ry, rz; + T deltaT, deltaS; + + t = helper.In.x; + + //Put t in range -rectX to +rectX, then map that to 0 - 2pi. + if (m_RectX == 0) + { + t = 0; + } + else + { + deltaT = (t + m_RectX) / (2 * m_RectX); + deltaT -= Floor(deltaT); + t = M_2PI * deltaT; + } + + s = helper.In.y; + + //Put s in range -rectY to +rectY, then map that to -width to +width. + if (m_RectY == 0) + { + s = 0; + } + else + { + deltaS = (s + m_RectY) / (2 * m_RectY); + deltaS -= Floor(deltaS); + s = 2 * m_Width * deltaS - m_Width; + } + + //Initial "object" co-ordinates. + mx = (m_Radius + s * cos(t / 2)) * cos(t); + my = (m_Radius + s * cos(t / 2)) * sin(t); + mz = s * sin(t / 2); + + //Rotate around X axis (change y & z) and store temporarily in R variables. + rx = mx; + ry = my * m_RotyCos + mz * m_RotySin; + rz = mz * m_RotyCos - my * m_RotySin; + + //Rotate around Y axis (change x & z) and store back in M variables. + mx = rx * m_RotxCos - rz * m_RotxSin; + my = ry; + mz = rz * m_RotxCos + rx * m_RotxSin; + + //Add final values in to variations totals. + helper.Out.x = m_Weight * mx; + helper.Out.y = m_Weight * my; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string radius = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string width = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string rectX = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string rectY = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string rotateX = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string rotateY = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string rotxSin = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string rotxCos = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string rotySin = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string rotyCos = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t s, t, mx, my, mz, rx, ry, rz;\n" + << "\t\treal_t deltaT, deltaS;\n" + << "\n" + << "\t\tt = vIn.x;\n" + << "\n" + << "\t\tif (" << rectX << " == 0)\n" + << "\t\t{\n" + << "\t\t t = 0;\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t deltaT = (t + " << rectX << ") / (2 * " << rectX << ");\n" + << "\t\t deltaT -= floor(deltaT);\n" + << "\t\t t = M_2PI * deltaT;\n" + << "\t\t}\n" + << "\n" + << "\t\ts = vIn.y;\n" + << "\n" + << "\t\tif (" << rectY << " == 0)\n" + << "\t\t{\n" + << "\t\t s = 0;\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t deltaS = (s + " << rectY << ") / (2 * " << rectY << ");\n" + << "\t\t deltaS -= floor(deltaS);\n" + << "\t\t s = 2 * " << width << " * deltaS - " << width << ";\n" + << "\t\t}\n" + << "\n" + << "\t\tmx = (" << radius << " + s * cos(t / 2)) * cos(t);\n" + << "\t\tmy = (" << radius << " + s * cos(t / 2)) * sin(t);\n" + << "\t\tmz = s * sin(t / 2);\n" + << "\n" + << "\t\trx = mx;\n" + << "\t\try = my * " << rotyCos << " + mz * " << rotySin << ";\n" + << "\t\trz = mz * " << rotyCos << " - my * " << rotySin << ";\n" + << "\n" + << "\t\tmx = rx * " << rotxCos << " - rz * " << rotxSin << ";\n" + << "\t\tmy = ry;\n" + << "\t\tmz = rz * " << rotxCos << " + rx * " << rotxSin << ";\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * mx;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * my;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_RotxSin = sin(m_RotateX * M_2PI); + m_RotxCos = cos(m_RotateX * M_2PI); + m_RotySin = sin(m_RotateY * M_2PI); + m_RotyCos = cos(m_RotateY * M_2PI); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Radius, prefix + "mobius_strip_radius", 2)); + m_Params.push_back(ParamWithName(&m_Width, prefix + "mobius_strip_width", 1)); + m_Params.push_back(ParamWithName(&m_RectX, prefix + "mobius_strip_rect_x", M_2PI)); + m_Params.push_back(ParamWithName(&m_RectY, prefix + "mobius_strip_rect_y", 1)); + m_Params.push_back(ParamWithName(&m_RotateX, prefix + "mobius_strip_rotate_x")); + m_Params.push_back(ParamWithName(&m_RotateY, prefix + "mobius_strip_rotate_y")); + m_Params.push_back(ParamWithName(true, &m_RotxSin, prefix + "mobius_strip_rotxsin"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_RotxCos, prefix + "mobius_strip_rotxcos")); + m_Params.push_back(ParamWithName(true, &m_RotySin, prefix + "mobius_strip_rotysin")); + m_Params.push_back(ParamWithName(true, &m_RotyCos, prefix + "mobius_strip_rotycos")); + } + +private: + T m_Radius; + T m_Width; + T m_RectX; + T m_RectY; + T m_RotateX; + T m_RotateY; + T m_RotxSin;//Precalc. + T m_RotxCos; + T m_RotySin; + T m_RotyCos; +}; + +/// +/// Lissajous. +/// +template +class EMBER_API LissajousVariation : public ParametricVariation +{ +public: + LissajousVariation(T weight = 1.0) : ParametricVariation("Lissajous", VAR_LISSAJOUS, weight) + { + Init(); + } + + PARVARCOPY(LissajousVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T t = (m_Max - m_Min) * rand.Frand01() + m_Min; + T y = rand.Frand01() - T(0.5); + T x1 = sin(m_A * t + m_D); + T y1 = sin(m_B * t); + + helper.Out.x = m_Weight * (x1 + m_C * t + m_E * y); + helper.Out.y = m_Weight * (y1 + m_C * t + m_E * y); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string min = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string max = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string a = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string b = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string d = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string e = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t t = (" << max << " - " << min << ") * MwcNext01(mwc) + " << min << ";\n" + << "\t\treal_t y = MwcNext01(mwc) - 0.5;\n" + << "\t\treal_t x1 = sin(" << a << " * t + " << d << ");\n" + << "\t\treal_t y1 = sin(" << b << " * t);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (x1 + " << c << " * t + " << e << " * y);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (y1 + " << c << " * t + " << e << " * y);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Min, prefix + "Lissajous_tmin", -T(M_PI))); + m_Params.push_back(ParamWithName(&m_Max, prefix + "Lissajous_tmax", T(M_PI))); + m_Params.push_back(ParamWithName(&m_A, prefix + "Lissajous_a", 3)); + m_Params.push_back(ParamWithName(&m_B, prefix + "Lissajous_b", 2)); + m_Params.push_back(ParamWithName(&m_C, prefix + "Lissajous_c")); + m_Params.push_back(ParamWithName(&m_D, prefix + "Lissajous_d")); + m_Params.push_back(ParamWithName(&m_E, prefix + "Lissajous_e")); + } + +private: + T m_Min; + T m_Max; + T m_A; + T m_B; + T m_C; + T m_D; + T m_E; +}; + +/// +/// svf. +/// +template +class EMBER_API SvfVariation : public ParametricVariation +{ +public: + SvfVariation(T weight = 1.0) : ParametricVariation("svf", VAR_SVF, weight) + { + Init(); + } + + PARVARCOPY(SvfVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T cn = cos(m_N * helper.In.y); + T sx = sin(helper.In.x); + T cx = cos(helper.In.x); + T sy = sin(helper.In.y); + T cy = cos(helper.In.y); + + helper.Out.x = m_Weight * (cy * (cn * cx)); + helper.Out.y = m_Weight * (cy * (cn * sx)); + helper.Out.z = m_Weight * (sy * cn); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string n = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t cn = cos(" << n << " * vIn.y);\n" + << "\t\treal_t sx = sin(vIn.x);\n" + << "\t\treal_t cx = cos(vIn.x);\n" + << "\t\treal_t sy = sin(vIn.y);\n" + << "\t\treal_t cy = cos(vIn.y);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (cy * (cn * cx));\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (cy * (cn * sx));\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * (sy * cn);\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_N, prefix + "svf_n", 2)); + } + +private: + T m_N; +}; + +/// +/// target. +/// +template +class EMBER_API TargetVariation : public ParametricVariation +{ +public: + TargetVariation(T weight = 1.0) : ParametricVariation("target", VAR_TARGET, weight, true, true, false, false, true) + { + Init(); + } + + PARVARCOPY(TargetVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T a = helper.m_PrecalcAtanyx; + T t = log(helper.m_PrecalcSqrtSumSquares); + + if (t < 0) + t -= m_SizeDiv2; + + t = fmod(fabs(t), m_Size); + + if (t < m_SizeDiv2) + a += m_Even; + else + a += m_Odd; + + helper.Out.x = helper.m_PrecalcSqrtSumSquares * cos(a); + helper.Out.y = helper.m_PrecalcSqrtSumSquares * sin(a); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string even = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string odd = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string size = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string sizeDiv2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t a = precalcAtanyx;\n" + << "\t\treal_t t = log(precalcSqrtSumSquares);\n" + << "\n" + << "\t\tif (t < 0.0)\n" + << "\t\t t -= " << sizeDiv2 << ";\n" + << "\n" + << "\t\tt = fmod(fabs(t), " << size << ");\n" + << "\n" + << "\t\tif (t < " << sizeDiv2 << ")\n" + << "\t\t a += " << even << ";\n" + << "\t\telse\n" + << "\t\t a += " << odd << ";\n" + << "\n" + << "\t\tvOut.x = precalcSqrtSumSquares * cos(a);\n" + << "\t\tvOut.y = precalcSqrtSumSquares * sin(a);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_SizeDiv2 = m_Size * T(0.5); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Even, prefix + "target_even", 0, REAL_CYCLIC, 0, M_2PI)); + m_Params.push_back(ParamWithName(&m_Odd, prefix + "target_odd", 0, REAL_CYCLIC, 0, M_2PI)); + m_Params.push_back(ParamWithName(&m_Size, prefix + "target_size", 1, REAL, EPS6, TMAX)); + m_Params.push_back(ParamWithName(true, &m_SizeDiv2, prefix + "target_size_2"));//Precalc. + } + +private: + T m_Even; + T m_Odd; + T m_Size; + T m_SizeDiv2;//Precalc. +}; + +/// +/// taurus. +/// +template +class EMBER_API TaurusVariation : public ParametricVariation +{ +public: + TaurusVariation(T weight = 1.0) : ParametricVariation("taurus", VAR_TAURUS, weight) + { + Init(); + } + + PARVARCOPY(TaurusVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T sx = sin(helper.In.x); + T cx = cos(helper.In.x); + T sy = sin(helper.In.y); + T cy = cos(helper.In.y); + T ir = m_InvTimesR + (m_1MinusInv * (m_R * cos(m_N * helper.In.x))); + + helper.Out.x = m_Weight * (cx * (ir + sy)); + helper.Out.y = m_Weight * (sx * (ir + sy)); + helper.Out.z = m_Weight * (m_Sor * cy) + (m_1MinusSor * helper.In.y); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string r = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string n = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string inv = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string sor = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string invTimesR = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string oneMinusInv = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string oneMinusSor = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t sx = sin(vIn.x);\n" + << "\t\treal_t cx = cos(vIn.x);\n" + << "\t\treal_t sy = sin(vIn.y);\n" + << "\t\treal_t cy = cos(vIn.y);\n" + << "\t\treal_t ir = " << invTimesR << " + (" << oneMinusInv << " * (" << r << " * cos(" << n << " * vIn.x)));\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (cx * (ir + sy));\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (sx * (ir + sy));\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * (" << sor << " * cy) + (" << oneMinusSor << " * vIn.y);\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_InvTimesR = m_Inv * m_R; + m_1MinusInv = 1 - m_Inv; + m_1MinusSor = 1 - m_Sor; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_R, prefix + "taurus_r", 3)); + m_Params.push_back(ParamWithName(&m_N, prefix + "taurus_n", 5)); + m_Params.push_back(ParamWithName(&m_Inv, prefix + "taurus_inv", T(1.5))); + m_Params.push_back(ParamWithName(&m_Sor, prefix + "taurus_sor", 1)); + m_Params.push_back(ParamWithName(true, &m_InvTimesR, prefix + "taurus_inv_times_r"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_1MinusInv, prefix + "taurus_1_minus_inv")); + m_Params.push_back(ParamWithName(true, &m_1MinusSor, prefix + "taurus_1_minus_sor")); + } + +private: + T m_R; + T m_N; + T m_Inv; + T m_Sor; + T m_InvTimesR;//Precalc. + T m_1MinusInv; + T m_1MinusSor; +}; + +/// +/// collideoscope. +/// +template +class EMBER_API CollideoscopeVariation : public ParametricVariation +{ +public: + CollideoscopeVariation(T weight = 1.0) : ParametricVariation("collideoscope", VAR_COLLIDEOSCOPE, weight, true, true, false, false, true) + { + Init(); + } + + PARVARCOPY(CollideoscopeVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + int alt; + T a = helper.m_PrecalcAtanyx; + T r = m_Weight * helper.m_PrecalcSqrtSumSquares; + + if (a >= 0) + { + alt = (int)(a * m_KnPi); + + if (alt % 2 == 0) + a = alt * m_PiKn + fmod(m_KaKn + a, m_PiKn); + else + a = alt * m_PiKn + fmod(-m_KaKn + a, m_PiKn); + } + else + { + alt = (int)(-a * m_KnPi); + + if (alt % 2 == 1) + a = -(alt * m_PiKn + fmod(-m_KaKn - a, m_PiKn)); + else + a = -(alt * m_PiKn + fmod(m_KaKn - a, m_PiKn)); + } + + helper.Out.x = r * cos(a); + helper.Out.y = r * sin(a); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string a = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string num = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ka = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string knpi = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string kakn = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string pikn = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tint alt;\n" + << "\t\treal_t a = precalcAtanyx;\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * precalcSqrtSumSquares;\n" + << "\n" + << "\t\tif (a >= 0)\n" + << "\t\t{\n" + << "\t\t alt = (int)(a * " << knpi << ");\n" + << "\n" + << "\t\t if (alt % 2 == 0)\n" + << "\t\t a = alt * " << pikn << " + fmod(" << kakn << " + a, " << pikn << ");\n" + << "\t\t else\n" + << "\t\t a = alt * " << pikn << " + fmod(-" << kakn << " + a, " << pikn << ");\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t alt = (int)(-a * " << knpi << ");\n" + << "\n" + << "\t\t if (alt % 2 == 1)\n" + << "\t\t a = -(alt * " << pikn << " + fmod(-" << kakn << " - a, " << pikn << "));\n" + << "\t\t else\n" + << "\t\t a = -(alt * " << pikn << " + fmod(" << kakn << " - a, " << pikn << "));\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.x = r * cos(a);\n" + << "\t\tvOut.y = r * sin(a);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + if (m_Num == 0) + m_Num = EPS6; + + m_KnPi = m_Num * T(M_1_PI); + m_PiKn = T(M_PI) / m_Num; + m_Ka = T(M_PI) * m_A; + m_KaKn = m_Ka / m_Num; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_A, prefix + "collideoscope_a", 0, REAL_CYCLIC, 0, 1)); + m_Params.push_back(ParamWithName(&m_Num, prefix + "collideoscope_num", 1, INTEGER)); + m_Params.push_back(ParamWithName(true, &m_Ka, prefix + "collideoscope_ka"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_KnPi, prefix + "collideoscope_kn_pi")); + m_Params.push_back(ParamWithName(true, &m_KaKn, prefix + "collideoscope_ka_kn")); + m_Params.push_back(ParamWithName(true, &m_PiKn, prefix + "collideoscope_pi_kn")); + } + +private: + T m_A; + T m_Num; + T m_Ka;//Precalc. + T m_KnPi; + T m_KaKn; + T m_PiKn; +}; + +/// +/// bMod. +/// +template +class EMBER_API BModVariation : public ParametricVariation +{ +public: + BModVariation(T weight = 1.0) : ParametricVariation("bMod", VAR_BMOD, weight) + { + Init(); + } + + PARVARCOPY(BModVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T tau = T(0.5) * (log(Sqr(helper.In.x + 1) + SQR(helper.In.y)) - log(Sqr(helper.In.x - 1) + SQR(helper.In.y))); + T sigma = T(M_PI) - atan2(helper.In.y, helper.In.x + 1) - atan2(helper.In.y, 1 - helper.In.x); + + if (tau < m_Radius && -tau < m_Radius) + tau = fmod(tau + m_Radius + m_Distance * m_Radius, 2 * m_Radius) - m_Radius; + + T temp = cosh(tau) - cos(sigma); + + helper.Out.x = m_Weight * sinh(tau) / temp; + helper.Out.y = m_Weight * sin(sigma) / temp; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string radius = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string dist = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t tau = 0.5 * (log(Sqr(vIn.x + 1.0) + SQR(vIn.y)) - log(Sqr(vIn.x - 1.0) + SQR(vIn.y)));\n" + << "\t\treal_t sigma = M_PI - atan2(vIn.y, vIn.x + 1.0) - atan2(vIn.y, 1.0 - vIn.x);\n" + << "\n" + << "\t\tif (tau < " << radius << " && -tau < " << radius << ")\n" + << "\t\t tau = fmod(tau + " << radius << " + " << dist << " * " << radius << ", 2.0 * " << radius << ") - " << radius << ";\n" + << "\n" + << "\t\treal_t temp = cosh(tau) - cos(sigma);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * sinh(tau) / temp;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * sin(sigma) / temp;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Radius, prefix + "bMod_radius", 1, REAL, 0, TMAX)); + m_Params.push_back(ParamWithName(&m_Distance, prefix + "bMod_distance", 0, REAL_CYCLIC, 0, 2)); + } + +private: + T m_Radius; + T m_Distance; +}; + +/// +/// bSwirl. +/// +template +class EMBER_API BSwirlVariation : public ParametricVariation +{ +public: + BSwirlVariation(T weight = 1.0) : ParametricVariation("bSwirl", VAR_BSWIRL, weight) + { + Init(); + } + + PARVARCOPY(BSwirlVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T tau = T(0.5) * (log(Sqr(helper.In.x + 1) + SQR(helper.In.y)) - log(Sqr(helper.In.x - 1) + SQR(helper.In.y))); + T sigma = T(M_PI) - atan2(helper.In.y, helper.In.x + 1) - atan2(helper.In.y, 1 - helper.In.x); + + sigma += tau * m_Out + m_In / tau; + + T temp = cosh(tau) - cos(sigma); + + helper.Out.x = m_Weight * sinh(tau) / temp; + helper.Out.y = m_Weight * sin(sigma) / temp; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string in = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string out = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t tau = 0.5 * (log(Sqr(vIn.x + 1.0) + SQR(vIn.y)) - log(Sqr(vIn.x - 1.0) + SQR(vIn.y)));\n" + << "\t\treal_t sigma = M_PI - atan2(vIn.y, vIn.x + 1.0) - atan2(vIn.y, 1.0 - vIn.x);\n" + << "\n" + << "\t\tsigma += tau * " << out << " + " << in << " / tau;\n" + << "\n" + << "\t\treal_t temp = cosh(tau) - cos(sigma);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * sinh(tau) / temp;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * sin(sigma) / temp;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_In, prefix + "bSwirl_in")); + m_Params.push_back(ParamWithName(&m_Out, prefix + "bSwirl_out")); + } + +private: + T m_In; + T m_Out; +}; + +/// +/// bTransform. +/// +template +class EMBER_API BTransformVariation : public ParametricVariation +{ +public: + BTransformVariation(T weight = 1.0) : ParametricVariation("bTransform", VAR_BTRANSFORM, weight) + { + Init(); + } + + PARVARCOPY(BTransformVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T tau = T(0.5) * (log(Sqr(helper.In.x + 1) + SQR(helper.In.y)) - log(Sqr(helper.In.x - 1) + SQR(helper.In.y))) / m_Power + m_Move; + T sigma = T(M_PI) - atan2(helper.In.y, helper.In.x + 1) - atan2(helper.In.y, 1 - helper.In.x) + m_Rotate; + + sigma /= m_Power + M_2PI / m_Power * Floor(rand.Frand01() * m_Power); + + if (helper.In.x >= 0) + tau += m_Split; + else + tau -= m_Split; + + T temp = cosh(tau) - cos(sigma); + + helper.Out.x = m_Weight * sinh(tau) / temp; + helper.Out.y = m_Weight * sin(sigma) / temp; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string rotate = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string power = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string move = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string split = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t tau = 0.5 * (log(Sqr(vIn.x + 1.0) + SQR(vIn.y)) - log(Sqr(vIn.x - 1.0) + SQR(vIn.y))) / " << power << " + " << move << ";\n" + << "\t\treal_t sigma = M_PI - atan2(vIn.y, vIn.x + 1.0) - atan2(vIn.y, 1.0 - vIn.x) + " << rotate << ";\n" + << "\n" + << "\t\tsigma /= " << power << " + M_2PI / " << power << " * floor(MwcNext01(mwc) * " << power << ");\n" + << "\n" + << "\t\tif (vIn.x >= 0)\n" + << "\t\t tau += " << split << ";\n" + << "\t\telse\n" + << "\t\t tau -= " << split << ";\n" + << "\n" + << "\t\treal_t temp = cosh(tau) - cos(sigma);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * sinh(tau) / temp;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * sin(sigma) / temp;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Rotate, prefix + "bTransform_rotate")); + m_Params.push_back(ParamWithName(&m_Power, prefix + "bTransform_power", 1, INTEGER, 1, T(INT_MAX))); + m_Params.push_back(ParamWithName(&m_Move, prefix + "bTransform_move")); + m_Params.push_back(ParamWithName(&m_Split, prefix + "bTransform_split")); + } + +private: + T m_Rotate; + T m_Power; + T m_Move; + T m_Split; +}; + +/// +/// bCollide. +/// +template +class EMBER_API BCollideVariation : public ParametricVariation +{ +public: + BCollideVariation(T weight = 1.0) : ParametricVariation("bCollide", VAR_BCOLLIDE, weight) + { + Init(); + } + + PARVARCOPY(BCollideVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T tau = T(0.5) * (log(Sqr(helper.In.x + 1) + SQR(helper.In.y)) - log(Sqr(helper.In.x - 1) + SQR(helper.In.y))); + T sigma = T(M_PI) - atan2(helper.In.y, helper.In.x + 1) - atan2(helper.In.y, 1 - helper.In.x); + int alt = (int)(sigma * m_CnPi); + + if (alt % 2 == 0) + sigma = alt * m_PiCn + fmod(sigma + m_CaCn, m_PiCn); + else + sigma = alt * m_PiCn + fmod(sigma - m_CaCn, m_PiCn); + + T temp = cosh(tau) - cos(sigma); + + helper.Out.x = m_Weight * sinh(tau) / temp; + helper.Out.y = m_Weight * sin(sigma) / temp; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string a = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string num = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ca = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cnPi = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string caCn = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string piCn = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t tau = 0.5 * (log(Sqr(vIn.x + 1.0) + SQR(vIn.y)) - log(Sqr(vIn.x - 1.0) + SQR(vIn.y)));\n" + << "\t\treal_t sigma = M_PI - atan2(vIn.y, vIn.x + 1.0) - atan2(vIn.y, 1.0 - vIn.x);\n" + << "\t\tint alt = (int)(sigma * " << cnPi << ");\n" + << "\n" + << "\t\tif (alt % 2 == 0)\n" + << "\t\t sigma = alt * " << piCn << " + fmod(sigma + " << caCn << ", " << piCn << ");\n" + << "\t\telse\n" + << "\t\t sigma = alt * " << piCn << " + fmod(sigma - " << caCn << ", " << piCn << ");\n" + << "\n" + << "\t\treal_t temp = cosh(tau) - cos(sigma);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * sinh(tau) / temp;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * sin(sigma) / temp;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_CnPi = m_Num * T(M_1_PI); + m_PiCn = T(M_PI) / m_Num; + m_Ca = T(M_PI) * m_A; + m_CaCn = m_Ca / m_Num; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_A, prefix + "bCollide_a", 0, REAL_CYCLIC, 0, 1)); + m_Params.push_back(ParamWithName(&m_Num, prefix + "bCollide_num", 1, INTEGER, 1, T(INT_MAX))); + m_Params.push_back(ParamWithName(true, &m_Ca, prefix + "bCollide_ca"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_CnPi, prefix + "bCollide_cn_pi")); + m_Params.push_back(ParamWithName(true, &m_CaCn, prefix + "bCollide_ca_cn")); + m_Params.push_back(ParamWithName(true, &m_PiCn, prefix + "bCollide_pi_cn")); + } + +private: + T m_A; + T m_Num; + T m_Ca;//Precalc. + T m_CnPi; + T m_CaCn; + T m_PiCn; +}; + +/// +/// eclipse. +/// +template +class EMBER_API EclipseVariation : public ParametricVariation +{ +public: + EclipseVariation(T weight = 1.0) : ParametricVariation("eclipse", VAR_ECLIPSE, weight) + { + Init(); + } + + PARVARCOPY(EclipseVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T x, c2; + + if (fabs(helper.In.y) <= m_Weight) + { + c2 = sqrt(SQR(m_Weight) - SQR(helper.In.y)); + + if (fabs(helper.In.x) <= c2) + { + x = helper.In.x + m_Shift * m_Weight; + + if (fabs(x) >= c2) + helper.Out.x = -(m_Weight * helper.In.x); + else + helper.Out.x = m_Weight * x; + } + else + { + helper.Out.x = m_Weight * helper.In.x; + } + + helper.Out.y = m_Weight * helper.In.y; + } + else + { + helper.Out.x = m_Weight * helper.In.x; + helper.Out.y = m_Weight * helper.In.y; + } + + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string shift = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t x, c2;\n" + << "\n" + << "\t\tif (fabs(vIn.y) <= xform->m_VariationWeights[" << varIndex << "])\n" + << "\t\t{\n" + << "\t\t c2 = sqrt(SQR(xform->m_VariationWeights[" << varIndex << "]) - SQR(vIn.y));\n" + << "\n" + << "\t\t if (fabs(vIn.x) <= c2)\n" + << "\t\t {\n" + << "\t\t x = vIn.x + " << shift << " * xform->m_VariationWeights[" << varIndex << "];\n" + << "\n" + << "\t\t if (fabs(x) >= c2)\n" + << "\t\t vOut.x = -(xform->m_VariationWeights[" << varIndex << "] * vIn.x);\n" + << "\t\t else\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * x;\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\t\t }\n" + << "\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Shift, prefix + "eclipse_shift", 0, REAL_CYCLIC, -2, 2)); + } + +private: + T m_Shift; +}; + +/// +/// flipcircle. +/// +template +class EMBER_API FlipCircleVariation : public ParametricVariation +{ +public: + FlipCircleVariation(T weight = 1.0) : ParametricVariation("flipcircle", VAR_FLIP_CIRCLE, weight, true) + { + Init(); + } + + PARVARCOPY(FlipCircleVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + helper.Out.x = m_Weight * helper.In.x; + + if (helper.m_PrecalcSumSquares > m_WeightSquared) + helper.Out.y = m_Weight * helper.In.y; + else + helper.Out.y = -(m_Weight * helper.In.y); + + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string ww = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\n" + << "\t\tif (precalcSumSquares > " << ww << ")\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\t\telse\n" + << "\t\t vOut.y = -(xform->m_VariationWeights[" << varIndex << "] * vIn.y);\n" + << "\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_WeightSquared = SQR(m_Weight); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(true, &m_WeightSquared, prefix + "flipcircle_weight_squared")); + } + +private: + T m_WeightSquared; +}; + +/// +/// flipy. +/// +template +class EMBER_API FlipYVariation : public Variation +{ +public: + FlipYVariation(T weight = 1.0) : Variation("flipy", VAR_FLIP_Y, weight) { } + + VARCOPY(FlipYVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + helper.Out.x = m_Weight * helper.In.x; + + if (helper.In.x > 0) + helper.Out.y = -(m_Weight * helper.In.y); + else + helper.Out.y = m_Weight * helper.In.y; + + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\n" + << "\t\tif (vIn.x > 0)\n" + << "\t\t vOut.y = -(xform->m_VariationWeights[" << varIndex << "] * vIn.y);\n" + << "\t\telse\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// eCollide. +/// +template +class EMBER_API ECollideVariation : public ParametricVariation +{ +public: + ECollideVariation(T weight = 1.0) : ParametricVariation("eCollide", VAR_ECOLLIDE, weight, true) + { + Init(); + } + + PARVARCOPY(ECollideVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T tmp = helper.m_PrecalcSumSquares + 1; + T tmp2 = 2 * helper.In.x; + T xmax = (SafeSqrt(tmp + tmp2) + SafeSqrt(tmp - tmp2)) * T(0.5); + int alt; + + if (xmax < 1) + xmax = 1; + + T nu = acos(Clamp(helper.In.x / xmax, -1, 1)); // -Pi < nu < Pi + + if (helper.In.y > 0) + { + alt = (int)(nu * m_CnPi); + + if (alt % 2 == 0) + nu = alt * m_PiCn + fmod(nu + m_CaCn, m_PiCn); + else + nu = alt * m_PiCn + fmod(nu - m_CaCn, m_PiCn); + } + else + { + alt = (int)(nu * m_CnPi); + + if (alt % 2 == 0) + nu = alt * m_PiCn + fmod(nu + m_CaCn, m_PiCn); + else + nu = alt * m_PiCn + fmod(nu - m_CaCn, m_PiCn); + + nu *= -1; + } + + helper.Out.x = m_Weight * xmax * cos(nu); + helper.Out.y = m_Weight * sqrt(xmax - 1) * sqrt(xmax + 1) * sin(nu); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string a = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string num = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ca = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cnPi = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string caCn = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string piCn = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t tmp = precalcSumSquares + 1;\n" + << "\t\treal_t tmp2 = 2 * vIn.x;\n" + << "\t\treal_t xmax = (SafeSqrt(tmp + tmp2) + SafeSqrt(tmp - tmp2)) * 0.5;\n" + << "\t\tint alt;\n" + << "\n" + << "\t\tif (xmax < 1)\n" + << "\t\t xmax = 1;\n" + << "\n" + << "\t\treal_t nu = acos(Clamp(vIn.x / xmax, -1.0, 1.0));\n" + << "\n" + << "\t\tif (vIn.y > 0)\n" + << "\t\t{\n" + << "\t\t alt = (int)(nu * " << cnPi << ");\n" + << "\n" + << "\t\t if (alt % 2 == 0)\n" + << "\t\t nu = alt * " << piCn << " + fmod(nu + " << caCn << ", " << piCn << ");\n" + << "\t\t else\n" + << "\t\t nu = alt * " << piCn << " + fmod(nu - " << caCn << ", " << piCn << ");\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t alt = (int)(nu * " << cnPi << ");\n" + << "\n" + << "\t\t if (alt % 2 == 0)\n" + << "\t\t nu = alt * " << piCn << " + fmod(nu + " << caCn << ", " << piCn << ");\n" + << "\t\t else\n" + << "\t\t nu = alt * " << piCn << " + fmod(nu - " << caCn << ", " << piCn << ");\n" + << "\n" + << "\t\t nu *= -1;\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * xmax * cos(nu);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * sqrt(xmax - 1) * sqrt(xmax + 1) * sin(nu);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_CnPi = m_Num * T(M_1_PI); + m_PiCn = T(M_PI) / m_Num; + m_Ca = T(M_PI) * m_A; + m_CaCn = m_Ca / m_Num; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_A, prefix + "eCollide_a", 0, REAL_CYCLIC, 0, 1)); + m_Params.push_back(ParamWithName(&m_Num, prefix + "eCollide_num", 1, INTEGER, 1, T(INT_MAX))); + m_Params.push_back(ParamWithName(true, &m_Ca, prefix + "eCollide_ca"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_CnPi, prefix + "eCollide_cn_pi")); + m_Params.push_back(ParamWithName(true, &m_CaCn, prefix + "eCollide_ca_cn")); + m_Params.push_back(ParamWithName(true, &m_PiCn, prefix + "eCollide_pi_cn")); + } + +private: + T m_A; + T m_Num; + T m_Ca;//Precalc. + T m_CnPi; + T m_CaCn; + T m_PiCn; +}; + +/// +/// eJulia. +/// +template +class EMBER_API EJuliaVariation : public ParametricVariation +{ +public: + EJuliaVariation(T weight = 1.0) : ParametricVariation("eJulia", VAR_EJULIA, weight, true) + { + Init(); + } + + PARVARCOPY(EJuliaVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T x, r2 = helper.m_PrecalcSumSquares; + + if (m_Sign == 1) + { + x = helper.In.x; + } + else + { + r2 = 1 / r2; + x = helper.In.x * r2; + } + + T tmp = r2 + 1; + T tmp2 = 2 * x; + T xmax = (SafeSqrt(tmp + tmp2) + SafeSqrt(tmp - tmp2)) * T(0.5); + + ClampGteRef(xmax, 1); + + T mu = acosh(xmax); + T nu = acos(Clamp(x / xmax, -1, 1));//-Pi < nu < Pi. + + if (helper.In.y < 0) + nu *= -1; + + nu = nu / m_Power + M_2PI / m_Power * Floor(rand.Frand01() * m_Power); + mu /= m_Power; + + helper.Out.x = m_Weight * cosh(mu) * cos(nu); + helper.Out.y = m_Weight * sinh(mu) * sin(nu); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string power = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string sign = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t x, r2 = precalcSumSquares;\n" + << "\n" + << "\t\tif (" << sign << " == 1)\n" + << "\t\t{\n" + << "\t\t x = vIn.x;\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t r2 = 1 / r2;\n" + << "\t\t x = vIn.x * r2;\n" + << "\t\t}\n" + << "\n" + << "\t\treal_t tmp = r2 + 1;\n" + << "\t\treal_t tmp2 = 2 * x;\n" + << "\t\treal_t xmax = (SafeSqrt(tmp + tmp2) + SafeSqrt(tmp - tmp2)) * 0.5;\n" + << "\n" + << "\t\tif (xmax < 1)\n" + << "\t\t xmax = 1;\n" + << "\n" + << "\t\treal_t mu = acosh(xmax);\n" + << "\t\treal_t nu = acos(Clamp(x / xmax, -1.0, 1.0));\n" + << "\n" + << "\t\tif (vIn.y < 0)\n" + << "\t\t nu *= -1;\n" + << "\n" + << "\t\tnu = nu / " << power << " + M_2PI / " << power << " * floor(MwcNext01(mwc) * " << power << ");\n" + << "\t\tmu /= " << power << ";\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * cosh(mu) * cos(nu);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * sinh(mu) * sin(nu);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Sign = 1; + + if (m_Power < 0) + m_Sign = -1; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Power, prefix + "eJulia_power", 2, INTEGER_NONZERO)); + m_Params.push_back(ParamWithName(true, &m_Sign, prefix + "eJulia_sign"));//Precalc. + } + +private: + T m_Power; + T m_Sign;//Precalc. +}; + +/// +/// eMod. +/// +template +class EMBER_API EModVariation : public ParametricVariation +{ +public: + EModVariation(T weight = 1.0) : ParametricVariation("eMod", VAR_EMOD, weight, true) + { + Init(); + } + + PARVARCOPY(EModVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T tmp = helper.m_PrecalcSumSquares + 1; + T tmp2 = 2 * helper.In.x; + T xmax = (SafeSqrt(tmp + tmp2) + SafeSqrt(tmp - tmp2)) * T(0.5); + + ClampGteRef(xmax, 1); + + T mu = acosh(xmax); + T nu = acos(Clamp(helper.In.x / xmax, -1, 1));//-Pi < nu < Pi. + + if (helper.In.y < 0) + nu *= -1; + + if (mu < m_Radius && -mu < m_Radius) + { + if (nu > 0) + mu = fmod(mu + m_Radius + m_Distance * m_Radius, 2 * m_Radius) - m_Radius; + else + mu = fmod(mu - m_Radius - m_Distance * m_Radius, 2 * m_Radius) + m_Radius; + } + + helper.Out.x = m_Weight * cosh(mu) * cos(nu); + helper.Out.y = m_Weight * sinh(mu) * sin(nu); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string radius = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string dist = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t tmp = precalcSumSquares + 1;\n" + << "\t\treal_t tmp2 = 2 * vIn.x;\n" + << "\t\treal_t xmax = (SafeSqrt(tmp + tmp2) + SafeSqrt(tmp - tmp2)) * 0.5;\n" + << "\n" + << "\t\tif (xmax < 1)\n" + << "\t\t xmax = 1;\n" + << "\n" + << "\t\treal_t mu = acosh(xmax);\n" + << "\t\treal_t nu = acos(Clamp(vIn.x / xmax, -1.0, 1.0));\n" + << "\n" + << "\t\tif (vIn.y < 0)\n" + << "\t\t nu *= -1;\n" + << "\n" + << "\t\tif (mu < " << radius << " && -mu < " << radius << ")\n" + << "\t\t{\n" + << "\t\t if (nu > 0)\n" + << "\t\t mu = fmod(mu + " << radius << " + " << dist << " * " << radius << ", 2 * " << radius << ") - " << radius << ";\n" + << "\t\t else\n" + << "\t\t mu = fmod(mu - " << radius << " - " << dist << " * " << radius << ", 2 * " << radius << ") + " << radius << ";\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * cosh(mu) * cos(nu);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * sinh(mu) * sin(nu);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Radius, prefix + "eMod_radius", 1, REAL, 0, TMAX)); + m_Params.push_back(ParamWithName(&m_Distance, prefix + "eMod_distance", 0, REAL_CYCLIC, 0, 2)); + } + +private: + T m_Radius; + T m_Distance; +}; + +/// +/// eMotion. +/// +template +class EMBER_API EMotionVariation : public ParametricVariation +{ +public: + EMotionVariation(T weight = 1.0) : ParametricVariation("eMotion", VAR_EMOTION, weight, true) + { + Init(); + } + + PARVARCOPY(EMotionVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T tmp = helper.m_PrecalcSumSquares + 1; + T tmp2 = 2 * helper.In.x; + T xmax = (SafeSqrt(tmp + tmp2) + SafeSqrt(tmp - tmp2)) * T(0.5); + + ClampGteRef(xmax, 1); + + T mu = acosh(xmax); + T nu = acos(Clamp(helper.In.x / xmax, -1, 1));//-Pi < nu < Pi. + + if (helper.In.y < 0) + nu *= -1; + + if (nu < 0) + mu += m_Move; + else + mu -= m_Move; + + if (mu <= 0) + { + mu *= -1; + nu *= -1; + } + + nu += m_Rotate; + + helper.Out.x = m_Weight * cosh(mu) * cos(nu); + helper.Out.y = m_Weight * sinh(mu) * sin(nu); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string move = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string rotate = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t tmp = precalcSumSquares + 1;\n" + << "\t\treal_t tmp2 = 2 * vIn.x;\n" + << "\t\treal_t xmax = (SafeSqrt(tmp + tmp2) + SafeSqrt(tmp - tmp2)) * 0.5;\n" + << "\n" + << "\t\tif (xmax < 1)\n" + << "\t\t xmax = 1;\n" + << "\n" + << "\t\treal_t mu = acosh(xmax);\n" + << "\t\treal_t nu = acos(Clamp(vIn.x / xmax, -1.0, 1.0));\n" + << "\n" + << "\t\tif (vIn.y < 0)\n" + << "\t\t nu *= -1;\n" + << "\n" + << "\t\tif (nu < 0)\n" + << "\t\t mu += " << move << ";\n" + << "\t\telse\n" + << "\t\t mu -= " << move << ";\n" + << "\n" + << "\t\tif (mu <= 0)\n" + << "\t\t{\n" + << "\t\t mu *= -1;\n" + << "\t\t nu *= -1;\n" + << "\t\t}\n" + << "\n" + << "\t\tnu += " << rotate << ";\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * cosh(mu) * cos(nu);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * sinh(mu) * sin(nu);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Move, prefix + "eMotion_move")); + m_Params.push_back(ParamWithName(&m_Rotate, prefix + "eMotion_rotate", 0, REAL_CYCLIC, 0, M_2PI)); + } + +private: + T m_Move; + T m_Rotate; +}; + +/// +/// ePush. +/// +template +class EMBER_API EPushVariation : public ParametricVariation +{ +public: + EPushVariation(T weight = 1.0) : ParametricVariation("ePush", VAR_EPUSH, weight, true) + { + Init(); + } + + PARVARCOPY(EPushVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T tmp = helper.m_PrecalcSumSquares + 1; + T tmp2 = 2 * helper.In.x; + T xmax = (SafeSqrt(tmp + tmp2) + SafeSqrt(tmp - tmp2)) * T(0.5); + + ClampGteRef(xmax, 1); + + T mu = acosh(xmax); + T nu = acos(Clamp(helper.In.x / xmax, -1, 1));//-Pi < nu < Pi. + + if (helper.In.y < 0) + nu *= -1; + + nu += m_Rotate; + mu *= m_Dist; + mu += m_Push; + + helper.Out.x = m_Weight * cosh(mu) * cos(nu); + helper.Out.y = m_Weight * sinh(mu) * sin(nu); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string push = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string dist = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string rotate = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t tmp = precalcSumSquares + 1;\n" + << "\t\treal_t tmp2 = 2 * vIn.x;\n" + << "\t\treal_t xmax = (SafeSqrt(tmp + tmp2) + SafeSqrt(tmp - tmp2)) * 0.5;\n" + << "\n" + << "\t\tif (xmax < 1)\n" + << "\t\t xmax = 1;\n" + << "\n" + << "\t\treal_t mu = acosh(xmax);\n" + << "\t\treal_t nu = acos(Clamp(vIn.x / xmax, -1.0, 1.0));\n" + << "\n" + << "\t\tif (vIn.y < 0)\n" + << "\t\t nu *= -1;\n" + << "\n" + << "\t\tnu += " << rotate << ";\n" + << "\t\tmu *= " << dist << ";\n" + << "\t\tmu += " << push << ";\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * cosh(mu) * cos(nu);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * sinh(mu) * sin(nu);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Push, prefix + "ePush_push")); + m_Params.push_back(ParamWithName(&m_Dist, prefix + "ePush_dist", 1)); + m_Params.push_back(ParamWithName(&m_Rotate, prefix + "ePush_rotate", 0, REAL_CYCLIC, T(-M_PI), T(M_PI))); + } + +private: + T m_Push; + T m_Dist; + T m_Rotate; +}; + +/// +/// eRotate. +/// +template +class EMBER_API ERotateVariation : public ParametricVariation +{ +public: + ERotateVariation(T weight = 1.0) : ParametricVariation("eRotate", VAR_EROTATE, weight, true) + { + Init(); + } + + PARVARCOPY(ERotateVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T tmp = helper.m_PrecalcSumSquares + 1; + T tmp2 = 2 * helper.In.x; + T xmax = (SafeSqrt(tmp + tmp2) + SafeSqrt(tmp - tmp2)) * T(0.5); + + if (xmax < 1) + xmax = 1; + + T nu = acos(Clamp(helper.In.x / xmax, -1, 1));//-Pi < nu < Pi. + + if (helper.In.y < 0) + nu *= -1; + + nu = fmod(nu + m_Rotate + T(M_PI), M_2PI) - T(M_PI); + + helper.Out.x = m_Weight * xmax * cos(nu); + helper.Out.y = m_Weight * sqrt(xmax - 1) * sqrt(xmax + 1) * sin(nu); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string rotate = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t tmp = precalcSumSquares + 1;\n" + << "\t\treal_t tmp2 = 2 * vIn.x;\n" + << "\t\treal_t xmax = (SafeSqrt(tmp + tmp2) + SafeSqrt(tmp - tmp2)) * 0.5;\n" + << "\n" + << "\t\tif (xmax < 1)\n" + << "\t\t xmax = 1;\n" + << "\n" + << "\t\treal_t nu = acos(Clamp(vIn.x / xmax, -1.0, 1.0));\n" + << "\n" + << "\t\tif (vIn.y < 0)\n" + << "\t\t nu *= -1;\n" + << "\n" + << "\t\tnu = fmod(nu + " << rotate << " + M_PI, M_2PI) - M_PI;\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * xmax * cos(nu);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * sqrt(xmax - 1) * sqrt(xmax + 1) * sin(nu);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Rotate, prefix + "eRotate_rotate", 0, REAL_CYCLIC, T(-M_PI), T(M_PI))); + } + +private: + T m_Rotate; +}; + +/// +/// eScale. +/// +template +class EMBER_API EScaleVariation : public ParametricVariation +{ +public: + EScaleVariation(T weight = 1.0) : ParametricVariation("eScale", VAR_ESCALE, weight, true) + { + Init(); + } + + PARVARCOPY(EScaleVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T tmp = helper.m_PrecalcSumSquares + 1; + T tmp2 = 2 * helper.In.x; + T xmax = (SafeSqrt(tmp + tmp2) + SafeSqrt(tmp - tmp2)) * T(0.5); + + ClampGteRef(xmax, 1); + + T mu = acosh(xmax); + T nu = acos(Clamp(helper.In.x / xmax, -1, 1));//-Pi < nu < Pi. + + if (helper.In.y < 0) + nu *= -1; + + mu *= m_Scale; + nu = fmod(fmod(m_Scale * (nu + T(M_PI) + m_Angle), M_2PI * m_Scale) - m_Angle - m_Scale * T(M_PI), M_2PI); + + if (nu > T(M_PI)) + nu -= M_2PI; + + if (nu < -T(M_PI)) + nu += M_2PI; + + helper.Out.x = m_Weight * cosh(mu) * cos(nu); + helper.Out.y = m_Weight * sinh(mu) * sin(nu); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string scale = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string angle = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t tmp = precalcSumSquares + 1;\n" + << "\t\treal_t tmp2 = 2 * vIn.x;\n" + << "\t\treal_t xmax = (SafeSqrt(tmp + tmp2) + SafeSqrt(tmp - tmp2)) * 0.5;\n" + << "\n" + << "\t\tif (xmax < 1)\n" + << "\t\t xmax = 1;\n" + << "\n" + << "\t\treal_t mu = acosh(xmax);\n" + << "\t\treal_t nu = acos(Clamp(vIn.x / xmax, -1.0, 1.0));\n" + << "\n" + << "\t\tif (vIn.y < 0)\n" + << "\t\t nu *= -1;\n" + << "\n" + << "\t\tmu *= " << scale << ";\n" + << "\t\tnu = fmod(fmod(" << scale << " * (nu + M_PI + " << angle << "), M_2PI * " << scale << ") - " << angle << " - " << scale << " * M_PI, M_2PI);\n" + << "\n" + << "\t\tif (nu > M_PI)\n" + << "\t\t nu -= M_2PI;\n" + << "\n" + << "\t\tif (nu < -M_PI)\n" + << "\t\t nu += M_2PI;\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * cosh(mu) * cos(nu);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * sinh(mu) * sin(nu);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Scale, prefix + "eScale_scale", 1, REAL_NONZERO, 0, 1)); + m_Params.push_back(ParamWithName(&m_Angle, prefix + "eScale_angle", 0, REAL_CYCLIC, 0, M_2PI)); + } + +private: + T m_Scale; + T m_Angle; +}; + +MAKEPREPOSTPARVAR(Funnel, funnel, FUNNEL) +MAKEPREPOSTVAR(Linear3D, linear3D, LINEAR3D) +MAKEPREPOSTPARVAR(PowBlock, pow_block, POW_BLOCK) +MAKEPREPOSTPARVAR(Squirrel, squirrel, SQUIRREL) +MAKEPREPOSTVAR(Ennepers, ennepers, ENNEPERS) +MAKEPREPOSTPARVAR(SphericalN, SphericalN, SPHERICALN) +MAKEPREPOSTPARVAR(Kaleidoscope, Kaleidoscope, KALEIDOSCOPE) +MAKEPREPOSTPARVAR(GlynnSim1, GlynnSim1, GLYNNSIM1) +MAKEPREPOSTPARVAR(GlynnSim2, GlynnSim2, GLYNNSIM2) +MAKEPREPOSTPARVAR(GlynnSim3, GlynnSim3, GLYNNSIM3) +MAKEPREPOSTPARVARASSIGN(Starblur, starblur, STARBLUR, ASSIGNTYPE_SUM) +MAKEPREPOSTPARVARASSIGN(Sineblur, sineblur, SINEBLUR, ASSIGNTYPE_SUM) +MAKEPREPOSTVARASSIGN(Circleblur, circleblur, CIRCLEBLUR, ASSIGNTYPE_SUM) +MAKEPREPOSTPARVAR(Depth, depth, DEPTH) +MAKEPREPOSTPARVAR(CropN, cropn, CROPN) +MAKEPREPOSTPARVAR(ShredRad, shredrad, SHRED_RAD) +MAKEPREPOSTPARVAR(Blob2, blob2, BLOB2) +MAKEPREPOSTPARVAR(Julia3D, julia3D, JULIA3D) +MAKEPREPOSTPARVAR(Julia3Dz, julia3Dz, JULIA3DZ) +MAKEPREPOSTPARVAR(LinearT, linearT, LINEAR_T) +MAKEPREPOSTPARVAR(LinearT3D, linearT3D, LINEAR_T3D) +MAKEPREPOSTPARVAR(Ovoid, ovoid, OVOID) +MAKEPREPOSTPARVAR(Ovoid3D, ovoid3d, OVOID3D) +MAKEPREPOSTPARVARASSIGN(Spirograph, Spirograph, SPIROGRAPH, ASSIGNTYPE_SUM) +MAKEPREPOSTVAR(Petal, petal, PETAL) +MAKEPREPOSTVAR(RoundSpher, roundspher, ROUNDSPHER) +MAKEPREPOSTVAR(RoundSpher3D, roundspher3D, ROUNDSPHER3D) +MAKEPREPOSTVAR(SpiralWing, spiralwing, SPIRAL_WING) +MAKEPREPOSTVAR(Squarize, squarize, SQUARIZE) +MAKEPREPOSTPARVAR(Sschecks, sschecks, SSCHECKS) +MAKEPREPOSTPARVAR(PhoenixJulia, phoenix_julia, PHOENIX_JULIA) +MAKEPREPOSTPARVAR(Mobius, Mobius, MOBIUS) +MAKEPREPOSTPARVAR(MobiusN, MobiusN, MOBIUSN) +MAKEPREPOSTPARVAR(MobiusStrip, mobius_strip, MOBIUS_STRIP) +MAKEPREPOSTPARVARASSIGN(Lissajous, Lissajous, LISSAJOUS, ASSIGNTYPE_SUM) +MAKEPREPOSTPARVAR(Svf, svf, SVF) +MAKEPREPOSTPARVAR(Target, target, TARGET) +MAKEPREPOSTPARVAR(Taurus, taurus, TAURUS) +MAKEPREPOSTPARVAR(Collideoscope, collideoscope, COLLIDEOSCOPE) +MAKEPREPOSTPARVAR(BMod, bMod, BMOD) +MAKEPREPOSTPARVAR(BSwirl, bSwirl, BSWIRL) +MAKEPREPOSTPARVAR(BTransform, bTransform, BTRANSFORM) +MAKEPREPOSTPARVAR(BCollide, bCollide, BCOLLIDE) +MAKEPREPOSTPARVAR(Eclipse, eclipse, ECLIPSE) +MAKEPREPOSTPARVAR(FlipCircle, flipcircle, FLIP_CIRCLE) +MAKEPREPOSTVAR(FlipY, flipy, FLIP_Y) +MAKEPREPOSTPARVAR(ECollide, eCollide, ECOLLIDE) +MAKEPREPOSTPARVAR(EJulia, eJulia, EJULIA) +MAKEPREPOSTPARVAR(EMod, eMod, EMOD) +MAKEPREPOSTPARVAR(EMotion, eMotion, EMOTION) +MAKEPREPOSTPARVAR(EPush, ePush, EPUSH) +MAKEPREPOSTPARVAR(ERotate, eRotate, EROTATE) +MAKEPREPOSTPARVAR(EScale, eScale, ESCALE) +} \ No newline at end of file diff --git a/Source/Ember/Variations04.h b/Source/Ember/Variations04.h new file mode 100644 index 0000000..56a3560 --- /dev/null +++ b/Source/Ember/Variations04.h @@ -0,0 +1,5233 @@ +#pragma once + +#include "Variation.h" + +namespace EmberNs +{ +/// +/// eSwirl. +/// +template +class EMBER_API ESwirlVariation : public ParametricVariation +{ +public: + ESwirlVariation(T weight = 1.0) : ParametricVariation("eSwirl", VAR_ESWIRL, weight, true) + { + Init(); + } + + PARVARCOPY(ESwirlVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T tmp = helper.m_PrecalcSumSquares + 1; + T tmp2 = 2 * helper.In.x; + T xmax = (SafeSqrt(tmp + tmp2) + SafeSqrt(tmp - tmp2)) * T(0.5); + + ClampGteRef(xmax, -1); + + T mu = acosh(xmax); + T nu = acos(Clamp(helper.In.x / xmax, -1, 1));//-Pi < nu < Pi. + + if (helper.In.y < 0) + nu *= -1; + + nu = nu + mu * m_Out + m_In / mu; + + helper.Out.x = m_Weight * cosh(mu) * cos(nu); + helper.Out.y = m_Weight * sinh(mu) * sin(nu); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string in = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string out = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t tmp = precalcSumSquares + 1;\n" + << "\t\treal_t tmp2 = 2 * vIn.x;\n" + << "\t\treal_t xmax = (SafeSqrt(tmp + tmp2) + SafeSqrt(tmp - tmp2)) * 0.5;\n" + << "\n" + << "\t\tif (xmax < 1)\n" + << "\t\t xmax = 1;\n" + << "\n" + << "\t\treal_t mu = acosh(xmax);\n" + << "\t\treal_t nu = acos(Clamp(vIn.x / xmax, -1.0, 1.0));\n" + << "\n" + << "\t\tif (vIn.y < 0)\n" + << "\t\t nu *= -1;\n" + << "\n" + << "\t\tnu = nu + mu * " << out << " + " << in << " / mu;\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * cosh(mu) * cos(nu);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * sinh(mu) * sin(nu);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_In, prefix + "eSwirl_in")); + m_Params.push_back(ParamWithName(&m_Out, prefix + "eSwirl_out")); + } + +private: + T m_In; + T m_Out; +}; + +/// +/// lazyTravis. +/// +template +class EMBER_API LazyTravisVariation : public ParametricVariation +{ +public: + LazyTravisVariation(T weight = 1.0) : ParametricVariation("lazyTravis", VAR_LAZY_TRAVIS, weight) + { + Init(); + } + + PARVARCOPY(LazyTravisVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T x = fabs(helper.In.x); + T y = fabs(helper.In.y); + T s; + T p; + T x2, y2; + + if (x > m_Weight || y > m_Weight) + { + if (x > y) + { + s = x; + + if (helper.In.x > 0) + p = s + helper.In.y + s * m_Out4; + else + p = 5 * s - helper.In.y + s * m_Out4; + } + else + { + s = y; + + if (helper.In.y > 0) + p = 3 * s - helper.In.x + s * m_Out4; + else + p = 7 * s + helper.In.x + s * m_Out4; + } + + p = fmod(p, s * 8); + + if (p <= 2 * s) + { + x2 = s + m_Space; + y2 = -(1 * s - p); + y2 = y2 + y2 / s * m_Space; + } + else if (p <= 4 * s) + { + y2 = s + m_Space; + x2 = (3 * s - p); + x2 = x2 + x2 / s * m_Space; + } + else if (p <= 6 * s) + { + x2 = -(s + m_Space); + y2 = (5 * s - p); + y2 = y2 + y2 / s * m_Space; + } + else + { + y2 = -(s + m_Space); + x2 = -(7 * s - p); + x2 = x2 + x2 / s * m_Space; + } + + helper.Out.x = m_Weight * x2; + helper.Out.y = m_Weight * y2; + } + else + { + if (x > y) + { + s = x; + + if (helper.In.x > 0) + p = s + helper.In.y + s * m_In4; + else + p = 5 * s - helper.In.y + s * m_In4; + } + else + { + s = y; + + if (helper.In.y > 0) + p = 3 * s - helper.In.x + s * m_In4; + else + p = 7 * s + helper.In.x + s * m_In4; + } + + p = fmod(p, s * 8); + + if (p <= 2 * s) + { + helper.Out.x = m_Weight * s; + helper.Out.y = -(m_Weight * (s - p)); + } + else if (p <= 4 * s) + { + helper.Out.x = m_Weight * (3 * s - p); + helper.Out.y = m_Weight * s; + } + else if (p <= 6 * s) + { + helper.Out.x = -(m_Weight * s); + helper.Out.y = m_Weight * (5 * s - p); + } + else + { + helper.Out.x = -(m_Weight * (7 * s - p)); + helper.Out.y = -(m_Weight * s); + } + } + + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string spinIn = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string spinOut = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string space = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string in4 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string out4 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t x = fabs(vIn.x);\n" + << "\t\treal_t y = fabs(vIn.y);\n" + << "\t\treal_t s;\n" + << "\t\treal_t p;\n" + << "\t\treal_t x2, y2;\n" + << "\n" + << "\t\tif (x > xform->m_VariationWeights[" << varIndex << "] || y > xform->m_VariationWeights[" << varIndex << "])\n" + << "\t\t{\n" + << "\t\t if (x > y)\n" + << "\t\t {\n" + << "\t\t s = x;\n" + << "\n" + << "\t\t if (vIn.x > 0)\n" + << "\t\t p = s + vIn.y + s * " << out4 << ";\n" + << "\t\t else\n" + << "\t\t p = 5 * s - vIn.y + s * " << out4 << ";\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t s = y;\n" + << "\n" + << "\t\t if (vIn.y > 0)\n" + << "\t\t p = 3 * s - vIn.x + s * " << out4 << ";\n" + << "\t\t else\n" + << "\t\t p = 7 * s + vIn.x + s * " << out4 << ";\n" + << "\t\t }\n" + << "\n" + << "\t\t p = fmod(p, s * 8);\n" + << "\n" + << "\t\t if (p <= 2 * s)\n" + << "\t\t {\n" + << "\t\t x2 = s + " << space << ";\n" + << "\t\t y2 = -(1 * s - p);\n" + << "\t\t y2 = y2 + y2 / s * " << space << ";\n" + << "\t\t }\n" + << "\t\t else if (p <= 4 * s)\n" + << "\t\t {\n" + << "\t\t y2 = s + " << space << ";\n" + << "\t\t x2 = (3 * s - p);\n" + << "\t\t x2 = x2 + x2 / s * " << space << ";\n" + << "\t\t }\n" + << "\t\t else if (p <= 6 * s)\n" + << "\t\t {\n" + << "\t\t x2 = -(s + " << space << ");\n" + << "\t\t y2 = (5 * s - p);\n" + << "\t\t y2 = y2 + y2 / s * " << space << ";\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t y2 = -(s + " << space << ");\n" + << "\t\t x2 = -(7 * s - p);\n" + << "\t\t x2 = x2 + x2 / s * " << space << ";\n" + << "\t\t }\n" + << "\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * x2;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * y2;\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t if (x > y)\n" + << "\t\t {\n" + << "\t\t s = x;\n" + << "\n" + << "\t\t if (vIn.x > 0)\n" + << "\t\t p = s + vIn.y + s * " << in4 << ";\n" + << "\t\t else\n" + << "\t\t p = 5 * s - vIn.y + s * " << in4 << ";\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t s = y;\n" + << "\n" + << "\t\t if (vIn.y > 0)\n" + << "\t\t p = 3 * s - vIn.x + s * " << in4 << ";\n" + << "\t\t else\n" + << "\t\t p = 7 * s + vIn.x + s * " << in4 << ";\n" + << "\t\t }\n" + << "\n" + << "\t\t p = fmod(p, s * 8);\n" + << "\n" + << "\t\t if (p <= 2 * s)\n" + << "\t\t {\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * s;\n" + << "\t\t vOut.y = -(xform->m_VariationWeights[" << varIndex << "] * (s - p));\n" + << "\t\t }\n" + << "\t\t else if (p <= 4 * s)\n" + << "\t\t {\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (3 * s - p);\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * s;\n" + << "\t\t }\n" + << "\t\t else if (p <= 6 * s)\n" + << "\t\t {\n" + << "\t\t vOut.x = -(xform->m_VariationWeights[" << varIndex << "] * s);\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * (5 * s - p);\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t vOut.x = -(xform->m_VariationWeights[" << varIndex << "] * (7 * s - p));\n" + << "\t\t vOut.y = -(xform->m_VariationWeights[" << varIndex << "] * s);\n" + << "\t\t }\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_In4 = 4 * m_SpinIn; + m_Out4 = 4 * m_SpinOut; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_SpinIn, prefix + "lazyTravis_spin_in", 1, REAL_CYCLIC, 0, 2)); + m_Params.push_back(ParamWithName(&m_SpinOut, prefix + "lazyTravis_spin_out", 0, REAL_CYCLIC, 0, 2)); + m_Params.push_back(ParamWithName(&m_Space, prefix + "lazyTravis_space")); + m_Params.push_back(ParamWithName(true, &m_In4, prefix + "lazyTravis_in4"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Out4, prefix + "lazyTravis_out4")); + } + +private: + T m_SpinIn; + T m_SpinOut; + T m_Space; + T m_In4;//Precalc. + T m_Out4; +}; + +/// +/// squish. +/// +template +class EMBER_API SquishVariation : public ParametricVariation +{ +public: + SquishVariation(T weight = 1.0) : ParametricVariation("squish", VAR_SQUISH, weight) + { + Init(); + } + + PARVARCOPY(SquishVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T x = fabs(helper.In.x); + T y = fabs(helper.In.y); + T s; + T p; + + if (x > y) + { + s = x; + + if (helper.In.x > 0) + p = helper.In.y; + else + p = 4 * s - helper.In.y; + } + else + { + s = y; + + if (helper.In.y > 0) + p = 2 * s - helper.In.x; + else + p = 6 * s + helper.In.x; + } + + p = m_InvPower * (p + 8 * s * Floor(m_Power * rand.Frand01())); + + if (p <= s) + { + helper.Out.x = m_Weight * s; + helper.Out.y = m_Weight * p; + } + else if (p <= 3 * s) + { + helper.Out.x = m_Weight * (2 * s - p); + helper.Out.y = m_Weight * s; + } + else if (p <= 5 * s) + { + helper.Out.x = -(m_Weight * s); + helper.Out.y = m_Weight * (4 * s - p); + } + else if (p <= 7 * s) + { + helper.Out.x = -(m_Weight * (6 * s - p)); + helper.Out.y = -(m_Weight * s); + } + else + { + helper.Out.x = m_Weight * s; + helper.Out.y = (m_Weight * (8 * s - p)); + } + + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string power = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string invPower = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t x = fabs(vIn.x);\n" + << "\t\treal_t y = fabs(vIn.y);\n" + << "\t\treal_t s;\n" + << "\t\treal_t p;\n" + << "\n" + << "\t\tif (x > y)\n" + << "\t\t{\n" + << "\t\t s = x;\n" + << "\n" + << "\t\t if (vIn.x > 0)\n" + << "\t\t p = vIn.y;\n" + << "\t\t else\n" + << "\t\t p = 4 * s - vIn.y;\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t s = y;\n" + << "\n" + << "\t\t if (vIn.y > 0)\n" + << "\t\t p = 2 * s - vIn.x;\n" + << "\t\t else\n" + << "\t\t p = 6 * s + vIn.x;\n" + << "\t\t}\n" + << "\n" + << "\t\tp = " << invPower << " * (p + 8 * s * floor(" << power << " * MwcNext01(mwc)));\n" + << "\n" + << "\t\tif (p <= s)\n" + << "\t\t{\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * s;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * p;\n" + << "\t\t}\n" + << "\t\telse if (p <= 3 * s)\n" + << "\t\t{\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (2 * s - p);\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * s;\n" + << "\t\t}\n" + << "\t\telse if (p <= 5 * s)\n" + << "\t\t{\n" + << "\t\t vOut.x = -(xform->m_VariationWeights[" << varIndex << "] * s);\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * (4 * s - p);\n" + << "\t\t}\n" + << "\t\telse if (p <= 7 * s)\n" + << "\t\t{\n" + << "\t\t vOut.x = -(xform->m_VariationWeights[" << varIndex << "] * (6 * s - p));\n" + << "\t\t vOut.y = -(xform->m_VariationWeights[" << varIndex << "] * s);\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * s;\n" + << "\t\t vOut.y = -(xform->m_VariationWeights[" << varIndex << "] * (8 * s - p));\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_InvPower = 1 / m_Power; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Power, prefix + "squish_power", 2, INTEGER, 2, T(INT_MAX))); + m_Params.push_back(ParamWithName(true, &m_InvPower, prefix + "squish_inv_power"));//Precalc. + } + +private: + T m_Power; + T m_InvPower;//Precalc. +}; + +/// +/// circus. +/// +template +class EMBER_API CircusVariation : public ParametricVariation +{ +public: + CircusVariation(T weight = 1.0) : ParametricVariation("circus", VAR_CIRCUS, weight, true, true, true) + { + Init(); + } + + PARVARCOPY(CircusVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r = helper.m_PrecalcSqrtSumSquares; + + if (r <= 1) + r *= m_Scale; + else + r *= m_InvScale; + + helper.Out.x = m_Weight * r * helper.m_PrecalcCosa; + helper.Out.y = m_Weight * r * helper.m_PrecalcSina; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string scale = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string invScale = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t r = precalcSqrtSumSquares;\n" + << "\n" + << "\t\tif (r <= 1)\n" + << "\t\t r *= " << scale << ";\n" + << "\t\telse\n" + << "\t\t r *= " << invScale << ";\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * r * precalcCosa;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * r * precalcSina;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_InvScale = 1 / m_Scale; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Scale, prefix + "circus_scale", 1)); + m_Params.push_back(ParamWithName(true, &m_InvScale, prefix + "circus_inv_power"));//Precalc. + } + +private: + T m_Scale; + T m_InvScale;//Precalc. +}; + +/// +/// tancos. +/// +template +class EMBER_API TancosVariation : public Variation +{ +public: + TancosVariation(T weight = 1.0) : Variation("tancos", VAR_TANCOS, weight, true) { } + + VARCOPY(TancosVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T d = helper.m_PrecalcSumSquares + EPS6; + + helper.Out.x = (m_Weight / d) * (tanh(d) * (2 * helper.In.x)); + helper.Out.y = (m_Weight / d) * (cos(d) * (2 * helper.In.y)); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t d = precalcSumSquares + EPS6;\n" + << "\n" + << "\t\tvOut.x = (xform->m_VariationWeights[" << varIndex << "] / d) * (tanh(d) * (2.0 * vIn.x));\n" + << "\t\tvOut.y = (xform->m_VariationWeights[" << varIndex << "] / d) * (cos(d) * (2.0 * vIn.y));\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// rippled. +/// +template +class EMBER_API RippledVariation : public Variation +{ +public: + RippledVariation(T weight = 1.0) : Variation("rippled", VAR_RIPPLED, weight, true) { } + + VARCOPY(RippledVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T d = helper.m_PrecalcSumSquares + EPS6; + + helper.Out.x = (m_Weight / 2) * (tanh(d) * (2 * helper.In.x)); + helper.Out.y = (m_Weight / 2) * (cos(d) * (2 * helper.In.y)); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t d = precalcSumSquares + EPS6;\n" + << "\n" + << "\t\tvOut.x = (xform->m_VariationWeights[" << varIndex << "] / 2.0) * (tanh(d) * (2.0 * vIn.x));\n" + << "\t\tvOut.y = (xform->m_VariationWeights[" << varIndex << "] / 2.0) * (cos(d) * (2.0 * vIn.y));\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// RotateX. +/// +template +class EMBER_API RotateXVariation : public ParametricVariation +{ +public: + RotateXVariation(T weight = 1.0) : ParametricVariation("rotate_x", VAR_ROTATE_X, weight) + { + Init(); + } + + PARVARCOPY(RotateXVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T z = m_RxCos * helper.In.x - m_RxSin * helper.In.y; + + if (m_VarType == VARTYPE_REG) + { + helper.Out.x = 0; + outPoint.m_X = helper.In.x; + } + else + { + helper.Out.x = helper.In.x; + } + + helper.Out.y = m_RxSin * helper.In.z + m_RxCos * helper.In.y; + helper.Out.z = z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string rxSin = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Precalcs only, no params. + string rxCos = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t z = " << rxCos << " * vIn.x - " << rxSin << " * vIn.y;\n" + << "\n"; + + if (m_VarType == VARTYPE_REG) + { + ss << + "\t\tvOut.x = 0;\n" + "\t\toutPoint->m_X = vIn.x;\n"; + } + else + { + ss << + "\t\tvOut.x = vIn.x;\n"; + } + + ss << "\t\tvOut.y = " << rxSin << " * vIn.z + " << rxCos << " * vIn.y;\n" + << "\t\tvOut.z = z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_RxSin = sin(m_Weight * T(M_PI_2)); + m_RxCos = cos(m_Weight * T(M_PI_2)); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(true, &m_RxSin, prefix + "rotate_x_sin"));//Precalcs only, no params. + m_Params.push_back(ParamWithName(true, &m_RxCos, prefix + "rotate_x_cos"));//Original used a prefix of rx_, which is incompatible with Ember's design. + } + +private: + T m_RxSin; + T m_RxCos; +}; + +/// +/// RotateY. +/// +template +class EMBER_API RotateYVariation : public ParametricVariation +{ +public: + RotateYVariation(T weight = 1.0) : ParametricVariation("rotate_y", VAR_ROTATE_Y, weight) + { + Init(); + } + + PARVARCOPY(RotateYVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + helper.Out.x = m_RyCos * helper.In.x - m_RySin * helper.In.z; + + if (m_VarType == VARTYPE_REG) + { + helper.Out.y = 0; + outPoint.m_Y = helper.In.y; + } + else + { + helper.Out.y = helper.In.y; + } + + helper.Out.z = m_RySin * helper.In.x + m_RyCos * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string rySin = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Precalcs only, no params. + string ryCos = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tvOut.x = " << ryCos << " * vIn.x - " << rySin << " * vIn.z;\n"; + + if (m_VarType == VARTYPE_REG) + { + ss << + "\t\tvOut.y = 0;\n" + "\t\toutPoint->m_Y = vIn.y;\n"; + } + else + { + ss << + "\t\tvOut.y = vIn.y;\n"; + } + + ss << "\t\tvOut.z = " << rySin << " * vIn.x + " << ryCos << " * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_RySin = sin(m_Weight * T(M_PI_2)); + m_RyCos = cos(m_Weight * T(M_PI_2)); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(true, &m_RySin, prefix + "rotate_y_sin"));//Precalcs only, no params. + m_Params.push_back(ParamWithName(true, &m_RyCos, prefix + "rotate_y_cos"));//Original used a prefix of ry_, which is incompatible with Ember's design. + } + +private: + T m_RySin; + T m_RyCos; +}; + +/// +/// RotateZ. +/// This was originally pre and post spin_z, consolidated here to be consistent with the other rotate variations. +/// +template +class EMBER_API RotateZVariation : public ParametricVariation +{ +public: + RotateZVariation(T weight = 1.0) : ParametricVariation("rotate_z", VAR_ROTATE_Z, weight) + { + Init(); + } + + PARVARCOPY(RotateZVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + helper.Out.x = m_RzSin * helper.In.y + m_RzCos * helper.In.x; + helper.Out.y = m_RzCos * helper.In.y - m_RzSin * helper.In.x; + + if (m_VarType == VARTYPE_REG) + { + helper.Out.z = 0; + outPoint.m_Z = helper.In.z; + } + else + { + helper.Out.z = helper.In.z; + } + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string rzSin = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Precalcs only, no params. + string rzCos = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tvOut.x = " << rzSin << " * vIn.y + " << rzCos << " * vIn.x;\n" + << "\t\tvOut.y = " << rzCos << " * vIn.y - " << rzSin << " * vIn.x;\n"; + + if (m_VarType == VARTYPE_REG) + { + ss << + "\t\tvOut.z = 0;\n" + "\t\toutPoint->m_Z = vIn.z;\n"; + } + else + { + ss << + "\t\tvOut.z = vIn.z;\n"; + } + + ss << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_RzSin = sin(m_Weight * T(M_PI_2)); + m_RzCos = cos(m_Weight * T(M_PI_2)); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(true, &m_RzSin, prefix + "rotate_z_sin"));//Precalcs only, no params. + m_Params.push_back(ParamWithName(true, &m_RzCos, prefix + "rotate_z_cos")); + } + +private: + T m_RzSin; + T m_RzCos; +}; + +/// +/// MirrorX. +/// +template +class EMBER_API MirrorXVariation : public Variation +{ +public: + MirrorXVariation(T weight = 1.0) : Variation("mirror_x", VAR_MIRROR_X, weight) { } + + VARCOPY(MirrorXVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + helper.Out.x = fabs(helper.In.x); + + if (rand.Rand() & 1) + helper.Out.x = -helper.Out.x; + + if (m_VarType == VARTYPE_REG) + { + helper.Out.y = 0; + helper.Out.z = 0; + outPoint.m_Y = helper.In.y; + outPoint.m_Z = helper.In.z; + } + else + { + helper.Out.y = helper.In.y; + helper.Out.z = helper.In.z; + } + } + + virtual string OpenCLString() + { + ostringstream ss; + + ss << "\t{\n" + << "\t\tvOut.x = fabs(vIn.x);\n" + << "\n" + << "\t\tif (MwcNext(mwc) & 1)\n" + << "\t\t vOut.x = -vOut.x;\n"; + + if (m_VarType == VARTYPE_REG) + { + ss << + "\t\tvOut.y = 0;\n" + "\t\tvOut.z = 0;\n" + "\t\toutPoint->m_Y = vIn.y;\n" + "\t\toutPoint->m_Z = vIn.z;\n"; + } + else + { + ss << + "\t\tvOut.y = vIn.y;\n" + "\t\tvOut.z = vIn.z;\n"; + } + + ss << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// MirrorY. +/// +template +class EMBER_API MirrorYVariation : public Variation +{ +public: + MirrorYVariation(T weight = 1.0) : Variation("mirror_y", VAR_MIRROR_Y, weight) { } + + VARCOPY(MirrorYVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + helper.Out.y = fabs(helper.In.y); + + if (rand.Rand() & 1) + helper.Out.y = -helper.Out.y; + + if (m_VarType == VARTYPE_REG) + { + helper.Out.x = 0; + helper.Out.z = 0; + outPoint.m_X = helper.In.x; + outPoint.m_Z = helper.In.z; + } + else + { + helper.Out.x = helper.In.x; + helper.Out.z = helper.In.z; + } + } + + virtual string OpenCLString() + { + ostringstream ss; + + ss << "\t{\n" + << "\t\tvOut.y = fabs(vIn.y);\n" + << "\n" + << "\t\tif (MwcNext(mwc) & 1)\n" + << "\t\t vOut.y = -vOut.y;\n"; + + if (m_VarType == VARTYPE_REG) + { + ss << + "\t\tvOut.x = 0;\n" + "\t\tvOut.z = 0;\n" + "\t\toutPoint->m_X = vIn.x;\n" + "\t\toutPoint->m_Z = vIn.z;\n"; + } + else + { + ss << + "\t\tvOut.x = vIn.x;\n" + "\t\tvOut.z = vIn.z;\n"; + } + + ss << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// MirrorZ. +/// +template +class EMBER_API MirrorZVariation : public Variation +{ +public: + MirrorZVariation(T weight = 1.0) : Variation("mirror_z", VAR_MIRROR_Z, weight) { } + + VARCOPY(MirrorZVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + if (m_VarType == VARTYPE_REG) + { + helper.Out.x = 0; + helper.Out.y = 0; + outPoint.m_X = helper.In.x; + outPoint.m_Y = helper.In.y; + } + else + { + helper.Out.x = helper.In.x; + helper.Out.y = helper.In.y; + } + + helper.Out.z = fabs(helper.In.z); + + if (rand.Rand() & 1) + helper.Out.z = -helper.Out.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + + ss << "\t{\n"; + + if (m_VarType == VARTYPE_REG) + { + ss << + "\t\tvOut.x = 0;\n" + "\t\tvOut.y = 0;\n" + "\t\toutPoint->m_X = vIn.x;\n" + "\t\toutPoint->m_Y = vIn.y;\n"; + } + else + { + ss << + "\t\tvOut.x = vIn.x;\n" + "\t\tvOut.y = vIn.y;\n"; + } + + ss << "\t\tvOut.z = fabs(vIn.z);\n" + << "\n" + << "\t\tif (MwcNext(mwc) & 1)\n" + << "\t\t vOut.z = -vOut.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// RBlur. +/// +template +class EMBER_API RBlurVariation : public ParametricVariation +{ +public: + RBlurVariation(T weight = 1.0) : ParametricVariation("rblur", VAR_RBLUR, weight) + { + Init(); + } + + PARVARCOPY(RBlurVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T sx = helper.In.x - m_CenterX; + T sy = helper.In.y - m_CenterY; + T r = sqrt(SQR(sx) + SQR(sy)) - m_Offset; + + r = r < 0 ? 0 : r; + r *= m_S2; + + helper.Out.x = m_Weight * (helper.In.x + (rand.Frand01() - T(0.5)) * r); + helper.Out.y = m_Weight * (helper.In.y + (rand.Frand01() - T(0.5)) * r); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string strength = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string offset = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string centerX = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string centerY = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string s2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t sx = vIn.x - " << centerX << ";\n" + << "\t\treal_t sy = vIn.y - " << centerY << ";\n" + << "\t\treal_t r = sqrt(SQR(sx) + SQR(sy)) - " << offset << ";\n" + << "\n" + << "\t\tr = r < 0 ? 0 : r;\n" + << "\t\tr *= " << s2 << ";\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (vIn.x + (MwcNext01(mwc) - 0.5) * r);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (vIn.y + (MwcNext01(mwc) - 0.5) * r);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_S2 = 2 * m_Strength; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Strength, prefix + "rblur_strength", 1)); + m_Params.push_back(ParamWithName(&m_Offset, prefix + "rblur_offset", 1)); + m_Params.push_back(ParamWithName(&m_CenterX, prefix + "rblur_center_x")); + m_Params.push_back(ParamWithName(&m_CenterY, prefix + "rblur_center_y")); + m_Params.push_back(ParamWithName(true, &m_S2, prefix + "rblur_s2"));//Precalc. + } + +private: + T m_Strength; + T m_Offset; + T m_CenterX; + T m_CenterY; + T m_S2;//Precalc. +}; + +/// +/// JuliaNab. +/// +template +class EMBER_API JuliaNabVariation : public ParametricVariation +{ +public: + JuliaNabVariation(T weight = 1.0) : ParametricVariation("juliaNab", VAR_JULIANAB, weight, true) + { + Init(); + } + + PARVARCOPY(JuliaNabVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T jun = EPS6; + + if (fabs(m_N) > EPS6) + jun = m_N; + + T a = (atan2(helper.In.y, pow(helper.In.x, m_Sep)) + M_2PI * Floor(rand.Frand01() * m_AbsN)) / jun; + T r = m_Weight * pow(helper.m_PrecalcSumSquares, m_Cn * m_A); + + helper.Out.x = r * cos(a) + m_B; + helper.Out.y = r * sin(a) + m_B; + helper.Out.z = m_Weight * helper.In.z;//Original did not multiply by weight. Do it here to be consistent with others. + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string n = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string a = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string b = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string sep = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string absN = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cn = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t jun = EPS6;\n" + << "\n" + << "\t\tif (fabs(" << n << ") > EPS6)\n" + << "\t\t jun = " << n << ";\n" + << "\n" + << "\t\treal_t a = (atan2(vIn.y, pow(vIn.x, " << sep << ")) + M_2PI * floor(MwcNext01(mwc) * " << absN << ")) / jun;\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * pow(precalcSumSquares, " << cn << " * " << a << ");\n" + << "\n" + << "\t\tvOut.x = r * cos(a) + " << b << ";\n" + << "\t\tvOut.y = r * sin(a) + " << b << ";\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + T jun = EPS6; + + if (abs(m_N) > EPS) + jun = m_N; + + m_AbsN = abs(m_N); + m_Cn = 1 / jun / 2; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_N, prefix + "juliaNab_n", 1)); + m_Params.push_back(ParamWithName(&m_A, prefix + "juliaNab_a", 1)); + m_Params.push_back(ParamWithName(&m_B, prefix + "juliaNab_b", 1)); + m_Params.push_back(ParamWithName(&m_Sep, prefix + "juliaNab_separ", 1)); + m_Params.push_back(ParamWithName(true, &m_AbsN, prefix + "juliaNab_absn"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Cn, prefix + "juliaNab_cn")); + } + +private: + T m_N; + T m_A; + T m_B; + T m_Sep; + T m_AbsN;//Precalc. + T m_Cn; +}; + +/// +/// Sintrange. +/// +template +class EMBER_API SintrangeVariation : public ParametricVariation +{ +public: + SintrangeVariation(T weight = 1.0) : ParametricVariation("sintrange", VAR_SINTRANGE, weight) + { + Init(); + } + + PARVARCOPY(SintrangeVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T sqX = SQR(helper.In.x); + T sqY = SQR(helper.In.y); + T v = (sqX + sqY) * m_W;//Do not use precalcSumSquares here because its components are needed below. + + helper.Out.x = m_Weight * sin(helper.In.x) * (sqX + m_W - v); + helper.Out.y = m_Weight * sin(helper.In.y) * (sqY + m_W - v); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string w = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t sqX = SQR(vIn.x);\n" + << "\t\treal_t sqY = SQR(vIn.y);\n" + << "\t\treal_t v = (sqX + sqY) * " << w << ";\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * sin(vIn.x) * (sqX + " << w << " - v);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * sin(vIn.y) * (sqY + " << w << " - v);\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_W, prefix + "sintrange_w", 1)); + } + +private: + T m_W; +}; + +/// +/// Voron. +/// +template +class EMBER_API VoronVariation : public ParametricVariation +{ +public: + VoronVariation(T weight = 1.0) : ParametricVariation("Voron", VAR_VORON, weight) + { + Init(); + } + + PARVARCOPY(VoronVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + int i, j, l, k, m, m1, n, n1; + T r, rMin, offsetX, offsetY, x0 = 0, y0 = 0, x, y; + + rMin = 20; + m = Floor(helper.In.x / m_Step); + n = Floor(helper.In.y / m_Step); + + for (i = -1; i < 2; i++) + { + m1 = m + i; + + for (j = -1; j < 2; j++) + { + n1 = n + j; + k = 1 + Floor(m_Num * DiscreteNoise(int(19 * m1 + 257 * n1 + m_XSeed))); + + for (l = 0; l < k; l++) + { + x = T(DiscreteNoise(int(l + 64 * m1 + 15 * n1 + m_XSeed)) + m1) * m_Step; + y = T(DiscreteNoise(int(l + 21 * m1 + 33 * n1 + m_YSeed)) + n1) * m_Step; + offsetX = helper.In.x - x; + offsetY = helper.In.y - y; + r = sqrt(SQR(offsetX) + SQR(offsetY)); + + if (r < rMin) + { + rMin = r; + x0 = x; + y0 = y; + } + } + } + } + + helper.Out.x = m_Weight * (m_K * (helper.In.x - x0) + x0); + helper.Out.y = m_Weight * (m_K * (helper.In.y - y0) + y0); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string m_k = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string step = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string num = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string xSeed = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ySeed = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tint i, j, l, k, m, m1, n, n1;\n" + << "\t\treal_t r, rMin, offsetX, offsetY, x0 = 0.0, y0 = 0.0, x, y;\n" + << "\n" + << "\t\trMin = 20;\n" + << "\t\tm = (int)floor(vIn.x / " << step << ");\n" + << "\t\tn = (int)floor(vIn.y / " << step << ");\n" + << "\n" + << "\t\tfor (i = -1; i < 2; i++)\n" + << "\t\t{\n" + << "\t\t m1 = m + i;\n" + << "\n" + << "\t\t for (j = -1; j < 2; j++)\n" + << "\t\t {\n" + << "\t\t n1 = n + j;\n" + << "\t\t k = 1 + (int)floor(" << num << " * VoronDiscreteNoise((int)(19 * m1 + 257 * n1 + " << xSeed << ")));\n" + << "\n" + << "\t\t for (l = 0; l < k; l++)\n" + << "\t\t {\n" + << "\t\t x = (real_t)(VoronDiscreteNoise((int)(l + 64 * m1 + 15 * n1 + " << xSeed << ")) + m1) * " << step << ";\n" + << "\t\t y = (real_t)(VoronDiscreteNoise((int)(l + 21 * m1 + 33 * n1 + " << ySeed << ")) + n1) * " << step << ";\n" + << "\t\t offsetX = vIn.x - x;\n" + << "\t\t offsetY = vIn.y - y;\n" + << "\t\t r = sqrt(SQR(offsetX) + SQR(offsetY));\n" + << "\n" + << "\t\t if (r < rMin)\n" + << "\t\t {\n" + << "\t\t rMin = r;\n" + << "\t\t x0 = x;\n" + << "\t\t y0 = y;\n" + << "\t\t }\n" + << "\t\t }\n" + << "\t\t }\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (" << m_k << " * (vIn.x - x0) + x0);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (" << m_k << " * (vIn.y - y0) + y0);\n" + << "\t}\n"; + + return ss.str(); + } + + virtual string OpenCLFuncsString() + { + return + "real_t VoronDiscreteNoise(int x)\n" + "{\n" + " const real_t im = 2147483647;\n" + " const real_t am = (1 / im);\n" + "\n" + " int n = x;\n" + " n = (n << 13) ^ n;\n" + " return ((n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) * am;\n" + "}\n" + "\n"; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_K, prefix + "Voron_K", T(0.99))); + m_Params.push_back(ParamWithName(&m_Step, prefix + "Voron_Step", T(0.25))); + m_Params.push_back(ParamWithName(&m_Num, prefix + "Voron_Num", 1, INTEGER, 1, 25)); + m_Params.push_back(ParamWithName(&m_XSeed, prefix + "Voron_XSeed", 3, INTEGER)); + m_Params.push_back(ParamWithName(&m_YSeed, prefix + "Voron_YSeed", 7, INTEGER)); + } + +private: + T DiscreteNoise(int x) + { + const T im = T(2147483647); + const T am = (1 / im); + + int n = x; + n = (n << 13) ^ n; + return ((n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) * am; + } + + T m_K;//Params. + T m_Step; + T m_Num; + T m_XSeed; + T m_YSeed; +}; + +/// +/// Waffle. +/// +template +class EMBER_API WaffleVariation : public ParametricVariation +{ +public: + WaffleVariation(T weight = 1.0) : ParametricVariation("waffle", VAR_WAFFLE, weight) + { + Init(); + } + + PARVARCOPY(WaffleVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T a = 0, r = 0; + + switch (rand.Rand(5)) + { + case 0: + a = (rand.Rand((ISAAC_INT)m_Slices) + rand.Frand01() * m_XThickness) / m_Slices; + r = (rand.Rand((ISAAC_INT)m_Slices) + rand.Frand01() * m_YThickness) / m_Slices; + break; + case 1: + a = (rand.Rand((ISAAC_INT)m_Slices) + rand.Frand01()) / m_Slices; + r = (rand.Rand((ISAAC_INT)m_Slices) + m_YThickness) / m_Slices; + break; + case 2: + a = (rand.Rand((ISAAC_INT)m_Slices) + m_XThickness) / m_Slices; + r = (rand.Rand((ISAAC_INT)m_Slices) + rand.Frand01()) / m_Slices; + break; + case 3: + a = rand.Frand01(); + r = (rand.Rand((ISAAC_INT)m_Slices) + m_YThickness + rand.Frand01() * (1 - m_YThickness)) / m_Slices; + break; + case 4: + a = (rand.Rand((ISAAC_INT)m_Slices) + m_XThickness + rand.Frand01() * (1 - m_XThickness)) / m_Slices; + r = rand.Frand01(); + break; + } + + helper.Out.x = m_CosR * a + m_SinR * r; + helper.Out.y = -m_SinR * a + m_CosR * r; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string slices = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string xThickness = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string yThickness = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string rotation = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string sinr = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cosr = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t a = 0, r = 0;\n" + << "\n" + << "\t\tswitch (MwcNextRange(mwc, 5))\n" + << "\t\t{\n" + << "\t\t case 0:\n" + << "\t\t a = (MwcNextRange(mwc, (int)" << slices << ") + MwcNext01(mwc) * " << xThickness << ") / " << slices << ";\n" + << "\t\t r = (MwcNextRange(mwc, (int)" << slices << ") + MwcNext01(mwc) * " << yThickness << ") / " << slices << ";\n" + << "\t\t break;\n" + << "\t\t case 1:\n" + << "\t\t a = (MwcNextRange(mwc, (int)" << slices << ") + MwcNext01(mwc)) / " << slices << ";\n" + << "\t\t r = (MwcNextRange(mwc, (int)" << slices << ") + " << yThickness << ") / " << slices << ";\n" + << "\t\t break;\n" + << "\t\t case 2:\n" + << "\t\t a = (MwcNextRange(mwc, (int)" << slices << ") + " << xThickness << ") / " << slices << ";\n" + << "\t\t r = (MwcNextRange(mwc, (int)" << slices << ") + MwcNext01(mwc)) / " << slices << ";\n" + << "\t\t break;\n" + << "\t\t case 3:\n" + << "\t\t a = MwcNext01(mwc);\n" + << "\t\t r = (MwcNextRange(mwc, (int)" << slices << ") + " << yThickness << " + MwcNext01(mwc) * (1 - " << yThickness << ")) / " << slices << ";\n" + << "\t\t break;\n" + << "\t\t case 4:\n" + << "\t\t a = (MwcNextRange(mwc, (int)" << slices << ") + " << xThickness << " + MwcNext01(mwc) * (1 - " << xThickness << ")) / " << slices << ";\n" + << "\t\t r = MwcNext01(mwc);\n" + << "\t\t break;\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.x = " << cosr << " * a + " << sinr << " * r;\n" + << "\t\tvOut.y = -" << sinr << " * a + " << cosr << " * r;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_SinR = sin(m_Rotation); + m_CosR = cos(m_Rotation); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Slices, prefix + "waffle_slices", 6, INTEGER)); + m_Params.push_back(ParamWithName(&m_XThickness, prefix + "waffle_xthickness", T(0.5))); + m_Params.push_back(ParamWithName(&m_YThickness, prefix + "waffle_ythickness", T(0.5))); + m_Params.push_back(ParamWithName(&m_Rotation, prefix + "waffle_rotation")); + m_Params.push_back(ParamWithName(true, &m_SinR, prefix + "waffle_sinr")); + m_Params.push_back(ParamWithName(true, &m_CosR, prefix + "waffle_cosr")); + } + +private: + T m_Slices; + T m_XThickness; + T m_YThickness; + T m_Rotation; + T m_SinR;//Precalc. + T m_CosR; +}; + +/// +/// Square3D. +/// +template +class EMBER_API Square3DVariation : public Variation +{ +public: + Square3DVariation(T weight = 1.0) : Variation("square3D", VAR_SQUARE3D, weight) { } + + VARCOPY(Square3DVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + helper.Out.x = m_Weight * (rand.Frand01() - T(0.5)); + helper.Out.y = m_Weight * (rand.Frand01() - T(0.5)); + helper.Out.z = m_Weight * (rand.Frand01() - T(0.5)); + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (MwcNext01(mwc) - 0.5);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (MwcNext01(mwc) - 0.5);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * (MwcNext01(mwc) - 0.5);\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// SuperShape3D. +/// +template +class EMBER_API SuperShape3DVariation : public ParametricVariation +{ +public: + SuperShape3DVariation(T weight = 1.0) : ParametricVariation("SuperShape3D", VAR_SUPER_SHAPE3D, weight) + { + Init(); + } + + PARVARCOPY(SuperShape3DVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T pr1, r1, pr2, r2, rho1, phi1, sinr, sinp, cosr, cosp, msinr, msinp, mcosr, mcosp, temp; + + rho1 = rand.Frand01() * m_Rho2Pi; + phi1 = rand.Frand01() * m_Phi2Pi; + + if (rand.RandBit()) + phi1 = -phi1; + + sinr = sin(rho1); + cosr = cos(rho1); + + sinp = sin(phi1); + cosp = cos(phi1); + + temp = m_M4_1 * rho1; + msinr = sin(temp); + mcosr = cos(temp); + + temp = m_M4_2 * phi1; + msinp = sin(temp); + mcosp = cos(temp); + + pr1 = m_An2_1 * pow(fabs(mcosr), m_N2_1) + m_Bn3_1 * pow(fabs(msinr), m_N3_1); + pr2 = m_An2_2 * pow(fabs(mcosp), m_N2_2) + m_Bn3_2 * pow(fabs(msinp), m_N3_2); + r1 = pow(pr1, m_N1_1) + m_Spiral * rho1; + r2 = pow(pr2, m_N1_2); + + if ((int)m_Toroidmap == 1) + { + helper.Out.x = m_Weight * cosr * (r1 + r2 * cosp); + helper.Out.y = m_Weight * sinr * (r1 + r2 * cosp); + helper.Out.z = m_Weight * r2 * sinp; + } + else + { + helper.Out.x = m_Weight * r1 * cosr * r2 * cosp; + helper.Out.y = m_Weight * r1 * sinr * r2 * cosp; + helper.Out.z = m_Weight * r2 * sinp; + } + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string rho = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string phi = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string m1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string m2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string a1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string a2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string b1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string b2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string n1_1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string n1_2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string n2_1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string n2_2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string n3_1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string n3_2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string spiral = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string toroid = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string n1n_1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string n1n_2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string an2_1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string an2_2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string bn3_1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string bn3_2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string m4_1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string m4_2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string rho2pi = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string phi2pi = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t pr1, r1, pr2, r2, rho1, phi1, sinr, sinp, cosr, cosp, msinr, msinp, mcosr, mcosp, temp;\n" + << "\n" + << "\t\trho1 = MwcNext01(mwc) * " << rho2pi << ";\n" + << "\t\tphi1 = MwcNext01(mwc) * " << phi2pi<< ";\n" + << "\n" + << "\t\tif (MwcNext(mwc) & 1)\n" + << "\t\t phi1 = -phi1;\n" + << "\n" + << "\t\tsinr = sin(rho1);\n" + << "\t\tcosr = cos(rho1);\n" + << "\n" + << "\t\tsinp = sin(phi1);\n" + << "\t\tcosp = cos(phi1);\n" + << "\n" + << "\t\ttemp = " << m4_1<< " * rho1;\n" + << "\t\tmsinr = sin(temp);\n" + << "\t\tmcosr = cos(temp);\n" + << "\n" + << "\t\ttemp = " << m4_2 << " * phi1;\n" + << "\t\tmsinp = sin(temp);\n" + << "\t\tmcosp = cos(temp);\n" + << "\n" + << "\t\tpr1 = " << an2_1 << " * pow(fabs(mcosr), " << n2_1 << ") + " << bn3_1 << " * pow(fabs(msinr), " << n3_1 << ");\n" + << "\t\tpr2 = " << an2_2 << " * pow(fabs(mcosp), " << n2_2 << ") + " << bn3_2 << " * pow(fabs(msinp), " << n3_2 << ");\n" + << "\t\tr1 = pow(pr1, " << n1_1 << ") + " << spiral << " * rho1;\n" + << "\t\tr2 = pow(pr2, " << n1_2 << ");\n" + << "\n" + << "\t\tif ((int)" << toroid << " == 1)\n" + << "\t\t{\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * cosr * (r1 + r2 * cosp);\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * sinr * (r1 + r2 * cosp);\n" + << "\t\t vOut.z = xform->m_VariationWeights[" << varIndex << "] * r2 * sinp;\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * r1 * cosr * r2 * cosp;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * r1 * sinr * r2 * cosp;\n" + << "\t\t vOut.z = xform->m_VariationWeights[" << varIndex << "] * r2 * sinp;\n" + << "\t\t}\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_N1n_1 = (-1 / m_N1_1); + m_N1n_2 = (-1 / m_N1_2); + m_An2_1 = pow(fabs(1 / m_A1), m_N2_1); + m_An2_2 = pow(fabs(1 / m_A2), m_N2_2); + m_Bn3_1 = pow(fabs(1 / m_B1), m_N3_1); + m_Bn3_2 = pow(fabs(1 / m_B2), m_N3_2); + m_M4_1 = m_M1 / 4; + m_M4_2 = m_M2 / 4; + m_Rho2Pi = m_Rho * T(M_2_PI); + m_Phi2Pi = m_Phi * T(M_2_PI); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Rho, prefix + "SuperShape3D_rho", T(9.9))); + m_Params.push_back(ParamWithName(&m_Phi, prefix + "SuperShape3D_phi", T(2.5))); + m_Params.push_back(ParamWithName(&m_M1, prefix + "SuperShape3D_m1", 6)); + m_Params.push_back(ParamWithName(&m_M2, prefix + "SuperShape3D_m2", 3)); + m_Params.push_back(ParamWithName(&m_A1, prefix + "SuperShape3D_a1", 1)); + m_Params.push_back(ParamWithName(&m_A2, prefix + "SuperShape3D_a2", 1)); + m_Params.push_back(ParamWithName(&m_B1, prefix + "SuperShape3D_b1", 1)); + m_Params.push_back(ParamWithName(&m_B2, prefix + "SuperShape3D_b2", 1)); + m_Params.push_back(ParamWithName(&m_N1_1, prefix + "SuperShape3D_n1_1", 1)); + m_Params.push_back(ParamWithName(&m_N1_2, prefix + "SuperShape3D_n1_2", 1)); + m_Params.push_back(ParamWithName(&m_N2_1, prefix + "SuperShape3D_n2_1", 1)); + m_Params.push_back(ParamWithName(&m_N2_2, prefix + "SuperShape3D_n2_2", 1)); + m_Params.push_back(ParamWithName(&m_N3_1, prefix + "SuperShape3D_n3_1", 1)); + m_Params.push_back(ParamWithName(&m_N3_2, prefix + "SuperShape3D_n3_2", 1)); + m_Params.push_back(ParamWithName(&m_Spiral, prefix + "SuperShape3D_spiral")); + m_Params.push_back(ParamWithName(&m_Toroidmap, prefix + "SuperShape3D_toroidmap", 0, INTEGER, 0, 1)); + m_Params.push_back(ParamWithName(true, &m_N1n_1, prefix + "SuperShape3D_n1n1"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_N1n_2, prefix + "SuperShape3D_n1n2")); + m_Params.push_back(ParamWithName(true, &m_An2_1, prefix + "SuperShape3D_an21")); + m_Params.push_back(ParamWithName(true, &m_An2_2, prefix + "SuperShape3D_an22")); + m_Params.push_back(ParamWithName(true, &m_Bn3_1, prefix + "SuperShape3D_bn31")); + m_Params.push_back(ParamWithName(true, &m_Bn3_2, prefix + "SuperShape3D_bn32")); + m_Params.push_back(ParamWithName(true, &m_M4_1, prefix + "SuperShape3D_m41")); + m_Params.push_back(ParamWithName(true, &m_M4_2, prefix + "SuperShape3D_m42")); + m_Params.push_back(ParamWithName(true, &m_Rho2Pi, prefix + "SuperShape3D_rho2pi")); + m_Params.push_back(ParamWithName(true, &m_Phi2Pi, prefix + "SuperShape3D_phi2pi")); + } + +private: + T m_Rho; + T m_Phi; + T m_M1; + T m_M2; + T m_A1; + T m_A2; + T m_B1; + T m_B2; + T m_N1_1; + T m_N1_2; + T m_N2_1; + T m_N2_2; + T m_N3_1; + T m_N3_2; + T m_Spiral; + T m_Toroidmap; + T m_N1n_1;//Precalc. + T m_N1n_2; + T m_An2_1; + T m_An2_2; + T m_Bn3_1; + T m_Bn3_2; + T m_M4_1; + T m_M4_2; + T m_Rho2Pi; + T m_Phi2Pi; +}; + +/// +/// sphyp3D. +/// +template +class EMBER_API Sphyp3DVariation : public ParametricVariation +{ +public: + Sphyp3DVariation(T weight = 1.0) : ParametricVariation("sphyp3D", VAR_SPHYP3D, weight, true) + { + Init(); + } + + PARVARCOPY(Sphyp3DVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T t, rX, rY, rZ; + + t = helper.m_PrecalcSumSquares + SQR(helper.In.z) + EPS6; + rX = m_Weight / pow(t, m_StretchX); + rY = m_Weight / pow(t, m_StretchY); + + helper.Out.x = helper.In.x * rX; + helper.Out.y = helper.In.y * rY; + + //Optional 3D calculation. + if ((int)m_ZOn == 1) + { + rZ = m_Weight / pow(t, m_StretchZ); + helper.Out.z = helper.In.z * rZ; + } + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string stretchX = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string stretchY = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string stretchZ = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string zOn = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t t, rX, rY, rZ;\n" + << "\n" + << "\t\tt = precalcSumSquares + SQR(vIn.z) + EPS6;\n" + << "\t\trX = xform->m_VariationWeights[" << varIndex << "] / pow(t, " << stretchX << ");\n" + << "\t\trY = xform->m_VariationWeights[" << varIndex << "] / pow(t, " << stretchY << ");\n" + << "\n" + << "\t\tvOut.x = vIn.x * rX;\n" + << "\t\tvOut.y = vIn.y * rY;\n" + << "\n" + << "\t\tif ((int)" << zOn << " == 1)\n" + << "\t\t{\n" + << "\t\trZ = xform->m_VariationWeights[" << varIndex << "] / pow(t, " << stretchZ << ");\n" + << "\n" + << "\t\tvOut.z = vIn.z * rZ;\n" + << "\t\t}\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_StretchX, prefix + "sphyp3D_stretchX", 1)); + m_Params.push_back(ParamWithName(&m_StretchY, prefix + "sphyp3D_stretchY", 1)); + m_Params.push_back(ParamWithName(&m_StretchZ, prefix + "sphyp3D_stretchZ", 1)); + m_Params.push_back(ParamWithName(&m_ZOn, prefix + "sphyp3D_zOn", 1, INTEGER, 0, 1)); + } + +private: + T m_StretchX; + T m_StretchY; + T m_StretchZ; + T m_ZOn; +}; + +/// +/// circlecrop. +/// +template +class EMBER_API CirclecropVariation : public ParametricVariation +{ +public: + CirclecropVariation(T weight = 1.0) : ParametricVariation("circlecrop", VAR_CIRCLECROP, weight) + { + Init(); + } + + PARVARCOPY(CirclecropVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T xi = helper.In.x - m_X;//Original altered the input pointed to for reg, but not for pre/post. Don't do that here. + T yi = helper.In.y - m_Y; + + const T rad = sqrt(SQR(xi) + SQR(yi)); + const T ang = atan2(yi, xi); + const T rdc = m_Radius + (rand.Frand01() * T(0.5) * m_Ca); + const T s = sin(ang); + const T c = cos(ang); + + const int esc = rad > m_Radius; + const int cr0 = (int)m_Zero; + + if (cr0 && esc) + { + if (m_VarType == VARTYPE_PRE) + helper.m_TransX = helper.m_TransY = 0; + else + outPoint.m_X = outPoint.m_Y = 0; + + helper.Out.x = helper.Out.y = 0; + helper.Out.z = m_Weight * helper.In.z; + } + else if (cr0 && !esc) + { + helper.Out.x = m_Weight * xi + m_X; + helper.Out.y = m_Weight * yi + m_Y; + helper.Out.z = m_Weight * helper.In.z; + } + else if (!cr0 && esc) + { + helper.Out.x = m_Weight * rdc * c + m_X; + helper.Out.y = m_Weight * rdc * s + m_Y; + helper.Out.z = m_Weight * helper.In.z; + } + else if (!cr0 && !esc) + { + helper.Out.x = m_Weight * xi + m_X; + helper.Out.y = m_Weight * yi + m_Y; + helper.Out.z = m_Weight * helper.In.z; + } + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string radius = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string scatterArea = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string zero = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ca = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t xi = vIn.x - " << x << ";\n" + << "\t\treal_t yi = vIn.y - " << y << ";\n" + << "\n" + << "\t\tconst real_t rad = sqrt(SQR(xi) + SQR(yi));\n" + << "\t\tconst real_t ang = atan2(yi, xi);\n" + << "\t\tconst real_t rdc = " << radius << " + (MwcNext01(mwc) * 0.5 * " << ca << "); \n" + << "\t\tconst real_t s = sin(ang);\n" + << "\t\tconst real_t c = cos(ang);\n" + << "\n" + << "\t\tconst int esc = rad > " << radius << ";\n" + << "\t\tconst int cr0 = (int)" << zero << ";\n" + << "\n" + << "\t\tif (cr0 && esc)\n" + << "\t\t{\n"; + + if (m_VarType == VARTYPE_PRE) + ss << "\t\t transX = transY = 0;\n"; + else + ss << "\t\t outPoint->m_X = outPoint->m_Y = 0;\n"; + + ss + << "\t\t vOut.x = vOut.y = 0;\n" + << "\t\t vOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t\t}\n" + << "\t\telse if (cr0 && !esc)\n" + << "\t\t{\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * xi + " << x << ";\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * yi + " << y << ";\n" + << "\t\t vOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t\t}\n" + << "\t\telse if (!cr0 && esc)\n" + << "\t\t{\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * rdc * c + " << x << ";\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * rdc * s + " << y << ";\n" + << "\t\t vOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t\t}\n" + << "\t\telse if (!cr0 && !esc)\n" + << "\t\t{\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * xi + " << x << ";\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * yi + " << y << ";\n" + << "\t\t vOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t\t}\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Ca = Clamp(m_ScatterArea, -1, 1); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Radius, prefix + "circlecrop_radius", 1)); + m_Params.push_back(ParamWithName(&m_X, prefix + "circlecrop_x")); + m_Params.push_back(ParamWithName(&m_Y, prefix + "circlecrop_y")); + m_Params.push_back(ParamWithName(&m_ScatterArea, prefix + "circlecrop_scatter_area")); + m_Params.push_back(ParamWithName(&m_Zero, prefix + "circlecrop_zero", 1, INTEGER, 0, 1)); + m_Params.push_back(ParamWithName(true, &m_Ca, prefix + "circlecrop_ca")); + } + +private: + T m_Radius; + T m_X; + T m_Y; + T m_ScatterArea; + T m_Zero; + T m_Ca;//Precalc. +}; + +/// +/// julian3Dx. +/// +template +class EMBER_API Julian3DxVariation : public ParametricVariation +{ +public: + Julian3DxVariation(T weight = 1.0) : ParametricVariation("julian3Dx", VAR_JULIAN3DX, weight, true, true) + { + Init(); + } + + PARVARCOPY(Julian3DxVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + const T z = helper.In.z / m_AbsN; + const T radiusOut = m_Weight * pow(helper.m_PrecalcSumSquares + z * z, m_Cn); + const T x = m_A * helper.In.x + m_B * helper.In.y + m_E; + const T y = m_C * helper.In.x + m_D * helper.In.y + m_F; + const T tempRand = (T)(int)(rand.Frand01() * m_AbsN); + const T alpha = (atan2(y, x) + M_2PI * tempRand) / m_Power; + const T gamma = radiusOut * helper.m_PrecalcSqrtSumSquares; + + helper.Out.x = gamma * cos(alpha); + helper.Out.y = gamma * sin(alpha); + helper.Out.z = radiusOut * z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string power = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string dist = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string a = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string b = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string d = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string e = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string f = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string absn = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cn = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tconst real_t z = vIn.z / " << absn << ";\n" + << "\t\tconst real_t radiusOut = xform->m_VariationWeights[" << varIndex << "] * pow(precalcSumSquares + z * z, " << cn << ");\n" + << "\t\tconst real_t x = " << a << " * vIn.x + " << b << " * vIn.y + " << e << ";\n" + << "\t\tconst real_t y = " << c << " * vIn.x + " << d << " * vIn.y + " << f << ";\n" + << "\t\tconst real_t rand = (int)(MwcNext01(mwc) * " << absn << ");\n" + << "\t\tconst real_t alpha = (atan2(y, x) + M_2PI * rand) / " << power << ";\n" + << "\t\tconst real_t gamma = radiusOut * precalcSqrtSumSquares;\n" + << "\n" + << "\t\tvOut.x = gamma * cos(alpha);\n" + << "\t\tvOut.y = gamma * sin(alpha);\n" + << "\t\tvOut.z = radiusOut * z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_AbsN = fabs(m_Power); + m_Cn = (m_Dist / m_Power - 1) / 2; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Dist, prefix + "julian3Dx_dist", 1)); + m_Params.push_back(ParamWithName(&m_Power, prefix + "julian3Dx_power", 2, INTEGER_NONZERO)); + m_Params.push_back(ParamWithName(&m_A, prefix + "julian3Dx_a", 1)); + m_Params.push_back(ParamWithName(&m_B, prefix + "julian3Dx_b")); + m_Params.push_back(ParamWithName(&m_C, prefix + "julian3Dx_c")); + m_Params.push_back(ParamWithName(&m_D, prefix + "julian3Dx_d", 1)); + m_Params.push_back(ParamWithName(&m_E, prefix + "julian3Dx_e")); + m_Params.push_back(ParamWithName(&m_F, prefix + "julian3Dx_f")); + m_Params.push_back(ParamWithName(true, &m_AbsN, prefix + "julian3Dx_absn"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Cn, prefix + "julian3Dx_cn")); + } + +private: + T m_Dist;//Params. + T m_Power; + T m_A; + T m_B; + T m_C; + T m_D; + T m_E; + T m_F; + T m_AbsN;//Precalc. + T m_Cn; +}; + +/// +/// fourth. +/// +template +class EMBER_API FourthVariation : public ParametricVariation +{ +public: + FourthVariation(T weight = 1.0) : ParametricVariation("fourth", VAR_FOURTH, weight, true, true, false, false, true) + { + Init(); + } + + PARVARCOPY(FourthVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + if (helper.In.x > 0 && helper.In.y > 0)//Quadrant IV: spherical. + { + T r = 1 / helper.m_PrecalcSqrtSumSquares; + + helper.Out.x = m_Weight * r * cos(helper.m_PrecalcAtanyx); + helper.Out.y = m_Weight * r * sin(helper.m_PrecalcAtanyx); + } + else if (helper.In.x > 0 && helper.In.y < 0)//Quadrant I: loonie. + { + T r2 = helper.m_PrecalcSumSquares; + + if (r2 < m_SqrWeight) + { + T r = m_Weight * sqrt(m_SqrWeight / r2 - 1); + + helper.Out.x = r * helper.In.x; + helper.Out.y = r * helper.In.y; + } + else + { + helper.Out.x = m_Weight * helper.In.x; + helper.Out.y = m_Weight * helper.In.y; + } + } + else if (helper.In.x < 0 && helper.In.y > 0)//Quadrant III: susan. + { + T x = helper.In.x - m_X; + T y = helper.In.y + m_Y; + T r = sqrt(SQR(x) + SQR(y)); + + if (r < m_Weight) + { + T a = atan2(y, x) + m_Spin + m_Twist * (m_Weight - r); + + r *= m_Weight; + helper.Out.x = r * cos(a) + m_X; + helper.Out.y = r * sin(a) - m_Y; + } + else + { + r = m_Weight * (1 + m_Space / r); + helper.Out.x = r * x + m_X; + helper.Out.y = r * y - m_Y; + } + } + else//Quadrant II: Linear. + { + helper.Out.x = m_Weight * helper.In.x; + helper.Out.y = m_Weight * helper.In.y; + } + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string spin = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string space = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string twist = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string sqrWeight = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tif (vIn.x > 0 && vIn.y > 0)\n" + << "\t\t{\n" + << "\t\t real_t r = 1 / precalcSqrtSumSquares;\n" + << "\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * r * cos(precalcAtanyx);\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * r * sin(precalcAtanyx);\n" + << "\t\t}\n" + << "\t\telse if (vIn.x > 0 && vIn.y < 0)\n" + << "\t\t{\n" + << "\t\t real_t r2 = precalcSumSquares;\n" + << "\n" + << "\t\t if (r2 < " << sqrWeight << ")\n" + << "\t\t {\n" + << "\t\t real_t r = xform->m_VariationWeights[" << varIndex << "] * sqrt(" << sqrWeight << " / r2 - 1);\n" + << "\n" + << "\t\t vOut.x = r * vIn.x;\n" + << "\t\t vOut.y = r * vIn.y;\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\t\t }\n" + << "\t\t}\n" + << "\t\telse if (vIn.x < 0 && vIn.y > 0)\n" + << "\t\t{\n" + << "\t\t real_t x = vIn.x - " << x << ";\n" + << "\t\t real_t y = vIn.y + " << y << ";\n" + << "\t\t real_t r = sqrt(SQR(x) + SQR(y));\n" + << "\n" + << "\t\t if (r < xform->m_VariationWeights[" << varIndex << "])\n" + << "\t\t {\n" + << "\t\t real_t a = atan2(y, x) + " << spin << " + " << twist << " * (xform->m_VariationWeights[" << varIndex << "] - r);\n" + << "\n" + << "\t\t r *= xform->m_VariationWeights[" << varIndex << "];\n" + << "\t\t vOut.x = r * cos(a) + " << x << ";\n" + << "\t\t vOut.y = r * sin(a) - " << y << ";\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t r = xform->m_VariationWeights[" << varIndex << "] * (1 + " << space << " / r);\n" + << "\t\t vOut.x = r * x + " << x << ";\n" + << "\t\t vOut.y = r * y - " << y << ";\n" + << "\t\t }\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\t\t}\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_SqrWeight = SQR(m_Weight); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Spin, prefix + "fourth_spin", T(M_PI), REAL_CYCLIC, 0, M_2PI)); + m_Params.push_back(ParamWithName(&m_Space, prefix + "fourth_space")); + m_Params.push_back(ParamWithName(&m_Twist, prefix + "fourth_twist")); + m_Params.push_back(ParamWithName(&m_X, prefix + "fourth_x")); + m_Params.push_back(ParamWithName(&m_Y, prefix + "fourth_y")); + m_Params.push_back(ParamWithName(true, &m_SqrWeight, prefix + "fourth_sqr_weight"));//Precalc. + } + +private: + T m_Spin; + T m_Space; + T m_Twist; + T m_X; + T m_Y; + T m_SqrWeight;//Precalc. +}; + +/// +/// mobiq. +/// +template +class EMBER_API MobiqVariation : public ParametricVariation +{ +public: + MobiqVariation(T weight = 1.0) : ParametricVariation("mobiq", VAR_MOBIQ, weight) + { + Init(); + } + + PARVARCOPY(MobiqVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + const T t1 = m_At; + const T t2 = helper.In.x; + const T t3 = m_Bt; + const T t4 = m_Ct; + const T t5 = m_Dt; + const T x1 = m_Ax; + const T x2 = helper.In.y; + const T x3 = m_Bx; + const T x4 = m_Cx; + const T x5 = m_Dx; + const T y1 = m_Ay; + const T y2 = helper.In.z; + const T y3 = m_By; + const T y4 = m_Cy; + const T y5 = m_Dy; + const T z1 = m_Az; + const T z3 = m_Bz; + const T z4 = m_Cz; + const T z5 = m_Dz; + + T nt = t1 * t2 - x1 * x2 - y1 * y2 + t3; + T nx = t1 * x2 + x1 * t2 - z1 * y2 + x3; + T ny = t1 * y2 + y1 * t2 + z1 * x2 + y3; + T nz = z1 * t2 + x1 * y2 - y1 * x2 + z3; + T dt = t4 * t2 - x4 * x2 - y4 * y2 + t5; + T dx = t4 * x2 + x4 * t2 - z4 * y2 + x5; + T dy = t4 * y2 + y4 * t2 + z4 * x2 + y5; + T dz = z4 * t2 + x4 * y2 - y4 * x2 + z5; + T ni = m_Weight / (SQR(dt) + SQR(dx) + SQR(dy) + SQR(dz)); + + helper.Out.x = (nt * dt + nx * dx + ny * dy + nz * dz) * ni; + helper.Out.y = (nx * dt - nt * dx - ny * dz + nz * dy) * ni; + helper.Out.z = (ny * dt - nt * dy - nz * dx + nx * dz) * ni; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string at = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ax = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ay = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string az = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string bt = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string bx = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string by = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string bz = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ct = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cx = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cy = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cz = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string dt = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string dx = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string dy = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string dz = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tconst real_t t1 = " << at << ";\n" + << "\t\tconst real_t t2 = vIn.x;\n" + << "\t\tconst real_t t3 = " << bt << ";\n" + << "\t\tconst real_t t4 = " << ct << ";\n" + << "\t\tconst real_t t5 = " << dt << ";\n" + << "\t\tconst real_t x1 = " << ax << ";\n" + << "\t\tconst real_t x2 = vIn.y;\n" + << "\t\tconst real_t x3 = " << bx << ";\n" + << "\t\tconst real_t x4 = " << cx << ";\n" + << "\t\tconst real_t x5 = " << dx << ";\n" + << "\t\tconst real_t y1 = " << ay << ";\n" + << "\t\tconst real_t y2 = vIn.z;\n" + << "\t\tconst real_t y3 = " << by << ";\n" + << "\t\tconst real_t y4 = " << cy << ";\n" + << "\t\tconst real_t y5 = " << dy << ";\n" + << "\t\tconst real_t z1 = " << az << ";\n" + << "\t\tconst real_t z3 = " << bz << ";\n" + << "\t\tconst real_t z4 = " << cz << ";\n" + << "\t\tconst real_t z5 = " << dz << ";\n" + << "\n" + << "\t\treal_t nt = t1 * t2 - x1 * x2 - y1 * y2 + t3;\n" + << "\t\treal_t nx = t1 * x2 + x1 * t2 - z1 * y2 + x3;\n" + << "\t\treal_t ny = t1 * y2 + y1 * t2 + z1 * x2 + y3;\n" + << "\t\treal_t nz = z1 * t2 + x1 * y2 - y1 * x2 + z3;\n" + << "\t\treal_t dt = t4 * t2 - x4 * x2 - y4 * y2 + t5;\n" + << "\t\treal_t dx = t4 * x2 + x4 * t2 - z4 * y2 + x5;\n" + << "\t\treal_t dy = t4 * y2 + y4 * t2 + z4 * x2 + y5;\n" + << "\t\treal_t dz = z4 * t2 + x4 * y2 - y4 * x2 + z5;\n" + << "\t\treal_t ni = xform->m_VariationWeights[" << varIndex << "] / (SQR(dt) + SQR(dx) + SQR(dy) + SQR(dz));\n" + << "\n" + << "\t\tvOut.x = (nt * dt + nx * dx + ny * dy + nz * dz) * ni;\n" + << "\t\tvOut.y = (nx * dt - nt * dx - ny * dz + nz * dy) * ni;\n" + << "\t\tvOut.z = (ny * dt - nt * dy - nz * dx + nx * dz) * ni;\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_At, prefix + "mobiq_at", 1)); + m_Params.push_back(ParamWithName(&m_Ax, prefix + "mobiq_ax")); + m_Params.push_back(ParamWithName(&m_Ay, prefix + "mobiq_ay")); + m_Params.push_back(ParamWithName(&m_Az, prefix + "mobiq_az")); + m_Params.push_back(ParamWithName(&m_Bt, prefix + "mobiq_bt")); + m_Params.push_back(ParamWithName(&m_Bx, prefix + "mobiq_bx")); + m_Params.push_back(ParamWithName(&m_By, prefix + "mobiq_by")); + m_Params.push_back(ParamWithName(&m_Bz, prefix + "mobiq_bz")); + m_Params.push_back(ParamWithName(&m_Ct, prefix + "mobiq_ct")); + m_Params.push_back(ParamWithName(&m_Cx, prefix + "mobiq_cx")); + m_Params.push_back(ParamWithName(&m_Cy, prefix + "mobiq_cy")); + m_Params.push_back(ParamWithName(&m_Cz, prefix + "mobiq_cz")); + m_Params.push_back(ParamWithName(&m_Dt, prefix + "mobiq_dt", 1)); + m_Params.push_back(ParamWithName(&m_Dx, prefix + "mobiq_dx")); + m_Params.push_back(ParamWithName(&m_Dy, prefix + "mobiq_dy")); + m_Params.push_back(ParamWithName(&m_Dz, prefix + "mobiq_dz")); + } + +private: + T m_At; + T m_Ax; + T m_Ay; + T m_Az; + T m_Bt; + T m_Bx; + T m_By; + T m_Bz; + T m_Ct; + T m_Cx; + T m_Cy; + T m_Cz; + T m_Dt; + T m_Dx; + T m_Dy; + T m_Dz; +}; + +/// +/// spherivoid. +/// +template +class EMBER_API SpherivoidVariation : public ParametricVariation +{ +public: + SpherivoidVariation(T weight = 1.0) : ParametricVariation("spherivoid", VAR_SPHERIVOID, weight, true, true, false, false, true) + { + Init(); + } + + PARVARCOPY(SpherivoidVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + const T zr = Hypot(helper.In.z, helper.m_PrecalcSqrtSumSquares); + const T phi = acos(Clamp(helper.In.z / zr, -1, 1)); + const T ps = sin(phi); + const T pc = cos(phi); + + helper.Out.x = m_Weight * cos(helper.m_PrecalcAtanyx) * ps * (zr + m_Radius); + helper.Out.y = m_Weight * sin(helper.m_PrecalcAtanyx) * ps * (zr + m_Radius); + helper.Out.z = m_Weight * pc * (zr + m_Radius); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string radius = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tconst real_t zr = Hypot(vIn.z, precalcSqrtSumSquares);\n" + << "\t\tconst real_t phi = acos(Clamp(vIn.z / zr, -1.0, 1.0));\n" + << "\t\tconst real_t ps = sin(phi);\n" + << "\t\tconst real_t pc = cos(phi);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * cos(precalcAtanyx) * ps * (zr + " << radius << ");\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * sin(precalcAtanyx) * ps * (zr + " << radius << ");\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * pc * (zr + " << radius << ");\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Radius, prefix + "spherivoid_radius")); + } + +private: + T m_Radius; +}; + +/// +/// farblur. +/// +template +class EMBER_API FarblurVariation : public ParametricVariation +{ +public: + FarblurVariation(T weight = 1.0) : ParametricVariation("farblur", VAR_FARBLUR, weight) + { + Init(); + } + + PARVARCOPY(FarblurVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r = m_Weight * (Sqr(helper.In.x - m_XOrigin) + + Sqr(helper.In.y - m_YOrigin) + + Sqr(helper.In.z - m_ZOrigin)) * + (rand.Frand01() + rand.Frand01() + rand.Frand01() + rand.Frand01() - 2); + T u = rand.Frand01() * M_2PI; + T su = sin(u); + T cu = cos(u); + T v = rand.Frand01() * M_2PI; + T sv = sin(v); + T cv = cos(v); + + helper.Out.x += m_X * r * sv * cu; + helper.Out.y += m_Y * r * sv * su; + helper.Out.z += m_Z * r * cv; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string z = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string xOrigin = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string yOrigin = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string zOrigin = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * (Sqr(vIn.x - " << xOrigin << ") + \n" + << "\t\t Sqr(vIn.y - " << yOrigin << ") + \n" + << "\t\t Sqr(vIn.z - " << zOrigin << ")) *\n" + << "\t\t (MwcNext01(mwc) + MwcNext01(mwc) + MwcNext01(mwc) + MwcNext01(mwc) - 2);\n" + << "\t\treal_t u = MwcNext01(mwc) * M_2PI;\n" + << "\t\treal_t su = sin(u);\n" + << "\t\treal_t cu = cos(u);\n" + << "\t\treal_t v = MwcNext01(mwc) * M_2PI;\n" + << "\t\treal_t sv = sin(v);\n" + << "\t\treal_t cv = cos(v);\n" + << "\n" + << "\t\tvOut.x = " << x << " * r * sv * cu;\n" + << "\t\tvOut.y = " << y << " * r * sv * su;\n" + << "\t\tvOut.z = " << z << " * r * cv;\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_X, prefix + "farblur_x", 1)); + m_Params.push_back(ParamWithName(&m_Y, prefix + "farblur_y", 1)); + m_Params.push_back(ParamWithName(&m_Z, prefix + "farblur_z", 1)); + m_Params.push_back(ParamWithName(&m_XOrigin, prefix + "farblur_x_origin")); + m_Params.push_back(ParamWithName(&m_YOrigin, prefix + "farblur_y_origin")); + m_Params.push_back(ParamWithName(&m_ZOrigin, prefix + "farblur_z_origin")); + } + +private: + T m_X; + T m_Y; + T m_Z; + T m_XOrigin; + T m_YOrigin; + T m_ZOrigin; +}; + +/// +/// curl_sp. +/// +template +class EMBER_API CurlSPVariation : public ParametricVariation +{ +public: + CurlSPVariation(T weight = 1.0) : ParametricVariation("curl_sp", VAR_CURL_SP, weight) + { + Init(); + } + + PARVARCOPY(CurlSPVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + const T x = Powq4c(helper.In.x, m_Power); + const T y = Powq4c(helper.In.y, m_Power); + const T z = Powq4c(helper.In.z, m_Power); + const T d = SQR(x) - SQR(y); + const T re = Spread(m_C1 * x + m_C2 * d, m_Sx) + 1; + const T im = Spread(m_C1 * y + m_C2x2 * x * y, m_Sy); + T c = Powq4c(SQR(re) + SQR(im), m_PowerInv); + + if (c == 0) + c = EPS6; + + const T r = m_Weight / c; + + helper.Out.x = (x * re + y * im) * r; + helper.Out.y = (y * re - x * im) * r; + helper.Out.z = (z * m_Weight) / c; + outPoint.m_ColorX = Clamp(outPoint.m_ColorX + m_DcAdjust * c, 0, 1); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string power = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string sx = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string sy = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string dc = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c2x2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string dcAdjust = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string powerInv = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tconst real_t x = Powq4c(vIn.x, " << power << ");\n" + << "\t\tconst real_t y = Powq4c(vIn.y, " << power << ");\n" + << "\t\tconst real_t z = Powq4c(vIn.z, " << power << ");\n" + << "\t\tconst real_t d = SQR(x) - SQR(y);\n" + << "\t\tconst real_t re = Spread(" << c1 << " * x + " << c2 << " * d, " << sx << ") + 1.0;\n" + << "\t\tconst real_t im = Spread(" << c1 << " * y + " << c2x2 << " * x * y, " << sy << ");\n" + << "\t\treal_t c = Powq4c(SQR(re) + SQR(im), " << powerInv << ");\n" + << "\n" + << "\t\tif (c == 0.0)\n" + << "\t\t c = EPS6;\n" + << "\n" + << "\t\tconst real_t r = xform->m_VariationWeights[" << varIndex << "] / c;\n" + << "\n" + << "\t\tvOut.x = (x * re + y * im) * r;\n" + << "\t\tvOut.y = (y * re - x * im) * r;\n" + << "\t\tvOut.z = (z * xform->m_VariationWeights[" << varIndex << "]) / c;\n" + << "\t\toutPoint->m_ColorX = Clamp(outPoint->m_ColorX + " << dcAdjust << " * c, 0.0, 1.0);\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_C2x2 = 2 * m_C2; + m_DcAdjust = T(0.1) * m_Dc; + m_Power = Zeps(m_Power); + m_PowerInv = 1 / m_Power; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Power, prefix + "curl_sp_pow", 1, REAL_NONZERO)); + m_Params.push_back(ParamWithName(&m_C1, prefix + "curl_sp_c1")); + m_Params.push_back(ParamWithName(&m_C2, prefix + "curl_sp_c2")); + m_Params.push_back(ParamWithName(&m_Sx, prefix + "curl_sp_sx")); + m_Params.push_back(ParamWithName(&m_Sy, prefix + "curl_sp_sy")); + m_Params.push_back(ParamWithName(&m_Dc, prefix + "curl_sp_dc")); + m_Params.push_back(ParamWithName(true, &m_C2x2, prefix + "curl_sp_c2_x2")); + m_Params.push_back(ParamWithName(true, &m_DcAdjust, prefix + "curl_sp_dc_adjust")); + m_Params.push_back(ParamWithName(true, &m_PowerInv, prefix + "curl_sp_power_inv")); + } + +private: + T m_Power; + T m_C1; + T m_C2; + T m_Sx; + T m_Sy; + T m_Dc; + T m_C2x2;//Precalc. + T m_DcAdjust; + T m_PowerInv; +}; + +/// +/// heat. +/// +template +class EMBER_API HeatVariation : public ParametricVariation +{ +public: + HeatVariation(T weight = 1.0) : ParametricVariation("heat", VAR_HEAT, weight, true, false, false, false, true) + { + Init(); + } + + PARVARCOPY(HeatVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r = sqrt(fabs(helper.m_PrecalcSumSquares + helper.In.z)); + + r += m_Ar * sin(fma(m_Br, r, m_Cr)); + + if (r == 0) + r = EPS6; + + T temp = fma(m_At, sin(fma(m_Bt, r, m_Ct)), helper.m_PrecalcAtanyx); + T st = sin(temp); + T ct = cos(temp); + + temp = fma(m_Ap, sin(fma(m_Bp, r, m_Cp)), acos(Clamp(helper.In.z / r, -1, 1))); + + T sp = sin(temp); + T cp = cos(temp); + + helper.Out.x = r * ct * sp; + helper.Out.y = r * st * sp; + helper.Out.z = r * cp; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string thetaPeriod = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string thetaPhase = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string thetaAmp = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string phiPeriod = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string phiPhase = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string phiAmp = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string rperiod = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string rphase = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ramp = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string at = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string bt = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ct = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ap = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string bp = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cp = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ar = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string br = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cr = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t r = sqrt(fabs(precalcSumSquares + vIn.z));\n" + << "\n" + << "\t\tr += " << ar << " * sin(fma(" << br << ", r, " << cr << "));\n" + << "\n" + << "\t\tif (r == 0)\n" + << "\t\t r = EPS6;\n" + << "\n" + << "\t\treal_t temp = fma(" << at << ", sin(fma(" << bt << ", r, " << ct << ")), precalcAtanyx);\n" + << "\t\treal_t st = sin(temp);\n" + << "\t\treal_t ct = cos(temp);\n" + << "\n" + << "\t\ttemp = fma(" << ap << ", sin(fma(" << bp << ", r, " << cp << ")), acos(Clamp(vIn.z / r, -1.0, 1.0)));\n" + << "\n" + << "\t\treal_t sp = sin(temp);\n" + << "\t\treal_t cp = cos(temp);\n" + << "\n" + << "\t\tvOut.x = r * ct * sp;\n" + << "\t\tvOut.y = r * st * sp;\n" + << "\t\tvOut.z = r * cp;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + T tx = m_ThetaPeriod == 0 ? 0 : (1 / m_ThetaPeriod); + T px = m_PhiPeriod == 0 ? 0 : (1 / m_PhiPeriod); + T rx = m_Rperiod == 0 ? 0 : (1 / m_Rperiod); + + m_At = m_Weight * m_ThetaAmp; + m_Bt = M_2PI * tx; + m_Ct = m_ThetaPhase * tx; + m_Ap = m_Weight * m_PhiAmp; + m_Bp = M_2PI * px; + m_Cp = m_PhiPhase * px; + m_Ar = m_Weight * m_Ramp; + m_Br = M_2PI * rx; + m_Cr = m_Rphase * rx; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_ThetaPeriod, prefix + "heat_theta_period", 1)); + m_Params.push_back(ParamWithName(&m_ThetaPhase, prefix + "heat_theta_phase")); + m_Params.push_back(ParamWithName(&m_ThetaAmp, prefix + "heat_theta_amp", 1)); + m_Params.push_back(ParamWithName(&m_PhiPeriod, prefix + "heat_phi_period", 1)); + m_Params.push_back(ParamWithName(&m_PhiPhase, prefix + "heat_phi_phase")); + m_Params.push_back(ParamWithName(&m_PhiAmp, prefix + "heat_phi_amp")); + m_Params.push_back(ParamWithName(&m_Rperiod, prefix + "heat_r_period", 1)); + m_Params.push_back(ParamWithName(&m_Rphase, prefix + "heat_r_phase")); + m_Params.push_back(ParamWithName(&m_Ramp, prefix + "heat_r_amp")); + m_Params.push_back(ParamWithName(true, &m_At, prefix + "heat_at")); + m_Params.push_back(ParamWithName(true, &m_Bt, prefix + "heat_bt")); + m_Params.push_back(ParamWithName(true, &m_Ct, prefix + "heat_ct")); + m_Params.push_back(ParamWithName(true, &m_Ap, prefix + "heat_ap")); + m_Params.push_back(ParamWithName(true, &m_Bp, prefix + "heat_bp")); + m_Params.push_back(ParamWithName(true, &m_Cp, prefix + "heat_cp")); + m_Params.push_back(ParamWithName(true, &m_Ar, prefix + "heat_ar")); + m_Params.push_back(ParamWithName(true, &m_Br, prefix + "heat_br")); + m_Params.push_back(ParamWithName(true, &m_Cr, prefix + "heat_cr")); + } + +private: + T m_ThetaPeriod; + T m_ThetaPhase; + T m_ThetaAmp; + T m_PhiPeriod; + T m_PhiPhase; + T m_PhiAmp; + T m_Rperiod; + T m_Rphase; + T m_Ramp; + T m_At;//Precalc. + T m_Bt; + T m_Ct; + T m_Ap; + T m_Bp; + T m_Cp; + T m_Ar; + T m_Br; + T m_Cr; +}; + +/// +/// interference2. +/// +template +class EMBER_API Interference2Variation : public ParametricVariation +{ +public: + Interference2Variation(T weight = 1.0) : ParametricVariation("interference2", VAR_INTERFERENCE2, weight) + { + Init(); + } + + PARVARCOPY(Interference2Variation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T fp1x; + T fp1y; + T fp2x; + T fp2y; + + switch ((int)m_T1) + { + case 0: + fp1x = Sine(m_A1, m_B1, m_C1, m_P1, helper.In.x); + fp1y = Sine(m_A1, m_B1, m_C1, m_P1, helper.In.y); + break; + case 1: + fp1x = Tri(m_A1, m_B1, m_C1, m_P1, helper.In.x); + fp1y = Tri(m_A1, m_B1, m_C1, m_P1, helper.In.y); + break; + case 2: + fp1x = Squ(m_A1, m_B1, m_C1, m_P1, helper.In.x); + fp1y = Squ(m_A1, m_B1, m_C1, m_P1, helper.In.y); + break; + default: + fp1x = Sine(m_A1, m_B1, m_C1, m_P1, helper.In.x); + fp1y = Sine(m_A1, m_B1, m_C1, m_P1, helper.In.y); + break; + } + + switch ((int)m_T2) + { + case 0: + fp2x = Sine(m_A2, m_B2, m_C2, m_P2, helper.In.x); + fp2y = Sine(m_A2, m_B2, m_C2, m_P2, helper.In.y); + break; + case 1: + fp2x = Tri(m_A2, m_B2, m_C2, m_P2, helper.In.x); + fp2y = Tri(m_A2, m_B2, m_C2, m_P2, helper.In.y); + break; + case 2: + fp2x = Squ(m_A2, m_B2, m_C2, m_P2, helper.In.x); + fp2y = Squ(m_A2, m_B2, m_C2, m_P2, helper.In.y); + break; + default: + fp2x = Sine(m_A2, m_B2, m_C2, m_P2, helper.In.x); + fp2y = Sine(m_A2, m_B2, m_C2, m_P2, helper.In.y); + break; + } + + helper.Out.x = m_Weight * (fp1x + fp2x); + helper.Out.y = m_Weight * (fp1y + fp2y); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string a1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string b1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string p1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string t1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string a2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string b2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string p2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string t2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t fp1x;\n" + << "\t\treal_t fp1y;\n" + << "\t\treal_t fp2x;\n" + << "\t\treal_t fp2y;\n" + << "\n" + << "\t\tswitch ((int)" << t1 << ")\n" + << "\t\t{\n" + << "\t\t case 0:\n" + << "\t\t fp1x = Interference2Sine(" << a1 << ", " << b1 << ", " << c1 << ", " << p1 << ", vIn.x);\n" + << "\t\t fp1y = Interference2Sine(" << a1 << ", " << b1 << ", " << c1 << ", " << p1 << ", vIn.y);\n" + << "\t\t break;\n" + << "\t\t case 1:\n" + << "\t\t fp1x = Interference2Tri(" << a1 << ", " << b1 << ", " << c1 << ", " << p1 << ", vIn.x);\n" + << "\t\t fp1y = Interference2Tri(" << a1 << ", " << b1 << ", " << c1 << ", " << p1 << ", vIn.y);\n" + << "\t\t break;\n" + << "\t\t case 2:\n" + << "\t\t fp1x = Interference2Squ(" << a1 << ", " << b1 << ", " << c1 << ", " << p1 << ", vIn.x);\n" + << "\t\t fp1y = Interference2Squ(" << a1 << ", " << b1 << ", " << c1 << ", " << p1 << ", vIn.y);\n" + << "\t\t break;\n" + << "\t\t default:\n" + << "\t\t fp1x = Interference2Sine(" << a1 << ", " << b1 << ", " << c1 << ", " << p1 << ", vIn.x);\n" + << "\t\t fp1y = Interference2Sine(" << a1 << ", " << b1 << ", " << c1 << ", " << p1 << ", vIn.y);\n" + << "\t\t break;\n" + << "\t\t}\n" + << "\n" + << "\t\tswitch ((int)" << t2 << ")\n" + << "\t\t{\n" + << "\t\t case 0:\n" + << "\t\t fp2x = Interference2Sine(" << a2 << ", " << b2 << ", " << c2 << ", " << p2 << ", vIn.x);\n" + << "\t\t fp2y = Interference2Sine(" << a2 << ", " << b2 << ", " << c2 << ", " << p2 << ", vIn.y);\n" + << "\t\t break;\n" + << "\t\t case 1:\n" + << "\t\t fp2x = Interference2Tri(" << a2 << ", " << b2 << ", " << c2 << ", " << p2 << ", vIn.x);\n" + << "\t\t fp2y = Interference2Tri(" << a2 << ", " << b2 << ", " << c2 << ", " << p2 << ", vIn.y);\n" + << "\t\t break;\n" + << "\t\t case 2:\n" + << "\t\t fp2x = Interference2Squ(" << a2 << ", " << b2 << ", " << c2 << ", " << p2 << ", vIn.x);\n" + << "\t\t fp2y = Interference2Squ(" << a2 << ", " << b2 << ", " << c2 << ", " << p2 << ", vIn.y);\n" + << "\t\t break;\n" + << "\t\t default:\n" + << "\t\t fp2x = Interference2Sine(" << a2 << ", " << b2 << ", " << c2 << ", " << p2 << ", vIn.x);\n" + << "\t\t fp2y = Interference2Sine(" << a2 << ", " << b2 << ", " << c2 << ", " << p2 << ", vIn.y);\n" + << "\t\t break;\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (fp1x + fp2x);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (fp1y + fp2y);\n" + << "\t}\n"; + + return ss.str(); + } + + virtual string OpenCLFuncsString() + { + return + "real_t Interference2Sine(real_t a, real_t b, real_t c, real_t p, real_t x)\n" + "{\n" + " return a * pow(ClampGte(sin(b * x + c), EPS6), p);\n" + "}\n" + "\n" + "real_t Interference2Tri(real_t a, real_t b, real_t c, real_t p, real_t x)\n" + "{\n" + " return a * 2 * pow(ClampGte(asin(cos(b * x + c - M_PI_2)), EPS6) * M_1_PI, p);\n" + "}\n" + "\n" + "real_t Interference2Squ(real_t a, real_t b, real_t c, real_t p, real_t x)\n" + "{\n" + " return a * pow(sin(b * x + c) < 0 ? EPS6 : 1, p);\n" + "}\n" + "\n"; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_A1, prefix + "interference2_a1", 1));//Original used a prefix of intrfr2_, which is incompatible with Ember's design. + m_Params.push_back(ParamWithName(&m_B1, prefix + "interference2_b1", 1)); + m_Params.push_back(ParamWithName(&m_C1, prefix + "interference2_c1")); + m_Params.push_back(ParamWithName(&m_P1, prefix + "interference2_p1", 1)); + m_Params.push_back(ParamWithName(&m_T1, prefix + "interference2_t1", 0, INTEGER, 0, 2)); + m_Params.push_back(ParamWithName(&m_A2, prefix + "interference2_a2", 1)); + m_Params.push_back(ParamWithName(&m_B2, prefix + "interference2_b2", 1)); + m_Params.push_back(ParamWithName(&m_C2, prefix + "interference2_c2")); + m_Params.push_back(ParamWithName(&m_P2, prefix + "interference2_p2", 1)); + m_Params.push_back(ParamWithName(&m_T2, prefix + "interference2_t2", 0, INTEGER, 0, 2)); + } + +private: + inline static T Sine(T a, T b, T c, T p, T x) + { + return a * pow(ClampGte(sin(b * x + c), EPS6), p);//Original did not clamp. + } + + inline static T Tri(T a, T b, T c, T p, T x) + { + return a * 2 * pow(ClampGte(asin(cos(b * x + c - T(M_PI_2))), EPS6) * T(M_1_PI), p);//Original did not clamp. + } + + inline static T Squ(T a, T b, T c, T p, T x) + { + return a * pow(sin(b * x + c) < 0 ? EPS6 : T(1), p);//Original passed -1 to pow if sin() was < 0. Doing so will return NaN, so EPS6 is passed instead. + } + + T m_A1; + T m_B1; + T m_C1; + T m_P1; + T m_T1; + T m_A2; + T m_B2; + T m_C2; + T m_P2; + T m_T2; +}; + +/// +/// sinq. +/// +template +class EMBER_API SinqVariation : public Variation +{ +public: + SinqVariation(T weight = 1.0) : Variation("sinq", VAR_SINQ, weight) { } + + VARCOPY(SinqVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T absV = Hypot(helper.In.y, helper.In.z); + T s = sin(helper.In.x); + T c = cos(helper.In.x); + T sh = sinh(absV); + T ch = cosh(absV); + T d = m_Weight * c * sh / absV; + + helper.Out.x = m_Weight * s * ch; + helper.Out.y = d * helper.In.y; + helper.Out.z = d * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t absV = Hypot(vIn.y, vIn.z);\n" + << "\t\treal_t s = sin(vIn.x);\n" + << "\t\treal_t c = cos(vIn.x);\n" + << "\t\treal_t sh = sinh(absV);\n" + << "\t\treal_t ch = cosh(absV);\n" + << "\t\treal_t d = xform->m_VariationWeights[" << varIndex << "] * c * sh / absV;\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * s * ch;\n" + << "\t\tvOut.y = d * vIn.y;\n" + << "\t\tvOut.z = d * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// sinhq. +/// +template +class EMBER_API SinhqVariation : public Variation +{ +public: + SinhqVariation(T weight = 1.0) : Variation("sinhq", VAR_SINHQ, weight) { } + + VARCOPY(SinhqVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T absV = Hypot(helper.In.y, helper.In.z); + T s = sin(absV); + T c = cos(absV); + T sh = sinh(helper.In.x); + T ch = cosh(helper.In.x); + T d = m_Weight * c * sh / absV; + + helper.Out.x = m_Weight * sh * c; + helper.Out.y = d * helper.In.y; + helper.Out.z = d * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t absV = Hypot(vIn.y, vIn.z);\n" + << "\t\treal_t s = sin(absV);\n" + << "\t\treal_t c = cos(absV);\n" + << "\t\treal_t sh = sinh(vIn.x);\n" + << "\t\treal_t ch = cosh(vIn.x);\n" + << "\t\treal_t d = xform->m_VariationWeights[" << varIndex << "] * c * sh / absV;\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * sh * c;\n" + << "\t\tvOut.y = d * vIn.y;\n" + << "\t\tvOut.z = d * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// secq. +/// +template +class EMBER_API SecqVariation : public Variation +{ +public: + SecqVariation(T weight = 1.0) : Variation("secq", VAR_SECQ, weight, true) { } + + VARCOPY(SecqVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T absV = Hypot(helper.In.y, helper.In.z); + T ni = m_Weight / (helper.m_PrecalcSumSquares + SQR(helper.In.z)); + T s = sin(-helper.In.x); + T c = cos(-helper.In.x); + T sh = sinh(absV); + T ch = cosh(absV); + T d = ni * s * sh / absV; + + helper.Out.x = c * ch * ni; + helper.Out.y = -(d * helper.In.y); + helper.Out.z = -(d * helper.In.z); + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t absV = Hypot(vIn.y, vIn.z);\n" + << "\t\treal_t ni = xform->m_VariationWeights[" << varIndex << "] / (precalcSumSquares + SQR(vIn.z));\n" + << "\t\treal_t s = sin(-vIn.x);\n" + << "\t\treal_t c = cos(-vIn.x);\n" + << "\t\treal_t sh = sinh(absV);\n" + << "\t\treal_t ch = cosh(absV);\n" + << "\t\treal_t d = ni * s * sh / absV;\n" + << "\n" + << "\t\tvOut.x = c * ch * ni;\n" + << "\t\tvOut.y = -(d * vIn.y);\n" + << "\t\tvOut.z = -(d * vIn.z);\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// sechq. +/// +template +class EMBER_API SechqVariation : public Variation +{ +public: + SechqVariation(T weight = 1.0) : Variation("sechq", VAR_SECHQ, weight, true) { } + + VARCOPY(SechqVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T absV = Hypot(helper.In.y, helper.In.z); + T ni = m_Weight / (helper.m_PrecalcSumSquares + SQR(helper.In.z)); + T s = sin(absV); + T c = cos(absV); + T sh = sinh(helper.In.x); + T ch = cosh(helper.In.x); + T d = ni * sh * s / absV; + + helper.Out.x = ch * c * ni; + helper.Out.y = -(d * helper.In.y); + helper.Out.z = -(d * helper.In.z); + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t absV = Hypot(vIn.y, vIn.z);\n" + << "\t\treal_t ni = xform->m_VariationWeights[" << varIndex << "] / (precalcSumSquares + SQR(vIn.z));\n" + << "\t\treal_t s = sin(absV);\n" + << "\t\treal_t c = cos(absV);\n" + << "\t\treal_t sh = sinh(vIn.x);\n" + << "\t\treal_t ch = cosh(vIn.x);\n" + << "\t\treal_t d = ni * sh * s / absV;\n" + << "\n" + << "\t\tvOut.x = ch * c * ni;\n" + << "\t\tvOut.y = -(d * vIn.y);\n" + << "\t\tvOut.z = -(d * vIn.z);\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// tanq. +/// +template +class EMBER_API TanqVariation : public Variation +{ +public: + TanqVariation(T weight = 1.0) : Variation("tanq", VAR_TANQ, weight) { } + + VARCOPY(TanqVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T sysz = SQR(helper.In.y) + SQR(helper.In.z); + T absV = sqrt(sysz); + T ni = m_Weight / (SQR(helper.In.x) + sysz); + T s = sin(helper.In.x); + T c = cos(helper.In.x); + T sh = sinh(absV); + T ch = cosh(absV); + T d = c * sh / absV; + T b = -s * sh / absV; + T stcv = s * ch; + T nstcv = -stcv; + T ctcv = c * ch; + + helper.Out.x = (stcv * ctcv + d * b * sysz) * ni; + helper.Out.y = (nstcv * b * helper.In.y + d * helper.In.y * ctcv) * ni; + helper.Out.z = (nstcv * b * helper.In.z + d * helper.In.z * ctcv) * ni; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t sysz = SQR(vIn.y) + SQR(vIn.z);\n" + << "\t\treal_t absV = sqrt(sysz);\n" + << "\t\treal_t ni = xform->m_VariationWeights[" << varIndex << "] / (SQR(vIn.x) + sysz);\n" + << "\t\treal_t s = sin(vIn.x);\n" + << "\t\treal_t c = cos(vIn.x);\n" + << "\t\treal_t sh = sinh(absV);\n" + << "\t\treal_t ch = cosh(absV);\n" + << "\t\treal_t d = c * sh / absV;\n" + << "\t\treal_t b = -s * sh / absV;\n" + << "\t\treal_t stcv = s * ch;\n" + << "\t\treal_t nstcv = -stcv;\n" + << "\t\treal_t ctcv = c * ch;\n" + << "\n" + << "\t\tvOut.x = (stcv * ctcv + d * b * sysz) * ni;\n" + << "\t\tvOut.y = (nstcv * b * vIn.y + d * vIn.y * ctcv) * ni;\n" + << "\t\tvOut.z = (nstcv * b * vIn.z + d * vIn.z * ctcv) * ni;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// tanhq. +/// +template +class EMBER_API TanhqVariation : public Variation +{ +public: + TanhqVariation(T weight = 1.0) : Variation("tanhq", VAR_TANHQ, weight) { } + + VARCOPY(TanhqVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T sysz = SQR(helper.In.y) + SQR(helper.In.z); + T absV = sqrt(sysz); + T ni = m_Weight / (SQR(helper.In.x) + sysz); + T s = sin(absV); + T c = cos(absV); + T sh = sinh(helper.In.x); + T ch = cosh(helper.In.x); + T d = ch * s / absV; + T b = sh * s / absV; + T stcv = sh * c; + T nstcv = -stcv; + T ctcv = c * ch; + + helper.Out.x = (stcv * ctcv + d * b * sysz) * ni; + helper.Out.y = (nstcv * b * helper.In.y + d * helper.In.y * ctcv) * ni; + helper.Out.z = (nstcv * b * helper.In.z + d * helper.In.z * ctcv) * ni; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t sysz = SQR(vIn.y) + SQR(vIn.z);\n" + << "\t\treal_t absV = sqrt(sysz);\n" + << "\t\treal_t ni = xform->m_VariationWeights[" << varIndex << "] / (SQR(vIn.x) + sysz);\n" + << "\t\treal_t s = sin(absV);\n" + << "\t\treal_t c = cos(absV);\n" + << "\t\treal_t sh = sinh(vIn.x);\n" + << "\t\treal_t ch = cosh(vIn.x);\n" + << "\t\treal_t d = ch * s / absV;\n" + << "\t\treal_t b = sh * s / absV;\n" + << "\t\treal_t stcv = sh * c;\n" + << "\t\treal_t nstcv = -stcv;\n" + << "\t\treal_t ctcv = c * ch;\n" + << "\n" + << "\t\tvOut.x = (stcv * ctcv + d * b * sysz) * ni;\n" + << "\t\tvOut.y = (nstcv * b * vIn.y + d * vIn.y * ctcv) * ni;\n" + << "\t\tvOut.z = (nstcv * b * vIn.z + d * vIn.z * ctcv) * ni;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// cosq. +/// +template +class EMBER_API CosqVariation : public Variation +{ +public: + CosqVariation(T weight = 1.0) : Variation("cosq", VAR_COSQ, weight) { } + + VARCOPY(CosqVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T absV = Hypot(helper.In.y, helper.In.z); + T s = sin(helper.In.x); + T c = cos(helper.In.x); + T sh = sinh(absV); + T ch = cosh(absV); + T d = -m_Weight * s * sh / absV; + + helper.Out.x = m_Weight * c * ch; + helper.Out.y = d * helper.In.y; + helper.Out.z = d * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t absV = Hypot(vIn.y, vIn.z);\n" + << "\t\treal_t s = sin(vIn.x);\n" + << "\t\treal_t c = cos(vIn.x);\n" + << "\t\treal_t sh = sinh(absV);\n" + << "\t\treal_t ch = cosh(absV);\n" + << "\t\treal_t d = -xform->m_VariationWeights[" << varIndex << "] * s * sh / absV;\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * c * ch;\n" + << "\t\tvOut.y = d * vIn.y;\n" + << "\t\tvOut.z = d * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// coshq. +/// +template +class EMBER_API CoshqVariation : public Variation +{ +public: + CoshqVariation(T weight = 1.0) : Variation("coshq", VAR_COSHQ, weight) { } + + VARCOPY(CoshqVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T absV = Hypot(helper.In.y, helper.In.z); + T s = sin(absV); + T c = cos(absV); + T sh = sinh(helper.In.x); + T ch = cosh(helper.In.x); + T d = -m_Weight * sh * s / absV; + + helper.Out.x = m_Weight * c * ch; + helper.Out.y = d * helper.In.y; + helper.Out.z = d * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t absV = Hypot(vIn.y, vIn.z);\n" + << "\t\treal_t s = sin(absV);\n" + << "\t\treal_t c = cos(absV);\n" + << "\t\treal_t sh = sinh(vIn.x);\n" + << "\t\treal_t ch = cosh(vIn.x);\n" + << "\t\treal_t d = -xform->m_VariationWeights[" << varIndex << "] * sh * s / absV;\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * c * ch;\n" + << "\t\tvOut.y = d * vIn.y;\n" + << "\t\tvOut.z = d * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// cotq. +/// +template +class EMBER_API CotqVariation : public Variation +{ +public: + CotqVariation(T weight = 1.0) : Variation("cotq", VAR_COTQ, weight) { } + + VARCOPY(CotqVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T sysz = SQR(helper.In.y) + SQR(helper.In.z); + T absV = sqrt(sysz); + T ni = m_Weight / (SQR(helper.In.x) + sysz); + T s = sin(helper.In.x); + T c = cos(helper.In.x); + T sh = sinh(absV); + T ch = cosh(absV); + T d = c * sh / absV; + T b = -s * sh / absV; + T stcv = s * ch; + T nstcv = -stcv; + T ctcv = c * ch; + + helper.Out.x = (stcv * ctcv + d * b * sysz) * ni; + helper.Out.y = -(nstcv * b * helper.In.y + d * helper.In.y * ctcv) * ni; + helper.Out.z = -(nstcv * b * helper.In.z + d * helper.In.z * ctcv) * ni; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t sysz = SQR(vIn.y) + SQR(vIn.z);\n" + << "\t\treal_t absV = sqrt(sysz);\n" + << "\t\treal_t ni = xform->m_VariationWeights[" << varIndex << "] / (SQR(vIn.x) + sysz);\n" + << "\t\treal_t s = sin(vIn.x);\n" + << "\t\treal_t c = cos(vIn.x);\n" + << "\t\treal_t sh = sinh(absV);\n" + << "\t\treal_t ch = cosh(absV);\n" + << "\t\treal_t d = c * sh / absV;\n" + << "\t\treal_t b = -s * sh / absV;\n" + << "\t\treal_t stcv = s * ch;\n" + << "\t\treal_t nstcv = -stcv;\n" + << "\t\treal_t ctcv = c * ch;\n" + << "\n" + << "\t\tvOut.x = (stcv * ctcv + d * b * sysz) * ni;\n" + << "\t\tvOut.y = -(nstcv * b * vIn.y + d * vIn.y * ctcv) * ni;\n" + << "\t\tvOut.z = -(nstcv * b * vIn.z + d * vIn.z * ctcv) * ni;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// cothq. +/// +template +class EMBER_API CothqVariation : public Variation +{ +public: + CothqVariation(T weight = 1.0) : Variation("cothq", VAR_COTHQ, weight) { } + + VARCOPY(CothqVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T sysz = SQR(helper.In.y) + SQR(helper.In.z); + T absV = sqrt(sysz); + T ni = m_Weight / (SQR(helper.In.x) + sysz); + T s = sin(absV); + T c = cos(absV); + T sh = sinh(helper.In.x); + T ch = cosh(helper.In.x); + T d = ch * s / absV; + T b = sh * s / absV; + T stcv = sh * c; + T nstcv = -stcv; + T ctcv = ch * c; + + helper.Out.x = (stcv * ctcv + d * b * sysz) * ni; + helper.Out.y = -(nstcv * b * helper.In.y + d * helper.In.y * ctcv) * ni; + helper.Out.z = -(nstcv * b * helper.In.z + d * helper.In.z * ctcv) * ni; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t sysz = SQR(vIn.y) + SQR(vIn.z);\n" + << "\t\treal_t absV = sqrt(sysz);\n" + << "\t\treal_t ni = xform->m_VariationWeights[" << varIndex << "] / (SQR(vIn.x) + sysz);\n" + << "\t\treal_t s = sin(absV);\n" + << "\t\treal_t c = cos(absV);\n" + << "\t\treal_t sh = sinh(vIn.x);\n" + << "\t\treal_t ch = cosh(vIn.x);\n" + << "\t\treal_t d = ch * s / absV;\n" + << "\t\treal_t b = sh * s / absV;\n" + << "\t\treal_t stcv = sh * c;\n" + << "\t\treal_t nstcv = -stcv;\n" + << "\t\treal_t ctcv = ch * c;\n" + << "\n" + << "\t\tvOut.x = (stcv * ctcv + d * b * sysz) * ni;\n" + << "\t\tvOut.y = -(nstcv * b * vIn.y + d * vIn.y * ctcv) * ni;\n" + << "\t\tvOut.z = -(nstcv * b * vIn.z + d * vIn.z * ctcv) * ni;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// cscq. +/// +template +class EMBER_API CscqVariation : public Variation +{ +public: + CscqVariation(T weight = 1.0) : Variation("cscq", VAR_CSCQ, weight, true) { } + + VARCOPY(CscqVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T absV = Hypot(helper.In.y, helper.In.z); + T ni = m_Weight / (helper.m_PrecalcSumSquares + SQR(helper.In.z)); + T s = sin(helper.In.x); + T c = cos(helper.In.x); + T sh = sinh(absV); + T ch = cosh(absV); + T d = ni * c * sh / absV; + + helper.Out.x = s * ch * ni; + helper.Out.y = -(d * helper.In.y); + helper.Out.z = -(d * helper.In.z); + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t absV = Hypot(vIn.y, vIn.z);\n" + << "\t\treal_t ni = xform->m_VariationWeights[" << varIndex << "] / (precalcSumSquares + SQR(vIn.z));\n" + << "\t\treal_t s = sin(vIn.x);\n" + << "\t\treal_t c = cos(vIn.x);\n" + << "\t\treal_t sh = sinh(absV);\n" + << "\t\treal_t ch = cosh(absV);\n" + << "\t\treal_t d = ni * c * sh / absV;\n" + << "\n" + << "\t\tvOut.x = s * ch * ni;\n" + << "\t\tvOut.y = -(d * vIn.y);\n" + << "\t\tvOut.z = -(d * vIn.z);\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// cschq. +/// +template +class EMBER_API CschqVariation : public Variation +{ +public: + CschqVariation(T weight = 1.0) : Variation("cschq", VAR_CSCHQ, weight, true) { } + + VARCOPY(CschqVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T absV = Hypot(helper.In.y, helper.In.z); + T ni = m_Weight / (helper.m_PrecalcSumSquares + SQR(helper.In.z)); + T s = sin(absV); + T c = cos(absV); + T sh = sinh(helper.In.x); + T ch = cosh(helper.In.x); + T d = ni * ch * s / absV; + + helper.Out.x = sh * c * ni; + helper.Out.y = -(d * helper.In.y); + helper.Out.z = -(d * helper.In.z); + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t absV = Hypot(vIn.y, vIn.z);\n" + << "\t\treal_t ni = xform->m_VariationWeights[" << varIndex << "] / (precalcSumSquares + SQR(vIn.z));\n" + << "\t\treal_t s = sin(absV);\n" + << "\t\treal_t c = cos(absV);\n" + << "\t\treal_t sh = sinh(vIn.x);\n" + << "\t\treal_t ch = cosh(vIn.x);\n" + << "\t\treal_t d = ni * ch * s / absV;\n" + << "\n" + << "\t\tvOut.x = sh * c * ni;\n" + << "\t\tvOut.y = -(d * vIn.y);\n" + << "\t\tvOut.z = -(d * vIn.z);\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// estiq. +/// +template +class EMBER_API EstiqVariation : public Variation +{ +public: + EstiqVariation(T weight = 1.0) : Variation("estiq", VAR_ESTIQ, weight) { } + + VARCOPY(EstiqVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T absV = Hypot(helper.In.y, helper.In.z); + T e = exp(helper.In.x); + T s = sin(absV); + T c = cos(absV); + T a = e * s / absV; + + helper.Out.x = m_Weight * e * c; + helper.Out.y = m_Weight * a * helper.In.y; + helper.Out.z = m_Weight * a * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t absV = Hypot(vIn.y, vIn.z);\n" + << "\t\treal_t e = exp(vIn.x);\n" + << "\t\treal_t s = sin(absV);\n" + << "\t\treal_t c = cos(absV);\n" + << "\t\treal_t a = e * s / absV;\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * e * c;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * a * vIn.y;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * a * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// loq. +/// +template +class EMBER_API LoqVariation : public ParametricVariation +{ +public: + LoqVariation(T weight = 1.0) : ParametricVariation("loq", VAR_LOQ, weight) + { + Init(); + } + + PARVARCOPY(LoqVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T absV = Hypot(helper.In.y, helper.In.z); + T c = m_Weight * atan2(absV, helper.In.x) / absV; + + helper.Out.x = log(SQR(helper.In.x) + SQR(absV)) * m_Denom; + helper.Out.y = c * helper.In.y; + helper.Out.z = c * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string base = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string denom = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t absV = Hypot(vIn.y, vIn.z);\n" + << "\t\treal_t c = xform->m_VariationWeights[" << varIndex << "] * atan2(absV, vIn.x) / absV;\n" + << "\n" + << "\t\tvOut.x = log(SQR(vIn.x) + SQR(absV)) * " << denom << ";\n" + << "\t\tvOut.y = c * vIn.y;\n" + << "\t\tvOut.z = c * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Denom = T(0.5) / log(m_Base); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Base, prefix + "loq_base", T(M_E), REAL, EPS6, TMAX)); + m_Params.push_back(ParamWithName(true, &m_Denom, prefix + "loq_denom"));//Precalc. + } + +private: + T m_Base; + T m_Denom;//Precalc. +}; + +/// +/// curvature. +/// +template +class EMBER_API CurvatureVariation : public Variation +{ +public: + CurvatureVariation(T weight = 1.0) : Variation("curvature", VAR_CURVATURE, weight, true, true, false, false, true) { } + + VARCOPY(CurvatureVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + helper.Out.x = m_Weight / helper.m_PrecalcSqrtSumSquares; + helper.Out.y = helper.m_PrecalcAtanyx; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] / precalcSqrtSumSquares;\n" + << "\t\tvOut.y = precalcAtanyx;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// q_ode. +/// +template +class EMBER_API QodeVariation : public ParametricVariation +{ +public: + QodeVariation(T weight = 1.0) : ParametricVariation("q_ode", VAR_Q_ODE, weight) + { + Init(); + } + + PARVARCOPY(QodeVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T sqx = SQR(helper.In.x); + T sqy = SQR(helper.In.y); + T xy = helper.In.x * helper.In.y; + + helper.Out.x = (m_Q01 + m_Weight * m_Q02 * helper.In.x + m_Q03 * sqx) + + (m_Q04 * xy + m_Q05 * helper.In.y + m_Q06 * sqy); + helper.Out.y = (m_Q07 + m_Q08 * helper.In.x + m_Q09 * sqx) + + (m_Q10 * xy + m_Weight * m_Q11 * helper.In.y + m_Q12 * sqy); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string q01 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string q02 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string q03 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string q04 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string q05 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string q06 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string q07 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string q08 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string q09 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string q10 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string q11 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string q12 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t sqx = SQR(vIn.x);\n" + << "\t\treal_t sqy = SQR(vIn.y);\n" + << "\t\treal_t xy = vIn.x * vIn.y;\n" + << "\n" + << "\t\tvOut.x = (" << q01 << " + xform->m_VariationWeights[" << varIndex << "] * " << q02 << " * vIn.x + " << q03 << " * sqx) + \n" + << "\t\t (" << q04 << " * xy + " << q05 << " * vIn.y + " << q06 << " * sqy);\n" + << "\t\tvOut.y = (" << q07 << " + " << q08 << " * vIn.x + " << q09 << " * sqx) + \n" + << "\t\t (" << q10 << " * xy + xform->m_VariationWeights[" << varIndex << "] * " << q11 << " * vIn.y + " << q12 << " * sqy);\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Q01, prefix + "q_ode01", 1)); + m_Params.push_back(ParamWithName(&m_Q02, prefix + "q_ode02", -1)); + m_Params.push_back(ParamWithName(&m_Q03, prefix + "q_ode03")); + m_Params.push_back(ParamWithName(&m_Q04, prefix + "q_ode04")); + m_Params.push_back(ParamWithName(&m_Q05, prefix + "q_ode05")); + m_Params.push_back(ParamWithName(&m_Q06, prefix + "q_ode06")); + m_Params.push_back(ParamWithName(&m_Q07, prefix + "q_ode07", 1)); + m_Params.push_back(ParamWithName(&m_Q08, prefix + "q_ode08")); + m_Params.push_back(ParamWithName(&m_Q09, prefix + "q_ode09")); + m_Params.push_back(ParamWithName(&m_Q10, prefix + "q_ode10")); + m_Params.push_back(ParamWithName(&m_Q11, prefix + "q_ode11")); + m_Params.push_back(ParamWithName(&m_Q12, prefix + "q_ode12")); + } + +private: + T m_Q01; + T m_Q02; + T m_Q03; + T m_Q04; + T m_Q05; + T m_Q06; + T m_Q07; + T m_Q08; + T m_Q09; + T m_Q10; + T m_Q11; + T m_Q12; +}; + +/// +/// blur_heart. +/// +template +class EMBER_API BlurHeartVariation : public ParametricVariation +{ +public: + BlurHeartVariation(T weight = 1.0) : ParametricVariation("blur_heart", VAR_BLUR_HEART, weight) + { + Init(); + } + + PARVARCOPY(BlurHeartVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T xx = (rand.Frand01() - T(0.5)) * 2; + T yy = (rand.Frand01() - T(0.5)) * 2; + T k = yy < 0 ? T(-1) : T(1); + T yymax = ((m_A * pow(fabs(xx), m_P) + k * m_B * sqrt(fabs(1 - SQR(xx)))) - m_A); + + //The function must be in a range 0-1 to work properly. + yymax /= (fabs(m_A) + fabs(m_B)); + + //Quick and dirty way to force y to be in range without altering the density. + if (k > 0) + { + if (yy > yymax) + yy = yymax; + } + else + { + if (yy < yymax) + yy = yymax; + } + + helper.Out.x = xx * m_Weight; + helper.Out.y = yy * m_Weight; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string p = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string a = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string b = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t xx = (MwcNext01(mwc) - 0.5) * 2;\n" + << "\t\treal_t yy = (MwcNext01(mwc) - 0.5) * 2;\n" + << "\t\treal_t k = yy < 0 ? -1 : 1;\n" + << "\t\treal_t yymax = ((" << a << " * pow(fabs(xx), " << p << ") + k * " << b << " * sqrt(fabs(1 - SQR(xx)))) - " << a << ");\n" + << "\n" + << "\t\tyymax /= (fabs(" << a << ") + fabs(" << b << "));\n" + << "\n" + << "\t\tif (k > 0)\n" + << "\t\t{\n" + << "\t\t if (yy > yymax)\n" + << "\t\t yy = yymax;\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t if (yy < yymax)\n" + << "\t\t yy = yymax;\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.x = xx * xform->m_VariationWeights[" << varIndex << "];\n" + << "\t\tvOut.y = yy * xform->m_VariationWeights[" << varIndex << "];\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_P, prefix + "blur_heart_p", T(0.5))); + m_Params.push_back(ParamWithName(&m_A, prefix + "blur_heart_a", T(-0.6))); + m_Params.push_back(ParamWithName(&m_B, prefix + "blur_heart_b", T(0.7))); + } + +private: + T m_P; + T m_A; + T m_B; +}; + +/// +/// Truchet. +/// +template +class EMBER_API TruchetVariation : public ParametricVariation +{ +public: + TruchetVariation(T weight = 1.0) : ParametricVariation("Truchet", VAR_TRUCHET, weight) + { + Init(); + } + + PARVARCOPY(TruchetVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + int extended = (int)m_Extended; + T seed = m_AbsSeed; + T r = -m_Rotation; + T r0 = 0; + T r1 = 0; + T tileType = 0; + T randInt = 0; + T modBase = 65535; + T multiplier = 32747; + T offset = 12345; + T niter = 0; + T x = helper.In.x * m_Scale; + T y = helper.In.y * m_Scale; + int intx = (int)Round(x); + int inty = (int)Round(y); + int randiter; + + r = x - intx; + + if (r < 0) + x = 1 + r; + else + x = r; + + r = y - inty; + + if (r < 0) + y = 1 + r; + else + y = r; + + //Calculate the tile type. + if (seed == 0) + tileType = 0; + else if (seed == 1) + tileType = 1; + else + { + if (extended == 0) + { + T xrand = Round(helper.In.x); + T yrand = Round(helper.In.y); + + xrand = xrand * m_Seed2; + yrand = yrand * m_Seed2; + niter = xrand + yrand + xrand*yrand; + randInt = (niter + seed) * m_Seed2 / 2; + randInt = fmod((randInt * multiplier + offset), modBase); + } + else + { + int xrand = (int)Round(helper.In.x); + int yrand = (int)Round(helper.In.y); + + seed = (T)Floor(seed); + niter = (T)abs(xrand + yrand + xrand * yrand); + randInt = seed + niter; + randiter = 0; + + while (randiter < niter) + { + randiter += 1; + randInt = fmod((randInt * multiplier + offset), modBase); + } + } + + tileType = fmod(randInt, T(2)); + } + + //Drawing the points. + if (extended == 0)//Fast drawmode + { + if (tileType < 1) + { + r0 = pow((pow(fabs(x ), m_Exponent) + pow(fabs(y ), m_Exponent)), m_OneOverEx); + r1 = pow((pow(fabs(x - 1), m_Exponent) + pow(fabs(y - 1), m_Exponent)), m_OneOverEx); + } + else + { + r0 = pow((pow(fabs(x - 1), m_Exponent) + pow(fabs(y ), m_Exponent)), m_OneOverEx); + r1 = pow((pow(fabs(x ), m_Exponent) + pow(fabs(y - 1), m_Exponent)), m_OneOverEx); + } + } + else//Slow drawmode + { + if (tileType == 1) + { + r0 = pow((pow(fabs(x ), m_Exponent) + pow(fabs(y ), m_Exponent)), m_OneOverEx); + r1 = pow((pow(fabs(x - 1), m_Exponent) + pow(fabs(y - 1), m_Exponent)), m_OneOverEx); + } + else + { + r0 = pow((pow(fabs(x - 1), m_Exponent) + pow(fabs(y ), m_Exponent)), m_OneOverEx); + r1 = pow((pow(fabs(x ), m_Exponent) + pow(fabs(y - 1), m_Exponent)), m_OneOverEx); + } + } + + helper.Out.x = 0;//Needed because of possible sum below. + helper.Out.y = 0; + r = fabs(r0 - T(0.5)) * m_OneOverRmax; + + if (r < 1) + { + helper.Out.x = m_Size * (x + Floor(helper.In.x)); + helper.Out.y = m_Size * (y + Floor(helper.In.y)); + } + + r = fabs(r1 - T(0.5)) * m_OneOverRmax; + + if (r < 1) + { + helper.Out.x += m_Size * (x + Floor(helper.In.x));//The += is intended here. + helper.Out.y += m_Size * (y + Floor(helper.In.y)); + } + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string extended = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string exponent = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string arcWidth = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string rotation = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string size = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string seed = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string oneOverEx = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string absSeed = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string seed2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string oneOverRmax = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string scale = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tint extended = (int)" << extended << ";\n" + << "\t\treal_t seed = " << absSeed << ";\n" + << "\t\treal_t r = -" << rotation << ";\n" + << "\t\treal_t r0 = 0;\n" + << "\t\treal_t r1 = 0;\n" + << "\t\treal_t tileType = 0;\n" + << "\t\treal_t randInt = 0;\n" + << "\t\treal_t modBase = 65535;\n" + << "\t\treal_t multiplier = 32747;\n" + << "\t\treal_t offset = 12345;\n" + << "\t\treal_t niter = 0;\n" + << "\t\treal_t x = vIn.x * " << scale << ";\n" + << "\t\treal_t y = vIn.y * " << scale << ";\n" + << "\t\tint intx = (int)Round(x);\n" + << "\t\tint inty = (int)Round(y);\n" + << "\t\tint randiter;\n" + << "\n" + << "\t\tr = x - intx;\n" + << "\n" + << "\t\tif (r < 0)\n" + << "\t\t x = 1 + r;\n" + << "\t\telse\n" + << "\t\t x = r;\n" + << "\n" + << "\t\tr = y - inty;\n" + << "\n" + << "\t\tif (r < 0)\n" + << "\t\t y = 1 + r;\n" + << "\t\telse\n" + << "\t\t y = r;\n" + << "\n" + << "\t\tif (seed == 0)\n" + << "\t\t tileType = 0;\n" + << "\t\telse if (seed == 1)\n" + << "\t\t tileType = 1;\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t if (extended == 0)\n" + << "\t\t {\n" + << "\t\t real_t xrand = Round(vIn.x);\n" + << "\t\t real_t yrand = Round(vIn.y);\n" + << "\n" + << "\t\t xrand = xrand * " << seed2 << ";\n" + << "\t\t yrand = yrand * " << seed2 << ";\n" + << "\t\t niter = xrand + yrand + xrand * yrand;\n" + << "\t\t randInt = (niter + seed) * " << seed2 << " / 2;\n" + << "\t\t randInt = fmod((randInt * multiplier + offset), modBase);\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t int xrand = (int)Round(vIn.x);\n" + << "\t\t int yrand = (int)Round(vIn.y);\n" + << "\n" + << "\t\t seed = floor(seed);\n" + << "\t\t niter = (real_t)abs(xrand + yrand + xrand * yrand);\n" + << "\t\t randInt = seed + niter;\n" + << "\t\t randiter = 0;\n" + << "\n" + << "\t\t while (randiter < niter)\n" + << "\t\t {\n" + << "\t\t randiter += 1;\n" + << "\t\t randInt = fmod((randInt * multiplier + offset), modBase);\n" + << "\t\t }\n" + << "\t\t }\n" + << "\n" + << "\t\t tileType = fmod(randInt, 2);\n" + << "\t\t}\n" + << "\n" + << "\t\tif (extended == 0)\n" + << "\t\t{\n" + << "\t\t if (tileType < 1)\n" + << "\t\t {\n" + << "\t\t r0 = pow((pow(fabs(x ), " << exponent << ") + pow(fabs(y ), " << exponent << ")), " << oneOverEx << ");\n" + << "\t\t r1 = pow((pow(fabs(x - 1), " << exponent << ") + pow(fabs(y - 1), " << exponent << ")), " << oneOverEx << ");\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t r0 = pow((pow(fabs(x - 1), " << exponent << ") + pow(fabs(y ), " << exponent << ")), " << oneOverEx << ");\n" + << "\t\t r1 = pow((pow(fabs(x ), " << exponent << ") + pow(fabs(y - 1), " << exponent << ")), " << oneOverEx << ");\n" + << "\t\t }\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t if (tileType == 1)\n" + << "\t\t {\n" + << "\t\t r0 = pow((pow(fabs(x ), " << exponent << ") + pow(fabs(y ), " << exponent << ")), " << oneOverEx << ");\n" + << "\t\t r1 = pow((pow(fabs(x - 1), " << exponent << ") + pow(fabs(y - 1), " << exponent << ")), " << oneOverEx << ");\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t r0 = pow((pow(fabs(x - 1), " << exponent << ") + pow(fabs(y ), " << exponent << ")), " << oneOverEx << ");\n" + << "\t\t r1 = pow((pow(fabs(x ), " << exponent << ") + pow(fabs(y - 1), " << exponent << ")), " << oneOverEx << ");\n" + << "\t\t }\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.x = 0;\n" + << "\t\tvOut.y = 0;\n" + << "\t\tr = fabs(r0 - 0.5) * " << oneOverRmax << ";\n" + << "\n" + << "\t\tif (r < 1)\n" + << "\t\t{\n" + << "\t\t vOut.x = " << size << " * (x + floor(vIn.x));\n" + << "\t\t vOut.y = " << size << " * (y + floor(vIn.y));\n" + << "\t\t}\n" + << "\n" + << "\t\tr = fabs(r1 - 0.5) * " << oneOverRmax << ";\n" + << "\n" + << "\t\tif (r < 1)\n" + << "\t\t{\n" + << "\t\t vOut.x += " << size << " * (x + floor(vIn.x));\n" + << "\t\t vOut.y += " << size << " * (y + floor(vIn.y));\n" + << "\t\t}\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_OneOverEx = 1 / m_Exponent; + m_AbsSeed = fabs(m_Seed); + m_Seed2 = sqrt(m_AbsSeed + (m_AbsSeed / 2) + EPS6) / ((m_AbsSeed * T(0.5)) + EPS6) * T(0.25); + m_OneOverRmax = 1 / (T(0.5) * (pow(T(2), 1 / m_Exponent) - 1) * m_ArcWidth); + m_Scale = (cos(-m_Rotation) - sin(-m_Rotation)) / m_Weight; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Extended, prefix + "Truchet_extended", 0, INTEGER, 0, 1)); + m_Params.push_back(ParamWithName(&m_Exponent, prefix + "Truchet_exponent", 2, REAL_CYCLIC, T(0.001), 2)); + m_Params.push_back(ParamWithName(&m_ArcWidth, prefix + "Truchet_arc_width", T(0.5), REAL_CYCLIC, T(0.001), 1)); + m_Params.push_back(ParamWithName(&m_Rotation, prefix + "Truchet_rotation")); + m_Params.push_back(ParamWithName(&m_Size, prefix + "Truchet_size", 1, REAL_CYCLIC, T(0.001), 10)); + m_Params.push_back(ParamWithName(&m_Seed, prefix + "Truchet_seed", 50)); + m_Params.push_back(ParamWithName(true, &m_OneOverEx, prefix + "Truchet_one_over_ex"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_AbsSeed, prefix + "Truchet_abs_seed")); + m_Params.push_back(ParamWithName(true, &m_Seed2, prefix + "Truchet_seed2")); + m_Params.push_back(ParamWithName(true, &m_OneOverRmax, prefix + "Truchet_one_over_rmax")); + m_Params.push_back(ParamWithName(true, &m_Scale, prefix + "Truchet_scale")); + } + +private: + T m_Extended; + T m_Exponent; + T m_ArcWidth; + T m_Rotation; + T m_Size; + T m_Seed; + T m_OneOverEx;//Precalc. + T m_AbsSeed; + T m_Seed2; + T m_OneOverRmax; + T m_Scale; +}; + +/// +/// gdoffs. +/// +template +class EMBER_API GdoffsVariation : public ParametricVariation +{ +public: + GdoffsVariation(T weight = 1.0) : ParametricVariation("gdoffs", VAR_GDOFFS, weight) + { + Init(); + } + + PARVARCOPY(GdoffsVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T oscX = GdoffsFosc(m_Dx, 1); + T oscY = GdoffsFosc(m_Dy, 1); + T inX = helper.In.x + m_Cx; + T inY = helper.In.y + m_Cy; + T outX; + T outY; + + if (m_Square != 0) + { + outX = GdoffsFlip(GdoffsFlip(inX, GdoffsFosc(inX, 4), oscX), GdoffsFosc(GdoffsFclp(m_B * inX), 4), oscX); + outY = GdoffsFlip(GdoffsFlip(inY, GdoffsFosc(inY, 4), oscX), GdoffsFosc(GdoffsFclp(m_B * inY), 4), oscX); + } + else + { + outX = GdoffsFlip(GdoffsFlip(inX, GdoffsFosc(inX, 4), oscX), GdoffsFosc(GdoffsFclp(m_B * inX), 4), oscX); + outY = GdoffsFlip(GdoffsFlip(inY, GdoffsFosc(inY, 4), oscY), GdoffsFosc(GdoffsFclp(m_B * inY), 4), oscY); + } + + helper.Out.x = m_Weight * outX; + helper.Out.y = m_Weight * outY; + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string deltaX = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string deltaY = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string areaX = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string areaY = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string centerX = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string centerY = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string gamma = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string square = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string dx = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ax = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cx = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string dy = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ay = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cy = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string b = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t oscX = GdoffsFosc(" << dx << ", 1);\n" + << "\t\treal_t oscY = GdoffsFosc(" << dy << ", 1);\n" + << "\t\treal_t inX = vIn.x + " << cx << ";\n" + << "\t\treal_t inY = vIn.y + " << cy << ";\n" + << "\t\treal_t outX;\n" + << "\t\treal_t outY;\n" + << "\n" + << "\t\tif (" << square << " != 0)\n" + << "\t\t{\n" + << "\t\t outX = GdoffsFlip(GdoffsFlip(inX, GdoffsFosc(inX, 4), oscX), GdoffsFosc(GdoffsFclp(" << b << " * inX), 4), oscX);\n" + << "\t\t outY = GdoffsFlip(GdoffsFlip(inY, GdoffsFosc(inY, 4), oscX), GdoffsFosc(GdoffsFclp(" << b << " * inY), 4), oscX);\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t outX = GdoffsFlip(GdoffsFlip(inX, GdoffsFosc(inX, 4), oscX), GdoffsFosc(GdoffsFclp(" << b << " * inX), 4), oscX);\n" + << "\t\t outY = GdoffsFlip(GdoffsFlip(inY, GdoffsFosc(inY, 4), oscY), GdoffsFosc(GdoffsFclp(" << b << " * inY), 4), oscY);\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * outX;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * outY;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual string OpenCLFuncsString() + { + return + "inline real_t GdoffsFcip(real_t a) { return (real_t)((a < 0) ? -((int)(fabs(a)) + 1) : 0) + ((a > 1) ? ((int)(a)) : 0); }\n" + "inline real_t GdoffsFclp(real_t a) { return ((a < 0) ? -(fmod(fabs(a), 1)) : fmod(fabs(a), 1)); }\n" + "inline real_t GdoffsFscl(real_t a) { return GdoffsFclp((a + 1) / 2); }\n" + "inline real_t GdoffsFosc(real_t p, real_t a) { return GdoffsFscl(-1 * cos(p * a * M_2PI)); }\n" + "inline real_t GdoffsFlip(real_t a, real_t b, real_t c) { return (c * (b - a) + a); }\n" + "\n"; + } + + virtual void Precalc() + { + const T agdod = T(0.1); + const T agdoa = 2; + const T agdoc = 1; + + m_Dx = m_DeltaX * agdod; + m_Dy = m_DeltaY * agdod; + m_Ax = ((fabs(m_AreaX) < 0.1) ? T(0.1) : fabs(m_AreaX)) * agdoa; + m_Ay = ((fabs(m_AreaY) < 0.1) ? T(0.1) : fabs(m_AreaY)) * agdoa; + m_Cx = m_CenterX * agdoc; + m_Cy = m_CenterY * agdoc; + m_B = m_Gamma * agdoa / (max(m_Ax, m_Ay)); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_DeltaX, prefix + "gdoffs_delta_x", 0, REAL, 0, 16)); + m_Params.push_back(ParamWithName(&m_DeltaY, prefix + "gdoffs_delta_y", 0, REAL, 0, 16)); + m_Params.push_back(ParamWithName(&m_AreaX, prefix + "gdoffs_area_x", 2)); + m_Params.push_back(ParamWithName(&m_AreaY, prefix + "gdoffs_area_y", 2)); + m_Params.push_back(ParamWithName(&m_CenterX, prefix + "gdoffs_center_x")); + m_Params.push_back(ParamWithName(&m_CenterY, prefix + "gdoffs_center_y")); + m_Params.push_back(ParamWithName(&m_Gamma, prefix + "gdoffs_gamma", 1, INTEGER, 1, 6)); + m_Params.push_back(ParamWithName(&m_Square, prefix + "gdoffs_square", 0, INTEGER, 0, 1)); + m_Params.push_back(ParamWithName(true, &m_Dx, prefix + "gdoffs_dx")); + m_Params.push_back(ParamWithName(true, &m_Ax, prefix + "gdoffs_ax")); + m_Params.push_back(ParamWithName(true, &m_Cx, prefix + "gdoffs_cx")); + m_Params.push_back(ParamWithName(true, &m_Dy, prefix + "gdoffs_dyd")); + m_Params.push_back(ParamWithName(true, &m_Ay, prefix + "gdoffs_ay")); + m_Params.push_back(ParamWithName(true, &m_Cy, prefix + "gdoffs_cy")); + m_Params.push_back(ParamWithName(true, &m_B, prefix + "gdoffs_b")); + } + +private: + static inline T GdoffsFcip(T a) { return (T)((a < 0) ? -((int)(fabs(a)) + 1) : 0) + ((a > 1) ? ((int)(a)) : 0); } + static inline T GdoffsFclp(T a) { return ((a < 0) ? -(fmod(fabs(a), 1)) : fmod(fabs(a), 1)); } + static inline T GdoffsFscl(T a) { return GdoffsFclp((a + 1) / 2); } + static inline T GdoffsFosc(T p, T a) { return GdoffsFscl(-1 * cos(p * a * M_2PI)); } + static inline T GdoffsFlip(T a, T b, T c) { return (c * (b - a) + a); } + + T m_DeltaX;//Params. + T m_DeltaY; + T m_AreaX; + T m_AreaY; + T m_CenterX; + T m_CenterY; + T m_Gamma; + T m_Square; + T m_Dx;//Precalc. + T m_Ax; + T m_Cx; + T m_Dy; + T m_Ay; + T m_Cy; + T m_B; +}; + +/// +/// octagon. +/// +template +class EMBER_API OctagonVariation : public ParametricVariation +{ +public: + OctagonVariation(T weight = 1.0) : ParametricVariation("octagon", VAR_OCTAGON, weight) + { + Init(); + } + + PARVARCOPY(OctagonVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r = m_Weight / ((SQR(SQR(helper.In.x)) + SQR(helper.In.z) + SQR(SQR(helper.In.y)) + SQR(helper.In.z)) + EPS6); + + if (r < 2) + { + helper.Out.x = r * helper.In.x; + helper.Out.y = r * helper.In.y; + helper.Out.z = r * helper.In.z; + } + else + { + helper.Out.x = m_Weight * helper.In.x; + helper.Out.y = m_Weight * helper.In.y; + helper.Out.z = m_Weight * helper.In.z; + + T t = m_Weight / ((sqrt(SQR(helper.In.x)) + sqrt(helper.In.z) + sqrt(SQR(helper.In.y)) + sqrt(helper.In.z)) + EPS6); + + if (r >= 0) + { + helper.Out.x = t * helper.In.x; + helper.Out.y = t * helper.In.y; + helper.Out.z = t * helper.In.z; + } + else + { + helper.Out.x = m_Weight * helper.In.x; + helper.Out.y = m_Weight * helper.In.y; + helper.Out.z = m_Weight * helper.In.z; + } + + if (helper.In.x >= 0) + helper.Out.x = m_Weight * (helper.In.x + m_X); + else + helper.Out.x = m_Weight * (helper.In.x - m_X); + + if (helper.In.y >= 0) + helper.Out.y = m_Weight * (helper.In.y + m_Y); + else + helper.Out.y = m_Weight * (helper.In.y - m_Y); + + if (helper.In.z >= 0) + helper.Out.z = m_Weight * (helper.In.z + m_Z); + else + helper.Out.z = m_Weight * (helper.In.z - m_Z); + } + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string z = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] / ((SQR(SQR(vIn.x)) + SQR(vIn.z) + SQR(SQR(vIn.y)) + SQR(vIn.z)) + EPS6);\n" + << "\n" + << "\t\tif (r < 2)\n" + << "\t\t{\n" + << "\t\t vOut.x = r * vIn.x;\n" + << "\t\t vOut.y = r * vIn.y;\n" + << "\t\t vOut.z = r * vIn.z;\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\t\t vOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\n" + << "\t\t real_t t = xform->m_VariationWeights[" << varIndex << "] / ((sqrt(SQR(vIn.x)) + sqrt(vIn.z) + sqrt(SQR(vIn.y)) + sqrt(vIn.z)) + EPS6);\n" + << "\n" + << "\t\t if (r >= 0)\n" + << "\t\t {\n" + << "\t\t vOut.x = t * vIn.x;\n" + << "\t\t vOut.y = t * vIn.y;\n" + << "\t\t vOut.z = t * vIn.z;\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\t\t vOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t\t }\n" + << "\n" + << "\t\t if (vIn.x >= 0)\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (vIn.x + " << x << ");\n" + << "\t\t else\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (vIn.x - " << x << ");\n" + << "\n" + << "\t\t if (vIn.y >= 0)\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * (vIn.y + " << y << ");\n" + << "\t\t else\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * (vIn.y - " << y << ");\n" + << "\n" + << "\t\t if (vIn.z >= 0)\n" + << "\t\t vOut.z = xform->m_VariationWeights[" << varIndex << "] * (vIn.z + " << z << ");\n" + << "\t\t else\n" + << "\t\t vOut.z = xform->m_VariationWeights[" << varIndex << "] * (vIn.z - " << z << ");\n" + << "\t\t}\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_X, prefix + "octagon_x"));//Original used a prefix of octa_, which is incompatible with Ember's design. + m_Params.push_back(ParamWithName(&m_Y, prefix + "octagon_y")); + m_Params.push_back(ParamWithName(&m_Z, prefix + "octagon_z")); + } + +private: + T m_X; + T m_Y; + T m_Z; +}; + +/// +/// trade. +/// +template +class EMBER_API TradeVariation : public ParametricVariation +{ +public: + TradeVariation(T weight = 1.0) : ParametricVariation("trade", VAR_TRADE, weight) + { + Init(); + } + + PARVARCOPY(TradeVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r, temp, c1mx; + + if (helper.In.x > 0) + { + c1mx = m_C1 - helper.In.x; + r = sqrt(SQR(c1mx) + SQR(helper.In.y)); + + if (r <= m_R1) + { + r *= m_R2 / m_R1; + temp = atan2(helper.In.y, c1mx); + + helper.Out.x = m_Weight * (r * cos(temp) - m_C2); + helper.Out.y = m_Weight * r * sin(temp); + } + else + { + helper.Out.x = m_Weight * helper.In.x; + helper.Out.y = m_Weight * helper.In.y; + } + } + else + { + c1mx = -m_C2 - helper.In.x; + r = sqrt(SQR(c1mx) + SQR(helper.In.y)); + + if (r <= m_R2) + { + r *= m_R1 / m_R2; + temp = atan2(helper.In.y, c1mx); + + helper.Out.x = m_Weight * (r * cos(temp) + m_C1); + helper.Out.y = m_Weight * r * sin(temp); + } + else + { + helper.Out.x = m_Weight * helper.In.x; + helper.Out.y = m_Weight * helper.In.y; + } + } + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string r1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string d1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string r2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string d2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t r, temp, c1mx;\n" + << "\n" + << "\t\tif (vIn.x > 0)\n" + << "\t\t{\n" + << "\t\t c1mx = " << c1 << " - vIn.x;\n" + << "\t\t r = sqrt(SQR(c1mx) + SQR(vIn.y));\n" + << "\n" + << "\t\t if (r <= " << r1 << ")\n" + << "\t\t {\n" + << "\t\t r *= " << r2 << " / " << r1 << ";\n" + << "\t\t temp = atan2(vIn.y, c1mx);\n" + << "\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (r * cos(temp) - " << c2 << ");\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * r * sin(temp);\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\t\t }\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t c1mx = -" << c2 << " - vIn.x;\n" + << "\t\t r = sqrt(SQR(c1mx) + SQR(vIn.y));\n" + << "\n" + << "\t\t if (r <= " << r2 << ")\n" + << "\t\t {\n" + << "\t\t r *= " << r1 << " / " << r2 << ";\n" + << "\t\t temp = atan2(vIn.y, c1mx);\n" + << "\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (r * cos(temp) + " << c1 << ");\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * r * sin(temp);\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\t\t }\n" + << "\t\t}\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_C1 = m_R1 + m_D1; + m_C2 = m_R2 + m_D2; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_R1, prefix + "trade_r1", 1, REAL, EPS6, TMAX)); + m_Params.push_back(ParamWithName(&m_D1, prefix + "trade_d1", 1, REAL, 0, TMAX)); + m_Params.push_back(ParamWithName(&m_R2, prefix + "trade_r2", 1, REAL, EPS6, TMAX)); + m_Params.push_back(ParamWithName(&m_D2, prefix + "trade_d2", 1, REAL, 0, TMAX)); + m_Params.push_back(ParamWithName(true, &m_C1, prefix + "trade_c1")); + m_Params.push_back(ParamWithName(true, &m_C2, prefix + "trade_c2")); + } + +private: + T m_R1; + T m_D1; + T m_R2; + T m_D2; + T m_C1;//Precalc. + T m_C2; +}; + +/// +/// Juliac. +/// +template +class EMBER_API JuliacVariation : public ParametricVariation +{ +public: + JuliacVariation(T weight = 1.0) : ParametricVariation("Juliac", VAR_JULIAC, weight, true, false, false, false, true) + { + Init(); + } + + PARVARCOPY(JuliacVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T arg = helper.m_PrecalcAtanyx + fmod(T(rand.Rand()), T(1 / m_ReInv)) * M_2PI; + T lnmod = m_Dist * T(0.5) * log(helper.m_PrecalcSumSquares); + T temp = arg * m_ReInv + lnmod * m_Im100; + T mod2 = exp(lnmod * m_ReInv - arg * m_Im100); + + helper.Out.x = m_Weight * mod2 * cos(temp); + helper.Out.y = m_Weight * mod2 * sin(temp); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string re = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string im = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string dist = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string reInv = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string im100 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t arg = precalcAtanyx + fmod((real_t)MwcNext(mwc), (real_t)(1 / " << reInv << ")) * M_2PI;\n" + << "\t\treal_t lnmod = " << dist << " * 0.5 * log(precalcSumSquares);\n" + << "\t\treal_t temp = arg * " << reInv << " + lnmod * " << im100 << ";\n" + << "\t\treal_t mod2 = exp(lnmod * " << reInv << " - arg * " << im100 << ");\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * mod2 * cos(temp);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * mod2 * sin(temp);\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_ReInv = 1 / (m_Re + EPS6); + m_Im100 = m_Im * T(0.01); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Re, prefix + "Juliac_re", 2)); + m_Params.push_back(ParamWithName(&m_Im, prefix + "Juliac_im", 1)); + m_Params.push_back(ParamWithName(&m_Dist, prefix + "Juliac_dist", 1)); + m_Params.push_back(ParamWithName(true, &m_ReInv, prefix + "Juliac_re_inv")); + m_Params.push_back(ParamWithName(true, &m_Im100, prefix + "Juliac_im100")); + } + +private: + T m_Re; + T m_Im; + T m_Dist; + T m_ReInv; + T m_Im100; +}; + +/// +/// blade3D. +/// +template +class EMBER_API Blade3DVariation : public Variation +{ +public: + Blade3DVariation(T weight = 1.0) : Variation("blade3D", VAR_BLADE3D, weight, true, true) { } + + VARCOPY(Blade3DVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r = rand.Frand01() * m_Weight * helper.m_PrecalcSqrtSumSquares; + T sinr, cosr; + + sincos(r, &sinr, &cosr); + helper.Out.x = m_Weight * helper.In.x * (cosr + sinr); + helper.Out.y = m_Weight * helper.In.x * (cosr - sinr); + helper.Out.z = m_Weight * helper.In.z * (sinr - cosr); + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t r = MwcNext01(mwc) * xform->m_VariationWeights[" << varIndex << "] * precalcSqrtSumSquares;\n" + << "\t\treal_t sinr = sin(r);\n" + << "\t\treal_t cosr = cos(r);\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x * (cosr + sinr);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.x * (cosr - sinr);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z * (sinr - cosr);\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// Blob3D. +/// +template +class EMBER_API Blob3DVariation : public ParametricVariation +{ +public: + Blob3DVariation(T weight = 1.0) : ParametricVariation("blob3D", VAR_BLOB3D, weight, true, true, true, true) + { + Init(); + } + + PARVARCOPY(Blob3DVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r = helper.m_PrecalcSqrtSumSquares * (m_BlobLow + m_BlobDiff * (T(0.5) + T(0.5) * sin(m_BlobWaves * helper.m_PrecalcAtanxy))); + + helper.Out.x = m_Weight * helper.m_PrecalcSina * r; + helper.Out.y = m_Weight * helper.m_PrecalcCosa * r; + helper.Out.z = m_Weight * sin(m_BlobWaves * helper.m_PrecalcAtanxy) * r; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string blobLow = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string blobHigh = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string blobWaves = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string blobDiff = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t r = precalcSqrtSumSquares * (" << blobLow << " + " << blobDiff << " * (0.5 + 0.5 * sin(" << blobWaves << " * precalcAtanxy)));\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (precalcSina * r);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (precalcCosa * r);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * (sin(" << blobWaves << " * precalcAtanxy) * r);\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_BlobDiff = m_BlobHigh - m_BlobLow; + } + + virtual void Random(QTIsaac& rand) + { + m_BlobLow = T(0.2) + T(0.5) * rand.Frand01(); + m_BlobHigh = T(0.8) + T(0.4) * rand.Frand01(); + m_BlobWaves = (T)(int)(2 + 5 * rand.Frand01()); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_BlobLow, prefix + "blob3D_low")); + m_Params.push_back(ParamWithName(&m_BlobHigh, prefix + "blob3D_high", 1)); + m_Params.push_back(ParamWithName(&m_BlobWaves, prefix + "blob3D_waves", 1)); + m_Params.push_back(ParamWithName(true, &m_BlobDiff, prefix + "blob3D_diff"));//Precalc. + } + +private: + T m_BlobLow; + T m_BlobHigh; + T m_BlobWaves; + T m_BlobDiff;//Precalc. +}; + +/// +/// blocky. +/// +template +class EMBER_API BlockyVariation : public ParametricVariation +{ +public: + BlockyVariation(T weight = 1.0) : ParametricVariation("blocky", VAR_BLOCKY, weight, true) + { + Init(); + } + + PARVARCOPY(BlockyVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T t = ((cos(helper.In.x) + cos(helper.In.y)) / m_Mp + 1); + T r = m_Weight / t; + T tmp = helper.m_PrecalcSumSquares + 1; + T x2 = 2 * helper.In.x; + T y2 = 2 * helper.In.y; + T xmax = T(0.5) * (sqrt(tmp + x2) + sqrt(tmp - x2)); + T ymax = T(0.5) * (sqrt(tmp + y2) + sqrt(tmp - y2)); + T a = helper.In.x / xmax; + T b = SafeSqrt(1 - SQR(a)); + + helper.Out.x = m_Vx * atan2(a, b) * r; + + a = helper.In.y / ymax; + b = SafeSqrt(1 - SQR(a)); + + helper.Out.y = m_Vy * atan2(a, b) * r; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string mp = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string v = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string vx = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string vy = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t t = ((cos(vIn.x) + cos(vIn.y)) / " << mp << " + 1);\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] / t;\n" + << "\t\treal_t tmp = precalcSumSquares + 1;\n" + << "\t\treal_t x2 = 2 * vIn.x;\n" + << "\t\treal_t y2 = 2 * vIn.y;\n" + << "\t\treal_t xmax = 0.5 * (sqrt(tmp + x2) + sqrt(tmp - x2));\n" + << "\t\treal_t ymax = 0.5 * (sqrt(tmp + y2) + sqrt(tmp - y2));\n" + << "\t\treal_t a = vIn.x / xmax;\n" + << "\t\treal_t b = SafeSqrt(1 - SQR(a));\n" + << "\n" + << "\t\tvOut.x = " << vx << " * atan2(a, b) * r;\n" + << "\n" + << "\t\ta = vIn.y / ymax;\n" + << "\t\tb = SafeSqrt(1 - SQR(a));\n" + << "\n" + << "\t\tvOut.y = " << vy << " * atan2(a, b) * r;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_V = m_Weight / T(M_PI_2); + m_Vx = m_V * m_X; + m_Vy = m_V * m_Y; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_X, prefix + "blocky_x", 1)); + m_Params.push_back(ParamWithName(&m_Y, prefix + "blocky_y", 1)); + m_Params.push_back(ParamWithName(&m_Mp, prefix + "blocky_mp", 4)); + m_Params.push_back(ParamWithName(true, &m_V, prefix + "blocky_v"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Vx, prefix + "blocky_vx")); + m_Params.push_back(ParamWithName(true, &m_Vy, prefix + "blocky_vy")); + } + +private: + T m_X; + T m_Y; + T m_Mp; + T m_V;//Precalc. + T m_Vx; + T m_Vy; +}; + +MAKEPREPOSTPARVAR(ESwirl, eSwirl, ESWIRL) +MAKEPREPOSTPARVAR(LazyTravis, lazyTravis, LAZY_TRAVIS) +MAKEPREPOSTPARVAR(Squish, squish, SQUISH) +MAKEPREPOSTPARVAR(Circus, circus, CIRCUS) +MAKEPREPOSTVAR(Tancos, tancos, TANCOS) +MAKEPREPOSTVAR(Rippled, rippled, RIPPLED) +MAKEPREPOSTPARVAR(RotateX, rotate_x, ROTATE_X) +MAKEPREPOSTPARVAR(RotateY, rotate_y, ROTATE_Y) +MAKEPREPOSTPARVAR(RotateZ, rotate_z, ROTATE_Z) +MAKEPREPOSTVAR(MirrorX, mirror_x, MIRROR_X) +MAKEPREPOSTVAR(MirrorY, mirror_y, MIRROR_Y) +MAKEPREPOSTVAR(MirrorZ, mirror_z, MIRROR_Z) +MAKEPREPOSTPARVAR(RBlur, rblur, RBLUR) +MAKEPREPOSTPARVAR(JuliaNab, juliaNab, JULIANAB) +MAKEPREPOSTPARVAR(Sintrange, sintrange, SINTRANGE) +MAKEPREPOSTPARVAR(Voron, Voron, VORON) +MAKEPREPOSTPARVARASSIGN(Waffle, waffle, WAFFLE, ASSIGNTYPE_SUM) +MAKEPREPOSTVARASSIGN(Square3D, square3D, SQUARE3D, ASSIGNTYPE_SUM) +MAKEPREPOSTPARVARASSIGN(SuperShape3D, SuperShape3D, SUPER_SHAPE3D, ASSIGNTYPE_SUM) +MAKEPREPOSTPARVAR(Sphyp3D, sphyp3D, SPHYP3D) +MAKEPREPOSTPARVAR(Circlecrop, circlecrop, CIRCLECROP) +MAKEPREPOSTPARVAR(Julian3Dx, julian3Dx, JULIAN3DX) +MAKEPREPOSTPARVAR(Fourth, fourth, FOURTH) +MAKEPREPOSTPARVAR(Mobiq, mobiq, MOBIQ) +MAKEPREPOSTPARVAR(Spherivoid, spherivoid, SPHERIVOID) +MAKEPREPOSTPARVAR(Farblur, farblur, FARBLUR) +MAKEPREPOSTPARVAR(CurlSP, curl_sp, CURL_SP) +MAKEPREPOSTPARVAR(Heat, heat, HEAT) +MAKEPREPOSTPARVAR(Interference2, interference2, INTERFERENCE2) +MAKEPREPOSTVAR(Sinq, sinq, SINQ) +MAKEPREPOSTVAR(Sinhq, sinhq, SINHQ) +MAKEPREPOSTVAR(Secq, secq, SECQ) +MAKEPREPOSTVAR(Sechq, sechq, SECHQ) +MAKEPREPOSTVAR(Tanq, tanq, TANQ) +MAKEPREPOSTVAR(Tanhq, tanhq, TANHQ) +MAKEPREPOSTVAR(Cosq, cosq, COSQ) +MAKEPREPOSTVAR(Coshq, coshq, COSHQ) +MAKEPREPOSTVAR(Cotq, cotq, COTQ) +MAKEPREPOSTVAR(Cothq, cothq, COTHQ) +MAKEPREPOSTVAR(Cscq, cscq, CSCQ) +MAKEPREPOSTVAR(Cschq, cschq, CSCHQ) +MAKEPREPOSTVAR(Estiq, estiq, ESTIQ) +MAKEPREPOSTPARVAR(Loq, loq, LOQ) +MAKEPREPOSTVAR(Curvature, curvature, CURVATURE) +MAKEPREPOSTPARVAR(Qode, q_ode, Q_ODE) +MAKEPREPOSTPARVARASSIGN(BlurHeart, blur_heart, BLUR_HEART, ASSIGNTYPE_SUM) +MAKEPREPOSTPARVAR(Truchet, Truchet, TRUCHET) +MAKEPREPOSTPARVAR(Gdoffs, gdoffs, GDOFFS) +MAKEPREPOSTPARVAR(Octagon, octagon, OCTAGON) +MAKEPREPOSTPARVAR(Trade, trade, TRADE) +MAKEPREPOSTPARVAR(Juliac, Juliac, JULIAC) +MAKEPREPOSTVAR(Blade3D, blade3D, BLADE3D) +MAKEPREPOSTPARVAR(Blob3D, blob3D, BLOB3D) +MAKEPREPOSTPARVAR(Blocky, blocky, BLOCKY) + + +///// +///// LinearXZ. +///// +//template +//class EMBER_API LinearXZVariation : public Variation +//{ +//public: +// LinearXZVariation(T weight = 1.0) : Variation("linearxz", VAR_LINEAR_XZ, weight) { } +// +// VARCOPY(LinearXZVariation) +// +// void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) +// { +// helper.Out.x = m_Weight * helper.In.x; +// helper.Out.z = m_Weight * helper.In.z; +// } +// +// virtual string OpenCLString() +// { +// ostringstream ss; +// int i = 0, varIndex = IndexInXform(); +// +// ss << "\t{\n" +// << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" +// << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" +// << "\t}\n"; +// +// return ss.str(); +// } +//}; +// +///// +///// LinearYZ. +///// +//template +//class EMBER_API LinearYZVariation : public Variation +//{ +//public: +// LinearYZVariation(T weight = 1.0) : Variation("linearyz", VAR_LINEAR_YZ, weight) { } +// +// VARCOPY(LinearYZVariation) +// +// void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) +// { +// helper.Out.y = m_Weight * helper.In.y; +// helper.Out.z = m_Weight * helper.In.z; +// } +// +// virtual string OpenCLString() +// { +// ostringstream ss; +// int i = 0, varIndex = IndexInXform(); +// +// ss << "\t{\n" +// << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" +// << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" +// << "\t}\n"; +// +// return ss.str(); +// } +//}; +} \ No newline at end of file diff --git a/Source/Ember/Variations05.h b/Source/Ember/Variations05.h new file mode 100644 index 0000000..4df8f4d --- /dev/null +++ b/Source/Ember/Variations05.h @@ -0,0 +1,3253 @@ +#pragma once + +#include "Variation.h" + +namespace EmberNs +{ +/// +/// bubble2. +/// +template +class EMBER_API Bubble2Variation : public ParametricVariation +{ +public: + Bubble2Variation(T weight = 1.0) : ParametricVariation("bubble2", VAR_BUBBLE2, weight, true) + { + Init(); + } + + PARVARCOPY(Bubble2Variation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T t = T(0.25) * (helper.m_PrecalcSumSquares + SQR(helper.In.z)) + 1; + T r = m_Weight / t; + + helper.Out.x = helper.In.x * r * m_X; + helper.Out.y = helper.In.y * r * m_Y; + + if (helper.In.z >= 0) + helper.Out.z = m_Weight * (helper.In.z + m_Z); + else + helper.Out.z = m_Weight * (helper.In.z - m_Z); + + helper.Out.z += helper.In.z * r * m_Z;//The += is intentional. + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string z = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t t = 0.25 * (precalcSumSquares + SQR(vIn.z)) + 1;\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] / t;\n" + << "\n" + << "\t\tvOut.x = vIn.x * r * " << x << ";\n" + << "\t\tvOut.y = vIn.y * r * " << y << ";\n" + << "\n" + << "\t\tif (vIn.z >= 0)\n" + << "\t\t vOut.z = xform->m_VariationWeights[" << varIndex << "] * (vIn.z + " << z << ");\n" + << "\t\telse\n" + << "\t\t vOut.z = xform->m_VariationWeights[" << varIndex << "] * (vIn.z - " << z << ");\n" + << "\n" + << "\t\tvOut.z += vIn.z * r * " << z << ";\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_X, prefix + "bubble2_x", 1));//Original used a prefix of bubble_, which is incompatible with Ember's design. + m_Params.push_back(ParamWithName(&m_Y, prefix + "bubble2_y", 1)); + m_Params.push_back(ParamWithName(&m_Z, prefix + "bubble2_z")); + } + +private: + T m_X; + T m_Y; + T m_Z; +}; + +/// +/// CircleLinear. +/// +template +class EMBER_API CircleLinearVariation : public ParametricVariation +{ +public: + CircleLinearVariation(T weight = 1.0) : ParametricVariation("CircleLinear", VAR_CIRCLELINEAR, weight) + { + Init(); + } + + PARVARCOPY(CircleLinearVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + int m = Floor(T(0.5) * helper.In.x / m_Sc); + int n = Floor(T(0.5) * helper.In.y / m_Sc); + T x = helper.In.x - (m * 2 + 1) * m_Sc; + T y = helper.In.y - (n * 2 + 1) * m_Sc; + T u = Hypot(x, y); + T v = (T(0.3) + T(0.7) * DiscreteNoise2(m + 10, n + 3)) * m_Sc; + T z1 = DiscreteNoise2(int(m + m_Seed), n); + + if ((z1 < m_Dens1) && (u < v)) + { + if (m_Reverse > 0) + { + if (z1 < m_Dens1 * m_Dens2) + { + x *= m_K; + y *= m_K; + } + else + { + T z = v / u * (1 - m_K) + m_K; + + x *= z; + y *= z; + } + } + else + { + if (z1 > m_Dens1 * m_Dens2) + { + x *= m_K; + y *= m_K; + } + else + { + T z = v / u * (1 - m_K) + m_K; + + x *= z; + y *= z; + } + } + } + + helper.Out.x = m_Weight * (x + (m * 2 + 1) * m_Sc); + helper.Out.y = m_Weight * (y + (n * 2 + 1) * m_Sc); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string sc = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string k = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string dens1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string dens2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string reverse = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string seed = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tint m = (int)floor(0.5 * vIn.x / " << sc << ");\n" + << "\t\tint n = (int)floor(0.5 * vIn.y / " << sc << ");\n" + << "\t\treal_t x = vIn.x - (m * 2 + 1) * " << sc << ";\n" + << "\t\treal_t y = vIn.y - (n * 2 + 1) * " << sc << ";\n" + << "\t\treal_t u = Hypot(x, y);\n" + << "\t\treal_t v = (0.3 + 0.7 * CircleLinearDiscreteNoise2(m + 10, n + 3)) * " << sc << ";\n" + << "\t\treal_t z1 = CircleLinearDiscreteNoise2((int)(m + " << seed << "), n);\n" + << "\n" + << "\t\tif ((z1 < " << dens1 << ") && (u < v))\n" + << "\t\t{\n" + << "\t\t if (" << reverse << " > 0)\n" + << "\t\t {\n" + << "\t\t if (z1 < " << dens1 << " * " << dens2 << ")\n" + << "\t\t {\n" + << "\t\t x *= " << k << ";\n" + << "\t\t y *= " << k << ";\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t real_t z = v / u * (1 - " << k << ") + " << k << ";\n" + << "\n" + << "\t\t x *= z;\n" + << "\t\t y *= z;\n" + << "\t\t }\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t if (z1 > " << dens1 << " * " << dens2 << ")\n" + << "\t\t {\n" + << "\t\t x *= " << k << ";\n" + << "\t\t y *= " << k << ";\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t real_t z = v / u * (1 - " << k << ") + " << k << ";\n" + << "\n" + << "\t\t x *= z;\n" + << "\t\t y *= z;\n" + << "\t\t }\n" + << "\t\t }\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (x + (m * 2 + 1) * " << sc << ");\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (y + (n * 2 + 1) * " << sc << ");\n" + << "\t}\n"; + + return ss.str(); + } + + virtual string OpenCLFuncsString() + { + return + "real_t CircleLinearDiscreteNoise2(int x, int y)\n" + "{\n" + " const real_t im = 2147483647;\n" + " const real_t am = 1 / im;\n" + "\n" + " int n = x + y * 57;\n" + " n = (n << 13) ^ n;\n" + " return ((n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) * am;\n" + "}\n" + "\n"; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Sc, prefix + "CircleLinear_Sc", 1)); + m_Params.push_back(ParamWithName(&m_K, prefix + "CircleLinear_K", T(0.5))); + m_Params.push_back(ParamWithName(&m_Dens1, prefix + "CircleLinear_Dens1", T(0.5))); + m_Params.push_back(ParamWithName(&m_Dens2, prefix + "CircleLinear_Dens2", T(0.5))); + m_Params.push_back(ParamWithName(&m_Reverse, prefix + "CircleLinear_Reverse", 1)); + m_Params.push_back(ParamWithName(&m_X, prefix + "CircleLinear_X", 10)); + m_Params.push_back(ParamWithName(&m_Y, prefix + "CircleLinear_Y", 10)); + m_Params.push_back(ParamWithName(&m_Seed, prefix + "CircleLinear_Seed", 0, INTEGER)); + } + +private: + T DiscreteNoise2(int x, int y) + { + const T im = T(2147483647); + const T am = (1 / im); + + int n = x + y * 57; + n = (n << 13) ^ n; + return ((n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) * am; + } + + T m_Sc; + T m_K; + T m_Dens1; + T m_Dens2; + T m_Reverse; + T m_X; + T m_Y; + T m_Seed; +}; + +/// +/// CircleRand. +/// The original would loop infinitely as x and y approached zero, so put a check for a max of 10 iters. +/// +template +class EMBER_API CircleRandVariation : public ParametricVariation +{ +public: + CircleRandVariation(T weight = 1.0) : ParametricVariation("CircleRand", VAR_CIRCLERAND, weight) + { + Init(); + } + + PARVARCOPY(CircleRandVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + int m, n, iters = 0; + T x, y, u; + + do + { + x = m_X * (1 - 2 * rand.Frand01()); + y = m_Y * (1 - 2 * rand.Frand01()); + m = Floor(T(0.5) * x / m_Sc); + n = Floor(T(0.5) * y / m_Sc); + x -= (m * 2 + 1) * m_Sc; + y -= (n * 2 + 1) * m_Sc; + u = Hypot(x, y); + + if (++iters > 10) + break; + } + while ((DiscreteNoise2((int)(m + m_Seed), n) > m_Dens) || (u > (T(0.3) + T(0.7) * DiscreteNoise2(m + 10, n + 3)) * m_Sc)); + + helper.Out.x = m_Weight * (x + (m * 2 + 1) * m_Sc); + helper.Out.y = m_Weight * (y + (n * 2 + 1) * m_Sc); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string sc = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string dens = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string seed = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tint m, n, iters = 0;\n" + << "\t\treal_t x, y, u;\n" + << "\n" + << "\t\tdo\n" + << "\t\t{\n" + << "\t\t x = " << x << " * (1 - 2 * MwcNext01(mwc));\n" + << "\t\t y = " << y << " * (1 - 2 * MwcNext01(mwc));\n" + << "\t\t m = (int)floor(0.5 * x / " << sc << ");\n" + << "\t\t n = (int)floor(0.5 * y / " << sc << ");\n" + << "\t\t x = x - (m * 2 + 1) * " << sc << ";\n" + << "\t\t y = y - (n * 2 + 1) * " << sc << ";\n" + << "\t\t u = Hypot(x, y);\n" + << "\n" + << "\t\t if (++iters > 10)\n" + << "\t\t break;\n" + << "\t\t}\n" + << "\t\twhile ((CircleRandDiscreteNoise2((int)(m + " << seed << "), n) > " << dens << ") || (u > (0.3 + 0.7 * CircleRandDiscreteNoise2(m + 10, n + 3)) * " << sc << "));\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (x + (m * 2 + 1) * " << sc << ");\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (y + (n * 2 + 1) * " << sc << ");\n" + << "\t}\n"; + + return ss.str(); + } + + virtual string OpenCLFuncsString() + { + return + "real_t CircleRandDiscreteNoise2(int x, int y)\n" + "{\n" + " const real_t im = 2147483647;\n" + " const real_t am = 1 / im;\n" + "\n" + " int n = x + y * 57;\n" + " n = (n << 13) ^ n;\n" + " return ((n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) * am;\n" + "}\n" + "\n"; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Sc, prefix + "CircleRand_Sc", 1)); + m_Params.push_back(ParamWithName(&m_Dens, prefix + "CircleRand_Dens", T(0.5))); + m_Params.push_back(ParamWithName(&m_X, prefix + "CircleRand_X", 10)); + m_Params.push_back(ParamWithName(&m_Y, prefix + "CircleRand_Y", 10)); + m_Params.push_back(ParamWithName(&m_Seed, prefix + "CircleRand_Seed", 0, INTEGER)); + } + +private: + T DiscreteNoise2(int x, int y) + { + const T im = T(2147483647); + const T am = (1 / im); + + int n = x + y * 57; + n = (n << 13) ^ n; + return ((n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) * am; + } + + T m_Sc; + T m_Dens; + T m_X; + T m_Y; + T m_Seed; +}; + +/// +/// CircleTrans1. +/// The original would loop infinitely as x and y approached zero, so put a check for a max of 10 iters. +/// +template +class EMBER_API CircleTrans1Variation : public ParametricVariation +{ +public: + CircleTrans1Variation(T weight = 1.0) : ParametricVariation("CircleTrans1", VAR_CIRCLETRANS1, weight) + { + Init(); + } + + PARVARCOPY(CircleTrans1Variation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T ux, uy, u, x, y; + + Trans(m_X, m_Y, helper.In.x, helper.In.y, &ux, &uy); + + int m = Floor(T(0.5) * ux / m_Sc); + int n = Floor(T(0.5) * uy / m_Sc); + + x = ux - (m * 2 + 1) * m_Sc; + y = uy - (n * 2 + 1) * m_Sc; + u = Hypot(x, y); + + if ((DiscreteNoise2((int)(m + m_Seed), n) > m_Dens) || (u > (T(0.3) + T(0.7) * DiscreteNoise2(m + 10, n + 3)) * m_Sc)) + { + ux = ux; + uy = uy; + } + else + { + CircleR(&ux, &uy, rand); + } + + helper.Out.x = m_Weight * ux; + helper.Out.y = m_Weight * uy; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string sc = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string dens = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string seed = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t ux, uy, u, x, y;\n" + << "\n" + << "\t\tCircleTrans1Trans(" << x << ", " << y << ", vIn.x, vIn.y, &ux, &uy);\n" + << "\n" + << "\t\tint m = (int)floor(0.5 * ux / " << sc << ");\n" + << "\t\tint n = (int)floor(0.5 * uy / " << sc << ");\n" + << "\n" + << "\t\tx = ux - (m * 2 + 1) * " << sc << ";\n" + << "\t\ty = uy - (n * 2 + 1) * " << sc << ";\n" + << "\t\tu = Hypot(x, y);\n" + << "\n" + << "\t\tif ((CircleTrans1DiscreteNoise2((int)(m + " << seed << "), n) > " << dens << ") || (u > (0.3 + 0.7 * CircleTrans1DiscreteNoise2(m + 10, n + 3)) * " << sc << "))\n" + << "\t\t{\n" + << "\t\t ux = ux;\n" + << "\t\t uy = uy;\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t CircleTrans1CircleR(" << x << ", " << y << ", " << sc << ", " << seed << ", " << dens << ", &ux, &uy, mwc);\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * ux;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * uy;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual string OpenCLFuncsString() + { + return + "real_t CircleTrans1DiscreteNoise2(int x, int y)\n" + "{\n" + " const real_t im = 2147483647;\n" + " const real_t am = 1 / im;\n" + "\n" + " int n = x + y * 57;\n" + " n = (n << 13) ^ n;\n" + " return ((n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) * am;\n" + "}\n" + "\n" + "void CircleTrans1Trans(real_t a, real_t b, real_t x, real_t y, real_t* x1, real_t* y1)\n" + "{\n" + " *x1 = (x - a) * 0.5 + a;\n" + " *y1 = (y - b) * 0.5 + b;\n" + "}\n" + "\n" + "void CircleTrans1CircleR(real_t mx, real_t my, real_t sc, real_t seed, real_t dens, real_t* ux, real_t* vy, uint2* mwc)\n" + "{\n" + " int m, n, iters = 0;\n" + " real_t x, y, alpha, u;\n" + "\n" + " do\n" + " {\n" + " x = fabs(mx) * (1 - 2 * MwcNext01(mwc));\n" + " y = fabs(my) * (1 - 2 * MwcNext01(mwc));\n" + " m = (int)floor(0.5 * x / sc);\n" + " n = (int)floor(0.5 * y / sc);\n" + " alpha = M_2PI * MwcNext01(mwc);\n" + " u = 0.3 + 0.7 * CircleTrans1DiscreteNoise2(m + 10, n + 3);\n" + " x = u * cos(alpha);\n" + " y = u * sin(alpha);\n" + "\n" + " if (++iters > 10)\n" + " break;\n" + " }\n" + " while (CircleTrans1DiscreteNoise2((int)(m + seed), n) > dens);\n" + "\n" + " *ux = x + (m * 2 + 1) * sc;\n" + " *vy = y + (n * 2 + 1) * sc;\n" + "}\n" + "\n"; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Sc, prefix + "CircleTrans1_Sc", 1)); + m_Params.push_back(ParamWithName(&m_Dens, prefix + "CircleTrans1_Dens", T(0.5))); + m_Params.push_back(ParamWithName(&m_X, prefix + "CircleTrans1_X", 10)); + m_Params.push_back(ParamWithName(&m_Y, prefix + "CircleTrans1_Y", 10)); + m_Params.push_back(ParamWithName(&m_Seed, prefix + "CircleTrans1_Seed", 0, INTEGER)); + } + +private: + T DiscreteNoise2(int x, int y) + { + const T im = T(2147483647); + const T am = (1 / im); + + int n = x + y * 57; + n = (n << 13) ^ n; + return ((n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) * am; + } + + void Trans(T a, T b, T x, T y, T* x1, T* y1) + { + *x1 = (x - a) * T(0.5) + a; + *y1 = (y - b) * T(0.5) + b; + } + + void CircleR(T* ux, T* vy, QTIsaac& rand) + { + int m, n, iters = 0; + T x, y, alpha, u; + + do + { + x = fabs(m_X) * (1 - 2 * rand.Frand01()); + y = fabs(m_Y) * (1 - 2 * rand.Frand01()); + m = Floor(T(0.5) * x / m_Sc); + n = Floor(T(0.5) * y / m_Sc); + alpha = M_2PI * rand.Frand01(); + u = T(0.3) + T(0.7) * DiscreteNoise2(m + 10, n + 3); + x = u * cos(alpha); + y = u * sin(alpha); + + if (++iters > 10) + break; + } + while (DiscreteNoise2((int)(m + m_Seed), n) > m_Dens); + + *ux = x + (m * 2 + 1) * m_Sc; + *vy = y + (n * 2 + 1) * m_Sc; + } + + T m_Sc; + T m_Dens; + T m_X; + T m_Y; + T m_Seed; +}; + +/// +/// cubic3D. +/// +template +class EMBER_API Cubic3DVariation : public ParametricVariation +{ +public: + Cubic3DVariation(T weight = 1.0) : ParametricVariation("cubic3D", VAR_CUBIC3D, weight) + { + Init(); + } + + PARVARCOPY(Cubic3DVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + int useNode = rand.Rand() & 7;//Faster than % 8. + T exnze, wynze, znxy; + T lattd = m_Weight * T(0.5); + T px, py, pz; + + exnze = 1 - (m_SmoothStyle * (1 - (cos(atan2(helper.In.x, helper.In.z))))); + wynze = 1 - (m_SmoothStyle * (1 - (sin(atan2(helper.In.y, helper.In.z))))); + + if (m_SmoothStyle > 1) + znxy = 1 - (m_SmoothStyle * (1 - ((exnze + wynze) / 2 * m_SmoothStyle))); + else + znxy = 1 - (m_SmoothStyle * (1 - ((exnze + wynze) * T(0.5)))); + + if (m_VarType == VARTYPE_PRE) + { + px = helper.In.x; + py = helper.In.y; + pz = helper.In.z; + } + else + { + px = outPoint.m_X; + py = outPoint.m_Y; + pz = outPoint.m_Z; + } + + switch (useNode) + { + case 0 : + helper.Out.x = ((px - (m_Smooth * (1 - m_Fill) * px * exnze)) + (helper.In.x * m_Smooth * m_Fill * exnze)) + lattd; + helper.Out.y = ((py - (m_Smooth * (1 - m_Fill) * py * wynze)) + (helper.In.y * m_Smooth * m_Fill * wynze)) + lattd; + helper.Out.z = ((pz - (m_Smooth * (1 - m_Fill) * pz * znxy)) + (helper.In.z * m_Smooth * m_Fill * znxy)) + lattd; + break; + case 1 : + helper.Out.x = ((px - (m_Smooth * (1 - m_Fill) * px * exnze)) + (helper.In.x * m_Smooth * m_Fill * exnze)) + lattd; + helper.Out.y = ((py - (m_Smooth * (1 - m_Fill) * py * wynze)) + (helper.In.y * m_Smooth * m_Fill * wynze)) - lattd; + helper.Out.z = ((pz - (m_Smooth * (1 - m_Fill) * pz * znxy)) + (helper.In.z * m_Smooth * m_Fill * znxy)) + lattd; + break; + case 2 : + helper.Out.x = ((px - (m_Smooth * (1 - m_Fill) * px * exnze)) + (helper.In.x * m_Smooth * m_Fill * exnze)) + lattd; + helper.Out.y = ((py - (m_Smooth * (1 - m_Fill) * py * wynze)) + (helper.In.y * m_Smooth * m_Fill * wynze)) + lattd; + helper.Out.z = ((pz - (m_Smooth * (1 - m_Fill) * pz * znxy)) + (helper.In.z * m_Smooth * m_Fill * znxy)) - lattd; + break; + case 3 : + helper.Out.x = ((px - (m_Smooth * (1 - m_Fill) * px * exnze)) + (helper.In.x * m_Smooth * m_Fill * exnze)) + lattd; + helper.Out.y = ((py - (m_Smooth * (1 - m_Fill) * py * wynze)) + (helper.In.y * m_Smooth * m_Fill * wynze)) - lattd; + helper.Out.z = ((pz - (m_Smooth * (1 - m_Fill) * pz * znxy)) + (helper.In.z * m_Smooth * m_Fill * znxy)) - lattd; + break; + case 4 : + helper.Out.x = ((px - (m_Smooth * (1 - m_Fill) * px * exnze)) + (helper.In.x * m_Smooth * m_Fill * exnze)) - lattd; + helper.Out.y = ((py - (m_Smooth * (1 - m_Fill) * py * wynze)) + (helper.In.y * m_Smooth * m_Fill * wynze)) + lattd; + helper.Out.z = ((pz - (m_Smooth * (1 - m_Fill) * pz * znxy)) + (helper.In.z * m_Smooth * m_Fill * znxy)) + lattd; + break; + case 5 : + helper.Out.x = ((px - (m_Smooth * (1 - m_Fill) * px * exnze)) + (helper.In.x * m_Smooth * m_Fill * exnze)) - lattd; + helper.Out.y = ((py - (m_Smooth * (1 - m_Fill) * py * wynze)) + (helper.In.y * m_Smooth * m_Fill * wynze)) - lattd; + helper.Out.z = ((pz - (m_Smooth * (1 - m_Fill) * pz * znxy)) + (helper.In.z * m_Smooth * m_Fill * znxy)) + lattd; + break; + case 6 : + helper.Out.x = ((px - (m_Smooth * (1 - m_Fill) * px * exnze)) + (helper.In.x * m_Smooth * m_Fill * exnze)) - lattd; + helper.Out.y = ((py - (m_Smooth * (1 - m_Fill) * py * wynze)) + (helper.In.y * m_Smooth * m_Fill * wynze)) + lattd; + helper.Out.z = ((pz - (m_Smooth * (1 - m_Fill) * pz * znxy)) + (helper.In.z * m_Smooth * m_Fill * znxy)) - lattd; + break; + case 7 : + helper.Out.x = ((px - (m_Smooth * (1 - m_Fill) * px * exnze)) + (helper.In.x * m_Smooth * m_Fill * exnze)) - lattd; + helper.Out.y = ((py - (m_Smooth * (1 - m_Fill) * py * wynze)) + (helper.In.y * m_Smooth * m_Fill * wynze)) - lattd; + helper.Out.z = ((pz - (m_Smooth * (1 - m_Fill) * pz * znxy)) + (helper.In.z * m_Smooth * m_Fill * znxy)) - lattd; + break; + } + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string xpand = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string style = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string fill = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string smooth = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string smoothStyle = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tint useNode = MwcNext(mwc) & 7;\n" + << "\t\treal_t exnze, wynze, znxy;\n" + << "\t\treal_t lattd = xform->m_VariationWeights[" << varIndex << "] * 0.5;\n" + << "\t\treal_t px, py, pz;\n" + << "\n" + << "\t\texnze = 1 - (" << smoothStyle << " * (1 - (cos(atan2(vIn.x, vIn.z)))));\n" + << "\t\twynze = 1 - (" << smoothStyle << " * (1 - (sin(atan2(vIn.y, vIn.z)))));\n" + << "\n" + << "\t\tif (" << smoothStyle << " > 1)\n" + << "\t\t znxy = 1 - (" << smoothStyle << " * (1 - ((exnze + wynze) / 2 * " << smoothStyle << ")));\n" + << "\t\telse\n" + << "\t\t znxy = 1 - (" << smoothStyle << " * (1 - ((exnze + wynze) * 0.5)));\n"; + + if (m_VarType == VARTYPE_PRE) + { + ss << + "\t\tpx = vIn.x;\n" + "\t\tpy = vIn.y;\n" + "\t\tpz = vIn.z;\n"; + } + else + { + ss << + "\t\tpx = outPoint->m_X;\n" + "\t\tpy = outPoint->m_Y;\n" + "\t\tpz = outPoint->m_Z;\n"; + } + + ss << + "\t\tswitch (useNode)\n" + "\t\t{\n" + "\t\t case 0 :\n" + "\t\t vOut.x = ((px - (" << smooth << " * (1 - " << fill << ") * px * exnze)) + (vIn.x * " << smooth << " * " << fill << " * exnze)) + lattd;\n" + "\t\t vOut.y = ((py - (" << smooth << " * (1 - " << fill << ") * py * wynze)) + (vIn.y * " << smooth << " * " << fill << " * wynze)) + lattd;\n" + "\t\t vOut.z = ((pz - (" << smooth << " * (1 - " << fill << ") * pz * znxy)) + (vIn.z * " << smooth << " * " << fill << " * znxy)) + lattd;\n" + "\t\t break;\n" + "\t\t case 1 :\n" + "\t\t vOut.x = ((px - (" << smooth << " *(1 - " << fill << ") * px * exnze)) + (vIn.x * " << smooth << " * " << fill << " * exnze)) + lattd;\n" + "\t\t vOut.y = ((py - (" << smooth << " *(1 - " << fill << ") * py * wynze)) + (vIn.y * " << smooth << " * " << fill << " * wynze)) - lattd;\n" + "\t\t vOut.z = ((pz - (" << smooth << " *(1 - " << fill << ") * pz * znxy)) + (vIn.z * " << smooth << " * " << fill << " * znxy)) + lattd;\n" + "\t\t break;\n" + "\t\t case 2 :\n" + "\t\t vOut.x = ((px - (" << smooth << " * (1 - " << fill << ") * px * exnze)) + (vIn.x * " << smooth << " * " << fill << " * exnze)) + lattd;\n" + "\t\t vOut.y = ((py - (" << smooth << " * (1 - " << fill << ") * py * wynze)) + (vIn.y * " << smooth << " * " << fill << " * wynze)) + lattd;\n" + "\t\t vOut.z = ((pz - (" << smooth << " * (1 - " << fill << ") * pz * znxy)) + (vIn.z * " << smooth << " * " << fill << " * znxy)) - lattd;\n" + "\t\t break;\n" + "\t\t case 3 :\n" + "\t\t vOut.x = ((px - (" << smooth << " * (1 - " << fill << ") * px * exnze)) + (vIn.x * " << smooth << " * " << fill << " * exnze)) + lattd;\n" + "\t\t vOut.y = ((py - (" << smooth << " * (1 - " << fill << ") * py * wynze)) + (vIn.y * " << smooth << " * " << fill << " * wynze)) - lattd;\n" + "\t\t vOut.z = ((pz - (" << smooth << " * (1 - " << fill << ") * pz * znxy)) + (vIn.z * " << smooth << " * " << fill << " * znxy)) - lattd;\n" + "\t\t break;\n" + "\t\t case 4 :\n" + "\t\t vOut.x = ((px - (" << smooth << " * (1 - " << fill << ") * px * exnze)) + (vIn.x * " << smooth << " * " << fill << " * exnze)) - lattd;\n" + "\t\t vOut.y = ((py - (" << smooth << " * (1 - " << fill << ") * py * wynze)) + (vIn.y * " << smooth << " * " << fill << " * wynze)) + lattd;\n" + "\t\t vOut.z = ((pz - (" << smooth << " * (1 - " << fill << ") * pz * znxy)) + (vIn.z * " << smooth << " * " << fill << " * znxy)) + lattd;\n" + "\t\t break;\n" + "\t\t case 5 :\n" + "\t\t vOut.x = ((px - (" << smooth << " * (1 - " << fill << ") * px * exnze)) + (vIn.x * " << smooth << " * " << fill << " * exnze)) - lattd;\n" + "\t\t vOut.y = ((py - (" << smooth << " * (1 - " << fill << ") * py * wynze)) + (vIn.y * " << smooth << " * " << fill << " * wynze)) - lattd;\n" + "\t\t vOut.z = ((pz - (" << smooth << " * (1 - " << fill << ") * pz * znxy)) + (vIn.z * " << smooth << " * " << fill << " * znxy)) + lattd;\n" + "\t\t break;\n" + "\t\t case 6 :\n" + "\t\t vOut.x = ((px - (" << smooth << " * (1 - " << fill << ") * px * exnze)) + (vIn.x * " << smooth << " * " << fill << " * exnze)) - lattd;\n" + "\t\t vOut.y = ((py - (" << smooth << " * (1 - " << fill << ") * py * wynze)) + (vIn.y * " << smooth << " * " << fill << " * wynze)) + lattd;\n" + "\t\t vOut.z = ((pz - (" << smooth << " * (1 - " << fill << ") * pz * znxy)) + (vIn.z * " << smooth << " * " << fill << " * znxy)) - lattd;\n" + "\t\t break;\n" + "\t\t case 7 :\n" + "\t\t vOut.x = ((px - (" << smooth << " * (1 - " << fill << ") * px * exnze)) + (vIn.x * " << smooth << " * " << fill << " * exnze)) - lattd;\n" + "\t\t vOut.y = ((py - (" << smooth << " * (1 - " << fill << ") * py * wynze)) + (vIn.y * " << smooth << " * " << fill << " * wynze)) - lattd;\n" + "\t\t vOut.z = ((pz - (" << smooth << " * (1 - " << fill << ") * pz * znxy)) + (vIn.z * " << smooth << " * " << fill << " * znxy)) - lattd;\n" + "\t\t break;\n" + "\t\t}\n" + "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + if (fabs(m_Xpand) <= 1) + m_Fill = m_Xpand * T(0.5); + else + m_Fill = sqrt(m_Xpand) * T(0.5); + + if (fabs(m_Weight) <= T(0.5)) + m_Smooth = m_Weight * 2;//Causes full effect above m_Weight = 0.5. + else + m_Smooth = 1; + + if (fabs(m_Style) <= 1) + { + m_SmoothStyle = m_Style; + } + else + { + if (m_Style > 1) + m_SmoothStyle = 1 + (m_Style - 1) * T(0.25); + else + m_SmoothStyle = (m_Style + 1) * T(0.25) - 1; + } + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Xpand, prefix + "cubic3D_xpand", T(0.25))); + m_Params.push_back(ParamWithName(&m_Style, prefix + "cubic3D_style")); + m_Params.push_back(ParamWithName(true, &m_Fill, prefix + "cubic3D_fill"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Smooth, prefix + "cubic3D_smooth")); + m_Params.push_back(ParamWithName(true, &m_SmoothStyle, prefix + "cubic3D_smooth_style")); + } + +private: + T m_Xpand; + T m_Style; + T m_Fill;//Precalc. + T m_Smooth; + T m_SmoothStyle; +}; + +/// +/// cubicLattice_3D. +/// +template +class EMBER_API CubicLattice3DVariation : public ParametricVariation +{ +public: + CubicLattice3DVariation(T weight = 1.0) : ParametricVariation("cubicLattice_3D", VAR_CUBIC_LATTICE3D, weight) + { + Init(); + } + + PARVARCOPY(CubicLattice3DVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + int useNode = rand.Rand() & 7;//Faster than % 8. + T exnze, wynze, znxy, px, py, pz, lattd = m_Weight; + + if (m_Style == 2) + { + exnze = cos(atan2(helper.In.x, helper.In.z)); + wynze = sin(atan2(helper.In.y, helper.In.z)); + znxy = (exnze + wynze) * T(0.5); + } + else + { + exnze = 1; + wynze = 1; + znxy = 1; + } + + if (m_VarType == VARTYPE_PRE) + { + px = helper.In.x; + py = helper.In.y; + pz = helper.In.z; + } + else + { + px = outPoint.m_X; + py = outPoint.m_Y; + pz = outPoint.m_Z; + } + + T pxtx = px + helper.In.x; + T pyty = py + helper.In.y; + T pztz = pz + helper.In.z; + + switch (useNode) + { + case 0 : + helper.Out.x = pxtx * m_Fill * exnze + lattd; + helper.Out.y = pyty * m_Fill * wynze + lattd; + helper.Out.z = pztz * m_Fill * znxy + lattd; + break; + case 1 : + helper.Out.x = pxtx * m_Fill * exnze + lattd; + helper.Out.y = pyty * m_Fill * wynze - lattd; + helper.Out.z = pztz * m_Fill * znxy + lattd; + break; + case 2 : + helper.Out.x = pxtx * m_Fill * exnze + lattd; + helper.Out.y = pyty * m_Fill * wynze + lattd; + helper.Out.z = pztz * m_Fill * znxy - lattd; + break; + case 3 : + helper.Out.x = pxtx * m_Fill * exnze + lattd; + helper.Out.y = pyty * m_Fill * wynze - lattd; + helper.Out.z = pztz * m_Fill * znxy - lattd; + break; + case 4 : + helper.Out.x = pxtx * m_Fill * exnze - lattd; + helper.Out.y = pyty * m_Fill * wynze + lattd; + helper.Out.z = pztz * m_Fill * znxy + lattd; + break; + case 5 : + helper.Out.x = pxtx * m_Fill * exnze - lattd; + helper.Out.y = pyty * m_Fill * wynze - lattd; + helper.Out.z = pztz * m_Fill * znxy + lattd; + break; + case 6 : + helper.Out.x = pxtx * m_Fill * exnze - lattd; + helper.Out.y = pyty * m_Fill * wynze + lattd; + helper.Out.z = pztz * m_Fill * znxy - lattd; + break; + case 7 : + helper.Out.x = pxtx * m_Fill * exnze - lattd; + helper.Out.y = pyty * m_Fill * wynze - lattd; + helper.Out.z = pztz * m_Fill * znxy - lattd; + break; + } + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string xpand = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string style = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string fill = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tint useNode = MwcNext(mwc) & 7;\n" + << "\t\treal_t exnze, wynze, znxy, px, py, pz, lattd = xform->m_VariationWeights[" << varIndex << "];\n" + << "\n" + << "\t\tif (" << style << " == 2)\n" + << "\t\t{\n" + << "\t\t exnze = cos(atan2(vIn.x, vIn.z));\n" + << "\t\t wynze = sin(atan2(vIn.y, vIn.z));\n" + << "\t\t znxy = (exnze + wynze) * 0.5;\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t exnze = 1;\n" + << "\t\t wynze = 1;\n" + << "\t\t znxy = 1;\n" + << "\t\t}\n"; + + if (m_VarType == VARTYPE_PRE) + { + ss << + "\t\tpx = vIn.x;\n" + "\t\tpy = vIn.y;\n" + "\t\tpz = vIn.z;\n"; + } + else + { + ss << + "\t\tpx = outPoint->m_X;\n" + "\t\tpy = outPoint->m_Y;\n" + "\t\tpz = outPoint->m_Z;\n"; + } + + ss << "\t\treal_t pxtx = px + vIn.x;\n" + << "\t\treal_t pyty = py + vIn.y;\n" + << "\t\treal_t pztz = pz + vIn.z;\n" + << "\n" + << "\t\tswitch (useNode)\n" + << "\t\t{\n" + << "\t\t case 0 :\n" + << "\t\t vOut.x = pxtx * " << fill << " * exnze + lattd;\n" + << "\t\t vOut.y = pyty * " << fill << " * wynze + lattd;\n" + << "\t\t vOut.z = pztz * " << fill << " * znxy + lattd;\n" + << "\t\t break;\n" + << "\t\t case 1 :\n" + << "\t\t vOut.x = pxtx * " << fill << " * exnze + lattd;\n" + << "\t\t vOut.y = pyty * " << fill << " * wynze - lattd;\n" + << "\t\t vOut.z = pztz * " << fill << " * znxy + lattd;\n" + << "\t\t break;\n" + << "\t\t case 2 :\n" + << "\t\t vOut.x = pxtx * " << fill << " * exnze + lattd;\n" + << "\t\t vOut.y = pyty * " << fill << " * wynze + lattd;\n" + << "\t\t vOut.z = pztz * " << fill << " * znxy - lattd;\n" + << "\t\t break;\n" + << "\t\t case 3 :\n" + << "\t\t vOut.x = pxtx * " << fill << " * exnze + lattd;\n" + << "\t\t vOut.y = pyty * " << fill << " * wynze - lattd;\n" + << "\t\t vOut.z = pztz * " << fill << " * znxy - lattd;\n" + << "\t\t break;\n" + << "\t\t case 4 :\n" + << "\t\t vOut.x = pxtx * " << fill << " * exnze - lattd;\n" + << "\t\t vOut.y = pyty * " << fill << " * wynze + lattd;\n" + << "\t\t vOut.z = pztz * " << fill << " * znxy + lattd;\n" + << "\t\t break;\n" + << "\t\t case 5 :\n" + << "\t\t vOut.x = pxtx * " << fill << " * exnze - lattd;\n" + << "\t\t vOut.y = pyty * " << fill << " * wynze - lattd;\n" + << "\t\t vOut.z = pztz * " << fill << " * znxy + lattd;\n" + << "\t\t break;\n" + << "\t\t case 6 :\n" + << "\t\t vOut.x = pxtx * " << fill << " * exnze - lattd;\n" + << "\t\t vOut.y = pyty * " << fill << " * wynze + lattd;\n" + << "\t\t vOut.z = pztz * " << fill << " * znxy - lattd;\n" + << "\t\t break;\n" + << "\t\t case 7 :\n" + << "\t\t vOut.x = pxtx * " << fill << " * exnze - lattd;\n" + << "\t\t vOut.y = pyty * " << fill << " * wynze - lattd;\n" + << "\t\t vOut.z = pztz * " << fill << " * znxy - lattd;\n" + << "\t\t break;\n" + << "\t\t}\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + if (fabs(m_Xpand) <= 1) + m_Fill = m_Xpand * T(0.5); + else + m_Fill = sqrt(m_Xpand) * T(0.5); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Xpand, prefix + "cubicLattice_3D_xpand", T(0.2)));//Original used a prefix of cubic3D_, which is incompatible with Ember's design. + m_Params.push_back(ParamWithName(&m_Style, prefix + "cubicLattice_3D_style", 1, INTEGER, 1, 2)); + m_Params.push_back(ParamWithName(true, &m_Fill, prefix + "cubicLattice_3D_fill"));//Precalc. + } + +private: + T m_Xpand; + T m_Style; + T m_Fill;//Precalc. +}; + +/// +/// foci_3D. +/// +template +class EMBER_API Foci3DVariation : public Variation +{ +public: + Foci3DVariation(T weight = 1.0) : Variation("foci_3D", VAR_FOCI3D, weight, false, false, false, false, true) { } + + VARCOPY(Foci3DVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T expx = exp(helper.In.x) * T(0.5); + T expnx = T(0.25) / expx; + T boot = helper.In.z == 0 ? helper.m_PrecalcAtanyx : helper.In.z; + T tmp = m_Weight / (expx + expnx - (cos(helper.In.y) * cos(boot))); + + helper.Out.x = (expx - expnx) * tmp; + helper.Out.y = sin(helper.In.y) * tmp; + helper.Out.z = sin(boot) * tmp; + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t expx = exp(vIn.x) * 0.5;\n" + << "\t\treal_t expnx = 0.25 / expx;\n" + << "\t\treal_t boot = vIn.z == 0 ? precalcAtanyx : vIn.z;\n" + << "\t\treal_t tmp = xform->m_VariationWeights[" << varIndex << "] / (expx + expnx - (cos(vIn.y) * cos(boot)));\n" + << "\n" + << "\t\tvOut.x = (expx - expnx) * tmp;\n" + << "\t\tvOut.y = sin(vIn.y) * tmp;\n" + << "\t\tvOut.z = sin(boot) * tmp;\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// ho. +/// +template +class EMBER_API HoVariation : public ParametricVariation +{ +public: + HoVariation(T weight = 1.0) : ParametricVariation("ho", VAR_HO, weight) + { + Init(); + } + + PARVARCOPY(HoVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T uu = SQR(helper.In.x); + T vv = SQR(helper.In.y); + T ww = SQR(helper.In.z); + T alpha = (vv + uu + EPS6); + T beta = (uu + ww + EPS6); + T delta = (vv + ww + EPS6); + T epsilon = (vv + uu + ww + EPS6); + T atOmegaX = atan2(vv, ww); + T atOmegaY = atan2(uu, ww); + T atOmegaZ = atan2(vv, uu); + T su = sin(helper.In.x); + T cu = cos(helper.In.x); + T sv = sin(helper.In.y); + T cv = cos(helper.In.y); + T cucv = cu * cv; + T sucv = su * cv; + T x = pow(ClampGte(cucv, EPS6), m_XPow) + (cucv * m_XPow) + (T(0.25) * atOmegaX);//Must clamp first argument to pow, because negative values will return NaN. + T y = pow(ClampGte(sucv, EPS6), m_YPow) + (sucv * m_YPow) + (T(0.25) * atOmegaY);//Original did not do this and would frequently return bad values. + T z = pow(ClampGte(sv, EPS6), m_ZPow) + sv * m_ZPow; + + helper.Out.x = m_Weight * x; + helper.Out.y = m_Weight * y; + helper.Out.z = m_Weight * z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string xpow = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ypow = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string zpow = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t uu = SQR(vIn.x);\n" + << "\t\treal_t vv = SQR(vIn.y);\n" + << "\t\treal_t ww = SQR(vIn.z);\n" + << "\t\treal_t alpha = (vv + uu + EPS6);\n" + << "\t\treal_t beta = (uu + ww + EPS6);\n" + << "\t\treal_t delta = (vv + ww + EPS6);\n" + << "\t\treal_t epsilon = (vv + uu + ww + EPS6);\n" + << "\t\treal_t atOmegaX = atan2(vv, ww);\n" + << "\t\treal_t atOmegaY = atan2(uu, ww);\n" + << "\t\treal_t atOmegaZ = atan2(vv, uu);\n" + << "\t\treal_t su = sin(vIn.x);\n" + << "\t\treal_t cu = cos(vIn.x);\n" + << "\t\treal_t sv = sin(vIn.y);\n" + << "\t\treal_t cv = cos(vIn.y);\n" + << "\t\treal_t cucv = cu * cv;\n" + << "\t\treal_t sucv = su * cv;\n" + << "\t\treal_t x = pow(ClampGte(cucv, EPS6), " << xpow << ") + (cucv * " << xpow << ") + (0.25 * atOmegaX);\n" + << "\t\treal_t y = pow(ClampGte(sucv, EPS6), " << ypow << ") + (sucv * " << ypow << ") + (0.25 * atOmegaY);\n" + << "\t\treal_t z = pow(ClampGte(sv, EPS6), " << zpow << ") + sv * " << zpow << ";\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * x;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * y;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * z;\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_XPow, prefix + "ho_xpow", 3)); + m_Params.push_back(ParamWithName(&m_YPow, prefix + "ho_ypow", 3)); + m_Params.push_back(ParamWithName(&m_ZPow, prefix + "ho_zpow", 3)); + } + +private: + T m_XPow; + T m_YPow; + T m_ZPow; +}; + +/// +/// Julia3Dq. +/// +template +class EMBER_API Julia3DqVariation : public ParametricVariation +{ +public: + Julia3DqVariation(T weight = 1.0) : ParametricVariation("julia3Dq", VAR_JULIA3DQ, weight, true, true, false, false, true) + { + Init(); + } + + PARVARCOPY(Julia3DqVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T temp = helper.m_PrecalcAtanyx * m_InvPower + rand.Rand() * m_InvPower2pi; + T sina = sin(temp); + T cosa = cos(temp); + T z = helper.In.z * m_AbsInvPower; + T r2d = helper.m_PrecalcSumSquares; + T r = m_Weight * pow(r2d + SQR(z), m_HalfInvPower); + T rsss = r * helper.m_PrecalcSqrtSumSquares; + + helper.Out.x = rsss * cosa; + helper.Out.y = rsss * sina; + helper.Out.z = r * z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string power = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string divisor = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string invPower = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string absInvPower = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string halfInvPower = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string invPower2pi = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t temp = precalcAtanyx * " << invPower << " + MwcNext(mwc) * " << invPower2pi << ";\n" + << "\t\treal_t sina = sin(temp);\n" + << "\t\treal_t cosa = cos(temp);\n" + << "\t\treal_t z = vIn.z * " << absInvPower << ";\n" + << "\t\treal_t r2d = precalcSumSquares;\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * pow(r2d + SQR(z), " << halfInvPower << ");\n" + << "\t\treal_t rsss = r * precalcSqrtSumSquares;\n" + << "\n" + << "\t\tvOut.x = rsss * cosa;\n" + << "\t\tvOut.y = rsss * sina;\n" + << "\t\tvOut.z = r * z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_InvPower = m_Divisor / m_Power; + m_AbsInvPower = fabs(m_InvPower); + m_HalfInvPower = T(0.5) * m_InvPower - T(0.5); + m_InvPower2pi = M_2PI / m_Power; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Power, prefix + "julia3Dq_power", 3, INTEGER_NONZERO)); + m_Params.push_back(ParamWithName(&m_Divisor, prefix + "julia3Dq_divisor", 2, INTEGER_NONZERO)); + m_Params.push_back(ParamWithName(true, &m_InvPower, prefix + "julia3Dq_inv_power"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_AbsInvPower, prefix + "julia3Dq_abs_inv_power")); + m_Params.push_back(ParamWithName(true, &m_HalfInvPower, prefix + "julia3Dq_half_inv_power")); + m_Params.push_back(ParamWithName(true, &m_InvPower2pi, prefix + "julia3Dq_inv_power_2pi")); + } + +private: + T m_Power; + T m_Divisor; + T m_InvPower;//Precalc. + T m_AbsInvPower; + T m_HalfInvPower; + T m_InvPower2pi; +}; + +/// +/// line. +/// +template +class EMBER_API LineVariation : public ParametricVariation +{ +public: + LineVariation(T weight = 1.0) : ParametricVariation("line", VAR_LINE, weight) + { + Init(); + } + + PARVARCOPY(LineVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r = rand.Frand01() * m_Weight; + + helper.Out.x = m_Ux * r; + helper.Out.y = m_Uy * r; + helper.Out.z = m_Uz * r; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string delta = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string phi = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ux = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string uy = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string uz = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t r = MwcNext01(mwc) * xform->m_VariationWeights[" << varIndex << "];\n" + << "\n" + << "\t\tvOut.x = " << ux << " * r;\n" + << "\t\tvOut.y = " << uy << " * r;\n" + << "\t\tvOut.z = " << uz << " * r;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + //Unit vector of the line. + m_Ux = cos(m_Delta * T(M_PI)) * cos(m_Phi * T(M_PI)); + m_Uy = sin(m_Delta * T(M_PI)) * cos(m_Phi * T(M_PI)); + m_Uz = sin(m_Phi * T(M_PI)); + + T r = sqrt(SQR(m_Ux) + SQR(m_Uy) + SQR(m_Uz)); + + //Normalize. + m_Ux /= r; + m_Uy /= r; + m_Uz /= r; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Delta, prefix + "line_delta")); + m_Params.push_back(ParamWithName(&m_Phi, prefix + "line_phi")); + m_Params.push_back(ParamWithName(true, &m_Ux, prefix + "line_ux"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Uy, prefix + "line_uy")); + m_Params.push_back(ParamWithName(true, &m_Uz, prefix + "line_uz")); + } + +private: + T m_Delta; + T m_Phi; + T m_Ux;//Precalc. + T m_Uy; + T m_Uz; +}; + +/// +/// loonie_3D. +/// +template +class EMBER_API Loonie3DVariation : public ParametricVariation +{ +public: + Loonie3DVariation(T weight = 1.0) : ParametricVariation("loonie_3D", VAR_LOONIE3D, weight, true, false, false, false, true) + { + Init(); + } + + PARVARCOPY(Loonie3DVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T rmod = rand.Frand01() * T(0.5) + T(0.125); + T kikr = helper.m_PrecalcAtanyx; + T efTez = helper.In.z == 0 ? kikr : helper.In.z; + T r2 = helper.m_PrecalcSumSquares + SQR(efTez); + + if (r2 < m_Vv) + { + T r = m_Weight * sqrt(m_Vv / r2 - 1); + + helper.Out.x = r * helper.In.x; + helper.Out.y = r * helper.In.y; + helper.Out.z = r * efTez * T(0.5); + } + else + { + helper.Out.x = m_Weight * helper.In.x; + helper.Out.y = m_Weight * helper.In.y; + helper.Out.z = m_Weight * efTez * T(0.5); + } + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string vv = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t rmod = MwcNext01(mwc) * 0.5 + 0.125;\n" + << "\t\treal_t kikr = precalcAtanyx;\n" + << "\t\treal_t efTez = vIn.z == 0 ? kikr : vIn.z;\n" + << "\t\treal_t r2 = precalcSumSquares + SQR(efTez);\n" + << "\n" + << "\t\tif (r2 < " << vv << ")\n" + << "\t\t{\n" + << "\t\t real_t r = xform->m_VariationWeights[" << varIndex << "] * sqrt(" << vv << " / r2 - 1);\n" + << "\n" + << "\t\t vOut.x = r * vIn.x;\n" + << "\t\t vOut.y = r * vIn.y;\n" + << "\t\t vOut.z = r * efTez * 0.5;\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\t\t vOut.z = xform->m_VariationWeights[" << varIndex << "] * efTez * 0.5;\n" + << "\t\t}\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Vv = SQR(m_Weight); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(true, &m_Vv, prefix + "loonie_3D_vv"));//Precalcs only, no params. + } + +private: + T m_Vv;//Precalcs only, no params. +}; + +/// +/// mcarpet. +/// +template +class EMBER_API McarpetVariation : public ParametricVariation +{ +public: + McarpetVariation(T weight = 1.0) : ParametricVariation("mcarpet", VAR_MCARPET, weight, true) + { + Init(); + } + + PARVARCOPY(McarpetVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T t = helper.m_PrecalcSumSquares * T(0.25) + 1; + T r = m_Weight / t; + + helper.Out.x = helper.In.x * r * m_X; + helper.Out.y = helper.In.y * r * m_Y; + helper.Out.x += (1 - (m_Twist * SQR(helper.In.x)) + helper.In.y) * m_Weight;//The += is intentional. + helper.Out.y += m_Tilt * helper.In.x * m_Weight; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string twist = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string tilt = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t t = precalcSumSquares * 0.25 + 1;\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] / t;\n" + << "\n" + << "\t\tvOut.x = vIn.x * r * " << x << ";\n" + << "\t\tvOut.y = vIn.y * r * " << y << ";\n" + << "\t\tvOut.x += (1 - (" << twist << " * SQR(vIn.x)) + vIn.y) * xform->m_VariationWeights[" << varIndex << "];\n" + << "\t\tvOut.y += " << tilt << " * vIn.x * xform->m_VariationWeights[" << varIndex << "];\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_X, prefix + "mcarpet_x")); + m_Params.push_back(ParamWithName(&m_Y, prefix + "mcarpet_y")); + m_Params.push_back(ParamWithName(&m_Twist, prefix + "mcarpet_twist")); + m_Params.push_back(ParamWithName(&m_Tilt, prefix + "mcarpet_tilt")); + } + +private: + T m_X; + T m_Y; + T m_Twist; + T m_Tilt; +}; + +/// +/// waves2_3D. +/// Original used a precalc for the input points, but it doesn't +/// work with Ember's design, so it gets calculated on every iter +/// which is slightly slower. +/// +template +class EMBER_API Waves23DVariation : public ParametricVariation +{ +public: + Waves23DVariation(T weight = 1.0) : ParametricVariation("waves2_3D", VAR_WAVES23D, weight) + { + Init(); + } + + PARVARCOPY(Waves23DVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T avgxy = (helper.In.x + helper.In.y) * T(0.5); + + helper.Out.x = m_Weight * (helper.In.x + m_Scale * sin(helper.In.y * m_Freq)); + helper.Out.y = m_Weight * (helper.In.y + m_Scale * sin(helper.In.x * m_Freq)); + helper.Out.z = m_Weight * (helper.In.z + m_Scale * sin(avgxy * m_Freq));//Averages the XY to get Z. + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string freq = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string scale = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t avgxy = (vIn.x + vIn.y) * 0.5;\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (vIn.x + " << scale << " * sin(vIn.y * " << freq << "));\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (vIn.y + " << scale << " * sin(vIn.x * " << freq << "));\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * (vIn.z + " << scale << " * sin(avgxy * " << freq << "));\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Freq, prefix + "waves2_3D_freq", 2)); + m_Params.push_back(ParamWithName(&m_Scale, prefix + "waves2_3D_scale", 1)); + } + +private: + T m_Freq; + T m_Scale; +}; + +/// +/// Pie3D. +/// +template +class EMBER_API Pie3DVariation : public ParametricVariation +{ +public: + Pie3DVariation(T weight = 1.0) : ParametricVariation("pie3D", VAR_PIE3D, weight) + { + Init(); + } + + PARVARCOPY(Pie3DVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + int sl = (int)(rand.Frand01() * m_Slices + T(0.5)); + T a = m_Rotation + M_2PI * (sl + rand.Frand01() * m_Thickness) / m_Slices; + T r = m_Weight * rand.Frand01(); + + helper.Out.x = r * cos(a); + helper.Out.y = r * sin(a); + helper.Out.z = m_Weight * sin(r); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string slices = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string rotation = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string thickness = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tint sl = (int)(MwcNext01(mwc) * " << slices << " + 0.5);\n" + << "\t\treal_t a = " << rotation << " + M_2PI * (sl + MwcNext01(mwc) * " << thickness << ") / " << slices << ";\n" + << "\t\treal_t r = xform->m_VariationWeights[" << varIndex << "] * MwcNext01(mwc);\n" + << "\n" + << "\t\tvOut.x = r * cos(a);\n" + << "\t\tvOut.y = r * sin(a);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * sin(r);\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Random(QTIsaac& rand) + { + m_Params[0].Set((int)10 * rand.Frand01());//Slices. + m_Params[1].Set(M_2PI * rand.Frand11());//Rotation. + m_Thickness = rand.Frand01(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Slices, prefix + "pie3D_slices", 6, INTEGER_NONZERO, 1)); + m_Params.push_back(ParamWithName(&m_Rotation, prefix + "pie3D_rotation", T(0.5), REAL_CYCLIC, 0, M_2PI)); + m_Params.push_back(ParamWithName(&m_Thickness, prefix + "pie3D_thickness", T(0.5), REAL, 0, 1)); + } + +private: + T m_Slices; + T m_Rotation; + T m_Thickness; +}; + +/// +/// popcorn2_3D. +/// +template +class EMBER_API Popcorn23DVariation : public ParametricVariation +{ +public: + Popcorn23DVariation(T weight = 1.0) : ParametricVariation("popcorn2_3D", VAR_POPCORN23D, weight, false, false, false, false, true) + { + Init(); + } + + PARVARCOPY(Popcorn23DVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T otherZ, tempPZ = 0; + T tempTZ = helper.In.z == 0 ? m_Vv * m_SinTanC * helper.m_PrecalcAtanyx : helper.In.z; + + if (m_VarType == VARTYPE_PRE) + otherZ = helper.In.z; + else + otherZ = outPoint.m_Z; + + if (otherZ == 0) + tempPZ = m_Vv * m_SinTanC * helper.m_PrecalcAtanyx; + + helper.Out.x = m_HalfWeight * (helper.In.x + m_X * sin(tan(m_C * helper.In.y))); + helper.Out.y = m_HalfWeight * (helper.In.y + m_Y * sin(tan(m_C * helper.In.x))); + helper.Out.z = tempPZ + m_Vv * (m_Z * m_SinTanC * tempTZ); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string z = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string c = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string stc = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string hw = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string vv = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t otherZ, tempPZ = 0;\n" + << "\t\treal_t tempTZ = vIn.z == 0 ? " << vv << " * " << stc << " * precalcAtanyx : vIn.z;\n"; + + if (m_VarType == VARTYPE_PRE) + ss << "\t\totherZ = vIn.z;\n"; + else + ss << "\t\totherZ = outPoint->m_Z;\n"; + + ss << "\t\tif (otherZ == 0)\n" + << "\t\t tempPZ = " << vv << " * " << stc << " * precalcAtanyx;\n" + << "\n" + << "\t\tvOut.x = " << hw << " * (vIn.x + " << x << " * sin(tan(" << c << " * vIn.y)));\n" + << "\t\tvOut.y = " << hw << " * (vIn.y + " << y << " * sin(tan(" << c << " * vIn.x)));\n" + << "\t\tvOut.z = tempPZ + " << vv << " * (" << z << " * " << stc << " * tempTZ);\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_SinTanC = sin(tan(m_C)); + m_HalfWeight = m_Weight * T(0.5); + + if (fabs(m_Weight) <= 1) + m_Vv = fabs(m_Weight) * m_Weight;//Sqr(m_Weight) value retaining sign. + else + m_Vv = m_Weight; + } + + virtual void Random(QTIsaac& rand) + { + m_X = T(0.2) + rand.Frand01(); + m_Y = T(0.2) * rand.Frand01(); + m_Z = T(0.2) * rand.Frand01(); + m_C = 5 * rand.Frand01(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_X, prefix + "popcorn2_3D_x", T(0.1))); + m_Params.push_back(ParamWithName(&m_Y, prefix + "popcorn2_3D_y", T(0.1))); + m_Params.push_back(ParamWithName(&m_Z, prefix + "popcorn2_3D_z", T(0.1))); + m_Params.push_back(ParamWithName(&m_C, prefix + "popcorn2_3D_c", 3)); + m_Params.push_back(ParamWithName(true, &m_SinTanC, prefix + "popcorn2_3D_sintanc")); + m_Params.push_back(ParamWithName(true, &m_HalfWeight, prefix + "popcorn2_3D_half_weight")); + m_Params.push_back(ParamWithName(true, &m_Vv, prefix + "popcorn2_3D_vv")); + } + +private: + T m_X; + T m_Y; + T m_Z; + T m_C; + T m_SinTanC;//Precalcs. + T m_HalfWeight; + T m_Vv; +}; + +/// +/// sinusoidal3d. +/// +template +class EMBER_API Sinusoidal3DVariation : public Variation +{ +public: + Sinusoidal3DVariation(T weight = 1.0) : Variation("sinusoidal3D", VAR_SINUSOIDAL3D, weight) { } + + VARCOPY(Sinusoidal3DVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + helper.Out.x = m_Weight * sin(helper.In.x); + helper.Out.y = m_Weight * sin(helper.In.y); + helper.Out.z = m_Weight * (atan2(SQR(helper.In.x), SQR(helper.In.y)) * cos(helper.In.z)); + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * sin(vIn.x);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * sin(vIn.y);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * (atan2(SQR(vIn.x), SQR(vIn.y)) * cos(vIn.z));\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// scry_3D. +/// +template +class EMBER_API Scry3DVariation : public ParametricVariation +{ +public: + Scry3DVariation(T weight = 1.0) : ParametricVariation("scry_3D", VAR_SCRY3D, weight, true, false, false, false, true) + { + Init(); + } + + PARVARCOPY(Scry3DVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T t = helper.m_PrecalcSumSquares + SQR(helper.In.z); + T r = 1 / (sqrt(t) * (t + m_InvWeight)); + T z = helper.In.z == 0 ? helper.m_PrecalcAtanyx : helper.In.z; + + helper.Out.x = helper.In.x * r; + helper.Out.y = helper.In.y * r; + helper.Out.z = z * r; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string invWeight = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t t = precalcSumSquares + SQR(vIn.z);\n" + << "\t\treal_t r = 1 / (sqrt(t) * (t + " << invWeight << "));\n" + << "\t\treal_t z = vIn.z == 0 ? precalcAtanyx : vIn.z;\n" + << "\n" + << "\t\tvOut.x = vIn.x * r;\n" + << "\t\tvOut.y = vIn.y * r;\n" + << "\t\tvOut.z = z * r;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_InvWeight = 1 / (m_Weight + EPS6); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(true, &m_InvWeight, prefix + "scry_3D_inv_weight"));//Precalcs only, no params. + } + +private: + T m_InvWeight;//Precalcs only, no params. +}; + +/// +/// shredlin. +/// +template +class EMBER_API ShredlinVariation : public ParametricVariation +{ +public: + ShredlinVariation(T weight = 1.0) : ParametricVariation("shredlin", VAR_SHRED_LIN, weight) + { + Init(); + } + + PARVARCOPY(ShredlinVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + const int xpos = helper.In.x < 0; + const int ypos = helper.In.y < 0; + const T xrng = helper.In.x / m_XDistance; + const T yrng = helper.In.y / m_YDistance; + + helper.Out.x = m_Xw * ((xrng - (int)xrng) * m_XWidth + (int)xrng + (T(0.5) - xpos) * m_1mX); + helper.Out.y = m_Yw * ((yrng - (int)yrng) * m_YWidth + (int)yrng + (T(0.5) - ypos) * m_1mY); + helper.Out.z = m_Weight * helper.In.z; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string xdist = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string xwidth = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ydist = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ywidth = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string xw = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string yw = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string onemx = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string onemy = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tconst int xpos = vIn.x < 0;\n" + << "\t\tconst int ypos = vIn.y < 0;\n" + << "\t\tconst real_t xrng = vIn.x / " << xdist << ";\n" + << "\t\tconst real_t yrng = vIn.y / " << ydist << ";\n" + << "\n" + << "\t\tvOut.x = " << xw << " * ((xrng - (int)xrng) * " << xwidth << " + (int)xrng + (0.5 - xpos) * " << onemx << ");\n" + << "\t\tvOut.y = " << yw << " * ((yrng - (int)yrng) * " << ywidth << " + (int)yrng + (0.5 - ypos) * " << onemy << ");\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Xw = m_Weight * m_XDistance; + m_Yw = m_Weight * m_YDistance; + m_1mX = 1 - m_XWidth; + m_1mY = 1 - m_YWidth; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_XDistance, prefix + "shredlin_xdistance", 1, REAL_NONZERO)); + m_Params.push_back(ParamWithName(&m_XWidth, prefix + "shredlin_xwidth", T(0.5), REAL, -1, 1)); + m_Params.push_back(ParamWithName(&m_YDistance, prefix + "shredlin_ydistance", 1, REAL_NONZERO)); + m_Params.push_back(ParamWithName(&m_YWidth, prefix + "shredlin_ywidth", T(0.5), REAL, -1, 1)); + m_Params.push_back(ParamWithName(true, &m_Xw, prefix + "shredlin_xw")); + m_Params.push_back(ParamWithName(true, &m_Yw, prefix + "shredlin_yw")); + m_Params.push_back(ParamWithName(true, &m_1mX, prefix + "shredlin_1mx")); + m_Params.push_back(ParamWithName(true, &m_1mY, prefix + "shredlin_1my")); + } + +private: + T m_XDistance; + T m_XWidth; + T m_YDistance; + T m_YWidth; + T m_Xw;//Precalc. + T m_Yw; + T m_1mX; + T m_1mY; +}; + +/// +/// splitbrdr. +/// +template +class EMBER_API SplitBrdrVariation : public ParametricVariation +{ +public: + SplitBrdrVariation(T weight = 1.0) : ParametricVariation("SplitBrdr", VAR_SPLIT_BRDR, weight, true) + { + Init(); + } + + PARVARCOPY(SplitBrdrVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T b = m_Weight / (helper.m_PrecalcSumSquares * T(0.25) + 1); + T roundX = Rint(helper.In.x); + T roundY = Rint(helper.In.y); + T offsetX = helper.In.x - roundX; + T offsetY = helper.In.y - roundY; + + helper.Out.x = helper.In.x * b; + helper.Out.y = helper.In.y * b; + + if (rand.Frand01() >= T(0.75)) + { + helper.Out.x += m_Weight * (offsetX * T(0.5) + roundX); + helper.Out.y += m_Weight * (offsetY * T(0.5) + roundY); + } + else + { + if (fabs(offsetX) >= fabs(offsetY)) + { + if (offsetX >= 0) + { + helper.Out.x += m_Weight * (offsetX * T(0.5) + roundX + m_X); + helper.Out.y += m_Weight * (offsetY * T(0.5) + roundY + m_Y * offsetY / offsetX); + } + else + { + helper.Out.x += m_Weight * (offsetX * T(0.5) + roundX - m_Y); + helper.Out.y += m_Weight * (offsetY * T(0.5) + roundY - m_Y * offsetY / offsetX); + } + } + else + { + if (offsetY >= 0) + { + helper.Out.y += m_Weight * (offsetY * T(0.5) + roundY + m_Y); + helper.Out.x += m_Weight * (offsetX * T(0.5) + roundX + offsetX / offsetY * m_Y); + } + else + { + helper.Out.y += m_Weight * (offsetY * T(0.5) + roundY - m_Y); + helper.Out.x += m_Weight * (offsetX * T(0.5) + roundX - offsetX / offsetY * m_X); + } + } + } + + helper.Out.x += helper.In.x * m_Px; + helper.Out.y += helper.In.y * m_Py; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string px = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string py = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t b = xform->m_VariationWeights[" << varIndex << "] / (precalcSumSquares * 0.25 + 1);\n" + << "\t\treal_t roundX = rint(vIn.x);\n" + << "\t\treal_t roundY = rint(vIn.y);\n" + << "\t\treal_t offsetX = vIn.x - roundX;\n" + << "\t\treal_t offsetY = vIn.y - roundY;\n" + << "\n" + << "\t\tvOut.x = vIn.x * b;\n" + << "\t\tvOut.y = vIn.y * b;\n" + << "\n" + << "\t\tif (MwcNext01(mwc) >= 0.75)\n" + << "\t\t{\n" + << "\t\t vOut.x += xform->m_VariationWeights[" << varIndex << "] * (offsetX * 0.5 + roundX);\n" + << "\t\t vOut.y += xform->m_VariationWeights[" << varIndex << "] * (offsetY * 0.5 + roundY);\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t if (fabs(offsetX) >= fabs(offsetY))\n" + << "\t\t {\n" + << "\t\t if (offsetX >= 0)\n" + << "\t\t {\n" + << "\t\t vOut.x += xform->m_VariationWeights[" << varIndex << "] * (offsetX * 0.5 + roundX + " << x << ");\n" + << "\t\t vOut.y += xform->m_VariationWeights[" << varIndex << "] * (offsetY * 0.5 + roundY + " << y << " * offsetY / offsetX);\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t vOut.x += xform->m_VariationWeights[" << varIndex << "] * (offsetX * 0.5 + roundX - " << y << ");\n" + << "\t\t vOut.y += xform->m_VariationWeights[" << varIndex << "] * (offsetY * 0.5 + roundY - " << y << " * offsetY / offsetX);\n" + << "\t\t }\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t if (offsetY >= 0)\n" + << "\t\t {\n" + << "\t\t vOut.y += xform->m_VariationWeights[" << varIndex << "] * (offsetY * 0.5 + roundY + " << y << ");\n" + << "\t\t vOut.x += xform->m_VariationWeights[" << varIndex << "] * (offsetX * 0.5 + roundX + offsetX / offsetY * " << y << ");\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t vOut.y += xform->m_VariationWeights[" << varIndex << "] * (offsetY * 0.5 + roundY - " << y << ");\n" + << "\t\t vOut.x += xform->m_VariationWeights[" << varIndex << "] * (offsetX * 0.5 + roundX - offsetX / offsetY * " << x << ");\n" + << "\t\t }\n" + << "\t\t }\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.x += vIn.x * " << px << ";\n" + << "\t\tvOut.y += vIn.y * " << py << ";\n" + << "\t}\n"; + + return ss.str(); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_X, prefix + "SplitBrdr_x", T(0.25)));//Original used a prefix of splitb_, which is incompatible with Ember's design. + m_Params.push_back(ParamWithName(&m_Y, prefix + "SplitBrdr_y", T(0.25))); + m_Params.push_back(ParamWithName(&m_Px, prefix + "SplitBrdr_px")); + m_Params.push_back(ParamWithName(&m_Py, prefix + "SplitBrdr_py")); + } + +private: + T m_X; + T m_Y; + T m_Px; + T m_Py; +}; + +/// +/// wdisc. +/// +template +class EMBER_API WdiscVariation : public Variation +{ +public: + WdiscVariation(T weight = 1.0) : Variation("wdisc", VAR_WDISC, weight, true, true, false, false, true) { } + + VARCOPY(WdiscVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T a = T(M_PI) / (helper.m_PrecalcSqrtSumSquares + 1); + T r = helper.m_PrecalcAtanyx * T(M_1_PI); + + if (r > 0) + a = T(M_PI) - a; + + helper.Out.x = m_Weight * r * cos(a); + helper.Out.y = m_Weight * r * sin(a); + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t a = M_PI / (precalcSqrtSumSquares + 1);\n" + << "\t\treal_t r = precalcAtanyx * M_1_PI;\n" + << "\n" + << "\t\tif (r > 0)\n" + << "\t\t a = M_PI - a;\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * r * cos(a);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * r * sin(a);\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// falloff. +/// +template +class EMBER_API FalloffVariation : public ParametricVariation +{ +public: + FalloffVariation(T weight = 1.0) : ParametricVariation("falloff", VAR_FALLOFF, weight, false, false, false, false, true) + { + Init(); + } + + PARVARCOPY(FalloffVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + const T ax = rand.Frand11(); + const T ay = rand.Frand11(); + const T az = rand.Frand11(); + const T r = sqrt(Sqr(helper.In.x - m_X0) + Sqr(helper.In.y - m_Y0) + Sqr(helper.In.z - m_Z0)); + const T rc = ((m_Invert != 0 ? min(1 - r, 0) : min(r, 0)) - m_MinDist) * m_InternalScatter; + const T rs = min(rc, 0); + + T sigma, phi, rad, sigmas, sigmac, phis, phic; + T scale, denom; + + switch ((int)m_Type) + { + case 0://Linear. + helper.Out.x = m_Weight * (helper.In.x + m_MulX * ax * rs); + helper.Out.y = m_Weight * (helper.In.y + m_MulY * ay * rs); + helper.Out.z = m_Weight * (helper.In.z + m_MulZ * az * rs); + break; + case 1://Radial. + sigma = asin(r == 0 ? 0 : helper.In.z / r) + m_MulZ * az * rs; + phi = helper.m_PrecalcAtanyx + m_MulY * ay * rs; + rad = r + m_MulX * ax * rs; + + sigmas = sin(sigma); + sigmac = cos(sigma); + phis = sin(phi); + phic = cos(phi); + + helper.Out.x = m_Weight * (rad * sigmac * phic); + helper.Out.y = m_Weight * (rad * sigmac * phis); + helper.Out.z = m_Weight * (rad * sigmas); + break; + case 2://Box. + scale = Clamp(rs, 0, T(0.9)) + T(0.1); + denom = 1 / scale; + helper.Out.x = m_Weight * Lerp(helper.In.x, floor(helper.In.x * denom) + scale * ax, m_MulX * rs) + m_MulX * pow(ax, m_BoxPow) * rs * denom; + helper.Out.y = m_Weight * Lerp(helper.In.y, floor(helper.In.y * denom) + scale * ay, m_MulY * rs) + m_MulY * pow(ay, m_BoxPow) * rs * denom; + helper.Out.z = m_Weight * Lerp(helper.In.z, floor(helper.In.z * denom) + scale * az, m_MulZ * rs) + m_MulZ * pow(az, m_BoxPow) * rs * denom; + break; + } + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string scatter = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string minDist = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string mulX = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string mulY = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string mulZ = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string x0 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string y0 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string z0 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string invert = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string type = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string boxPow = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string internalScatter = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tconst real_t ax = MwcNextNeg1Pos1(mwc);\n" + << "\t\tconst real_t ay = MwcNextNeg1Pos1(mwc);\n" + << "\t\tconst real_t az = MwcNextNeg1Pos1(mwc);\n" + << "\t\tconst real_t r = sqrt(Sqr(vIn.x - " << x0 << ") + Sqr(vIn.y - " << y0 << ") + Sqr(vIn.z - " << z0 << "));\n" + << "\t\tconst real_t rc = ((" << invert << " != 0 ? min(1 - r, 0.0) : min(r, 0.0)) - " << minDist << ") * " << internalScatter << ";\n" + << "\t\tconst real_t rs = min(rc, 0.0);\n" + << "\n" + << "\t\treal_t sigma, phi, rad, sigmas, sigmac, phis, phic;\n" + << "\t\treal_t scale, denom;\n" + << "\n" + << "\t\tswitch ((int)" << type << ")\n" + << "\t\t{\n" + << "\t\t case 0:\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (vIn.x + " << mulX << " * ax * rs);\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * (vIn.y + " << mulY << " * ay * rs);\n" + << "\t\t vOut.z = xform->m_VariationWeights[" << varIndex << "] * (vIn.z + " << mulZ << " * az * rs);\n" + << "\t\t break;\n" + << "\t\t case 1:\n" + << "\t\t sigma = asin(r == 0 ? 0 : vIn.z / r) + " << mulZ << " * az * rs;\n" + << "\t\t phi = precalcAtanyx + " << mulY << " * ay * rs;\n" + << "\t\t rad = r + " << mulX << " * ax * rs;\n" + << "\n" + << "\t\t sigmas = sin(sigma);\n" + << "\t\t sigmac = cos(sigma);\n" + << "\t\t phis = sin(phi);\n" + << "\t\t phic = cos(phi);\n" + << "\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (rad * sigmac * phic);\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * (rad * sigmac * phis);\n" + << "\t\t vOut.z = xform->m_VariationWeights[" << varIndex << "] * (rad * sigmas);\n" + << "\t\t break;\n" + << "\t\t case 2:\n" + << "\t\t scale = Clamp(rs, 0, 0.9) + 0.1;\n" + << "\t\t denom = 1 / scale;\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * Lerp(vIn.x, floor(vIn.x * denom) + scale * ax, " << mulX << " * rs) + " << mulX << " * pow(ax, " << boxPow << ") * rs * denom;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * Lerp(vIn.y, floor(vIn.y * denom) + scale * ay, " << mulY << " * rs) + " << mulY << " * pow(ay, " << boxPow << ") * rs * denom;\n" + << "\t\t vOut.z = xform->m_VariationWeights[" << varIndex << "] * Lerp(vIn.z, floor(vIn.z * denom) + scale * az, " << mulZ << " * rs) + " << mulZ << " * pow(az, " << boxPow << ") * rs * denom;\n" + << "\t\t break;\n" + << "\t\t}\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_InternalScatter = T(0.04) * m_Scatter; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Scatter, prefix + "falloff_scatter", 1, REAL, EPS6, TMAX)); + m_Params.push_back(ParamWithName(&m_MinDist, prefix + "falloff_mindist", T(0.5), REAL, 0, TMAX)); + m_Params.push_back(ParamWithName(&m_MulX, prefix + "falloff_mul_x", 1, REAL, 0, 1)); + m_Params.push_back(ParamWithName(&m_MulY, prefix + "falloff_mul_y", 1, REAL, 0, 1)); + m_Params.push_back(ParamWithName(&m_MulZ, prefix + "falloff_mul_z", 0, REAL, 0, 1)); + m_Params.push_back(ParamWithName(&m_X0, prefix + "falloff_x0")); + m_Params.push_back(ParamWithName(&m_Y0, prefix + "falloff_y0")); + m_Params.push_back(ParamWithName(&m_Z0, prefix + "falloff_z0")); + m_Params.push_back(ParamWithName(&m_Invert, prefix + "falloff_invert", 0, INTEGER, 0, 1)); + m_Params.push_back(ParamWithName(&m_Type, prefix + "falloff_type", 0, INTEGER, 0, 2)); + m_Params.push_back(ParamWithName(&m_BoxPow, prefix + "falloff_boxpow", 2, INTEGER, 2, 32));//Original defaulted this to 0 which directly contradicts the specified range of 2-32. + m_Params.push_back(ParamWithName(true, &m_InternalScatter, prefix + "falloff_internal_scatter")); + } + +private: + T m_Scatter; + T m_MinDist; + T m_MulX; + T m_MulY; + T m_MulZ; + T m_X0; + T m_Y0; + T m_Z0; + T m_Invert; + T m_Type; + T m_BoxPow; + T m_InternalScatter; +}; + +/// +/// falloff2. +/// +template +class EMBER_API Falloff2Variation : public ParametricVariation +{ +public: + Falloff2Variation(T weight = 1.0) : ParametricVariation("falloff2", VAR_FALLOFF2, weight, true, false, false, false, true) + { + Init(); + } + + PARVARCOPY(Falloff2Variation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + const v4T random(rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11()); + const T distA = sqrt(Sqr(helper.In.x - m_X0) + Sqr(helper.In.y - m_Y0) + Sqr(helper.In.z - m_Z0)); + const T distB = m_Invert != 0 ? min(1 - distA, 0) : min(distA, 0); + const T dist = min((distB - m_MinDist) * m_RMax, 0); + + switch ((int)m_Type) + { + case 0://Linear. + { + helper.Out.x = helper.In.x + m_MulX * random.x * dist; + helper.Out.y = helper.In.y + m_MulY * random.y * dist; + helper.Out.z = helper.In.z + m_MulZ * random.z * dist; + outPoint.m_ColorX = fabs(fmod(outPoint.m_ColorX + m_MulC * random.w * dist, 1)); + } + break; + case 1://Radial. + if (helper.In.x == 0 && helper.In.y == 0 && helper.In.z == 0) + { + helper.Out.x = helper.In.x; + helper.Out.y = helper.In.y; + helper.Out.z = helper.In.z; + } + else + { + const T rIn = sqrt(helper.m_PrecalcSumSquares + SQR(helper.In.z)); + const T sigma = asin(helper.In.z / rIn) + m_MulZ * random.z * dist; + const T phi = helper.m_PrecalcAtanyx + m_MulY * random.y * dist; + const T r = rIn + m_MulX * random.x * dist; + const T sigmas = sin(sigma); + const T sigmac = cos(sigma); + const T phis = sin(phi); + const T phic = cos(phi); + + helper.Out.x = r * sigmac * phic; + helper.Out.y = r * sigmac * phis; + helper.Out.z = r * sigmas; + outPoint.m_ColorX = fabs(fmod(outPoint.m_ColorX + m_MulC * random.w * dist, 1)); + } + break; + case 2://Gaussian. + { + const T sigma = dist * random.y * M_2PI; + const T phi = dist * random.z * T(M_PI); + const T rad = dist * random.x; + const T sigmas = sin(sigma); + const T sigmac = cos(sigma); + const T phis = sin(phi); + const T phic = cos(phi); + + helper.Out.x = helper.In.x + m_MulX * rad * sigmac * phic; + helper.Out.y = helper.In.y + m_MulY * rad * sigmac * phis; + helper.Out.z = helper.In.z + m_MulZ * rad * sigmas; + outPoint.m_ColorX = fabs(fmod(outPoint.m_ColorX + m_MulC * random.w * dist, 1)); + } + break; + } + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string scatter = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string minDist = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string mulX = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string mulY = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string mulZ = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string mulC = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string x0 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string y0 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string z0 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string invert = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string type = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string rMax = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + + << "\t\tconst real_t randx = MwcNextNeg1Pos1(mwc);\n" + << "\t\tconst real_t randy = MwcNextNeg1Pos1(mwc);\n" + << "\t\tconst real_t randz = MwcNextNeg1Pos1(mwc);\n" + << "\t\tconst real_t randc = MwcNextNeg1Pos1(mwc);\n" + << "\t\tconst real_t distA = sqrt(Sqr(vIn.x - " << x0 << ") + Sqr(vIn.y - " << y0 << ") + Sqr(vIn.z - " << z0 << "));\n" + << "\t\tconst real_t distB = " << invert << " != 0 ? min(1 - distA, 0.0) : min(distA, 0.0);\n" + << "\t\tconst real_t dist = min((distB - " << minDist << ") * " << rMax<< ", 0.0);\n" + << "\n" + << "\t\tswitch ((int)" << type << ")\n" + << "\t\t{\n" + << "\t\t case 0:\n" + << "\t\t vOut.x = vIn.x + " << mulX << " * randx * dist;\n" + << "\t\t vOut.y = vIn.y + " << mulY << " * randy * dist;\n" + << "\t\t vOut.z = vIn.z + " << mulZ << " * randz * dist;\n" + << "\t\t outPoint->m_ColorX = fabs(fmod(outPoint->m_ColorX + " << mulC << " * randc * dist, 1.0));\n" + << "\t\t break;\n" + << "\t\t case 1:\n" + << "\t\t if (vIn.x == 0 && vIn.y == 0 && vIn.z == 0)\n" + << "\t\t {\n" + << "\t\t vOut.x = vIn.x;\n" + << "\t\t vOut.y = vIn.y;\n" + << "\t\t vOut.z = vIn.z;\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t real_t rIn = sqrt(precalcSumSquares + SQR(vIn.z));\n" + << "\t\t real_t sigma = asin(vIn.z / rIn) + " << mulZ << " * randz * dist;\n" + << "\t\t real_t phi = precalcAtanyx + " << mulY << " * randy * dist;\n" + << "\t\t real_t r = rIn + " << mulX << " * randx * dist;\n" + << "\t\t real_t sigmas = sin(sigma);\n" + << "\t\t real_t sigmac = cos(sigma);\n" + << "\t\t real_t phis = sin(phi);\n" + << "\t\t real_t phic = cos(phi);\n" + << "\n" + << "\t\t vOut.x = r * sigmac * phic;\n" + << "\t\t vOut.y = r * sigmac * phis;\n" + << "\t\t vOut.z = r * sigmas;\n" + << "\t\t outPoint->m_ColorX = fabs(fmod(outPoint->m_ColorX + " << mulC << " * randc * dist, 1.0));\n" + << "\t\t }\n" + << "\t\t break;\n" + << "\t\t case 2:\n" + << "\t\t real_t sigma = dist * randy * M_2PI;\n" + << "\t\t real_t phi = dist * randz * M_PI;\n" + << "\t\t real_t rad = dist * randx;\n" + << "\t\t real_t sigmas = sin(sigma);\n" + << "\t\t real_t sigmac = cos(sigma);\n" + << "\t\t real_t phis = sin(phi);\n" + << "\t\t real_t phic = cos(phi);\n" + << "\n" + << "\t\t vOut.x = vIn.x + " << mulX << " * rad * sigmac * phic;\n" + << "\t\t vOut.y = vIn.y + " << mulY << " * rad * sigmac * phis;\n" + << "\t\t vOut.z = vIn.z + " << mulZ << " * rad * sigmas;\n" + << "\t\t outPoint->m_ColorX = fabs(fmod(outPoint->m_ColorX + " << mulC << " * randc * dist, 1.0));\n" + << "\t\t break;\n" + << "\t\t}\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_RMax = T(0.04) * m_Scatter; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Scatter, prefix + "falloff2_scatter", 1, REAL, EPS6, TMAX)); + m_Params.push_back(ParamWithName(&m_MinDist, prefix + "falloff2_mindist", T(0.5), REAL, 0, TMAX)); + m_Params.push_back(ParamWithName(&m_MulX, prefix + "falloff2_mul_x", 1, REAL, 0, 1)); + m_Params.push_back(ParamWithName(&m_MulY, prefix + "falloff2_mul_y", 1, REAL, 0, 1)); + m_Params.push_back(ParamWithName(&m_MulZ, prefix + "falloff2_mul_z", 0, REAL, 0, 1)); + m_Params.push_back(ParamWithName(&m_MulC, prefix + "falloff2_mul_c", 0, REAL, 0, 1)); + m_Params.push_back(ParamWithName(&m_X0, prefix + "falloff2_x0")); + m_Params.push_back(ParamWithName(&m_Y0, prefix + "falloff2_y0")); + m_Params.push_back(ParamWithName(&m_Z0, prefix + "falloff2_z0")); + m_Params.push_back(ParamWithName(&m_Invert, prefix + "falloff2_invert", 0, INTEGER, 0, 1)); + m_Params.push_back(ParamWithName(&m_Type, prefix + "falloff2_type", 0, INTEGER, 0, 2)); + m_Params.push_back(ParamWithName(true, &m_RMax, prefix + "falloff2_rmax")); + } + +private: + T m_Scatter; + T m_MinDist; + T m_MulX; + T m_MulY; + T m_MulZ; + T m_MulC; + T m_X0; + T m_Y0; + T m_Z0; + T m_Invert; + T m_Type; + T m_RMax; +}; + +/// +/// falloff3. +/// +template +class EMBER_API Falloff3Variation : public ParametricVariation +{ +public: + Falloff3Variation(T weight = 1.0) : ParametricVariation("falloff3", VAR_FALLOFF3, weight, true, false, false, false, true) + { + Init(); + } + + PARVARCOPY(Falloff3Variation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + const v4T random(rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11()); + T radius; + + switch ((int)m_BlurShape) + { + case 0://Circle. + radius = sqrt(Sqr(helper.In.x - m_CenterX) + Sqr(helper.In.y - m_CenterY) + Sqr(helper.In.z - m_CenterZ)); + break; + case 1://Square. + radius = min(fabs(helper.In.x - m_CenterX), min(fabs(helper.In.y - m_CenterY), (fabs(helper.In.z - m_CenterZ)))); + break; + } + + const T dist = min(((m_InvertDistance != 0 ? min(1 - radius, 0) : min(radius, 0)) - m_MinDistance) * m_RMax, 0); + + switch ((int)m_BlurType) + { + case 0://Gaussian. + { + const T sigma = dist * random.y * M_2PI; + const T phi = dist * random.z * T(M_PI); + const T rad = dist * random.x; + const T sigmas = sin(sigma); + const T sigmac = cos(sigma); + const T phis = sin(phi); + const T phic = cos(phi); + + helper.Out.x = helper.In.x + m_MulX * rad * sigmac * phic; + helper.Out.y = helper.In.y + m_MulY * rad * sigmac * phis; + helper.Out.z = helper.In.z + m_MulZ * rad * sigmas; + outPoint.m_ColorX = fabs(fmod(outPoint.m_ColorX + m_MulC * random.w * dist, 1)); + } + break; + case 1://Radial. + if (helper.In.x == 0 && helper.In.y == 0 && helper.In.z == 0) + { + helper.Out.x = helper.In.x; + helper.Out.y = helper.In.y; + helper.Out.z = helper.In.z; + } + else + { + const T rIn = sqrt(helper.m_PrecalcSumSquares + SQR(helper.In.z)); + const T sigma = asin(helper.In.z / rIn) + m_MulZ * random.z * dist; + const T phi = helper.m_PrecalcAtanyx + m_MulY * random.y * dist; + const T r = rIn + m_MulX * random.x * dist; + const T sigmas = sin(sigma); + const T sigmac = cos(sigma); + const T phis = sin(phi); + const T phic = cos(phi); + + helper.Out.x = r * sigmac * phic; + helper.Out.y = r * sigmac * phis; + helper.Out.z = r * sigmas; + outPoint.m_ColorX = fabs(fmod(outPoint.m_ColorX + m_MulC * random.w * dist, 1)); + } + break; + case 2://Log. + { + const T coeff = m_RMax <= EPS6 ? dist : dist + m_Alpha * (LogMap(dist) - dist); + + helper.Out.x = helper.In.x + LogMap(m_MulX) * LogScale(random.x) * coeff, + helper.Out.y = helper.In.y + LogMap(m_MulY) * LogScale(random.y) * coeff, + helper.Out.z = helper.In.z + LogMap(m_MulZ) * LogScale(random.z) * coeff, + outPoint.m_ColorX = fabs(fmod(outPoint.m_ColorX + LogMap(m_MulC) * LogScale(random.w) * coeff, 1)); + } + break; + } + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string blurType = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string blurShape = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string blurStrength = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string minDist = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string invertDist = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string mulX = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string mulY = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string mulZ = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string mulC = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string centerX = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string centerY = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string centerZ = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string alpha = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string rMax = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tconst real_t randx = MwcNextNeg1Pos1(mwc);\n" + << "\t\tconst real_t randy = MwcNextNeg1Pos1(mwc);\n" + << "\t\tconst real_t randz = MwcNextNeg1Pos1(mwc);\n" + << "\t\tconst real_t randc = MwcNextNeg1Pos1(mwc);\n" + << "\t\treal_t radius;\n" + << "\n" + << "\t\tswitch ((int)" << blurShape << ")\n" + << "\t\t{\n" + << "\t\t case 0:\n" + << "\t\t radius = sqrt(Sqr(vIn.x - " << centerX << ") + Sqr(vIn.y - " << centerY << ") + Sqr(vIn.z - " << centerZ << "));\n" + << "\t\t break;\n" + << "\t\t case 1:\n" + << "\t\t radius = min(fabs(vIn.x - " << centerX << "), min(fabs(vIn.y - " << centerY << "), (fabs(vIn.z - " << centerZ << "))));\n" + << "\t\t break;\n" + << "\t\t}\n" + << "\n" + << "\t\tconst real_t dist = min(((" << invertDist << " != 0 ? min(1 - radius, 0.0) : min(radius, 0.0)) - " << minDist << ") * " << rMax << ", 0.0);\n" + << "\n" + << "\t\tswitch ((int)" << blurType << ")\n" + << "\t\t{\n" + << "\t\tcase 0:\n" + << "\t\t {\n" + << "\t\t real_t sigma = dist * randy * M_2PI;\n" + << "\t\t real_t phi = dist * randz * M_PI;\n" + << "\t\t real_t rad = dist * randx;\n" + << "\t\t real_t sigmas = sin(sigma);\n" + << "\t\t real_t sigmac = cos(sigma);\n" + << "\t\t real_t phis = sin(phi);\n" + << "\t\t real_t phic = cos(phi);\n" + << "\n" + << "\t\t vOut.x = vIn.x + " << mulX << " * rad * sigmac * phic;\n" + << "\t\t vOut.y = vIn.y + " << mulY << " * rad * sigmac * phis;\n" + << "\t\t vOut.z = vIn.z + " << mulZ << " * rad * sigmas;\n" + << "\t\t outPoint->m_ColorX = fabs(fmod(outPoint->m_ColorX + " << mulC << " * randc * dist, 1.0));\n" + << "\t\t }\n" + << "\t\t break;\n" + << "\t\tcase 1:\n" + << "\t\t if (vIn.x == 0 && vIn.y == 0 && vIn.z == 0)\n" + << "\t\t {\n" + << "\t\t vOut.x = vIn.x;\n" + << "\t\t vOut.y = vIn.y;\n" + << "\t\t vOut.z = vIn.z;\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t real_t rIn = sqrt(precalcSumSquares + SQR(vIn.z));\n" + << "\t\t real_t sigma = asin(vIn.z / rIn) + " << mulZ << " * randz * dist;\n" + << "\t\t real_t phi = precalcAtanyx + " << mulY << " * randy * dist;\n" + << "\t\t real_t r = rIn + " << mulX << " * randx * dist;\n" + << "\t\t real_t sigmas = sin(sigma);\n" + << "\t\t real_t sigmac = cos(sigma);\n" + << "\t\t real_t phis = sin(phi);\n" + << "\t\t real_t phic = cos(phi);\n" + << "\n" + << "\t\t vOut.x = r * sigmac * phic;\n" + << "\t\t vOut.y = r * sigmac * phis;\n" + << "\t\t vOut.z = r * sigmas;\n" + << "\t\t outPoint->m_ColorX = fabs(fmod(outPoint->m_ColorX + " << mulC << " * randc * dist, 1.0));\n" + << "\t\t }\n" + << "\t\t break;\n" + << "\t\tcase 2:\n" + << "\t\t {\n" + << "\t\t real_t coeff = " << rMax << " <= EPS6 ? dist : dist + " << alpha << " * (LogMap(dist) - dist);\n" + << "\n" + << "\t\t vOut.x = vIn.x + LogMap(" << mulX << ") * LogScale(randx) * coeff,\n" + << "\t\t vOut.y = vIn.y + LogMap(" << mulY << ") * LogScale(randy) * coeff,\n" + << "\t\t vOut.z = vIn.z + LogMap(" << mulZ << ") * LogScale(randz) * coeff,\n" + << "\t\t outPoint->m_ColorX = fabs(fmod(outPoint->m_ColorX + LogMap(" << mulC << ") * LogScale(randc) * coeff, 1.0));\n" + << "\t\t }\n" + << "\t\t break;\n" + << "\t\t}\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_RMax = T(0.04) * m_BlurStrength; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_BlurType, prefix + "falloff3_blur_type", 0, INTEGER, 0, 3)); + m_Params.push_back(ParamWithName(&m_BlurShape, prefix + "falloff3_blur_shape", 0, INTEGER, 0, 1)); + m_Params.push_back(ParamWithName(&m_BlurStrength, prefix + "falloff3_blur_strength", 1, REAL, EPS6, TMAX)); + m_Params.push_back(ParamWithName(&m_MinDistance, prefix + "falloff3_min_distance", T(0.5), REAL, 0, TMAX)); + m_Params.push_back(ParamWithName(&m_InvertDistance, prefix + "falloff3_invert_distance", 0, INTEGER, 0, 1)); + m_Params.push_back(ParamWithName(&m_MulX, prefix + "falloff3_mul_x", 1, REAL, 0, 1)); + m_Params.push_back(ParamWithName(&m_MulY, prefix + "falloff3_mul_y", 1, REAL, 0, 1)); + m_Params.push_back(ParamWithName(&m_MulZ, prefix + "falloff3_mul_z", 0, REAL, 0, 1)); + m_Params.push_back(ParamWithName(&m_MulC, prefix + "falloff3_mul_c", 0, REAL, 0, 1)); + m_Params.push_back(ParamWithName(&m_CenterX, prefix + "falloff3_center_x")); + m_Params.push_back(ParamWithName(&m_CenterY, prefix + "falloff3_center_y")); + m_Params.push_back(ParamWithName(&m_CenterZ, prefix + "falloff3_center_z")); + m_Params.push_back(ParamWithName(&m_Alpha, prefix + "falloff3_alpha")); + m_Params.push_back(ParamWithName(true, &m_RMax, prefix + "falloff3_rmax")); + } + +private: + T m_BlurType; + T m_BlurShape; + T m_BlurStrength; + T m_MinDistance; + T m_InvertDistance; + T m_MulX; + T m_MulY; + T m_MulZ; + T m_MulC; + T m_CenterX; + T m_CenterY; + T m_CenterZ; + T m_Alpha; + T m_RMax; +}; + +/// +/// xtrb. +/// +template +class EMBER_API XtrbVariation : public ParametricVariation +{ +public: + XtrbVariation(T weight = 1.0) : ParametricVariation("xtrb", VAR_XTRB, weight) + { + Init(); + } + + PARVARCOPY(XtrbVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + int m, n; + T alpha, beta, offsetAl, offsetBe, offsetGa, x, y; + + //Transfer to trilinear coordinates, normalized to real distances from triangle sides. + DirectTrilinear(helper.In.x, helper.In.y, alpha, beta); + + m = Floor(alpha / m_S2a); + offsetAl = alpha - m * m_S2a; + n = Floor(beta / m_S2b); + offsetBe = beta - n * m_S2b; + offsetGa = m_S2c - m_Ac * offsetAl - m_Bc * offsetBe; + + if (offsetGa > 0) + { + Hex(offsetAl, offsetBe, offsetGa, alpha, beta, rand); + } + else + { + offsetAl = m_S2a - offsetAl; + offsetBe = m_S2b - offsetBe; + offsetGa = -offsetGa; + Hex(offsetAl, offsetBe, offsetGa, alpha, beta, rand); + alpha = m_S2a - alpha; + beta = m_S2b - beta; + } + + alpha += m * m_S2a; + beta += n * m_S2b; + + InverseTrilinear(alpha, beta, x, y, rand); + helper.Out.x = m_Weight * x; + helper.Out.y = m_Weight * y; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string power = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string radius = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string width = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string dist = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string a = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string b = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string sinC = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cosC = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ha = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string hb = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string hc = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ab = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ac = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ba = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string bc = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ca = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cb = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string s2a = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string s2b = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string s2c = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string s2ab = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string s2ac = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string s2bc = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string width1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string width2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string width3 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string absN = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cn = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tint m, n;\n" + << "\t\treal_t alpha, beta, offsetAl, offsetBe, offsetGa, x, y;\n" + << "\n" + << "\t\t{\n"//DirectTrilinear function extracted out here. + << "\t\t alpha = vIn.y + " << radius << ";\n" + << "\t\t beta = vIn.x * " << sinC << " - vIn.y * " << cosC << " + " << radius << ";\n" + << "\t\t}\n" + << "\n" + << "\t\tm = floor(alpha / " << s2a << ");\n" + << "\t\toffsetAl = alpha - m * " << s2a << ";\n" + << "\t\tn = floor(beta / " << s2b << ");\n" + << "\t\toffsetBe = beta - n * " << s2b << ";\n" + << "\t\toffsetGa = " << s2c << " - " << ac << " * offsetAl - " << bc << " * offsetBe;\n" + << "\n" + << "\t\tif (offsetGa > 0)\n" + << "\t\t{\n" + << "\n" + << "\t\t Hex(offsetAl, offsetBe, offsetGa,\n" + << "\t\t " << width << ",\n" + << "\t\t " << ha << ",\n" + << "\t\t " << hb << ",\n" + << "\t\t " << hc << ",\n" + << "\t\t " << ab << ",\n" + << "\t\t " << ba << ",\n" + << "\t\t " << bc << ",\n" + << "\t\t " << ca << ",\n" + << "\t\t " << cb << ",\n" + << "\t\t " << s2a << ",\n" + << "\t\t " << s2b << ",\n" + << "\t\t " << s2ab << ",\n" + << "\t\t " << s2ac << ",\n" + << "\t\t " << s2bc << ",\n" + << "\t\t " << width1 << ",\n" + << "\t\t " << width2 << ",\n" + << "\t\t " << width3 << ",\n" + << "\t\t &alpha, &beta, mwc);\n" + << "\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t offsetAl = " << s2a << " - offsetAl;\n" + << "\t\t offsetBe = " << s2b << " - offsetBe;\n" + << "\t\t offsetGa = -offsetGa;\n" + << "\n" + << "\t\t Hex(offsetAl, offsetBe, offsetGa,\n" + << "\t\t " << width << ",\n" + << "\t\t " << ha << ",\n" + << "\t\t " << hb << ",\n" + << "\t\t " << hc << ",\n" + << "\t\t " << ab << ",\n" + << "\t\t " << ba << ",\n" + << "\t\t " << bc << ",\n" + << "\t\t " << ca << ",\n" + << "\t\t " << cb << ",\n" + << "\t\t " << s2a << ",\n" + << "\t\t " << s2b << ",\n" + << "\t\t " << s2ab << ",\n" + << "\t\t " << s2ac << ",\n" + << "\t\t " << s2bc << ",\n" + << "\t\t " << width1 << ",\n" + << "\t\t " << width2 << ",\n" + << "\t\t " << width3 << ",\n" + << "\t\t &alpha, &beta, mwc);\n" + << "\n" + << "\t\t alpha = " << s2a << " - alpha;\n" + << "\t\t beta = " << s2b << " - beta;\n" + << "\t\t}\n" + << "\n" + << "\t\talpha += m * " << s2a << ";\n" + << "\t\tbeta += n * " << s2b << ";\n" + << "\n" + << "\t\t{\n"//InverseTrilinear function extracted out here. + << "\t\t real_t inx = (beta - " << radius << " + (alpha - " << radius << ") * " << cosC << ") / " << sinC << ";\n" + << "\t\t real_t iny = alpha - " << radius << ";\n" + << "\t\t real_t angle = (atan2(iny, inx) + M_2PI * MwcNextRange(mwc, (int)" << absN << ")) / " << power << ";\n" + << "\t\t real_t r = xform->m_VariationWeights[" << varIndex << "] * pow(SQR(inx) + SQR(iny), " << cn << ");\n" + << "\n" + << "\t\t x = r * cos(angle);\n" + << "\t\t y = r * sin(angle);\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * x;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * y;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual string OpenCLFuncsString() + { + return + "\n" + "void Hex(real_t al, real_t be, real_t ga,\n" + " real_t width,\n" + " real_t ha,\n" + " real_t hb,\n" + " real_t hc,\n" + " real_t ab,\n" + " real_t ba,\n" + " real_t bc,\n" + " real_t ca,\n" + " real_t cb,\n" + " real_t s2a,\n" + " real_t s2b,\n" + " real_t s2ab,\n" + " real_t s2ac,\n" + " real_t s2bc,\n" + " real_t width1,\n" + " real_t width2,\n" + " real_t width3,\n" + " real_t* al1, real_t* be1, uint2* mwc)\n" + "{\n" + " real_t ga1, de1, r = MwcNext01(mwc);\n" + "\n" + " if (be < al)\n" + " {\n" + " if (ga < be)\n" + " {\n" + " if (r >= width3)\n" + " {\n" + " de1 = width * be;\n" + " ga1 = width * ga;\n" + " }\n" + " else\n" + " {\n" + " ga1 = width1 * ga + width2 * hc * ga / be;\n" + " de1 = width1 * be + width2 * s2ab * (3 - ga / be);\n" + " }\n" + "\n" + " *al1 = s2a - ba * de1 - ca * ga1;\n" + " *be1 = de1;\n" + " }\n" + " else\n" + " {\n" + " if (ga < al)\n" + " {\n" + " if (r >= width3)\n" + " {\n" + " ga1 = width * ga;\n" + " de1 = width * be;\n" + " }\n" + " else\n" + " {\n" + " de1 = width1 * be + width2 * hb * be / ga;\n" + " ga1 = width1 * ga + width2 * s2ac * (3 - be / ga);\n" + " }\n" + "\n" + " *al1 = s2a - ba * de1 - ca * ga1;\n" + " *be1 = de1;\n" + " }\n" + " else\n" + " {\n" + " if (r >= width3)\n" + " {\n" + " *al1 = width * al;\n" + " *be1 = width * be;\n" + " }\n" + " else\n" + " {\n" + " *be1 = width1 * be + width2 * hb * be / al;\n" + " *al1 = width1 * al + width2 * s2ac * (3 - be / al);\n" + " }\n" + " }\n" + " }\n" + " }\n" + " else\n" + " {\n" + " if (ga < al)\n" + " {\n" + " if (r >= width3)\n" + " {\n" + " de1 = width * al;\n" + " ga1 = width * ga;\n" + " }\n" + " else\n" + " {\n" + " ga1 = width1 * ga + width2 * hc * ga / al;\n" + " de1 = width1 * al + width2 * s2ab * (3 - ga / al);\n" + " }\n" + "\n" + " *be1 = s2b - ab * de1 - cb * ga1;\n" + " *al1 = de1;\n" + " }\n" + " else\n" + " {\n" + " if (ga < be)\n" + " {\n" + " if (r >= width3)\n" + " {\n" + " ga1 = width * ga;\n" + " de1 = width * al;\n" + " }\n" + " else\n" + " {\n" + " de1 = width1 * al + width2 * ha * al / ga;\n" + " ga1 = width1 * ga + width2 * s2bc * (3 - al / ga);\n" + " }\n" + "\n" + " *be1 = s2b - ab * de1 - cb * ga1;\n" + " *al1 = de1;\n" + " }\n" + " else\n" + " {\n" + " if (r >= width3)\n" + " {\n" + " *be1 = width * be;\n" + " *al1 = width * al;\n" + " }\n" + " else\n" + " {\n" + " *al1 = width1 * al + width2 * ha * al / be;\n" + " *be1 = width1 * be + width2 * s2bc * (3 - al / be);\n" + " }\n" + " }\n" + " }\n" + " }\n" + "}\n" + "\n" + ; + } + + virtual void Precalc() + { + T s2, sinA2, cosA2, sinB2, cosB2, sinC2, cosC2; + T br = T(0.047) + m_A; + T cr = T(0.047) + m_B; + T ar = T(M_PI) - br - cr; + T temp = ar / 2; + + sincos(temp, &sinA2, &cosA2); + temp = br / 2; + sincos(temp, &sinB2, &cosB2); + temp = cr / 2; + sincos(temp, &sinC2, &cosC2); + sincos(cr, &m_SinC, &m_CosC); + + T a = m_Radius * (sinC2 / cosC2 + sinB2 / cosB2); + T b = m_Radius * (sinC2 / cosC2 + sinA2 / cosA2); + T c = m_Radius * (sinB2 / cosB2 + sinA2 / cosA2); + + m_Width1 = 1 - m_Width; + m_Width2 = 2 * m_Width; + m_Width3 = 1 - m_Width * m_Width; + s2 = m_Radius * (a + b + c); + + m_Ha = s2 / a / 6; + m_Hb = s2 / b / 6; + m_Hc = s2 / c / 6; + + m_Ab = a / b;// a div on b + m_Ac = a / c; + m_Ba = b / a; + m_Bc = b / c; + m_Ca = c / a; + m_Cb = c / b; + m_S2a = 6 * m_Ha; + m_S2b = 6 * m_Hb; + m_S2c = 6 * m_Hc; + m_S2bc = s2 / (b + c) / 6; + m_S2ab = s2 / (a + b) / 6; + m_S2ac = s2 / (a + c) / 6; + + if (m_Power == 0) + m_Power = 2; + + m_AbsN = T((int)fabs(m_Power)); + m_Cn = m_Dist / m_Power / 2; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Power, prefix + "xtrb_power", 2, INTEGER_NONZERO)); + m_Params.push_back(ParamWithName(&m_Radius, prefix + "xtrb_radius", 1)); + m_Params.push_back(ParamWithName(&m_Width, prefix + "xtrb_width", T(0.5))); + m_Params.push_back(ParamWithName(&m_Dist, prefix + "xtrb_dist", 1)); + m_Params.push_back(ParamWithName(&m_A, prefix + "xtrb_a", 1)); + m_Params.push_back(ParamWithName(&m_B, prefix + "xtrb_b", 1)); + m_Params.push_back(ParamWithName(true, &m_SinC, prefix + "xtrb_sinc"));//Precalcs. + m_Params.push_back(ParamWithName(true, &m_CosC, prefix + "xtrb_cosc")); + m_Params.push_back(ParamWithName(true, &m_Ha, prefix + "xtrb_ha")); + m_Params.push_back(ParamWithName(true, &m_Hb, prefix + "xtrb_hb")); + m_Params.push_back(ParamWithName(true, &m_Hc, prefix + "xtrb_hc")); + m_Params.push_back(ParamWithName(true, &m_Ab, prefix + "xtrb_ab")); + m_Params.push_back(ParamWithName(true, &m_Ac, prefix + "xtrb_ac")); + m_Params.push_back(ParamWithName(true, &m_Ba, prefix + "xtrb_ba")); + m_Params.push_back(ParamWithName(true, &m_Bc, prefix + "xtrb_bc")); + m_Params.push_back(ParamWithName(true, &m_Ca, prefix + "xtrb_ca")); + m_Params.push_back(ParamWithName(true, &m_Cb, prefix + "xtrb_cb")); + m_Params.push_back(ParamWithName(true, &m_S2a, prefix + "xtrb_s2a")); + m_Params.push_back(ParamWithName(true, &m_S2b, prefix + "xtrb_s2b")); + m_Params.push_back(ParamWithName(true, &m_S2c, prefix + "xtrb_s2c")); + m_Params.push_back(ParamWithName(true, &m_S2ab, prefix + "xtrb_s2ab")); + m_Params.push_back(ParamWithName(true, &m_S2ac, prefix + "xtrb_s2ac")); + m_Params.push_back(ParamWithName(true, &m_S2bc, prefix + "xtrb_s2bc")); + m_Params.push_back(ParamWithName(true, &m_Width1, prefix + "xtrb_width1")); + m_Params.push_back(ParamWithName(true, &m_Width2, prefix + "xtrb_width2")); + m_Params.push_back(ParamWithName(true, &m_Width3, prefix + "xtrb_width3")); + m_Params.push_back(ParamWithName(true, &m_AbsN, prefix + "xtrb_absn")); + m_Params.push_back(ParamWithName(true, &m_Cn, prefix + "xtrb_cn")); + } + +private: + inline void DirectTrilinear(T x, T y, T& al, T& be) + { + al = y + m_Radius; + be = x * m_SinC - y * m_CosC + m_Radius; + } + + inline void InverseTrilinear(T al, T be, T& x, T& y, QTIsaac& rand) + { + T inx = (be - m_Radius + (al - m_Radius) * m_CosC) / m_SinC; + T iny = al - m_Radius; + T angle = (atan2(iny, inx) + M_2PI * (rand.Rand((int)m_AbsN))) / m_Power; + T r = m_Weight * pow(SQR(inx) + SQR(iny), m_Cn); + + x = r * cos(angle); + y = r * sin(angle); + } + + void Hex(T al, T be, T ga, T& al1, T& be1, QTIsaac& rand) + { + T ga1, de1, r = rand.Frand01(); + + if (be < al) + { + if (ga < be) + { + if (r >= m_Width3) + { + de1 = m_Width * be; + ga1 = m_Width * ga; + } + else + { + ga1 = m_Width1 * ga + m_Width2 * m_Hc * ga / be; + de1 = m_Width1 * be + m_Width2 * m_S2ab * (3 - ga / be); + } + + al1 = m_S2a - m_Ba * de1 - m_Ca * ga1; + be1 = de1; + } + else + { + if (ga < al) + { + if (r >= m_Width3) + { + ga1 = m_Width * ga; + de1 = m_Width * be; + } + else + { + de1 = m_Width1 * be + m_Width2 * m_Hb * be / ga; + ga1 = m_Width1 * ga + m_Width2 * m_S2ac * (3 - be / ga); + } + + al1 = m_S2a - m_Ba * de1 - m_Ca * ga1; + be1 = de1; + } + else + { + if (r >= m_Width3) + { + al1 = m_Width * al; + be1 = m_Width * be; + } + else + { + be1 = m_Width1 * be + m_Width2 * m_Hb * be / al; + al1 = m_Width1 * al + m_Width2 * m_S2ac * (3 - be / al); + } + } + } + } + else + { + if (ga < al) + { + if (r >= m_Width3) + { + de1 = m_Width * al; + ga1 = m_Width * ga; + } + else + { + ga1 = m_Width1 * ga + m_Width2 * m_Hc * ga / al; + de1 = m_Width1 * al + m_Width2 * m_S2ab * (3 - ga / al); + } + + be1 = m_S2b - m_Ab * de1 - m_Cb * ga1; + al1 = de1; + } + else + { + if (ga < be) + { + if (r >= m_Width3) + { + ga1 = m_Width * ga; + de1 = m_Width * al; + } + else + { + de1 = m_Width1 * al + m_Width2 * m_Ha * al / ga; + ga1 = m_Width1 * ga + m_Width2 * m_S2bc * (3 - al / ga); + } + + be1 = m_S2b - m_Ab * de1 - m_Cb * ga1; + al1 = de1; + } + else + { + if (r >= m_Width3) + { + be1 = m_Width * be; + al1 = m_Width * al; + } + else + { + al1 = m_Width1 * al + m_Width2 * m_Ha * al / be; + be1 = m_Width1 * be + m_Width2 * m_S2bc * (3 - al / be); + } + } + } + } + } + + T m_Power; + T m_Radius; + T m_Width; + T m_Dist; + T m_A; + T m_B; + T m_SinC;//Precalcs. + T m_CosC; + T m_Ha; + T m_Hb; + T m_Hc; + T m_Ab; + T m_Ac; + T m_Ba; + T m_Bc; + T m_Ca; + T m_Cb; + T m_S2a; + T m_S2b; + T m_S2c; + T m_S2ab; + T m_S2ac; + T m_S2bc; + T m_Width1; + T m_Width2; + T m_Width3; + T m_AbsN; + T m_Cn; +}; + +MAKEPREPOSTPARVAR(Bubble2, bubble2, BUBBLE2) +MAKEPREPOSTPARVAR(CircleLinear, CircleLinear, CIRCLELINEAR) +MAKEPREPOSTPARVARASSIGN(CircleRand, CircleRand, CIRCLERAND, ASSIGNTYPE_SUM) +MAKEPREPOSTPARVAR(CircleTrans1, CircleTrans1, CIRCLETRANS1) +MAKEPREPOSTPARVAR(Cubic3D, cubic3D, CUBIC3D) +MAKEPREPOSTPARVAR(CubicLattice3D, cubicLattice_3D, CUBIC_LATTICE3D) +MAKEPREPOSTVAR(Foci3D, foci_3D, FOCI3D) +MAKEPREPOSTPARVAR(Ho, ho, HO) +MAKEPREPOSTPARVAR(Julia3Dq, julia3Dq, JULIA3DQ) +MAKEPREPOSTPARVARASSIGN(Line, line, LINE, ASSIGNTYPE_SUM) +MAKEPREPOSTPARVAR(Loonie3D, loonie_3D, LOONIE3D) +MAKEPREPOSTPARVAR(Mcarpet, mcarpet, MCARPET) +MAKEPREPOSTPARVAR(Waves23D, waves2_3D, WAVES23D) +MAKEPREPOSTPARVARASSIGN(Pie3D, pie3D, PIE3D, ASSIGNTYPE_SUM) +MAKEPREPOSTPARVAR(Popcorn23D, popcorn2_3D, POPCORN23D) +MAKEPREPOSTVAR(Sinusoidal3D, sinusoidal3D, SINUSOIDAL3D) +MAKEPREPOSTPARVAR(Scry3D, scry_3D, SCRY3D) +MAKEPREPOSTPARVAR(Shredlin, shredlin, SHRED_LIN) +MAKEPREPOSTPARVAR(SplitBrdr, SplitBrdr, SPLIT_BRDR) +MAKEPREPOSTVAR(Wdisc, wdisc, WDISC) +MAKEPREPOSTPARVAR(Falloff, falloff, FALLOFF) +MAKEPREPOSTPARVAR(Falloff2, falloff2, FALLOFF2) +MAKEPREPOSTPARVAR(Falloff3, falloff3, FALLOFF3) +MAKEPREPOSTPARVAR(Xtrb, xtrb, XTRB) +} diff --git a/Source/Ember/VariationsDC.h b/Source/Ember/VariationsDC.h new file mode 100644 index 0000000..946f900 --- /dev/null +++ b/Source/Ember/VariationsDC.h @@ -0,0 +1,1039 @@ +#pragma once + +#include "Variation.h" + +namespace EmberNs +{ +/// +/// DC Bubble. +/// This accesses the summed output point in a rare and different way +/// and therefore cannot be made into pre and post variations. +/// +template +class EMBER_API DCBubbleVariation : public ParametricVariation +{ +public: + DCBubbleVariation(T weight = 1.0) : ParametricVariation("dc_bubble", VAR_DC_BUBBLE, weight, true) + { + Init(); + } + + PARVARCOPY(DCBubbleVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T r = helper.m_PrecalcSumSquares; + T r4_1 = r / 4 + 1; + r4_1 = m_Weight / r4_1; + + helper.Out.x = r4_1 * helper.In.x; + helper.Out.y = r4_1 * helper.In.y; + helper.Out.z = m_Weight * (2 / r4_1 - 1); + + T tempX = helper.Out.x + outPoint.m_X; + T tempY = helper.Out.y + outPoint.m_Y; + + outPoint.m_ColorX = fmod(fabs(m_Bdcs * (Sqr(tempX + m_CenterX) + Sqr(tempY + m_CenterY))), T(1.0)); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string scale = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Params. + string centerX = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string centerY = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string bdcs = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Precalc. + + ss << "\t{\n" + << "\t\treal_t r = precalcSumSquares;\n" + << "\t\treal_t r4_1 = r / 4 + 1;\n" + << "\t\tr4_1 = xform->m_VariationWeights[" << varIndex << "] / r4_1;\n" + << "\n" + << "\t\tvOut.x = r4_1 * vIn.x;\n" + << "\t\tvOut.y = r4_1 * vIn.y;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * (2 / r4_1 - 1);\n" + << "\n" + << "\t\treal_t tempX = vOut.x + outPoint->m_X;\n" + << "\t\treal_t tempY = vOut.y + outPoint->m_Y;\n" + << "\n" + << "\t\toutPoint->m_ColorX = fmod(fabs(" << bdcs << " * (Sqr(tempX + " << centerX << ") + Sqr(tempY + " << centerY << "))), 1.0);\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Bdcs = 1 / (m_Scale == 0 ? T(10E-6) : m_Scale); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_CenterX, prefix + "dc_bubble_centerx"));//Params. + m_Params.push_back(ParamWithName(&m_CenterY, prefix + "dc_bubble_centery")); + m_Params.push_back(ParamWithName(&m_Scale, prefix + "dc_bubble_scale", 1)); + m_Params.push_back(ParamWithName(true, &m_Bdcs, prefix + "dc_bubble_bdcs"));//Precalc. + } + +private: + T m_CenterX;//Params. + T m_CenterY; + T m_Scale; + T m_Bdcs;//Precalc. +}; + +/// +/// DC Carpet. +/// +template +class EMBER_API DCCarpetVariation : public ParametricVariation +{ +public: + DCCarpetVariation(T weight = 1.0) : ParametricVariation("dc_carpet", VAR_DC_CARPET, weight) + { + Init(); + } + + PARVARCOPY(DCCarpetVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + int x0 = rand.RandBit() ? -1 : 1; + int y0 = rand.RandBit() ? -1 : 1; + T x = helper.In.x + x0; + T y = helper.In.y + y0; + T x0_xor_y0 = T(x0 ^ y0); + T h = -m_H + (1 - x0_xor_y0) * m_H; + + helper.Out.x = m_Weight * (m_Xform->m_Affine.A() * x + m_Xform->m_Affine.B() * y + m_Xform->m_Affine.E()); + helper.Out.y = m_Weight * (m_Xform->m_Affine.C() * x + m_Xform->m_Affine.D() * y + m_Xform->m_Affine.F()); + outPoint.m_ColorX = fmod(fabs(outPoint.m_ColorX * T(0.5) * (1 + h) + x0_xor_y0 * (1 - h) * T(0.5)), T(1.0)); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string origin = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Params. + string h = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Precalc. + + ss << "\t{\n" + << "\t\tint x0 = (MwcNext(mwc) & 1) ? -1 : 1;\n" + << "\t\tint y0 = (MwcNext(mwc) & 1) ? -1 : 1;\n" + << "\t\treal_t x = vIn.x + x0;\n" + << "\t\treal_t y = vIn.y + y0;\n" + << "\t\treal_t x0_xor_y0 = (real_t)(x0 ^ y0);\n" + << "\t\treal_t h = -" << h << " + (1 - x0_xor_y0) * " << h << ";\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (xform->m_A * x + xform->m_B * y + xform->m_E);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (xform->m_C * x + xform->m_D * y + xform->m_F);\n" + << "\t\toutPoint->m_ColorX = fmod(fabs(outPoint->m_ColorX * 0.5 * (1 + h) + x0_xor_y0 * (1 - h) * 0.5), 1.0);\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_H = T(0.1) * m_Origin; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Origin, prefix + "dc_carpet_origin"));//Params. + m_Params.push_back(ParamWithName(true, &m_H, prefix + "dc_carpet_h"));//Precalc. + } + +private: + T m_Origin;//Params. + T m_H;//Precalc. +}; + +/// +/// DC Cube. +/// +template +class EMBER_API DCCubeVariation : public ParametricVariation +{ +public: + DCCubeVariation(T weight = 1.0) : ParametricVariation("dc_cube", VAR_DC_CUBE, weight) + { + Init(); + } + + PARVARCOPY(DCCubeVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T x, y, z; + T p = 2 * rand.Frand01() - 1; + T q = 2 * rand.Frand01() - 1; + unsigned int i = rand.Rand(3); + unsigned int j = rand.RandBit(); + + switch (i) + { + case 0: + x = m_Weight * (j ? -1 : 1); + y = m_Weight * p; + z = m_Weight * q; + + if (j) + outPoint.m_ColorX = m_ClampC1; + else + outPoint.m_ColorX = m_ClampC2; + + break; + case 1: + x = m_Weight * p; + y = m_Weight * (j ? -1 : 1); + z = m_Weight * q; + + if (j) + outPoint.m_ColorX = m_ClampC3; + else + outPoint.m_ColorX = m_ClampC4; + + break; + case 2: + x = m_Weight * p; + y = m_Weight * q; + z = m_Weight * (j ? -1 : 1); + + if (j) + outPoint.m_ColorX = m_ClampC5; + else + outPoint.m_ColorX = m_ClampC6; + + break; + } + + helper.Out.x = x * m_DcCubeX; + helper.Out.y = y * m_DcCubeY; + helper.Out.z = z * m_DcCubeZ; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string cubeC1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Params. + string cubeC2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cubeC3 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cubeC4 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cubeC5 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cubeC6 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cubeX = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cubeY = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cubeZ = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string clampC1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Precalc. + string clampC2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string clampC3 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string clampC4 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string clampC5 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string clampC6 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t x, y, z;\n" + << "\t\treal_t p = 2 * MwcNext01(mwc) - 1;\n" + << "\t\treal_t q = 2 * MwcNext01(mwc) - 1;\n" + << "\t\tuint i = MwcNext(mwc) & 3;\n" + << "\t\tuint j = MwcNext(mwc) & 1;\n" + << "\n" + << "\t\tswitch (i)\n" + << "\t\t{\n" + << "\t\t case 0:\n" + << "\t\t x = xform->m_VariationWeights[" << varIndex << "] * (j ? -1 : 1);\n" + << "\t\t y = xform->m_VariationWeights[" << varIndex << "] * p;\n" + << "\t\t z = xform->m_VariationWeights[" << varIndex << "] * q;\n" + << "\n" + << "\t\t if (j)\n" + << "\t\t outPoint->m_ColorX = " << clampC1 << ";\n" + << "\t\t else\n" + << "\t\t outPoint->m_ColorX = " << clampC2 << ";\n" + << "\n" + << "\t\t break;\n" + << "\t\t case 1:\n" + << "\t\t x =xform->m_VariationWeights[" << varIndex << "] * p;\n" + << "\t\t y =xform->m_VariationWeights[" << varIndex << "] * (j ? -1 : 1);\n" + << "\t\t z =xform->m_VariationWeights[" << varIndex << "] * q;\n" + << "\n" + << "\t\t if (j)\n" + << "\t\t outPoint->m_ColorX = " << clampC3 << ";\n" + << "\t\t else\n" + << "\t\t outPoint->m_ColorX = " << clampC4 << ";\n" + << "\n" + << "\t\t break;\n" + << "\t\t case 2:\n" + << "\t\t x = xform->m_VariationWeights[" << varIndex << "] * p;\n" + << "\t\t y = xform->m_VariationWeights[" << varIndex << "] * q;\n" + << "\t\t z = xform->m_VariationWeights[" << varIndex << "] * (j ? -1 : 1);\n" + << "\n" + << "\t\t if (j)\n" + << "\t\t outPoint->m_ColorX = " << clampC5 << ";\n" + << "\t\t else\n" + << "\t\t outPoint->m_ColorX = " << clampC6 << ";\n" + << "\n" + << "\t\t break;\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.x = x * " << cubeX << ";\n" + << "\t\tvOut.y = y * " << cubeY << ";\n" + << "\t\tvOut.z = z * " << cubeZ << ";\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_ClampC1 = Clamp(m_DcCubeC1, 0, 1); + m_ClampC2 = Clamp(m_DcCubeC2, 0, 1); + m_ClampC3 = Clamp(m_DcCubeC3, 0, 1); + m_ClampC4 = Clamp(m_DcCubeC4, 0, 1); + m_ClampC5 = Clamp(m_DcCubeC5, 0, 1); + m_ClampC6 = Clamp(m_DcCubeC6, 0, 1); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_DcCubeC1, prefix + "dc_cube_c1"));//Params. + m_Params.push_back(ParamWithName(&m_DcCubeC2, prefix + "dc_cube_c2")); + m_Params.push_back(ParamWithName(&m_DcCubeC3, prefix + "dc_cube_c3")); + m_Params.push_back(ParamWithName(&m_DcCubeC4, prefix + "dc_cube_c4")); + m_Params.push_back(ParamWithName(&m_DcCubeC5, prefix + "dc_cube_c5")); + m_Params.push_back(ParamWithName(&m_DcCubeC6, prefix + "dc_cube_c6")); + m_Params.push_back(ParamWithName(&m_DcCubeX, prefix + "dc_cube_x", 1)); + m_Params.push_back(ParamWithName(&m_DcCubeY, prefix + "dc_cube_y", 1)); + m_Params.push_back(ParamWithName(&m_DcCubeZ, prefix + "dc_cube_z", 1)); + m_Params.push_back(ParamWithName(true, &m_ClampC1, prefix + "dc_cube_clamp_c1"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_ClampC2, prefix + "dc_cube_clamp_c2")); + m_Params.push_back(ParamWithName(true, &m_ClampC3, prefix + "dc_cube_clamp_c3")); + m_Params.push_back(ParamWithName(true, &m_ClampC4, prefix + "dc_cube_clamp_c4")); + m_Params.push_back(ParamWithName(true, &m_ClampC5, prefix + "dc_cube_clamp_c5")); + m_Params.push_back(ParamWithName(true, &m_ClampC6, prefix + "dc_cube_clamp_c6")); + } + +private: + T m_DcCubeC1;//Params. + T m_DcCubeC2; + T m_DcCubeC3; + T m_DcCubeC4; + T m_DcCubeC5; + T m_DcCubeC6; + T m_DcCubeX; + T m_DcCubeY; + T m_DcCubeZ; + T m_ClampC1;//Precalc. + T m_ClampC2; + T m_ClampC3; + T m_ClampC4; + T m_ClampC5; + T m_ClampC6; +}; + +/// +/// DC Cylinder. +/// This accesses the summed output point in a rare and different way +/// and therefore cannot be made into pre and post variations. +/// +template +class EMBER_API DCCylinderVariation : public ParametricVariation +{ +public: + DCCylinderVariation(T weight = 1.0) : ParametricVariation("dc_cylinder", VAR_DC_CYLINDER, weight) + { + Init(); + } + + PARVARCOPY(DCCylinderVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T temp = rand.Frand01() * M_2PI; + T sr = sin(temp); + T cr = cos(temp); + T r = m_Blur * (rand.Frand01() + rand.Frand01() + rand.Frand01() + rand.Frand01() - 2); + + helper.Out.x = m_Weight * sin(helper.In.x + r * sr) * m_X; + helper.Out.y = r + helper.In.y * m_Y; + helper.Out.z = m_Weight * cos(helper.In.x + r * cr); + + T tempX = helper.Out.x + outPoint.m_X; + T tempY = helper.Out.y + outPoint.m_Y; + + outPoint.m_ColorX = fmod(fabs(T(0.5) * (m_Ldcs * ((m_Cosa * tempX + m_Sina * tempY + m_Offset)) + 1)), T(1.0)); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string offset = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Params. + string angle = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string scale = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string x = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string y = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string blur = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string sina = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Precalc. + string cosa = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ldcs = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ldca = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t temp = MwcNext(mwc) * M_2PI;\n" + << "\t\treal_t sr = sin(temp);\n" + << "\t\treal_t cr = cos(temp);\n" + << "\t\treal_t r = " << blur << " * (MwcNext01(mwc) + MwcNext01(mwc) + MwcNext01(mwc) + MwcNext01(mwc) - 2);\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * sin(vIn.x + r * sr)* " << x << ";\n" + << "\t\tvOut.y = r + vIn.y * " << y << ";\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * cos(vIn.x + r * cr);\n" + << "\n" + << "\t\treal_t tempX = vOut.x + outPoint->m_X;\n" + << "\t\treal_t tempY = vOut.y + outPoint->m_Y;\n" + << "\n" + << "\t\toutPoint->m_ColorX = fmod(fabs(0.5 * (" << ldcs << " * ((" << cosa << " * tempX + " << sina << " * tempY + " << offset << ")) + 1.0)), 1.0);\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + sincos(m_Angle, &m_Sina, &m_Cosa); + m_Ldcs = 1 / (m_Scale == 0.0 ? T(10E-6) : m_Scale); + m_Ldca = m_Offset * T(M_PI); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Offset, prefix + "dc_cylinder_offset"));//Params. + m_Params.push_back(ParamWithName(&m_Angle, prefix + "dc_cylinder_angle"));//Original used a prefix of dc_cyl_, which is incompatible with Ember's design. + m_Params.push_back(ParamWithName(&m_Scale, prefix + "dc_cylinder_scale", T(0.5))); + m_Params.push_back(ParamWithName(&m_X, prefix + "dc_cylinder_x", T(0.125)));//Original used a prefix of cyl_, which is incompatible with Ember's design. + m_Params.push_back(ParamWithName(&m_Y, prefix + "dc_cylinder_y", T(0.125))); + m_Params.push_back(ParamWithName(&m_Blur, prefix + "dc_cylinder_blur", 1)); + m_Params.push_back(ParamWithName(true, &m_Sina, prefix + "dc_cylinder_sina"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Cosa, prefix + "dc_cylinder_cosa")); + m_Params.push_back(ParamWithName(true, &m_Ldcs, prefix + "dc_cylinder_ldcs")); + m_Params.push_back(ParamWithName(true, &m_Ldca, prefix + "dc_cylinder_ldca")); + } + +private: + T m_Offset;//Params. + T m_Angle; + T m_Scale; + T m_X; + T m_Y; + T m_Blur; + T m_Sina;//Precalc. + T m_Cosa; + T m_Ldcs; + T m_Ldca; +}; + +/// +/// DC GridOut. +/// +template +class EMBER_API DCGridOutVariation : public Variation +{ +public: + DCGridOutVariation(T weight = 1.0) : Variation("dc_gridout", VAR_DC_GRIDOUT, weight) { } + + VARCOPY(DCGridOutVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T x = LRint(helper.In.x); + T y = LRint(helper.In.y); + T c = outPoint.m_ColorX; + + if (y <= 0) + { + if (x > 0) + { + if (-y >= x) + { + helper.Out.x = m_Weight * (helper.In.x + 1); + helper.Out.y = m_Weight * helper.In.y; + c += T(0.25); + } + else + { + helper.Out.x = m_Weight * helper.In.x; + helper.Out.y = m_Weight * (helper.In.y + 1); + c += T(0.75); + } + } + else + { + if (y <= x) + { + helper.Out.x = m_Weight * (helper.In.x + 1); + helper.Out.y = m_Weight * helper.In.y; + c += T(0.25); + } + else + { + helper.Out.x = m_Weight * helper.In.x; + helper.Out.y = m_Weight * (helper.In.y - 1); + c += T(0.75); + } + } + } + else + { + if (x > 0) + { + if (y >= x) + { + helper.Out.x = m_Weight * (helper.In.x - 1); + helper.Out.y = m_Weight * helper.In.y; + c += T(0.25); + } + else + { + helper.Out.x = m_Weight * helper.In.x; + helper.Out.y = m_Weight * (helper.In.y + 1); + c += T(0.75); + } + } + else + { + if (y > -x) + { + helper.Out.x = m_Weight * (helper.In.x - 1); + helper.Out.y = m_Weight * helper.In.y; + c += T(0.25); + } + else + { + helper.Out.x = m_Weight * helper.In.x; + helper.Out.y = m_Weight * (helper.In.y - 1); + c += T(0.75); + } + } + } + + helper.Out.z = m_Weight * helper.In.z; + outPoint.m_ColorX = fmod(c, T(1.0)); + } + + virtual string OpenCLString() + { + ostringstream ss; + int i = 0, varIndex = IndexInXform(); + + ss << "\t{\n" + << "\t\treal_t x = LRint(vIn.x);\n" + << "\t\treal_t y = LRint(vIn.y);\n" + << "\t\treal_t c = outPoint->m_ColorX;\n" + << "\n" + << "\t\tif (y <= 0)\n" + << "\t\t{\n" + << "\t\t if (x > 0)\n" + << "\t\t {\n" + << "\t\t if (-y >= x)\n" + << "\t\t {\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (vIn.x + 1);\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\t\t c += 0.25;\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * (vIn.y + 1);\n" + << "\t\t c += 0.75;\n" + << "\t\t }\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t if (y <= x)\n" + << "\t\t {\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (vIn.x + 1);\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\t\t c += 0.25;\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * (vIn.y - 1);\n" + << "\t\t c += 0.75;\n" + << "\t\t }\n" + << "\t\t }\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t if (x > 0)\n" + << "\t\t {\n" + << "\t\t if (y >= x)\n" + << "\t\t {\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (vIn.x - 1);\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\t\t c += 0.25;\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * (vIn.y + 1);\n" + << "\t\t c += 0.75;\n" + << "\t\t }\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t if (y > -x)\n" + << "\t\t {\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * (vIn.x - 1);\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\t\t c += 0.25;\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t vOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\t\t vOut.y = xform->m_VariationWeights[" << varIndex << "] * (vIn.y - 1);\n" + << "\t\t c += 0.75;\n" + << "\t\t }\n" + << "\t\t }\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t\toutPoint->m_ColorX = fmod(c, 1.0);\n" + << "\t}\n"; + + return ss.str(); + } +}; + +/// +/// DC Linear. +/// This accesses the summed output point in a rare and different way +/// and therefore cannot be made into pre and post variations. +/// +template +class EMBER_API DCLinearVariation : public ParametricVariation +{ +public: + DCLinearVariation(T weight = 1.0) : ParametricVariation("dc_linear", VAR_DC_LINEAR, weight) + { + Init(); + } + + PARVARCOPY(DCLinearVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + helper.Out.x = m_Weight * helper.In.x; + helper.Out.y = m_Weight * helper.In.y; + helper.Out.z = m_Weight * helper.In.z; + + T tempX = helper.Out.x + outPoint.m_X; + T tempY = helper.Out.y + outPoint.m_Y; + + outPoint.m_ColorX = fmod(fabs(T(0.5) * (m_Ldcs * ((m_Cosa * tempX + m_Sina * tempY + m_Offset)) + T(1.0))), T(1.0)); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string offset = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Params. + string angle = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string scale = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string ldcs = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Precalc. + string ldca = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string sina = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string cosa = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\n" + << "\t\treal_t tempX = vOut.x + outPoint->m_X;\n" + << "\t\treal_t tempY = vOut.y + outPoint->m_Y;\n" + << "\n" + << "\t\toutPoint->m_ColorX = fmod(fabs(0.5 * (" << ldcs << " * ((" << cosa << " * tempX + " << sina << " * tempY + " << offset << ")) + 1.0)), 1.0);\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_Ldcs = 1 / (m_Scale == 0 ? T(10E-6) : m_Scale); + m_Ldca = m_Offset * T(M_PI); + sincos(m_Angle, &m_Sina, &m_Cosa); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_Offset, prefix + "dc_linear_offset"));//Params. + m_Params.push_back(ParamWithName(&m_Angle, prefix + "dc_linear_angle")); + m_Params.push_back(ParamWithName(&m_Scale, prefix + "dc_linear_scale", 1)); + m_Params.push_back(ParamWithName(true, &m_Ldcs, prefix + "dc_linear_ldcs"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_Ldca, prefix + "dc_linear_ldca")); + m_Params.push_back(ParamWithName(true, &m_Sina, prefix + "dc_linear_sina")); + m_Params.push_back(ParamWithName(true, &m_Cosa, prefix + "dc_linear_cosa")); + } + +private: + T m_Offset;//Params. + T m_Angle; + T m_Scale; + T m_Ldcs;//Precalc. + T m_Ldca; + T m_Sina; + T m_Cosa; +}; + +/// +/// DC Triangle. +/// +template +class EMBER_API DCTriangleVariation : public ParametricVariation +{ +public: + DCTriangleVariation(T weight = 1.0) : ParametricVariation("dc_triangle", VAR_DC_TRIANGLE, weight) + { + Init(); + } + + PARVARCOPY(DCTriangleVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + // set up triangle + const T + xx = m_Xform->m_Affine.A(), xy = m_Xform->m_Affine.B(), // X + yx = m_Xform->m_Affine.C() * -1, yy = m_Xform->m_Affine.D() * -1, // Y + ox = m_Xform->m_Affine.E(), oy = m_Xform->m_Affine.F(), // O + px = helper.In.x - ox, py = helper.In.y - oy; // P + + // calculate dot products + const T dot00 = xx * xx + xy * xy; // X * X + const T dot01 = xx * yx + xy * yy; // X * Y + const T dot02 = xx * px + xy * py; // X * P + const T dot11 = yx * yx + yy * yy; // Y * Y + const T dot12 = yx * px + yy * py; // Y * P + + // calculate barycentric coordinates + const T denom = (dot00 * dot11 - dot01 * dot01); + const T num_u = (dot11 * dot02 - dot01 * dot12); + const T num_v = (dot00 * dot12 - dot01 * dot02); + + // u, v must not be constant + T u = num_u / denom; + T v = num_v / denom; + int inside = 0, f = 1; + + // case A - point escapes edge XY + if (u + v > 1) + { + f = -1; + + if (u > v) + { + ClampLteRef(u, 1); + v = 1 - u; + } + else + { + ClampLteRef(v, 1); + u = 1 - v; + } + } + else if ((u < 0) || (v < 0))// case B - point escapes either edge OX or OY + { + ClampRef(u, 0, 1); + ClampRef(v, 0, 1); + } + else + { + inside = 1;// case C - point is in triangle + } + + // handle outside points + if (m_ZeroEdges && !inside) + { + u = v = 0; + } + else if (!inside) + { + u = (u + rand.Frand01() * m_A * f); + v = (v + rand.Frand01() * m_A * f); + + ClampRef(u, -1, 1); + ClampRef(v, -1, 1); + + if ((u + v > 1) && (m_A > 0)) + { + if (u > v) + { + ClampLteRef(u, 1); + v = 1 - u; + } + else + { + ClampLteRef(v, 1); + u = 1 - v; + } + } + } + + // set output + helper.Out.x = m_Weight * (ox + u * xx + v * yx); + helper.Out.y = m_Weight * (oy + u * xy + v * yy); + helper.Out.z = m_Weight * helper.In.z; + outPoint.m_ColorX = fmod(fabs(u + v), T(1.0)); + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string scatterArea = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Params. + string zeroEdges = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string a = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Precalc. + + ss << "\t{\n" + << "\t\tconst real_t\n" + << "\t\txx = xform->m_A, xy = xform->m_B,\n" + << "\t\tyx = xform->m_C * -1, yy = xform->m_D * -1,\n" + << "\t\tox = xform->m_E, oy = xform->m_F,\n" + << "\t\tpx = vIn.x - ox, py = vIn.y - oy;\n" + << "\n" + << "\t\tconst real_t dot00 = xx * xx + xy * xy;\n" + << "\t\tconst real_t dot01 = xx * yx + xy * yy;\n" + << "\t\tconst real_t dot02 = xx * px + xy * py;\n" + << "\t\tconst real_t dot11 = yx * yx + yy * yy;\n" + << "\t\tconst real_t dot12 = yx * px + yy * py;\n" + << "\n" + << "\t\tconst real_t denom = (dot00 * dot11 - dot01 * dot01);\n" + << "\t\tconst real_t num_u = (dot11 * dot02 - dot01 * dot12);\n" + << "\t\tconst real_t num_v = (dot00 * dot12 - dot01 * dot02);\n" + << "\n" + << "\t\treal_t u = num_u / denom;\n" + << "\t\treal_t v = num_v / denom;\n" + << "\t\tint inside = 0, f = 1;\n" + << "\n" + << "\t\tif (u + v > 1)\n" + << "\t\t{\n" + << "\t\t f = -1;\n" + << "\n" + << "\t\t if (u > v)\n" + << "\t\t {\n" + << "\t\t u = u > 1 ? 1 : u;\n" + << "\t\t v = 1 - u;\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t v = v > 1 ? 1 : v;\n" + << "\t\t u = 1 - v;\n" + << "\t\t }\n" + << "\t\t}\n" + << "\t\telse if ((u < 0) || (v < 0))\n" + << "\t\t{\n" + << "\t\t u = u < 0 ? 0 : u > 1 ? 1 : u;\n" + << "\t\t v = v < 0 ? 0 : v > 1 ? 1 : v;\n" + << "\t\t}\n" + << "\t\telse\n" + << "\t\t{\n" + << "\t\t inside = 1;\n" + << "\t\t}\n" + << "\n" + << "\t\tif (" << zeroEdges << " && !inside)\n" + << "\t\t{\n" + << "\t\t u = v = 0;\n" + << "\t\t}\n" + << "\t\telse if (!inside)\n" + << "\t\t{\n" + << "\t\t u = (u + MwcNext01(mwc) * " << a << " * f);\n" + << "\t\t v = (v + MwcNext01(mwc) * " << a << " * f);\n" + << "\t\t u = u < -1 ? -1 : u > 1 ? 1 : u;\n" + << "\t\t v = v < -1 ? -1 : v > 1 ? 1 : v;\n" + << "\n" + << "\t\t if ((u + v > 1) && (" << a << " > 0))\n" + << "\t\t {\n" + << "\t\t if (u > v)\n" + << "\t\t {\n" + << "\t\t u = u > 1 ? 1 : u;\n" + << "\t\t v = 1 - u;\n" + << "\t\t }\n" + << "\t\t else\n" + << "\t\t {\n" + << "\t\t v = v > 1 ? 1 : v;\n" + << "\t\t u = 1 - v;\n" + << "\t\t }\n" + << "\t\t }\n" + << "\t\t}\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (ox + u * xx + v * yx);\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (oy + u * xy + v * yy);\n" + << "\t\tvOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z;\n" + << "\t\toutPoint->m_ColorX = fmod(fabs(u + v), 1.0);\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_A = Clamp(m_ScatterArea, -1, 1); + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_ScatterArea, prefix + "dc_triangle_scatter_area", 0, REAL, -1, 1));//Params. + m_Params.push_back(ParamWithName(&m_ZeroEdges, prefix + "dc_triangle_zero_edges", 0, INTEGER, 0, 1)); + m_Params.push_back(ParamWithName(true, &m_A, prefix + "dc_triangle_a"));//Precalc. + } + +private: + T m_ScatterArea;//Params. + T m_ZeroEdges; + T m_A;//Precalc. +}; + +/// +/// DC Transl. +/// The original used dc_ztransl and post_dcztransl incompatible with Ember's design. +/// These will follow the same naming convention as all other variations. +/// +template +class EMBER_API DCZTranslVariation : public ParametricVariation +{ +public: + DCZTranslVariation(T weight = 1.0) : ParametricVariation("dc_ztransl", VAR_DC_ZTRANSL, weight) + { + Init(); + } + + PARVARCOPY(DCZTranslVariation) + + void Func(IteratorHelper& helper, Point& outPoint, QTIsaac& rand) + { + T zf = m_Factor * (outPoint.m_ColorX - m_X0_) / m_X1_m_x0; + + if (m_Clamp != 0) + ClampRef(zf, 0, 1); + + helper.Out.x = m_Weight * helper.In.x; + helper.Out.y = m_Weight * helper.In.y; + + if (m_Overwrite == 0) + helper.Out.z = m_Weight * helper.In.z * zf; + else + helper.Out.z = m_Weight * zf; + } + + virtual string OpenCLString() + { + ostringstream ss, ss2; + int i = 0, varIndex = IndexInXform(); + ss2 << "_" << XformIndexInEmber() << "]"; + string index = ss2.str(); + string x0 = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Params. + string x1 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string factor = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string overwrite = "parVars[" + ToUpper(m_Params[i++].Name()) + index;//Precalc. + string clamp = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string x0_ = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string x1_ = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + string x1_m_x0 = "parVars[" + ToUpper(m_Params[i++].Name()) + index; + + ss << "\t{\n" + << "\t\treal_t zf = " << factor << " * (outPoint->m_ColorX - " << x0_ << ") / " << x1_m_x0 << ";\n" + << "\n" + << "\t\tif (" << clamp << " != 0)\n" + << "\t\t zf = zf < 0 ? 0 : zf > 1 ? 1 : zf;\n" + << "\n" + << "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * vIn.x;\n" + << "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n" + << "\n" + << "\t\tif (" << overwrite << " == 0)\n" + << "\t\t vOut.z = xform->m_VariationWeights[" << varIndex << "] * vIn.z * zf;\n" + << "\t\telse\n" + << "\t\t vOut.z = xform->m_VariationWeights[" << varIndex << "] * zf;\n" + << "\t}\n"; + + return ss.str(); + } + + virtual void Precalc() + { + m_X0_ = m_X0 < m_X1 ? m_X0 : m_X1; + m_X1_ = m_X0 > m_X1 ? m_X0 : m_X1; + m_X1_m_x0 = m_X1_ - m_X0_ == 0 ? EPS6 : m_X1_ - m_X0_; + } + +protected: + void Init() + { + string prefix = Prefix(); + + m_Params.clear(); + m_Params.push_back(ParamWithName(&m_X0, prefix + "dc_ztransl_x0", 0, REAL, 0, 1));//Params. + m_Params.push_back(ParamWithName(&m_X1, prefix + "dc_ztransl_x1", 1, REAL, 0, 1)); + m_Params.push_back(ParamWithName(&m_Factor, prefix + "dc_ztransl_factor", 1)); + m_Params.push_back(ParamWithName(&m_Overwrite, prefix + "dc_ztransl_overwrite", 1, INTEGER, 0, 1)); + m_Params.push_back(ParamWithName(&m_Clamp, prefix + "dc_ztransl_clamp", 0, INTEGER, 0, 1)); + m_Params.push_back(ParamWithName(true, &m_X0_, prefix + "dc_ztransl_x0_"));//Precalc. + m_Params.push_back(ParamWithName(true, &m_X1_, prefix + "dc_ztransl_x1_")); + m_Params.push_back(ParamWithName(true, &m_X1_m_x0, prefix + "dc_ztransl_x1_m_x0")); + } + +private: + T m_X0;//Params. + T m_X1; + T m_Factor; + T m_Overwrite; + T m_Clamp; + T m_X0_;//Precalc. + T m_X1_; + T m_X1_m_x0; +}; + +MAKEPREPOSTPARVAR(DCCarpet, dc_carpet, DC_CARPET) +MAKEPREPOSTPARVARASSIGN(DCCube, dc_cube, DC_CUBE, ASSIGNTYPE_SUM) +MAKEPREPOSTVAR(DCGridOut, dc_gridout, DC_GRIDOUT) +MAKEPREPOSTPARVAR(DCTriangle, dc_triangle, DC_TRIANGLE) +MAKEPREPOSTPARVAR(DCZTransl, dc_ztransl, DC_ZTRANSL) +} diff --git a/Source/Ember/Xform.h b/Source/Ember/Xform.h new file mode 100644 index 0000000..a181c02 --- /dev/null +++ b/Source/Ember/Xform.h @@ -0,0 +1,1173 @@ +#pragma once + +#include "VariationList.h" + +/// +/// Xform class. +/// + +namespace EmberNs +{ +/// +/// Xform and Variation need each other, but each can't include the other. +/// So Xform includes this file, and Ember is declared as a forward declaration here. +/// +template class Ember; + +/// +/// If both polymorphism and templating are needed, uncomment this, fill it out and derive from it. +/// +//class EMBER_API XformBase +//{ +//}; + +/// +/// An xform is a pre affine transform, a list of variations, and an optional final affine transform. +/// This is what gets applied to a point for each iteration. +/// Template argument expected to be float or double. +/// +template +class EMBER_API Xform +{ +public: + /// + /// Default constructor which calls Init() to set default values. + /// Pre and post affine are defaulted to the identity matrix. + /// + Xform() + { + Init(); + } + + /// + /// Constructor that takes default arguments. Mostly used for testing. + /// Post affine is defaulted to the identity matrix. + /// + /// The probability that this xform is chosen + /// The color index + /// The color speed + /// The opacity + /// The a value of the pre affine transform + /// The d value of the pre affine transform + /// The b value of the pre affine transform + /// The e value of the pre affine transform + /// The c value of the pre affine transform + /// The f value of the pre affine transform + /// The a value of the post affine transform. Default: 1. + /// The d value of the post affine transform. Default: 0. + /// The b value of the post affine transform. Default: 0. + /// The e value of the post affine transform. Default: 1. + /// The c value of the post affine transform. Default: 0. + /// The f value of the post affine transform. Default: 0. + Xform(T weight, T colorX, T colorSpeed, T opacity, + T a, T d, T b, T e, T c, T f, + T pa = 1, + T pd = 0, + T pb = 0, + T pe = 1, + T pc = 0, + T pf = 0) + + { + Init(); + + m_Weight = weight; + m_ColorX = colorX; + m_ColorSpeed = colorSpeed; + m_Opacity = opacity; + + m_Affine.A(a); + m_Affine.B(b); + m_Affine.C(c); + m_Affine.D(d); + m_Affine.E(e); + m_Affine.F(f); + + m_Post.A(pa); + m_Post.B(pb); + m_Post.C(pc); + m_Post.D(pd); + m_Post.E(pe); + m_Post.F(pf); + m_HasPost = !m_Post.IsID(); + m_HasPreOrRegularVars = PreVariationCount() > 0 || VariationCount() > 0; + + CacheColorVals();//Init already called this, but must call again since color was assigned above. + } + + /// + /// Default copy constructor. + /// + /// The Xform object to copy + Xform(const Xform& xform) + : m_ParentEmber(NULL)//Hack. + { + Xform::operator=(xform); + } + + /// + /// Copy constructor to copy an Xform object of type U. + /// + /// The Xform object to copy + template + Xform(const Xform& xform) + : m_ParentEmber(NULL)//Hack. + { + Xform::operator=(xform); + } + + /// + /// Deletes each element of the variation vector and clears it. + /// + ~Xform() + { + ClearAndDeleteVariations(); + } + + /// + /// Default assignment operator. + /// + /// The Xform object to copy + Xform& operator = (const Xform& xform) + { + if (this != &xform) + Xform::operator=(xform); + + return *this; + } + + /// + /// Assignment operator to assign a Xform object of type U. + /// This will delete all of the variations in the vector + /// and repopulate it with copes of the variation in xform's vector. + /// All other values are assigned directly. + /// + /// The Xform object to copy. + /// Reference to updated self + template + Xform& operator = (const Xform& xform) + { + m_Affine = xform.m_Affine; + m_Post = xform.m_Post; + m_Weight = T(xform.m_Weight); + m_ColorX = T(xform.m_ColorX); + m_ColorY = T(xform.m_ColorY); + m_DirectColor = T(xform.m_DirectColor); + m_ColorSpeed = T(xform.m_ColorSpeed); + m_Animate = T(xform.m_Animate); + m_Opacity = T(xform.m_Opacity); + CacheColorVals(); + m_HasPost = xform.HasPost(); + m_HasPreOrRegularVars = xform.PreVariationCount() > 0 || xform.VariationCount() > 0; + m_Wind[0] = T(xform.m_Wind[0]); + m_Wind[1] = T(xform.m_Wind[1]); + m_MotionFreq = xform.m_MotionFreq; + m_MotionFunc = xform.m_MotionFunc; + + ClearAndDeleteVariations(); + + //Must manually add them via the AddVariation() function so that + //the variation's m_IndexInXform member gets properly set to this. + for (unsigned int i = 0; i < xform.TotalVariationCount(); i++) + { + Variation* var = NULL; + + xform.GetVariation(i)->Copy(var);//Will convert from type U to type T. + AddVariation(var);//Will internally call SetPrecalcFlags(). + } + + if (TotalVariationCount() == 0) + SetPrecalcFlags(); + + //If this xform was already part of a different ember, then do not assign, else do. + if (!m_ParentEmber && (typeid(T) == typeid(U))) + m_ParentEmber = (Ember*)xform.ParentEmber(); + + CopyVec(m_Xaos, xform.XaosVec()); + CopyVec(m_Motion, xform.m_Motion); + m_Name = xform.m_Name; + + return *this; + } + + /// + /// Init default values. + /// + void Init() + { + static unsigned int count = 0; + + m_Weight = 0; + m_ColorSpeed = T(0.5); + m_Animate = 1; + m_ColorX = T(count & 1); + m_ColorY = 0; + m_DirectColor = 1; + m_Opacity = 1; + + m_Affine.A(1); + m_Affine.B(0); + m_Affine.C(0); + m_Affine.D(0); + m_Affine.E(1); + m_Affine.F(0); + + m_Post.A(1); + m_Post.B(0); + m_Post.C(0); + m_Post.D(0); + m_Post.E(1); + m_Post.F(0); + + m_Wind[0] = 0; + m_Wind[1] = 0; + m_MotionFreq = 0; + m_MotionFunc = MOTION_SIN; + m_Motion.clear(); + + m_NeedPrecalcSumSquares = false; + m_NeedPrecalcSqrtSumSquares = false; + m_NeedPrecalcAngles = false; + m_NeedPrecalcAtanXY = false; + m_NeedPrecalcAtanYX = false; + m_HasPost = false; + m_HasPreOrRegularVars = false; + m_ParentEmber = NULL; + m_PreVariations.reserve(MAX_VARS_PER_XFORM); + m_Variations.reserve(MAX_VARS_PER_XFORM); + m_PostVariations.reserve(MAX_VARS_PER_XFORM); + CacheColorVals(); + count++; + } + + /// + /// Add a pointer to a variation which will be deleted on destruction so the caller should not delete. + /// This checks if the total number of variations is less than or equal to MAX_VARS_PER_XFORM. + /// It also checks if the variation is already present, in which case it doesn't add. + /// If add, set all precalcs. + /// + /// Pointer to a varation to add + /// True if the successful, else false. + bool AddVariation(Variation* variation) + { + if (variation && (GetVariationById(variation->VariationId()) == NULL)) + { + string name = variation->Name(); + bool pre = name.find("pre_") == 0; + bool post = name.find("post_") == 0; + vector*>* vec; + + if (pre) + vec = &m_PreVariations; + else if (post) + vec = &m_PostVariations; + else + vec = &m_Variations; + + if (vec->size() < MAX_VARS_PER_XFORM) + { + vec->push_back(variation); + SetPrecalcFlags(); + return true; + } + } + + return false; + } + + /// + /// Get a pointer to the variation at the specified index. + /// + /// The index in the list to retrieve + /// A pointer to the variation at the index if in range, else NULL. + Variation* GetVariation(size_t index) const + { + size_t count = 0; + Variation* var = NULL; + + const_cast*>(this)->AllVarsFunc([&] (vector*>& variations, bool& keepGoing) + { + for (unsigned int i = 0; i < variations.size(); i++, count++) + { + if (count == index) + { + var = variations[i]; + keepGoing = false; + break; + } + } + }); + + return var; + } + + /// + /// Get a pointer to the variation with the specified ID. + /// + /// The ID to search for + /// A pointer to the variation if found, else NULL. + Variation* GetVariationById(eVariationId id) const + { + Variation* var = NULL; + + const_cast*>(this)->AllVarsFunc([&] (vector*>& variations, bool& keepGoing) + { + for (unsigned int i = 0; i < variations.size(); i++) + { + if (variations[i] != NULL && variations[i]->VariationId() == id) + { + var = variations[i]; + keepGoing = false; + break; + } + } + }); + + return var; + } + + /// + /// Get a pointer to the variation with the specified name. + /// + /// The name to search for + /// A pointer to the variation if found, else NULL. + Variation* GetVariationByName(string name) const + { + Variation* var = NULL; + + const_cast*>(this)->AllVarsFunc([&] (vector*>& variations, bool& keepGoing) + { + for (unsigned int i = 0; i < variations.size(); i++) + { + if (variations[i] != NULL && variations[i]->Name() == name) + { + var = variations[i]; + keepGoing = false; + break; + } + } + }); + + return var; + } + + /// + /// Get the index in the list of the variation pointer. + /// Note this is searching for the exact pointer address and not the name or ID of the variation. + /// + /// A pointer to the variation to search for + /// The index of the variation if found, else -1 + int GetVariationIndex(Variation* var) const + { + int count = 0, index = -1; + + const_cast*>(this)->AllVarsFunc([&] (vector*>& variations, bool& keepGoing) + { + for (unsigned int i = 0; i < variations.size(); i++, count++) + { + if (variations[i] == var) + { + index = count; + keepGoing = false; + break; + } + } + }); + + return index; + } + + /// + /// Delete the variation with the matching ID. + /// Update precalcs if deletion successful. + /// + /// The ID to search for + /// True if deletion successful, else false. + bool DeleteVariationById(eVariationId id) + { + bool found = false; + + AllVarsFunc([&] (vector*>& variations, bool& keepGoing) + { + for (unsigned int i = 0; i < variations.size(); i++) + { + if (variations[i] != NULL && variations[i]->VariationId() == id) + { + delete variations[i]; + variations.erase(variations.begin() + i); + found = true; + } + } + }); + + if (found) + SetPrecalcFlags(); + + return found; + } + + /// + /// Delete the motion elements. + /// + void DeleteMotionElements() + { + m_Motion.clear(); + } + + /// + /// Delete all variations, clear the list and update precalc flags. + /// + void ClearAndDeleteVariations() + { + AllVarsFunc([&] (vector*>& variations, bool& keepGoing) { ClearVec>(variations); }); + SetPrecalcFlags(); + } + + /// + /// Reset this xform to be totally empty by clearing all variations, resetting both affines to the + /// identity matrix, clearing xaos, color, visibility, wind, animate and setting name + /// to the empty string. + /// Note that this also sets the parent ember to NULL, so if this xform is reused after calling Clear(), + /// the caller must reset the parent ember to whatever ember they add it to again. + /// + void Clear() + { + ClearAndDeleteVariations(); + DeleteMotionElements(); + m_Affine.MakeID(); + m_Post.MakeID(); + m_Xaos.clear(); + m_ParentEmber = NULL; + m_ColorSpeedCache = 0; + m_OneMinusColorCache = 0; + m_VizAdjusted = 0; + m_Animate = 0; + m_Wind[0] = 0; + m_Wind[1] = 0; + m_Name = ""; + } + + /// + /// Compute color cache values: color speed, one minus color speed and adjusted visibility. + /// + void CacheColorVals() + { + //Figure out which is right. //TODO. + //m_ColorSpeedCache = m_ColorX * (1 - m_ColorSpeed) / 2;//Apo style. + //m_OneMinusColorCache = (1 + m_ColorSpeed) / 2; + + m_ColorSpeedCache = m_ColorSpeed * m_ColorX;//Flam3 style. + m_OneMinusColorCache = T(1.0) - m_ColorSpeed; + m_VizAdjusted = AdjustOpacityPercentage(m_Opacity); + } + + /// + /// Return the xaos value at the specified index. + /// If the index is out of range, return 1. + /// This has the convenient effect that xaos is not present + /// by default and only has a value if explicitly added. + /// + /// The xaos index to retrieve + /// The value at the index if in range, else 1. + T Xaos(size_t i) const + { + return i < m_Xaos.size() ? m_Xaos[i] : 1; + } + + /// + /// Set the xaos value for a given xform index. + /// If the index is out of range, a 1 value will be added + /// to the xaos vector repeatedly until it's one less than the + /// requested index in length, then finally add the specified value. + /// + /// The index to set + /// The xaos value to set it to + void SetXaos(unsigned int i, T val) + { + if (i < m_Xaos.size()) + { + m_Xaos[i] = val; + } + else + { + while (m_Xaos.size() <= i) + m_Xaos.push_back(1); + + m_Xaos[i] = val; + } + } + + /// + /// Determine if any xaos value in the vector up to the xform count + /// of the parent ember is anything other than 1. + /// + /// True if found, else false. + bool XaosPresent() const + { + if (m_ParentEmber) + for (unsigned int i = 0; i < m_Xaos.size(); i++) + if (i < m_ParentEmber->XformCount()) + if (!IsClose(m_Xaos[i], 1)) + return true;//If at least one entry is not equal to 1, then xaos is present. + + return false; + } + + /// + /// Truncate the xaos vector to match the xform count of the parent ember. + /// + void TruncateXaos() + { + if (m_ParentEmber) + while (m_Xaos.size() > m_ParentEmber->XformCount()) + m_Xaos.pop_back(); + } + + /// + /// Remove all xaos from this xform. + /// + void ClearXaos() + { + m_Xaos.clear(); + } + + /// + /// Normalize the variation weights. + /// + void NormalizeVariationWeights() + { + AllVarsFunc([&] (vector*>& variations, bool& keepGoing) + { + T norm = 0; + + std::for_each(variations.begin(), variations.end(), [&](Variation* var) { norm += var->m_Weight; }); + std::for_each(variations.begin(), variations.end(), [&](Variation* var) { var->m_Weight /= norm; }); + }); + } + + /// + /// Applies this xform to the point passed in and saves the result in the out point. + /// It's important to understand what happens here since it's the inner core of the algorithm. + /// See the internal comments for step by step details. + /// + /// The initial point from the previous iteration + /// The output point + /// The random context to use + /// True if a bad value was calculated, else false. + bool Apply(Point* inPoint, Point* outPoint, QTIsaac& rand) + { + size_t i; + + //This must be local, rather than a member, because this function can be called + //from multiple threads. If it were a member, they'd be clobbering each others' values. + IteratorHelper iterHelper; + + //Calculate the color coordinate/index in the palette to look up later when accumulating the output point + //to the histogram. Calculate this value by interpolating between the index value of the + //last iteration with the one specified in this xform. Note that some cached values are used + //to reduce the amount of processing. + outPoint->m_VizAdjusted = m_VizAdjusted; + iterHelper.m_Color.x = outPoint->m_ColorX = m_ColorSpeedCache + (m_OneMinusColorCache * inPoint->m_ColorX); + + //This modification returns the affine transformed points if no variations are present. + //Note this differs from flam3, which would just return zero in that scenario. + if (m_HasPreOrRegularVars) + { + //Compute the pre affine portion of the transform. + //These x, y values are what get passed to the variations below. + //Note that they are not changed after this, except in the case of pre_ variations. + iterHelper.m_TransX = (m_Affine.A() * inPoint->m_X) + (m_Affine.B() * inPoint->m_Y) + m_Affine.C(); + iterHelper.m_TransY = (m_Affine.D() * inPoint->m_X) + (m_Affine.E() * inPoint->m_Y) + m_Affine.F(); + iterHelper.m_TransZ = inPoint->m_Z; + + //Apply pre_ variations, these don't affect outPoint, only iterHelper.m_TransX, Y, Z. + for (i = 0; i < PreVariationCount(); i++) + { + iterHelper.In.x = iterHelper.m_TransX;//Read must be done before every pre variation because transX/Y are changing. + iterHelper.In.y = iterHelper.m_TransY; + iterHelper.In.z = iterHelper.m_TransZ; + m_PreVariations[i]->Precalc(iterHelper, inPoint);//Apply per-variation precalc, the second parameter is unused for pre variations. + m_PreVariations[i]->Func(iterHelper, *outPoint, rand); + WritePre(iterHelper, m_PreVariations[i]->AssignType()); + } + + if (VariationCount() > 0) + { + //The original calculates sumsq and sumsqrt every time, regardless if they're used or not. + //With Precalc(), only calculate those values if they're needed. + Precalc(iterHelper);//Only need per-xform precalc with regular variations. + iterHelper.In.x = iterHelper.m_TransX;//Only need to read once with regular variations, because transX/Y are fixed. + iterHelper.In.y = iterHelper.m_TransY; + iterHelper.In.z = iterHelper.m_TransZ; + + //Since these get summed, initialize them to zero. + outPoint->m_X = outPoint->m_Y = outPoint->m_Z = 0; + + //Apply variations to the transformed points, accumulating each time, and store the final value in outPoint. + //Using a virtual function is about 3% faster than using a large case statement like the original did. + //Although research says that using virtual functions is slow, experience says otherwise. They execute + //with the exact same speed as both regular and static member functions. + for (i = 0; i < VariationCount(); i++) + { + m_Variations[i]->Func(iterHelper, *outPoint, rand); + outPoint->m_X += iterHelper.Out.x; + outPoint->m_Y += iterHelper.Out.y; + outPoint->m_Z += iterHelper.Out.z; + } + } + else//Only pre variations are present, no regular ones, so assign the affine transformed points directly to the output points. + { + outPoint->m_X = iterHelper.m_TransX; + outPoint->m_Y = iterHelper.m_TransY; + outPoint->m_Z = iterHelper.m_TransZ; + } + } + else + { + //There are no variations, so the affine transformed points can be assigned directly to the output points. + T inX = inPoint->m_X; + + outPoint->m_X = (m_Affine.A() * inX) + (m_Affine.B() * inPoint->m_Y) + m_Affine.C(); + outPoint->m_Y = (m_Affine.D() * inX) + (m_Affine.E() * inPoint->m_Y) + m_Affine.F(); + outPoint->m_Z = inPoint->m_Z; + } + + //Apply post variations, these will modify outPoint. + for (i = 0; i < PostVariationCount(); i++) + { + iterHelper.In.x = outPoint->m_X;//Read must be done before every post variation because the out point is changing. + iterHelper.In.y = outPoint->m_Y; + iterHelper.In.z = outPoint->m_Z; + m_PostVariations[i]->Precalc(iterHelper, outPoint);//Apply per-variation precalc. + m_PostVariations[i]->Func(iterHelper, *outPoint, rand); + WritePost(iterHelper, *outPoint, m_PostVariations[i]->AssignType()); + } + + //Optionally apply the post affine transform if it's present. + if (m_HasPost) + { + T postX = outPoint->m_X; + + outPoint->m_X = (m_Post.A() * postX) + (m_Post.B() * outPoint->m_Y) + m_Post.C(); + outPoint->m_Y = (m_Post.D() * postX) + (m_Post.E() * outPoint->m_Y) + m_Post.F(); + } + + outPoint->m_ColorX = outPoint->m_ColorX + m_DirectColor * (iterHelper.m_Color.x - outPoint->m_ColorX); + + //Has the trajectory of x or y gone either to infinity, or too close to zero? + return BadVal(outPoint->m_X) || BadVal(outPoint->m_Y)/* || BadVal(outPoint->m_Z)*/; + } + +//Why are we not using template with member var addr as arg here?//TODO +#define APPMOT(x) do { x += mot[i].x * Interpolater::MotionFuncs(func, freq * blend); } while (0) + + /// + /// Apply the motion functions from the passed in xform to this xform. + /// + /// The xform containing the motion functions + /// The time blending value 0-1 + void ApplyMotion(Xform& xform, T blend) + { + unsigned int i, j, k; + Xform* mot = xform.m_Motion.data(); + + //Loop over the motion elements and add their contribution to the original vals. + for (i = 0; i < xform.m_Motion.size(); i++) + { + //Original only pulls these from the first motion xform which is a bug. Want to pull it from each one. + Xform* currentMot = &xform.m_Motion[i]; + int freq = currentMot->m_MotionFreq; + eMotion func = currentMot->m_MotionFunc; + + //Clamp these to the appropriate range after all are applied. + APPMOT(m_Weight); + APPMOT(m_ColorX); + //APPMOT(m_ColorY); + APPMOT(m_DirectColor); + APPMOT(m_Opacity); + APPMOT(m_ColorSpeed); + APPMOT(m_Animate); + + for (j = 0; j < currentMot->TotalVariationCount(); j++)//For each variation in the motion xform. + { + Variation* motVar = currentMot->GetVariation(j);//Get the variation, which may or may not be present in this xform. + ParametricVariation* motParVar = dynamic_cast*>(motVar); + + Variation* var = GetVariationById(motVar->VariationId());//See if the variation in the motion xform was present in the xform. + + if (!var)//It wasn't present, so add it and set the weight. + { + Variation* newVar = motVar->Copy(); + newVar->m_Weight = motVar->m_Weight * Interpolater::MotionFuncs(func, freq * blend); + AddVariation(newVar); + var = newVar;//Use this below for params. + } + else//It was present, so apply the motion func to the weight. + { + var->m_Weight += motVar->m_Weight * Interpolater::MotionFuncs(func, freq * blend); + } + + //At this point, we've added if needed, or just applied the motion func to the weight. + //Now apply the motion func to the params if needed. + if (motParVar != NULL) + { + ParametricVariation* parVar = dynamic_cast*>(var); + ParamWithName* params = parVar->Params(); + ParamWithName* motParams = motParVar->Params(); + + for (k = 0; k < motParVar->ParamCount(); k++) + { + if (!motParams[k].IsPrecalc()) + *(params[k].Param()) += motParams[k].ParamVal() * Interpolater::MotionFuncs(func, freq * blend); + } + } + } + + for (j = 0; j < 2; j++) + { + for (k = 0; k < 3; k++) + { + APPMOT(m_Affine.m_Mat[j][k]); + APPMOT(m_Post.m_Mat[j][k]); + } + } + } + + //Make sure certain params are within reasonable bounds. + ClampRef(m_ColorX, 0, 1); + //ClampRef(m_ColorY, 0, 1); + ClampRef(m_DirectColor, 0, 1); + ClampRef(m_Opacity, 0, 1);//Original didn't clamp these, but do it here for correctness. + ClampRef(m_ColorSpeed, 0, 1); + ClampGte0Ref(m_Weight); + } + + /// + /// Accessors. + /// The precalc flags are duplicated in each variation. Each value here + /// is true if any of the variations need it precalculated. + /// + inline bool NeedPrecalcSumSquares() const { return m_NeedPrecalcSumSquares; } + inline bool NeedPrecalcSqrtSumSquares() const { return m_NeedPrecalcSqrtSumSquares; } + inline bool NeedPrecalcAngles() const { return m_NeedPrecalcAngles; } + inline bool NeedPrecalcAtanXY() const { return m_NeedPrecalcAtanXY; } + inline bool NeedPrecalcAtanYX() const { return m_NeedPrecalcAtanYX; } + inline bool NeedAnyPrecalc() const { return NeedPrecalcSumSquares() || NeedPrecalcSqrtSumSquares() || NeedPrecalcAngles() || NeedPrecalcAtanXY() || NeedPrecalcAtanYX(); } + bool HasPost() const { return m_HasPost; } + unsigned int PreVariationCount() const { return (unsigned int)m_PreVariations.size(); } + unsigned int VariationCount() const { return (unsigned int)m_Variations.size(); } + unsigned int PostVariationCount() const { return (unsigned int)m_PostVariations.size(); } + unsigned int TotalVariationCount() const { return PreVariationCount() + VariationCount() + PostVariationCount(); } + bool Empty() const { return TotalVariationCount() == 0 && m_Affine.IsID(); }//Use this instead of padding like the original did. + T VizAdjusted() const { return m_VizAdjusted; } + T ColorSpeedCache() const { return m_ColorSpeedCache; } + T OneMinusColorCache() const { return m_OneMinusColorCache; } + const vector& XaosVec() const { return m_Xaos; } + Ember* ParentEmber() const { return m_ParentEmber; } + void ParentEmber(Ember* ember) { m_ParentEmber = ember; } + int IndexInParentEmber() { return m_ParentEmber ? m_ParentEmber->GetTotalXformIndex(this) : -1; } + + /// + /// Set the precalc flags based on whether any variation in the vector needs them. + /// Also call Precalc() virtual function on each variation, which will setup any needed + /// precalcs in parametric variations. + /// Set the parent xform of each variation to this. + /// + void SetPrecalcFlags() + { + m_NeedPrecalcSumSquares = false; + m_NeedPrecalcSqrtSumSquares = false; + m_NeedPrecalcAngles = false; + m_NeedPrecalcAtanXY = false; + m_NeedPrecalcAtanYX = false; + m_HasPost = !m_Post.IsID(); + m_HasPreOrRegularVars = PreVariationCount() > 0 || VariationCount() > 0; + + //Only set precalcs for regular variations, they work differently for pre and post. + for (size_t i = 0; i < m_Variations.size(); i++) + { + if (m_Variations[i]->NeedPrecalcSumSquares()) + m_NeedPrecalcSumSquares = true; + + if (m_Variations[i]->NeedPrecalcSqrtSumSquares()) + m_NeedPrecalcSqrtSumSquares = true; + + if (m_Variations[i]->NeedPrecalcAngles()) + m_NeedPrecalcAngles = true; + + if (m_Variations[i]->NeedPrecalcAtanXY()) + m_NeedPrecalcAtanXY = true; + + if (m_Variations[i]->NeedPrecalcAtanYX()) + m_NeedPrecalcAtanYX = true; + } + + AllVarsFunc([&] (vector*>& variations, bool& keepGoing) + { + for (size_t i = 0; i < variations.size(); i++) + { + /*if (variations[i]->NeedPrecalcSumSquares()) + m_NeedPrecalcSumSquares = true; + + if (variations[i]->NeedPrecalcSqrtSumSquares()) + m_NeedPrecalcSqrtSumSquares = true; + + if (variations[i]->NeedPrecalcAngles()) + m_NeedPrecalcAngles = true; + + if (variations[i]->NeedPrecalcAtanXY()) + m_NeedPrecalcAtanXY = true; + + if (variations[i]->NeedPrecalcAtanYX()) + m_NeedPrecalcAtanYX = true;*/ + + variations[i]->ParentXform(this); + variations[i]->Precalc(); + } + }); + } + + /// + /// Based on the precalc flags determined in SetPrecalcFlags(), do the appropriate precalcs. + /// + /// The iterator helper to store the precalculated values in + void Precalc(IteratorHelper& helper) + { + if (m_NeedPrecalcSumSquares) + { + helper.m_PrecalcSumSquares = SQR(helper.m_TransX) + SQR(helper.m_TransY); + + if (m_NeedPrecalcSqrtSumSquares) + { + helper.m_PrecalcSqrtSumSquares = sqrt(helper.m_PrecalcSumSquares); + + if (m_NeedPrecalcAngles) + { + helper.m_PrecalcSina = helper.m_TransX / helper.m_PrecalcSqrtSumSquares; + helper.m_PrecalcCosa = helper.m_TransY / helper.m_PrecalcSqrtSumSquares; + } + } + } + + if (m_NeedPrecalcAtanXY) + helper.m_PrecalcAtanxy = atan2(helper.m_TransX, helper.m_TransY); + + if (m_NeedPrecalcAtanYX) + helper.m_PrecalcAtanyx = atan2(helper.m_TransY, helper.m_TransX); + } + + /// + /// Generate the OpenCL string for reading input values to + /// be passed to a variation. + /// + /// Type of the variation these values will be passed to. + /// The OpenCL string + string ReadOpenCLString(eVariationType varType) + { + string s; + + switch (varType) + { + case VARTYPE_REG: + case VARTYPE_PRE: + s = + "\tvIn.x = transX;\n" + "\tvIn.y = transY;\n" + "\tvIn.z = transZ;\n"; + break; + case VARTYPE_POST: + s = + "\tvIn.x = outPoint->m_X;\n" + "\tvIn.y = outPoint->m_Y;\n" + "\tvIn.z = outPoint->m_Z;\n"; + break; + } + + return s; + } + + /// + /// Assing output values from the result of a pre variation. + /// + /// The helper to store the output values in + /// The type of assignment this variation uses, assign or sum. + inline void WritePre(IteratorHelper& helper, eVariationAssignType assignType) + { + switch (assignType) + { + case ASSIGNTYPE_SET: + { + helper.m_TransX = helper.Out.x; + helper.m_TransY = helper.Out.y; + helper.m_TransZ = helper.Out.z; + break; + } + case ASSIGNTYPE_SUM: + { + helper.m_TransX += helper.Out.x; + helper.m_TransY += helper.Out.y; + helper.m_TransZ += helper.Out.z; + break; + } + } + } + + /// + /// Assing output values from the result of a post variation. + /// + /// The helper to store the output values in + /// The type of assignment this variation uses, assign or sum. + inline void WritePost(IteratorHelper& helper, Point& outPoint, eVariationAssignType assignType) + { + switch (assignType) + { + case ASSIGNTYPE_SET: + { + outPoint.m_X = helper.Out.x; + outPoint.m_Y = helper.Out.y; + outPoint.m_Z = helper.Out.z; + break; + } + case ASSIGNTYPE_SUM: + { + outPoint.m_X += helper.Out.x; + outPoint.m_Y += helper.Out.y; + outPoint.m_Z += helper.Out.z; + break; + } + } + } + + /// + /// Generate the OpenCL string for writing output values from a call to a variation. + /// + /// The type of variation these values were calculated from, pre, reg or post. + /// The type of assignment used by the variation these values were calculated from, assign or sum. + /// The OpenCL string + string WriteOpenCLString(eVariationType varType, eVariationAssignType assignType) + { + string s; + + switch (varType) + { + case VARTYPE_REG: + { + s = + "\toutPoint->m_X += vOut.x;\n" + "\toutPoint->m_Y += vOut.y;\n" + "\toutPoint->m_Z += vOut.z;\n"; + break; + } + case VARTYPE_PRE: + { + switch (assignType) + { + case ASSIGNTYPE_SET: + { + s = + "\ttransX = vOut.x;\n" + "\ttransY = vOut.y;\n" + "\ttransZ = vOut.z;\n"; + break; + } + case ASSIGNTYPE_SUM: + { + s = + "\ttransX += vOut.x;\n" + "\ttransY += vOut.y;\n" + "\ttransZ += vOut.z;\n"; + break; + } + } + + break; + } + case VARTYPE_POST: + { + switch (assignType) + { + case ASSIGNTYPE_SET: + { + s = + "\toutPoint->m_X = vOut.x;\n" + "\toutPoint->m_Y = vOut.y;\n" + "\toutPoint->m_Z = vOut.z;\n"; + break; + } + case ASSIGNTYPE_SUM: + { + s = + "\toutPoint->m_X += vOut.x;\n" + "\toutPoint->m_Y += vOut.y;\n" + "\toutPoint->m_Z += vOut.z;\n"; + break; + } + } + + break; + } + } + + return s; + } + + /// + /// Return a string representation of this xform. + /// It will include all pre affine values, and optionally post affine values if present. + /// Various variables, all variations as strings and xaos values if present. + /// + /// The string representation of this xform + string ToString() const + { + ostringstream ss; + + ss << "A: " << m_Affine.A() << " " + << "B: " << m_Affine.B() << " " + << "C: " << m_Affine.C() << " " + << "D: " << m_Affine.D() << " " + << "E: " << m_Affine.E() << " " + << "F: " << m_Affine.F() << " " << endl; + + if (m_HasPost) + { + ss << "Post A: " << m_Post.A() << " " + << "Post B: " << m_Post.B() << " " + << "Post C: " << m_Post.C() << " " + << "Post D: " << m_Post.D() << " " + << "Post E: " << m_Post.E() << " " + << "Post F: " << m_Post.F() << " " << endl; + } + + ss << "Weight: " << m_Weight << endl; + ss << "ColorX: " << m_ColorX << endl; + ss << "ColorY: " << m_ColorY << endl; + ss << "Direct Color: " << m_DirectColor << endl; + ss << "Color Speed: " << m_ColorSpeed << endl; + ss << "Animate: " << m_Animate << endl; + ss << "Opacity: " << m_Opacity << endl; + ss << "Viz Adjusted: " << m_VizAdjusted << endl; + ss << "Wind: " << m_Wind[0] << ", " << m_Wind[1] << endl; + ss << "Motion Frequency: " << m_MotionFreq << endl; + ss << "Motion Func: " << m_MotionFunc << endl; + + const_cast*>(this)->AllVarsFunc([&] (vector*>& variations, bool& keepGoing) + { + for (unsigned int i = 0; i < variations.size(); i++) + ss << variations[i]->ToString() << endl; + + ss << endl; + }); + + if (XaosPresent()) + { + for (unsigned int i = 0; i < m_Xaos.size(); i++) + ss << m_Xaos[i] << " "; + + ss << endl; + } + + return ss.str(); + } + + /// + /// Members are listed in the exact order they are used in Apply() to make them + /// as cache efficient as possible. Not all are public, so there is repeated public/private + /// access specifiers. + /// + +private: + bool m_HasPreOrRegularVars;//Whethere there are any pre or regular variations present. + T m_VizAdjusted;//Adjusted visibility for better transitions. + +public: + //Color coordinates for this function. This is the index into the palette used to look up a color and add to the histogram for each iter. + //The original only allows for an x coord. Will eventually allow for a y coord like Fractron for 2D palettes. + T m_ColorX, m_ColorY; + +private: + T m_ColorSpeedCache;//Cache of m_ColorSpeed * m_ColorX. Need to recalc cache values whenever anything relating to color is set. Made private because one affects the other. + T m_OneMinusColorCache;//Cache of 1 - m_ColorSpeedCache. + +public: + //Coefficients for the affine portion of the transform. + //Discussed on page 3 of the paper: + //Fi(x, y) = (aix + biy + ci, dix + eiy + fi) + Affine2D m_Affine; + +private: + vector*> m_PreVariations;//The list of pre variations to call when applying this xform. + vector*> m_Variations;//The list of variations to call when applying this xform. + bool m_HasPost;//Whether a post affine transform is present. + +public: + //Coefficients for the affine portion of the post transform. + //Discussed on page 5 of the paper: + //Pi(x, y) = (αix + βiy + γi, δix + Ç«iy + ζi). + Affine2D m_Post; + +private: + vector*> m_PostVariations;//The list of post variations to call when applying this xform. + +public: + T m_DirectColor;//Used with direct color variations. + + //Probability that this function is chosen. Can be greater than 1. + //Discussed on page 4 of the paper: + //Probability wi. + T m_Weight; + + //Scaling factor on color added to current iteration, also known as color weight. Normally defaults to 0.5. + //Discussed on page 9 of the paper with a hard coded default value of 0.5: + //C = (C + Ci) * m_ColorSpeed. + T m_ColorSpeed; + T m_Opacity;//How much of this xform is seen. Range: 0.0 (invisible) - 1.0 (totally visible). + T m_Animate;//Whether or not this xform rotates during animation. 0 means stationary, > 0 means rotate. Use T instead of bool so it can be interpolated. + T m_Wind[2]; + eMotion m_MotionFunc; + int m_MotionFreq; + vector> m_Motion; + string m_Name; + +private: + /// + /// Perform an operation on all variation vectors. + /// The operation is supplied in the func parameter. + /// To stop performing the operation on vectors after the current one, + /// set the keepGoing parameter to false; + /// + /// The function to call for each variation vector. + void AllVarsFunc(std::function*>&, bool&)> func) + { + bool keepGoing = true; + + func(m_PreVariations, keepGoing); + + if (keepGoing) + func(m_Variations, keepGoing); + + if (keepGoing) + func(m_PostVariations, keepGoing); + } + + /// + /// Adjust opacity. + /// + /// The opacity to adjust, range 0-1. + /// The adjusted opacity + static T AdjustOpacityPercentage(T in) + { + if (in == 0) + return 0; + else + return pow(T(10.0), -log(T(1.0) / T(in)) / log(T(2))); + } + + vector m_Xaos;//Xaos vector which affects the probability that this xform is chosen. Usually empty. + Ember* m_ParentEmber;//The parent ember that contains this xform. + bool m_NeedPrecalcSumSquares;//Whether any variation uses the precalc sum squares value in its calculations. + bool m_NeedPrecalcSqrtSumSquares;//Whether any variation uses the sqrt precalc sum squares value in its calculations. + bool m_NeedPrecalcAngles;//Whether any variation uses the precalc sin and cos values in its calculations. + bool m_NeedPrecalcAtanXY;//Whether any variation uses the precalc atan XY value in its calculations. + bool m_NeedPrecalcAtanYX;//Whether any variation uses the precalc atan YX value in its calculations. +}; +} \ No newline at end of file diff --git a/Source/Ember/XmlToEmber.h b/Source/Ember/XmlToEmber.h new file mode 100644 index 0000000..d7bce7b --- /dev/null +++ b/Source/Ember/XmlToEmber.h @@ -0,0 +1,1350 @@ +#pragma once + +#include "Utils.h" +#include "PaletteList.h" +#include "VariationList.h" + +/// +/// XmlToEmber and Locale classes. +/// + +namespace EmberNs +{ +/// +/// Convenience class for setting and resetting the locale. +/// It's set up in the constructor and restored in the destructor. +/// This relieves the caller of having to manually do it everywhere. +/// +class EMBER_API Locale +{ +public: + /// + /// Constructor which saves the state of the current locale and + /// sets the new one based on the parameters passed in. + /// + /// The locale category. Default: LC_NUMERIC. + /// The locale. Default: "C". + Locale(int category = LC_NUMERIC, const char* loc = "C") + { + m_Category = category; + m_NewLocale = string(loc); + m_OriginalLocale = setlocale(category, NULL);//Query. + + if (m_OriginalLocale.empty()) + cout << "Couldn't get original locale." << endl; + + if (setlocale(category, loc) == NULL)//Set. + cout << "Couldn't set new locale " << category << ", " << loc << "." << endl; + } + + /// + /// Reset the locale to the value stored during construction. + /// + ~Locale() + { + if (!m_OriginalLocale.empty()) + if (setlocale(m_Category, m_OriginalLocale.c_str()) == NULL)//Restore. + cout << "Couldn't restore original locale " << m_Category << ", " << m_OriginalLocale << "." << endl; + } + +private: + int m_Category; + string m_NewLocale; + string m_OriginalLocale; +}; + +/// +/// Class for reading Xml files into ember objects. +/// This class derives from EmberReport, so the caller is able +/// to retrieve a text dump of error information if any errors occur. +/// Since this class contains a VariationList object, it's important to declare one +/// instance and reuse it for the duration of the program instead of creating and deleting +/// them as local variables. +/// Template argument expected to be float or double. +/// +template +class EMBER_API XmlToEmber : public EmberReport +{ +public: + /// + /// Constructor that initializes the random context. + /// + XmlToEmber() + { + Timing t; + + if (!m_Init) + { + m_BadParamNames.push_back(pair("swtin_distort", "stwin_distort"));//stwin. + m_BadParamNames.push_back(pair("pow_numerator", "pow_block_numerator"));//pow_block. + m_BadParamNames.push_back(pair("pow_denominator", "pow_block_denominator")); + m_BadParamNames.push_back(pair("pow_root", "pow_block_root")); + m_BadParamNames.push_back(pair("pow_correctn", "pow_block_correctn")); + m_BadParamNames.push_back(pair("pow_correctd", "pow_block_correctd")); + m_BadParamNames.push_back(pair("pow_power", "pow_block_power")); + m_BadParamNames.push_back(pair("lT", "linearT_powX"));//linearT. + m_BadParamNames.push_back(pair("lT", "linearT_powY")); + m_BadParamNames.push_back(pair("Re_A", "Mobius_Re_A"));//Mobius. + m_BadParamNames.push_back(pair("Im_A", "Mobius_Im_A")); + m_BadParamNames.push_back(pair("Re_B", "Mobius_Re_B")); + m_BadParamNames.push_back(pair("Im_B", "Mobius_Im_B")); + m_BadParamNames.push_back(pair("Re_C", "Mobius_Re_C")); + m_BadParamNames.push_back(pair("Im_C", "Mobius_Im_C")); + m_BadParamNames.push_back(pair("Re_D", "Mobius_Re_D")); + m_BadParamNames.push_back(pair("Im_D", "Mobius_Im_D")); + m_BadParamNames.push_back(pair("rx_sin", "rotate_x_sin"));//rotate_x. + m_BadParamNames.push_back(pair("rx_cos", "rotate_x_cos")); + m_BadParamNames.push_back(pair("ry_sin", "rotate_y_sin"));//rotate_y. + m_BadParamNames.push_back(pair("ry_cos", "rotate_y_cos")); + m_BadParamNames.push_back(pair("intrfr2_a1", "interference2_a1"));//interference2. + m_BadParamNames.push_back(pair("intrfr2_b1", "interference2_b1")); + m_BadParamNames.push_back(pair("intrfr2_c1", "interference2_c1")); + m_BadParamNames.push_back(pair("intrfr2_p1", "interference2_p1")); + m_BadParamNames.push_back(pair("intrfr2_t1", "interference2_t1")); + m_BadParamNames.push_back(pair("intrfr2_a2", "interference2_a2")); + m_BadParamNames.push_back(pair("intrfr2_b2", "interference2_b2")); + m_BadParamNames.push_back(pair("intrfr2_c2", "interference2_c2")); + m_BadParamNames.push_back(pair("intrfr2_p2", "interference2_p2")); + m_BadParamNames.push_back(pair("intrfr2_t2", "interference2_t2")); + m_BadParamNames.push_back(pair("octa_x", "octagon_x"));//octagon. + m_BadParamNames.push_back(pair("octa_y", "octagon_y")); + m_BadParamNames.push_back(pair("octa_z", "octagon_z")); + m_BadParamNames.push_back(pair("bubble_x", "bubble2_x"));//bubble2. + m_BadParamNames.push_back(pair("bubble_y", "bubble2_y")); + m_BadParamNames.push_back(pair("bubble_z", "bubble2_z")); + m_BadParamNames.push_back(pair("cubic3D_xpand", "cubicLattice_3D_xpand"));//cubicLattice_3D. + m_BadParamNames.push_back(pair("cubic3D_style", "cubicLattice_3D_style")); + m_BadParamNames.push_back(pair("splitb_x", "SplitBrdr_x"));//SplitBrdr. + m_BadParamNames.push_back(pair("splitb_y", "SplitBrdr_y")); + m_BadParamNames.push_back(pair("splitb_px", "SplitBrdr_px")); + m_BadParamNames.push_back(pair("splitb_py", "SplitBrdr_py")); + m_BadParamNames.push_back(pair("dc_cyl_offset", "dc_cylinder_offset"));//dc_cylinder. + m_BadParamNames.push_back(pair("dc_cyl_angle", "dc_cylinder_angle")); + m_BadParamNames.push_back(pair("dc_cyl_scale", "dc_cylinder_scale")); + m_BadParamNames.push_back(pair("cyl_x", "dc_cylinder_x")); + m_BadParamNames.push_back(pair("cyl_y", "dc_cylinder_y")); + m_BadParamNames.push_back(pair("cyl_blur", "dc_cylinder_blur")); + m_BadParamNames.push_back(pair("mobius_radius", "mobius_strip_radius"));//mobius_strip. + m_BadParamNames.push_back(pair("mobius_width", "mobius_strip_width")); + m_BadParamNames.push_back(pair("mobius_rect_x", "mobius_strip_rect_x")); + m_BadParamNames.push_back(pair("mobius_rect_y", "mobius_strip_rect_y")); + m_BadParamNames.push_back(pair("mobius_rotate_x", "mobius_strip_rotate_x")); + m_BadParamNames.push_back(pair("mobius_rotate_y", "mobius_strip_rotate_y")); + + m_BadVariationNames.push_back(pair("post_dcztransl", "post_dc_ztransl"));//dc_ztransl. + m_BadVariationNames.push_back(pair("mobius", "mobius_strip"));//mobius_strip, case sensitive to not clash with Mobius. + m_Init = true; + } + } + + /// + /// Parse the specified buffer and place the results in the vector of embers passed in. + /// + /// The buffer to parse + /// Full path and filename, optionally empty + /// The newly constructed embers based on what was parsed + /// True if there were no errors, else false. + bool Parse(unsigned char* buf, const char* filename, vector>& embers) + { + char* bn; + const char* xmlBuf; + const char* loc = __FUNCTION__; + unsigned int emberSize; + size_t bufSize; + xmlDocPtr doc;//Parsed XML document tree. + xmlNodePtr rootnode; + Locale locale;//Sets and restores on exit. + m_ErrorReport.clear(); + + //Parse XML string into internal document. + xmlBuf = (const char*)(&buf[0]); + bufSize = strlen(xmlBuf); + embers.reserve(bufSize / 2500);//The Xml text for an ember is around 2500 bytes, but can be much more. Pre-allocate to aovid unnecessary resizing. + doc = xmlReadMemory(xmlBuf, (int)bufSize, filename, NULL, XML_PARSE_NONET);//Forbid network access during read. + + if (doc == NULL) + { + m_ErrorReport.push_back(string(loc) + " : Error parsing xml file " + string(filename)); + return false; + } + + //What is the root node of the document? + rootnode = xmlDocGetRootElement(doc); + + //Scan for nodes, starting with this node. + bn = basename(filename); + ScanForEmberNodes(rootnode, bn, embers); + xmlFreeDoc(doc); + emberSize = (unsigned int)embers.size(); + + //Check to see if the first control point or the second-to-last + //control point has interpolation="smooth". This is invalid + //and should be reset to linear (with a warning). + if (emberSize > 0) + { + if (embers[0].m_Interp == EMBER_INTERP_SMOOTH) + { + cout << "Warning: smooth interpolation cannot be used for first segment.\n switching to linear.\n" << endl; + embers[0].m_Interp = EMBER_INTERP_LINEAR; + } + + if (emberSize >= 2 && embers[emberSize - 2].m_Interp == EMBER_INTERP_SMOOTH) + { + cout << "Warning: smooth interpolation cannot be used for last segment.\n switching to linear.\n" << endl; + embers[emberSize - 2].m_Interp = EMBER_INTERP_LINEAR; + } + } + + //Finally, ensure that consecutive 'rotate' parameters never exceed + //a difference of more than 180 degrees (+/-) for interpolation. + //An adjustment of +/- 360 degrees is made until this is true. + if (emberSize > 1) + { + for (unsigned int i = 1; i < emberSize; i++) + { + //Only do this adjustment if not in compat mode.. + if (embers[i - 1].m_AffineInterp != INTERP_COMPAT && embers[i - 1].m_AffineInterp != INTERP_OLDER) + { + while (embers[i].m_Rotate < embers[i - 1].m_Rotate - 180) + embers[i].m_Rotate += 360; + + while (embers[i].m_Rotate > embers[i - 1].m_Rotate + 180) + embers[i].m_Rotate -= 360; + } + } + } + + return true; + } + + /// + /// Parse the specified file and place the results in the vector of embers passed in. + /// + /// Full path and filename + /// The newly constructed embers based on what was parsed + /// True if there were no errors, else false. + bool Parse(const char* filename, vector>& embers) + { + const char* loc = __FUNCTION__; + string buf; + + //Ensure palette list is setup first. + if (!m_PaletteList.Init()) + { + m_ErrorReport.push_back(string(loc) + " : Palette list must be initialized before parsing embers."); + return false; + } + + if (ReadFile(filename, buf)) + { + if (buf.find_first_of('&') != std::string::npos) + { + FindAndReplace(buf, "&", "&"); + } + + return Parse((unsigned char*)buf.data(), filename, embers); + } + else + return false; + } + + /// + /// Convert the string to a floating point value and return a bool indicating success. + /// See error report for errors. + /// + /// The string to convert + /// The converted value + /// True if success, else false. + bool Atof(char* str, T& val) + { + bool b = true; + char* endp; + const char* loc = __FUNCTION__; + + //Reset errno. + errno = 0;//Note that this is not thread-safe. + + //Convert the string using strtod(). + val = (T)strtod(str, &endp); + + //Check errno & return string. + if (endp != str + strlen(str)) + { + m_ErrorReport.push_back(string(loc) + " : Error converting " + string(str) + ", extra chars"); + b = false; + } + + if (errno) + { + m_ErrorReport.push_back(string(loc) + " : Error converting " + string(str)); + b = false; + } + + return b; + } + + /// + /// Thin wrapper around Atoi(). + /// See error report for errors. + /// + /// The string to convert + /// The converted unsigned integer value + /// True if success, else false. + bool Atoi(char* str, unsigned int& val) + { + return Atoi(str, (int&)val); + } + + /// + /// Convert the string to an unsigned integer value and return a bool indicating success. + /// See error report for errors. + /// + /// The string to convert + /// The converted unsigned integer value + /// True if success, else false. + bool Atoi(char* str, int& val) + { + bool b = true; + char* endp; + const char* loc = __FUNCTION__; + + //Reset errno. + errno = 0;//Note that this is not thread-safe. + + //Convert the string using strtod(). + val = strtol(str, &endp, 10); + + //Check errno & return string. + if (endp != str + strlen(str)) + { + m_ErrorReport.push_back(string(loc) + " : Error converting " + string(str) + ", extra chars"); + b = false; + } + + if (errno) + { + m_ErrorReport.push_back(string(loc) + " : Error converting " + string(str)); + b = false; + } + + return b; + } + + /// + /// Convert an integer to a string. + /// Just a wrapper around _itoa_s() which wraps the result in a std::string. + /// + /// The integer to convert + /// The radix of the integer. Default: 10. + /// The converted string + static string Itos(int i, int radix = 10) + { + char ch[16]; + + _itoa_s(i, ch, 16, radix); + return string(ch); + } + + /// + /// Convert an unsigned 64-bit integer to a string. + /// Just a wrapper around _ui64toa_s() which wraps the result in a std::string. + /// + /// The unsigned 64-bit integer to convert + /// The radix of the integer. Default: 10. + /// The converted string + static string Itos64(unsigned __int64 i, int radix = 10) + { + char ch[64]; + + _ui64toa_s(i, ch, 64, radix); + return string(ch); + } + +private: + /// + /// Scan the file for ember nodes, and parse them out into the vector of embers. + /// + /// The current node to parse + /// The full path and filename + /// The newly constructed embers based on what was parsed + void ScanForEmberNodes(xmlNode* curNode, char* parentFile, vector>& embers) + { + bool parseEmberSuccess; + xmlNodePtr thisNode = NULL; + const char* loc = __FUNCTION__; + string parentFileString = string(parentFile); + + //Original memset to 0, but the constructors should handle that. + //Loop over this level of elements. + for (thisNode = curNode; thisNode; thisNode = thisNode->next) + { + //Check to see if this element is a element. + if (thisNode->type == XML_ELEMENT_NODE && !Compare(thisNode->name, "flame")) + { + Ember currentEmber;//Place this inside here so its constructor is called each time. + + parseEmberSuccess = ParseEmberElement(thisNode, currentEmber); + + if (!parseEmberSuccess) + { + //Original leaked memory here, ours doesn't. + m_ErrorReport.push_back(string(loc) + " : Error parsing ember element"); + return; + } + + if (currentEmber.PaletteIndex() != -1) + { + if (!m_PaletteList.GetHueAdjustedPalette(currentEmber.PaletteIndex(), currentEmber.m_Hue, currentEmber.m_Palette)) + { + m_ErrorReport.push_back(string(loc) + " : Error assigning palette with index " + Itos(currentEmber.PaletteIndex())); + } + } + + //if (!Interpolater::InterpMissingColors(currentEmber.m_Palette.m_Entries)) + // m_ErrorReport.push_back(string(loc) + " : Error interpolating missing palette colors"); + + currentEmber.CacheXforms(); + currentEmber.m_Index = (int)embers.size(); + currentEmber.m_ParentFilename = parentFileString; + embers.push_back(currentEmber); + } + else + { + //Check all of the children of this element. + ScanForEmberNodes(thisNode->children, parentFile, embers); + } + } + } + + /// + /// Parse an ember element. + /// + /// The current node to parse + /// The newly constructed ember based on what was parsed + /// True if there were no errors, else false. + bool ParseEmberElement(xmlNode* emberNode, Ember& currentEmber) + { + bool b = true; + char* attStr; + const char* loc = __FUNCTION__; + int soloXform = -1; + unsigned int i, count, index = 0; + double vals[10]; + xmlAttrPtr att, curAtt; + xmlNodePtr editNode, childNode, motionNode; + + currentEmber.m_Palette.Clear();//Wipe out the current palette. + att = emberNode->properties;//The top level element is a ember element, read the attributes of it and store them. + + if (att == NULL) + { + m_ErrorReport.push_back(string(loc) + " : element has no attributes"); + return false; + } + + for (curAtt = att; curAtt; curAtt = curAtt->next) + { + attStr = (char*)xmlGetProp(emberNode, curAtt->name); + + //First parse out simple float reads. + if (ParseAndAssignFloat(curAtt->name, attStr, "time", currentEmber.m_Time, b)) { } + else if (ParseAndAssignFloat(curAtt->name, attStr, "scale", currentEmber.m_PixelsPerUnit, b)) { } + else if (ParseAndAssignFloat(curAtt->name, attStr, "rotate", currentEmber.m_Rotate, b)) { } + else if (ParseAndAssignFloat(curAtt->name, attStr, "zoom", currentEmber.m_Zoom, b)) { } + else if (ParseAndAssignFloat(curAtt->name, attStr, "filter", currentEmber.m_SpatialFilterRadius, b)) { } + else if (ParseAndAssignFloat(curAtt->name, attStr, "temporal_filter_width", currentEmber.m_TemporalFilterWidth, b)) { } + else if (ParseAndAssignFloat(curAtt->name, attStr, "temporal_filter_exp", currentEmber.m_TemporalFilterExp, b)) { } + else if (ParseAndAssignFloat(curAtt->name, attStr, "quality", currentEmber.m_Quality, b)) { } + else if (ParseAndAssignFloat(curAtt->name, attStr, "brightness", currentEmber.m_Brightness, b)) { } + else if (ParseAndAssignFloat(curAtt->name, attStr, "gamma", currentEmber.m_Gamma, b)) { } + else if (ParseAndAssignFloat(curAtt->name, attStr, "highlight_power", currentEmber.m_HighlightPower, b)) { } + else if (ParseAndAssignFloat(curAtt->name, attStr, "vibrancy", currentEmber.m_Vibrancy, b)) { } + else if (ParseAndAssignFloat(curAtt->name, attStr, "estimator_radius", currentEmber.m_MaxRadDE, b)) { } + else if (ParseAndAssignFloat(curAtt->name, attStr, "estimator_minimum", currentEmber.m_MinRadDE, b)) { } + else if (ParseAndAssignFloat(curAtt->name, attStr, "estimator_curve", currentEmber.m_CurveDE, b)) { } + else if (ParseAndAssignFloat(curAtt->name, attStr, "gamma_threshold", currentEmber.m_GammaThresh, b)) { } + else if (ParseAndAssignFloat(curAtt->name, attStr, "cam_zpos", currentEmber.m_CamZPos, b)) { } + else if (ParseAndAssignFloat(curAtt->name, attStr, "cam_persp", currentEmber.m_CamPerspective, b)) { } + else if (ParseAndAssignFloat(curAtt->name, attStr, "cam_perspective", currentEmber.m_CamPerspective, b)) { }//Apo bug. + else if (ParseAndAssignFloat(curAtt->name, attStr, "cam_yaw", currentEmber.m_CamYaw, b)) { } + else if (ParseAndAssignFloat(curAtt->name, attStr, "cam_pitch", currentEmber.m_CamPitch, b)) { } + else if (ParseAndAssignFloat(curAtt->name, attStr, "cam_dof", currentEmber.m_CamDepthBlur, b)) { } + + //Parse simple int reads. + else if (ParseAndAssignInt(curAtt->name, attStr, "palette", currentEmber.m_Palette.m_Index, b)) { } + else if (ParseAndAssignInt(curAtt->name, attStr, "oversample", currentEmber.m_Supersample , b)) { } + else if (ParseAndAssignInt(curAtt->name, attStr, "supersample", currentEmber.m_Supersample , b)) { } + else if (ParseAndAssignInt(curAtt->name, attStr, "temporal_samples", currentEmber.m_TemporalSamples, b)) { } + else if (ParseAndAssignInt(curAtt->name, attStr, "soloxform", soloXform , b)) { } + + //Parse more complicated reads that have multiple possible values. + else if (!Compare(curAtt->name, "interpolation")) + { + if (!strcmp("linear", attStr)) + currentEmber.m_Interp = EMBER_INTERP_LINEAR; + else if (!strcmp("smooth", attStr)) + currentEmber.m_Interp = EMBER_INTERP_SMOOTH; + else + m_ErrorReport.push_back(string(loc) + " : Unrecognized interpolation type " + string(attStr)); + } + else if (!Compare(curAtt->name, "palette_interpolation")) + { + if (!strcmp("hsv", attStr)) + currentEmber.m_PaletteInterp = INTERP_HSV; + else if (!strcmp("sweep", attStr)) + currentEmber.m_PaletteInterp = INTERP_SWEEP; + else + m_ErrorReport.push_back(string(loc) + " : Unrecognized palette interpolation type " + string(attStr)); + } + else if (!Compare(curAtt->name, "interpolation_space") || !Compare(curAtt->name, "interpolation_type")) + { + if (!strcmp("linear", attStr)) + currentEmber.m_AffineInterp = INTERP_LINEAR; + else if (!strcmp("log", attStr)) + currentEmber.m_AffineInterp = INTERP_LOG; + else if (!strcmp("old", attStr)) + currentEmber.m_AffineInterp = INTERP_COMPAT; + else if (!strcmp("older", attStr)) + currentEmber.m_AffineInterp = INTERP_OLDER; + else + m_ErrorReport.push_back(string(loc) + " : Unrecognized interpolation type " + string(attStr)); + } + else if (!Compare(curAtt->name, "name")) + { + currentEmber.m_Name = string(attStr); + std::replace(currentEmber.m_Name.begin(), currentEmber.m_Name.end(), ' ', '_'); + } + else if (!Compare(curAtt->name, "size")) + { + if (sscanf_s(attStr, "%d %d", ¤tEmber.m_FinalRasW, ¤tEmber.m_FinalRasH) != 2) + { + m_ErrorReport.push_back(string(loc) + " : Invalid size attribute " + string(attStr)); + xmlFree(attStr); + + //These return statements are bad. One because they are inconsistent with others that just assign defaults. + //Two, because assigning easily guessable defaults is easy and less drastic. + return false; + } + + currentEmber.m_OrigFinalRasW = currentEmber.m_FinalRasW; + currentEmber.m_OrigFinalRasH = currentEmber.m_FinalRasH; + } + else if (!Compare(curAtt->name, "center")) + { + if (sscanf_s(attStr, "%lf %lf", &vals[0], &vals[1]) != 2) + { + m_ErrorReport.push_back(string(loc) + " : Invalid center attribute " + string(attStr)); + xmlFree(attStr); + return false; + } + + currentEmber.m_CenterX = T(vals[0]); + currentEmber.m_CenterY = T(vals[1]); + } + else if (!Compare(curAtt->name, "filter_shape")) + { + currentEmber.m_SpatialFilterType = SpatialFilterCreator::FromString(string(attStr)); + } + else if (!Compare(curAtt->name, "temporal_filter_type")) + { + currentEmber.m_TemporalFilterType = TemporalFilterCreator::FromString(string(attStr)); + } + else if (!Compare(curAtt->name, "palette_mode")) + { + if (!strcmp("step", attStr)) + currentEmber.m_PaletteMode = PALETTE_STEP; + else if (!strcmp("linear", attStr)) + currentEmber.m_PaletteMode = PALETTE_LINEAR; + else + { + currentEmber.m_PaletteMode = PALETTE_STEP; + m_ErrorReport.push_back(string(loc) + " : Unrecognized palette mode " + string(attStr) + ", using step"); + } + } + else if (!Compare(curAtt->name, "background")) + { + if (sscanf_s(attStr, "%lf %lf %lf", &vals[0], &vals[1], &vals[2]) != 3) + { + m_ErrorReport.push_back(string(loc) + " : Invalid background attribute " + string(attStr)); + xmlFree(attStr); + return false; + } + + currentEmber.m_Background[0] = T(vals[0]);//[0..1] + currentEmber.m_Background[1] = T(vals[1]); + currentEmber.m_Background[2] = T(vals[2]); + } + else if (!Compare(curAtt->name, "hue")) + { + Atof(attStr, currentEmber.m_Hue); + currentEmber.m_Hue = fmod(currentEmber.m_Hue, T(0.5));//Orig did fmod 1, but want it in the range -0.5 - 0.5. + } + + xmlFree(attStr); + } + + //Finished with ember attributes. Now look at the children of the ember element. + for (childNode = emberNode->children; childNode; childNode = childNode->next) + { + if (!Compare(childNode->name, "color")) + { + index = -1; + double r = 0, g = 0, b = 0, a = 0; + + //Loop through the attributes of the color element. + att = childNode->properties; + + if (att == NULL) + { + m_ErrorReport.push_back(string(loc) + " : No attributes for color element"); + continue; + } + + for (curAtt = att; curAtt; curAtt = curAtt->next) + { + attStr = (char*)xmlGetProp(childNode, curAtt->name); + a = 255; + + //This signifies that a palette is not being retrieved from the palette file, rather it's being parsed directly out of the ember xml. + //This also means the palette has already been hue adjusted and it doesn't need to be done again, which would be necessary if it were + //coming from the palette file. + currentEmber.m_Palette.m_Index = -1; + + if (!Compare(curAtt->name, "index")) + { + Atoi(attStr, index); + } + else if(!Compare(curAtt->name, "rgb")) + { + if (sscanf_s(attStr, "%lf %lf %lf", &r, &g, &b) != 3) + m_ErrorReport.push_back(string(loc) + " : Invalid rgb attribute " + string(attStr)); + } + else if(!Compare(curAtt->name, "rgba")) + { + if (sscanf_s(attStr, "%lf %lf %lf %lf", &r, &g, &b, &a) != 4) + m_ErrorReport.push_back(string(loc) + " : Invalid rgba attribute " + string(attStr)); + } + else if(!Compare(curAtt->name, "a")) + { + if (sscanf_s(attStr, "%lf", &a) != 1) + m_ErrorReport.push_back(string(loc) + " : Invalid a attribute " + string(attStr)); + } + else + { + m_ErrorReport.push_back(string(loc) + " : Unknown color attribute " + string((const char*)curAtt->name)); + } + + xmlFree(attStr); + } + + //Palette colors are [0..255], convert to [0..1]. + if (index >= 0 && index <= 255) + { + T alphaPercent = T(a) / T(255);//Aplha percentage in the range of 0 to 1. + + //Premultiply the palette. + currentEmber.m_Palette.m_Entries[index].r = alphaPercent * (T(r) / T(255)); + currentEmber.m_Palette.m_Entries[index].g = alphaPercent * (T(g) / T(255)); + currentEmber.m_Palette.m_Entries[index].b = alphaPercent * (T(b) / T(255)); + currentEmber.m_Palette.m_Entries[index].a = T(a) / 255;//Will be one for RGB, and other than one if RGBA with A != 255. + } + else + { + stringstream ss; + ss << "ParseEmberElement() : Color element with bad/missing index attribute " << index; + m_ErrorReport.push_back(ss.str()); + } + } + else if (!Compare(childNode->name, "colors")) + { + //Loop through the attributes of the color element. + att = childNode->properties; + + if (att == NULL) + { + m_ErrorReport.push_back(string(loc) + " : No attributes for colors element"); + continue; + } + + for (curAtt = att; curAtt; curAtt = curAtt->next) + { + attStr = (char*)xmlGetProp(childNode, curAtt->name); + + if (!Compare(curAtt->name, "count")) + { + Atoi(attStr, count); + } + else if (!Compare(curAtt->name, "data")) + { + if (!ParseHexColors(attStr, currentEmber, count, -4)) + { + m_ErrorReport.push_back(string(loc) + " : Error parsing hexformatted colors, some may be set to zero"); + } + } + else + { + m_ErrorReport.push_back(string(loc) + " : Unknown color attribute " + string((const char*)curAtt->name)); + } + + xmlFree(attStr); + } + } + else if (!Compare(childNode->name, "palette")) + { + //This could be either the old form of palette or the new form. + //Make sure BOTH are not specified, otherwise either are ok. + int numColors = 0; + int numBytes = 0; + bool oldFormat = false; + bool newFormat = false; + int index0, index1; + T hue0, hue1; + T blend = 0.5; + index0 = index1 = -1; + hue0 = hue1 = 0.0; + + //Loop through the attributes of the palette element. + att = childNode->properties; + + if (att == NULL) + { + m_ErrorReport.push_back(string(loc) + " : No attributes for palette element"); + continue; + } + + for (curAtt = att; curAtt; curAtt = curAtt->next) + { + attStr = (char*)xmlGetProp(childNode, curAtt->name); + + if (!Compare(curAtt->name, "index0")) + { + oldFormat = true; + Atoi(attStr, index0); + } + else if (!Compare(curAtt->name, "index1")) + { + oldFormat = true; + Atoi(attStr, index1); + } + else if (!Compare(curAtt->name, "hue0")) + { + oldFormat = true; + Atof(attStr, hue0); + } + else if (!Compare(curAtt->name, "hue1")) + { + oldFormat = true; + Atof(attStr, hue1); + } + else if (!Compare(curAtt->name, "blend")) + { + oldFormat = true; + Atof(attStr, blend); + } + else if (!Compare(curAtt->name, "count")) + { + newFormat = true; + Atoi(attStr, numColors); + } + else if (!Compare(curAtt->name, "format")) + { + newFormat = true; + + if (!strcmp(attStr, "RGB")) + numBytes = 3; + else if (!strcmp(attStr, "RGBA")) + numBytes = 4; + else + { + m_ErrorReport.push_back(string(loc) + " : Unrecognized palette format string " + string(attStr) + ", defaulting to RGB"); + numBytes = 3; + } + } + else + { + m_ErrorReport.push_back(string(loc) + " : Unknown palette attribute " + string((const char*)curAtt->name)); + } + + xmlFree(attStr); + } + + //Old or new format? + if (newFormat && oldFormat) + { + oldFormat = false; + m_ErrorReport.push_back(string(loc) + " : Mixing of old and new palette tag syntax not allowed, defaulting to new"); + } + + if (oldFormat) + { + InterpolateCmap(currentEmber.m_Palette, blend, index0, hue0, index1, hue1); + } + else + { + //Read formatted string from contents of tag. + char* palStr = (char*)xmlNodeGetContent(childNode); + + if (!ParseHexColors(palStr, currentEmber, numColors, numBytes)) + { + m_ErrorReport.push_back(string(loc) + " : Problem reading hexadecimal color data in palette"); + } + + xmlFree(palStr); + } + } + else if (!Compare(childNode->name, "symmetry")) + { + int symKind = INT_MAX; + + //Loop through the attributes of the palette element. + att = childNode->properties; + + if (att == NULL) + { + m_ErrorReport.push_back(string(loc) + " : No attributes for palette element"); + continue; + } + + for (curAtt = att; curAtt; curAtt = curAtt->next) + { + attStr = (char*)xmlGetProp(childNode, curAtt->name); + + if (!Compare(curAtt->name, "kind")) + { + Atoi(attStr, symKind); + } + else + { + m_ErrorReport.push_back(string(loc) + " : Unknown symmetry attribute " + string(attStr)); + continue; + } + + xmlFree(attStr); + } + + //if (symKind != INT_MAX)//What to do about this? Should sym not be saved? Or perhaps better intelligence when adding?//TODO//BUG. + //{ + // currentEmber.AddSymmetry(symKind, *(GlobalRand.get()));//Determine what to do here. + //} + } + else if (!Compare(childNode->name, "xform") || !Compare(childNode->name, "finalxform")) + { + Xform* theXform = NULL; + + if (!Compare(childNode->name, "finalxform")) + { + Xform finalXform; + + if (!ParseXform(childNode, finalXform, false)) + { + m_ErrorReport.push_back(string(loc) + " : Error parsing final xform"); + } + else + { + if (finalXform.m_Weight != 0) + { + finalXform.m_Weight = 0; + m_ErrorReport.push_back(string(loc) + " : Final xforms should not have weight specified, setting to zero"); + } + + currentEmber.SetFinalXform(finalXform); + theXform = currentEmber.NonConstFinalXform(); + } + } + else + { + Xform xform; + + if (!ParseXform(childNode, xform, false)) + { + m_ErrorReport.push_back(string(loc) + " : Error parsing xform"); + } + else + { + currentEmber.AddXform(xform); + theXform = currentEmber.GetXform(currentEmber.XformCount() - 1); + } + } + + if (theXform) + { + //Check for non-zero motion params. + if (theXform->m_MotionFreq != 0)//Original checked for motion func being non-zero, but it was set to MOTION_SIN (1) in Xform::Init(), so don't check for 0 here. + { + m_ErrorReport.push_back(string(loc) + " : Motion parameters should not be specified in regular, non-motion xforms"); + } + + //Motion Language: Check the xform element for children - should be named 'motion'. + for (motionNode = childNode->children; motionNode; motionNode = motionNode->next) + { + if (!Compare(motionNode->name, "motion")) + { + Xform xform; + + if (!ParseXform(motionNode, xform, true)) + m_ErrorReport.push_back(string(loc) + " : Error parsing motion xform"); + else + theXform->m_Motion.push_back(xform); + } + } + } + } + else if (!Compare(childNode->name, "edit")) + { + //Create a new XML document with this edit node as the root node. + currentEmber.m_Edits = xmlNewDoc((const xmlChar*)"1.0"); + editNode = xmlCopyNode(childNode, 1); + xmlDocSetRootElement(currentEmber.m_Edits, editNode); + } + } + + for (i = 0; i < currentEmber.XformCount(); i++) + if (soloXform >= 0 && i != soloXform) + currentEmber.GetXform(i)->m_Opacity = 0;//Will calc the cached adjusted viz value later. + + return m_ErrorReport.empty(); + } + + /// + /// Parse an xform element. + /// + /// The current node to parse + /// The newly constructed xform based on what was parsed + /// True if this xform is a motion within a parent xform, else false + /// True if there were no errors, else false. + bool ParseXform(xmlNode* childNode, Xform& xform, bool motion) + { + bool success = true; + char* attStr, *copy; + const char* loc = __FUNCTION__; + unsigned int j; + T temp; + double a, b, c, d, e, f; + double vals[10]; + xmlAttrPtr attPtr, curAtt; + + //Loop through the attributes of the xform element. + attPtr = childNode->properties; + + if (attPtr == NULL) + { + m_ErrorReport.push_back(string(loc) + " : Error: No attributes for element"); + return false; + } + + for (curAtt = attPtr; curAtt; curAtt = curAtt->next) + { + copy = attStr = (char*)xmlGetProp(childNode, curAtt->name); + + //First parse out simple float reads. + if (ParseAndAssignFloat(curAtt->name, attStr, "weight", xform.m_Weight, success)) { } + else if (ParseAndAssignFloat(curAtt->name, attStr, "color_speed", xform.m_ColorSpeed, success)) { } + else if (ParseAndAssignFloat(curAtt->name, attStr, "symmetry", xform.m_ColorSpeed, success)) { xform.m_ColorSpeed = (1 - xform.m_ColorSpeed) / 2; }//Legacy support. + else if (ParseAndAssignFloat(curAtt->name, attStr, "animate", xform.m_Animate, success)) { } + else if (ParseAndAssignFloat(curAtt->name, attStr, "opacity", xform.m_Opacity, success)) { } + else if (ParseAndAssignFloat(curAtt->name, attStr, "var_color", xform.m_DirectColor, success)) { } + + //Parse simple int reads. + else if (ParseAndAssignInt(curAtt->name, attStr, "motion_frequency", xform.m_MotionFreq, success)) { } + + //Parse more complicated reads that have multiple possible values. + else if (!Compare(curAtt->name, "name")) + { + xform.m_Name = string(attStr); + std::replace(xform.m_Name.begin(), xform.m_Name.end(), ' ', '_'); + } + else if (!Compare(curAtt->name, "symmetry")) + { + //Deprecated, set both color_speed and animate to this value. + //Huh? Either set it or not? + Atof(attStr, temp); + xform.m_ColorSpeed = (1 - temp) / 2; + xform.m_Animate = T(temp > 0 ? 0 : 1); + } + else if (!Compare(curAtt->name, "motion_function")) + { + if (!strcmp("sin", attStr)) + xform.m_MotionFunc = MOTION_SIN; + else if (!strcmp("triangle", attStr)) + xform.m_MotionFunc = MOTION_TRIANGLE; + else if (!strcmp("hill", attStr)) + xform.m_MotionFunc = MOTION_HILL; + else + { + xform.m_MotionFunc = MOTION_SIN; + m_ErrorReport.push_back(string(loc) + " : Unknown motion function " + string(attStr) + ", using sin"); + } + } + else if (!Compare(curAtt->name, "color")) + { + T tmpc1 = 0; + xform.m_ColorX = xform.m_ColorY = 0; + + //Try two coords first . + if (sscanf_s(attStr, "%lf %lf", &vals[0], &vals[1]) == 2) + { + xform.m_ColorX = T(vals[0]); + xform.m_ColorY = T(vals[1]); + } + else if (sscanf_s(attStr, "%lf", &vals[0]) == 1)//Try one color. + { + xform.m_ColorX = T(vals[0]); + } + else + { + xform.m_ColorX = xform.m_ColorY = T(0.5); + m_ErrorReport.push_back(string(loc) + " : Malformed xform color attribute " + string(attStr) + ", using 0.5, 0.5"); + } + } + else if (!Compare(curAtt->name, "chaos")) + { + stringstream ss(attStr); + j = 0; + + while (ss >> temp) + { + xform.SetXaos(j, temp); + j++; + } + } + else if (!Compare(curAtt->name, "plotmode")) + { + if (motion == 1) + { + m_ErrorReport.push_back(string(loc) + " : Motion element cannot have a plotmode attribute"); + } + else if (!strcmp("off", attStr)) + xform.m_Opacity = 0; + } + else if (!Compare(curAtt->name, "coefs")) + { + if (sscanf_s(attStr, "%lf %lf %lf %lf %lf %lf", &a, &d, &b, &e, &c, &f) != 6)//Original did a complicated parsing scheme. This is easier.//ORIG + { + a = d = b = e = c = f = 0; + m_ErrorReport.push_back(string(loc) + " : Bad coeffs attribute " + string(attStr)); + } + + xform.m_Affine.A(T(a)); + xform.m_Affine.B(T(b)); + xform.m_Affine.C(T(c)); + xform.m_Affine.D(T(d)); + xform.m_Affine.E(T(e)); + xform.m_Affine.F(T(f)); + } + else if (!Compare(curAtt->name, "post")) + { + if (sscanf_s(attStr, "%lf %lf %lf %lf %lf %lf", &a, &d, &b, &e, &c, &f) != 6)//Original did a complicated parsing scheme. This is easier.//ORIG + { + a = d = b = e = c = f = 0; + m_ErrorReport.push_back(string(loc) + " : Bad post coeffs attribute " + string(attStr)); + } + + xform.m_Post.A(T(a)); + xform.m_Post.B(T(b)); + xform.m_Post.C(T(c)); + xform.m_Post.D(T(d)); + xform.m_Post.E(T(e)); + xform.m_Post.F(T(f)); + } + else + { + string s = GetCorrectedName(m_BadVariationNames, (const char*)curAtt->name); + const char* name = s.c_str(); + + if (Variation* var = m_VariationList.GetVariation(s)) + { + Variation* varCopy = var->Copy(); + + Atof(attStr, varCopy->m_Weight); + xform.AddVariation(varCopy); + } + //else + //{ + // m_ErrorReport.push_back("Unsupported variation: " + string((const char*)curAtt->name)); + //} + } + + xmlFree(attStr); + } + + //Handle var1. + for (curAtt = attPtr; curAtt; curAtt = curAtt->next) + { + bool var1 = false; + + if (!Compare(curAtt->name, "var1")) + { + copy = attStr = (char*)xmlGetProp(childNode, curAtt->name); + + for (j = 0; j < xform.TotalVariationCount(); j++) + xform.GetVariation(j)->m_Weight = 0; + + if (Atof(attStr, temp)) + { + unsigned int iTemp = (unsigned int)temp; + + if (iTemp < xform.TotalVariationCount()) + { + xform.GetVariation(iTemp)->m_Weight = 1; + var1 = true; + } + } + + if (!var1) + m_ErrorReport.push_back(string(loc) + " : Bad value for var1 " + string(attStr)); + + xmlFree(attStr); + break; + } + } + + //Handle var. + for (curAtt = attPtr; curAtt; curAtt = curAtt->next) + { + bool var = false; + + if (!Compare(curAtt->name, "var")) + { + copy = attStr = (char*)xmlGetProp(childNode, curAtt->name); + + if (Atof(attStr, temp)) + { + for (j = 0; j < xform.TotalVariationCount(); j++) + xform.GetVariation(j)->m_Weight = temp; + + var = true; + } + + if (!var) + m_ErrorReport.push_back(string(loc) + " : Bad value for var " + string(attStr)); + + xmlFree(attStr); + break; + } + } + + //Now that all xforms have been parsed, go through and try to find params for the parametric variations. + for (unsigned int i = 0; i < xform.TotalVariationCount(); i++) + { + if (ParametricVariation* parVar = dynamic_cast*>(xform.GetVariation(i))) + { + for (curAtt = attPtr; curAtt; curAtt = curAtt->next) + { + string s = GetCorrectedName(m_BadParamNames, (const char*)curAtt->name); + const char* name = s.c_str(); + + if (parVar->ContainsParam(name)) + { + T val = 0; + copy = attStr = (char*)xmlGetProp(childNode, curAtt->name); + + if (Atof(attStr, val)) + { + parVar->SetParamVal(name, val); + } + else + { + m_ErrorReport.push_back(string(loc) + " : Failed to parse parametric variation parameter " + s + " - " + string(attStr)); + } + + xmlFree(attStr); + } + } + } + } + + return true; + } + + /// + /// Some Apophysis plugins use an inconsistent naming scheme for the parametric variation variables. + /// This function identifies and converts them to Ember's consistent naming convention. + /// + /// The name of the variable found in the Xml + /// The corrected name if one was found, else the passed in name. + static string GetCorrectedName(vector>& vec, const char* name) + { + for (size_t i = 0; i < vec.size(); i++) + if (!strcmp(vec[i].first.c_str(), name))//Case sensitive is intentional. + return vec[i].second; + + return name; + } + + /// + /// Parse hexadecimal colors. + /// This can read RGB and RGBA, however only RGB will be stored. + /// + /// The string of hex colors to parse + /// The ember whose palette will be assigned the colors + /// The number of colors present + /// The number of channels in each color + /// True if there were no errors, else false. + bool ParseHexColors(char* colstr, Ember& ember, int numColors, int chan) + { + int colorIndex = 0; + int colorCount = 0; + int r, g, b, a; + int ret; + char tmps[2]; + int skip = (int)abs(chan); + bool ok = true; + const char* loc = __FUNCTION__; + + //Strip whitespace prior to first color. + while (isspace((int)colstr[colorIndex])) + colorIndex++; + + do + { + //Parse an RGB triplet at a time. + if (chan == 3) + ret = sscanf_s(&(colstr[colorIndex]),"%2x%2x%2x", &r, &g, &b); + else if (chan == -4) + ret = sscanf_s(&(colstr[colorIndex]),"00%2x%2x%2x", &r, &g, &b); + else // chan==4 + ret = sscanf_s(&(colstr[colorIndex]),"%2x%2x%2x%2x", &r,&g, &b, &a); + + a = 1;//Original allows for alpha, even though it will most likely never happen. Ember omits support for it. + + if ((chan != 4 && ret != 3) || (chan == 4 && ret != 4)) + { + ok = false; + r = g = b = 0; + m_ErrorReport.push_back(string(loc) + " : Problem reading hexadecimal color data, assigning to 0"); + break; + } + + colorIndex += 2 * skip; + + while (isspace((int)colstr[colorIndex])) + colorIndex++; + + ember.m_Palette.m_Entries[colorCount].r = T(r) / T(255);//Hex palette is [0..255], convert to [0..1]. + ember.m_Palette.m_Entries[colorCount].g = T(g) / T(255); + ember.m_Palette.m_Entries[colorCount].b = T(b) / T(255); + ember.m_Palette.m_Entries[colorCount].a = T(a); + + colorCount++; + + } while (colorCount < numColors && colorCount < ember.m_Palette.m_Entries.size()); + + if (sscanf_s(&(colstr[colorIndex]),"%1s", tmps, sizeof(tmps)) > 0) + { + m_ErrorReport.push_back(string(loc) + " : Extra data at end of hex color data " + string(&(colstr[colorIndex]))); + ok = false; + } + + return ok; + } + + /// + /// Interpolate the palette. + /// Used with older formats, deprecated. + /// + /// The palette to interpolate + /// The blend + /// The first index + /// The first hue + /// The second index + /// The second hue/param> + void InterpolateCmap(Palette& palette, T blend, int index0, T hue0, int index1, T hue1) + { + int i, j; + const char* loc = __FUNCTION__; + Palette adjustedPal0, adjustedPal1; + + if (m_PaletteList.GetHueAdjustedPalette(index0, hue0, adjustedPal0) && + m_PaletteList.GetHueAdjustedPalette(index1, hue1, adjustedPal1)) + { + v4T* hueAdjusted0 = adjustedPal0.m_Entries.data(); + v4T* hueAdjusted1 = adjustedPal1.m_Entries.data(); + + for (i = 0; i < 256; i++) + { + T t[4], s[4]; + + Palette::RgbToHsv(glm::value_ptr(hueAdjusted0[i]), s); + Palette::RgbToHsv(glm::value_ptr(hueAdjusted1[i]), t); + + s[3] = hueAdjusted0[i][3]; + t[3] = hueAdjusted1[i][3]; + + for (j = 0; j < 4; j++) + t[j] = ((1 - blend) * s[j]) + (blend * t[j]); + + Palette::HsvToRgb(t, glm::value_ptr(palette.m_Entries[i])); + palette.m_Entries[i][3] = t[3]; + } + } + else + { + m_ErrorReport.push_back(string(loc) + " : Unable to retrieve palettes"); + } + } + + /// + /// Wrapper to parse a floating point Xml value and convert it to float. + /// + /// The xml tag to parse + /// The name of the Xml attribute + /// The name of the Xml tag + /// The parsed value + /// Bitwise ANDed with true if name matched str and the call to Atof() succeeded, else false. Used for keeping a running value between successive calls. + /// True if the tag was matched, else false + bool ParseAndAssignFloat(const xmlChar* name, char* attStr, char* str, T& val, bool& b) + { + bool ret = false; + + if (!Compare(name, str)) + { + b &= Atof(attStr, val); + ret = true;//Means the strcmp() was right, but doesn't necessarily mean the conversion went ok. + } + + return ret; + } + + /// + /// Wrapper to parse an unsigned int Xml value and convert it to unsigned int. + /// + /// The xml tag to parse + /// The name of the Xml attribute + /// The name of the Xml tag + /// The parsed value + /// Bitwise ANDed with true if name matched str and the call to Atoi() succeeded, else false. Used for keeping a running value between successive calls. + /// True if the tag was matched, else false + bool ParseAndAssignInt(const xmlChar* name, char* attStr, char* str, unsigned int& val, bool& b) + { + return ParseAndAssignInt(name, attStr, str, (int&)val, b); + } + + /// + /// Wrapper to parse an int Xml value and convert it to int. + /// + /// The xml tag to parse + /// The name of the Xml attribute + /// The name of the Xml tag + /// The parsed value + /// Bitwise ANDed with true if name matched str and the call to Atoi() succeeded, else false. Used for keeping a running value between successive calls. + /// True if the tag was matched, else false + bool ParseAndAssignInt(const xmlChar* name, char* attStr, char* str, int& val, bool& b) + { + bool ret = false; + + if (!Compare(name, str)) + { + b &= Atoi(attStr, val); + ret = true;//Means the strcmp() was right, but doesn't necessarily mean the conversion went ok. + } + + return ret; + } + + static bool m_Init; + static vector> m_BadParamNames; + static vector> m_BadVariationNames; + VariationList m_VariationList;//The variation list used to make copies of variations to populate the embers with. + PaletteList m_PaletteList; +}; +} \ No newline at end of file diff --git a/Source/EmberAnimate/EmberAnimate.cpp b/Source/EmberAnimate/EmberAnimate.cpp new file mode 100644 index 0000000..f7a0dd0 --- /dev/null +++ b/Source/EmberAnimate/EmberAnimate.cpp @@ -0,0 +1,372 @@ +#include "EmberCommonPch.h" +#include "EmberAnimate.h" +#include "JpegUtils.h" + +/// +/// The core of the EmberAnimate.exe program. +/// Template argument expected to be float or double. +/// +/// A populated EmberOptions object which specifies all program options to be used +/// True if success, else false. +template +bool EmberAnimate(EmberOptions& opt) +{ + OpenCLWrapper wrapper; + + std::cout.imbue(std::locale("")); + + if (opt.DumpArgs()) + cout << opt.GetValues(OPT_USE_ANIMATE) << endl; + + if (opt.OpenCLInfo()) + { + cout << "\nOpenCL Info: " << endl; + cout << wrapper.DumpInfo(); + return true; + } + + //Regular variables. + Timing t; + bool unsorted = false; + bool writeSuccess = false; + bool startXml = false; + bool finishXml = false; + bool appendXml = false; + unsigned char* finalImagep; + unsigned int i, channels, ftime; + string s, flameName, filename; + ostringstream os; + vector finalImage, vecRgb; + vector> embers; + EmberStats stats; + EmberReport emberReport; + EmberImageComments comments; + Ember centerEmber; + XmlToEmber parser; + EmberToXml emberToXml; + auto_ptr> progress(new RenderProgress()); + auto_ptr> renderer(CreateRenderer(opt.EmberCL() ? OPENCL_RENDERER : CPU_RENDERER, opt.Platform(), opt.Device(), false, 0, emberReport)); + vector errorReport = emberReport.ErrorReport(); + + if (!errorReport.empty()) + emberReport.DumpErrorReport(); + + if (!renderer.get()) + { + cout << "Renderer creation failed, exiting." << endl; + return false; + } + + if (opt.EmberCL() && renderer->RendererType() != OPENCL_RENDERER)//OpenCL init failed, so fall back to CPU. + opt.EmberCL(false); + + if (!InitPaletteList(opt.PalettePath())) + return false; + + if (!ParseEmberFile(parser, opt.Input(), embers)) + return false; + + if (!opt.EmberCL()) + { + if (opt.ThreadCount() == 0) + { + cout << "Using " << Timing::ProcessorCount() << " automatically detected threads." << endl; + opt.ThreadCount(Timing::ProcessorCount()); + } + else + { + cout << "Using " << opt.ThreadCount() << " manually specified threads." << endl; + } + + renderer->ThreadCount(opt.ThreadCount(), opt.IsaacSeed() != "" ? opt.IsaacSeed().c_str() : NULL); + } + else + { + cout << "Using OpenCL to render." << endl; + + if (opt.Verbose()) + { + cout << "Platform: " << wrapper.PlatformName(opt.Platform()) << endl; + cout << "Device: " << wrapper.DeviceName(opt.Platform(), opt.Device()) << endl; + } + + if (opt.ThreadCount() > 1) + cout << "Cannot specify threads with OpenCL, using 1 thread." << endl; + + opt.ThreadCount(1); + renderer->ThreadCount(opt.ThreadCount(), opt.IsaacSeed() != "" ? opt.IsaacSeed().c_str() : NULL); + + if (opt.BitsPerChannel() != 8) + { + cout << "Bits per channel cannot be anything other than 8 with OpenCL, setting to 8." << endl; + opt.BitsPerChannel(8); + } + } + + if (opt.Format() != "jpg" && + opt.Format() != "png" && + opt.Format() != "ppm" && + opt.Format() != "bmp") + { + cout << "Format must be jpg, png, ppm, or bmp not " << opt.Format() << ". Setting to jpg." << endl; + } + + channels = opt.Format() == "png" ? 4 : 3; + + if (opt.BitsPerChannel() == 16 && opt.Format() != "png") + { + cout << "Support for 16 bits per channel images is only present for the png format. Setting to 8." << endl; + opt.BitsPerChannel(8); + } + else if (opt.BitsPerChannel() != 8 && opt.BitsPerChannel() != 16) + { + cout << "Unexpected bits per channel specified " << opt.BitsPerChannel() << ". Setting to 8." << endl; + opt.BitsPerChannel(8); + } + + if (opt.InsertPalette() && opt.BitsPerChannel() != 8) + { + cout << "Inserting palette only supported with 8 bits per channel, insertion will not take place." << endl; + opt.InsertPalette(false); + } + + if (opt.AspectRatio() < 0) + { + cout << "Invalid pixel aspect ratio " << opt.AspectRatio() << endl << ". Must be positive, setting to 1." << endl; + opt.AspectRatio(1); + } + + if (opt.Dtime() < 1) + { + cout << "Warning: dtime must be positive, not " << opt.Dtime() << ". Setting to 1." << endl; + opt.Dtime(1); + } + + if (opt.Frame()) + { + if (opt.Time()) + { + cout << "Cannot specify both time and frame." << endl; + return false; + } + + if (opt.FirstFrame() || opt.LastFrame()) + { + cout << "Cannot specify both frame and begin or end." << endl; + return false; + } + + opt.FirstFrame(opt.Frame()); + opt.LastFrame(opt.Frame()); + } + + if (opt.Time()) + { + if (opt.FirstFrame() || opt.LastFrame()) + { + cout << "Cannot specify both time and begin or end." << endl; + return false; + } + + opt.FirstFrame(opt.Time()); + opt.LastFrame(opt.Time()); + } + + //Prep all embers, by ensuring they: + //-Are sorted by time. + //-Do not have a dimension of 0. + //-Do not have a memory requirement greater than max uint. + //-Have quality and size scales applied, if present. + //-Have equal dimensions. + for (i = 0; i < embers.size(); i++) + { + if (i > 0 && embers[i].m_Time <= embers[i - 1].m_Time) + unsorted = true; + + embers[i].m_Quality *= T(opt.QualityScale()); + embers[i].m_FinalRasW = (unsigned int)((T)embers[i].m_FinalRasW * opt.SizeScale()); + embers[i].m_FinalRasH = (unsigned int)((T)embers[i].m_FinalRasH * opt.SizeScale()); + embers[i].m_PixelsPerUnit *= T(opt.SizeScale()); + + //Cast to double in case the value exceeds 2^32. + double imageMem = (double)channels * (double)embers[i].m_FinalRasW + * (double)embers[i].m_FinalRasH * (double)renderer->BytesPerChannel(); + double maxMem = pow(2.0, double((sizeof(void*) * 8) - 1)); + + if (imageMem > maxMem)//Ensure the max amount of memory for a process isn't exceeded. + { + cout << "Image " << i << " size > " << maxMem << ". Setting to 1920 x 1080." << endl; + embers[i].m_FinalRasW = 1920; + embers[i].m_FinalRasH = 1080; + } + + if (embers[i].m_FinalRasW == 0 || embers[i].m_FinalRasH == 0) + { + cout << "Warning: Output image " << i << " has dimension 0: " << embers[i].m_FinalRasW << ", " << embers[i].m_FinalRasH << ". Setting to 1920 x 1080." << endl; + embers[i].m_FinalRasW = 1920; + embers[i].m_FinalRasH = 1080; + } + + if ((embers[i].m_FinalRasW != embers[0].m_FinalRasW) || + (embers[i].m_FinalRasH != embers[0].m_FinalRasH)) + { + cout << "Warning: flame " << i << " at time " << embers[i].m_Time << " size mismatch. (" << embers[i].m_FinalRasW << ", " << embers[i].m_FinalRasH << + ") should be (" << embers[0].m_FinalRasW << ", " << embers[0].m_FinalRasH << "). Setting to " << embers[0].m_FinalRasW << ", " << embers[0].m_FinalRasH << "." << endl; + + embers[i].m_FinalRasW = embers[0].m_FinalRasW; + embers[i].m_FinalRasH = embers[0].m_FinalRasH; + } + } + + if (unsorted) + { + cout << "Embers were unsorted by time. First out of order index was " << i << ". Sorting." << endl; + std::sort(embers.begin(), embers.end(), &CompareEmbers); + } + + if (!opt.Time() && !opt.Frame()) + { + if (opt.FirstFrame() == UINT_MAX) + opt.FirstFrame((int)embers[0].m_Time); + + if (opt.LastFrame() == UINT_MAX) + opt.LastFrame(ClampGte((unsigned int)embers.back().m_Time - 1, opt.FirstFrame())); + } + + if (!opt.Out().empty()) + { + appendXml = true; + filename = opt.Out(); + cout << "Single output file " << opt.Out() << " specified for multiple images. They will be all overwritten and only the last image will remain." << endl; + } + + //Final setup steps before running. + os.imbue(std::locale("")); + renderer->SetEmber(embers); + renderer->EarlyClip(opt.EarlyClip()); + renderer->LockAccum(opt.LockAccum()); + renderer->InsertPalette(opt.InsertPalette()); + renderer->SubBatchSize(opt.SubBatchSize()); + renderer->PixelAspectRatio(T(opt.AspectRatio())); + renderer->Transparency(opt.Transparency()); + renderer->NumChannels(channels); + renderer->BytesPerChannel(opt.BitsPerChannel() / 8); + renderer->Callback(opt.DoProgress() ? progress.get() : NULL); + + //Begin run. + for (ftime = opt.FirstFrame(); ftime <= opt.LastFrame(); ftime += opt.Dtime()) + { + T localTime = T(ftime); + + if ((opt.LastFrame() - opt.FirstFrame()) / opt.Dtime() >= 1) + VerbosePrint("Time = " << ftime << " / " << opt.LastFrame() << " / " << opt.Dtime()); + + renderer->Reset(); + + if ((renderer->Run(finalImage, localTime) != RENDER_OK) || renderer->Aborted() || finalImage.empty()) + { + cout << "Error: image rendering failed, skipping to next image." << endl; + renderer->DumpErrorReport();//Something went wrong, print errors. + continue; + } + + if (opt.Out().empty()) + { + os.str(""); + os << opt.Prefix() << setfill('0') << setw(5) << ftime << opt.Suffix() << "." << opt.Format(); + filename = os.str(); + } + + if (opt.WriteGenome()) + { + flameName = filename.substr(0, filename.find_last_of('.')) + ".flam3"; + VerbosePrint("Writing " + flameName); + Interpolater::Interpolate(embers, localTime, 0, centerEmber);//Get center flame. + + if (appendXml) + { + startXml = ftime == opt.FirstFrame(); + finishXml = ftime == opt.LastFrame(); + } + + emberToXml.Save(flameName, centerEmber, opt.PrintEditDepth(), true, opt.IntPalette(), opt.HexPalette(), true, startXml, finishXml); + } + + writeSuccess = false; + comments = renderer->ImageComments(opt.PrintEditDepth(), opt.IntPalette(), opt.HexPalette()); + stats = renderer->Stats(); + os.str(""); + os << comments.m_NumIters << "/" << renderer->TotalIterCount() << " (" << std::fixed << std::setprecision(2) << ((double)stats.m_Iters/(double)renderer->TotalIterCount() * 100) << "%)"; + + VerbosePrint("\nIters ran/requested: " + os.str()); + VerbosePrint("Bad values: " << stats.m_Badvals); + VerbosePrint("Render time: " + t.Format(stats.m_RenderSeconds * 1000)); + VerbosePrint("Writing " + filename); + + if ((opt.Format() == "jpg" || opt.Format() == "bmp") && renderer->NumChannels() == 4) + { + EmberNs::RgbaToRgb(finalImage, vecRgb, renderer->FinalRasW(), renderer->FinalRasH()); + + finalImagep = vecRgb.data(); + } + else + { + finalImagep = finalImage.data(); + } + + if (opt.Format() == "png") + writeSuccess = WritePng(filename.c_str(), finalImagep, renderer->FinalRasW(), renderer->FinalRasH(), opt.BitsPerChannel() / 8, opt.PngComments(), comments, opt.Id(), opt.Url(), opt.Nick()); + else if (opt.Format() == "jpg") + writeSuccess = WriteJpeg(filename.c_str(), finalImagep, renderer->FinalRasW(), renderer->FinalRasH(), opt.JpegQuality(), opt.JpegComments(), comments, opt.Id(), opt.Url(), opt.Nick()); + else if (opt.Format() == "ppm") + writeSuccess = WritePpm(filename.c_str(), finalImagep, renderer->FinalRasW(), renderer->FinalRasH()); + else if (opt.Format() == "bmp") + writeSuccess = WriteBmp(filename.c_str(), finalImagep, renderer->FinalRasW(), renderer->FinalRasH()); + + if (!writeSuccess) + cout << "Error writing " << filename << endl; + + centerEmber.Clear(); + } + + VerbosePrint("Done.\n"); + return true; +} + +/// +/// Main program entry point for EmberAnimate.exe. +/// +/// The number of command line arguments passed +/// The command line arguments passed +/// 0 if successful, else 1. +int _tmain(int argc, _TCHAR* argv[]) +{ + bool b, d = true; + EmberOptions opt; + + //Required for large allocs, else GPU memory usage will be severely limited to small sizes. + //This must be done in the application and not in the EmberCL DLL. + _putenv_s("GPU_MAX_ALLOC_PERCENT", "100"); + + if (opt.Populate(argc, argv, OPT_USE_ANIMATE)) + return 0; + +#ifdef DO_DOUBLE + if (opt.Bits() == 64) + { + b = EmberAnimate(opt); + } + else +#endif + if (opt.Bits() == 33) + { + b = EmberAnimate(opt); + } + else if (opt.Bits() == 32) + { + cout << "Bits 32/int histogram no longer supported. Using bits == 33 (float)." << endl; + b = EmberAnimate(opt); + } + + return b ? 0 : 1; +} \ No newline at end of file diff --git a/Source/EmberAnimate/EmberAnimate.h b/Source/EmberAnimate/EmberAnimate.h new file mode 100644 index 0000000..6370c60 --- /dev/null +++ b/Source/EmberAnimate/EmberAnimate.h @@ -0,0 +1,16 @@ +#pragma once + +#include "EmberOptions.h" + +/// +/// Declaration for the EmberAnimate() function. +/// + +/// +/// The core of the EmberAnimate.exe program. +/// Template argument expected to be float or double. +/// +/// A populated EmberOptions object which specifies all program options to be used +/// True if success, else false. +template +static bool EmberAnimate(EmberOptions& opt); \ No newline at end of file diff --git a/Source/EmberAnimate/EmberAnimate.rc b/Source/EmberAnimate/EmberAnimate.rc new file mode 100644 index 0000000..90af307 --- /dev/null +++ b/Source/EmberAnimate/EmberAnimate.rc @@ -0,0 +1,98 @@ +// Microsoft Visual C++ generated resource script. +// +#include +#include "resource.h" +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_ICON1 ICON "..\\Fractorium\\Icons\\\\Fractorium.ico" + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 0,4,0,2 + PRODUCTVERSION 0,4,0,2 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x0L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "Open Source" + VALUE "FileDescription", "Renders fractal flames as animations with motion blur" + VALUE "FileVersion", "0.4.0.2" + VALUE "InternalName", "EmberAnimate.rc" + VALUE "LegalCopyright", "Copyright (C) Matt Feemster 2013, GPL v3" + VALUE "OriginalFilename", "EmberAnimate.rc" + VALUE "ProductName", "Ember Animate" + VALUE "ProductVersion", "0.4.0.2" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/Source/EmberAnimate/resource.h b/Source/EmberAnimate/resource.h new file mode 100644 index 0000000..a556945 --- /dev/null +++ b/Source/EmberAnimate/resource.h @@ -0,0 +1,15 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by EmberAnimate.rc +// + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/Source/EmberCL/DEOpenCLKernelCreator.cpp b/Source/EmberCL/DEOpenCLKernelCreator.cpp new file mode 100644 index 0000000..7eb965b --- /dev/null +++ b/Source/EmberCL/DEOpenCLKernelCreator.cpp @@ -0,0 +1,698 @@ +#include "EmberCLPch.h" +#include "DEOpenCLKernelCreator.h" + +namespace EmberCLns +{ +/// +/// Empty constructor that does nothing. The user must call the one which takes a bool +/// argument before using this class. +/// This constructor only exists so the class can be a member of a class. +/// +template +DEOpenCLKernelCreator::DEOpenCLKernelCreator() +{ +} + +/// +/// Constructor for float template type that sets all kernel entry points as well as composes +/// all kernel source strings. +/// No program compilation is done here, the user must explicitly do it. +/// The caller must specify whether they are using an nVidia or AMD card because it changes +/// the amount of local memory available. +/// +/// True if running on an nVidia card, else false. +template <> +DEOpenCLKernelCreator::DEOpenCLKernelCreator(bool nVidia) +{ + m_NVidia = nVidia; + m_LogScaleSumDEEntryPoint = "LogScaleSumDensityFilterKernel"; + m_LogScaleAssignDEEntryPoint = "LogScaleAssignDensityFilterKernel"; + m_GaussianDEWithoutSsEntryPoint = "GaussianDEWithoutSsKernel"; + m_GaussianDESsWithScfEntryPoint = "GaussianDESsWithScfKernel"; + m_GaussianDESsWithoutScfEntryPoint = "GaussianDESsWithoutScfKernel"; + m_GaussianDEWithoutSsNoCacheEntryPoint = "GaussianDEWithoutSsNoCacheKernel"; + m_GaussianDESsWithScfNoCacheEntryPoint = "GaussianDESsWithScfNoCacheKernel"; + m_GaussianDESsWithoutScfNoCacheEntryPoint = "GaussianDESsWithoutScfNoCacheKernel"; + m_LogScaleSumDEKernel = CreateLogScaleSumDEKernelString(); + m_LogScaleAssignDEKernel = CreateLogScaleAssignDEKernelString(); + m_GaussianDEWithoutSsKernel = CreateGaussianDEKernel(1); + m_GaussianDESsWithScfKernel = CreateGaussianDEKernel(2); + m_GaussianDESsWithoutScfKernel = CreateGaussianDEKernel(3); + m_GaussianDEWithoutSsNoCacheKernel = CreateGaussianDEKernelNoLocalCache(1); + m_GaussianDESsWithScfNoCacheKernel = CreateGaussianDEKernelNoLocalCache(2); + m_GaussianDESsWithoutScfNoCacheKernel = CreateGaussianDEKernelNoLocalCache(3); +} + +/// +/// Constructor for double template type that sets all kernel entry points as well as composes +/// all kernel source strings. +/// Note that no versions of kernels that use the cache are compiled because +/// the cache is not big enough to hold double4. +/// No program compilation is done here, the user must explicitly do it. +/// Specifying true or false for the bool parameter has no effect since no local memory +/// is used when instantiated with type double. +/// +/// True if running on an nVidia card, else false. Ignored. +template <> +DEOpenCLKernelCreator::DEOpenCLKernelCreator(bool nVidia) +{ + m_NVidia = nVidia; + m_LogScaleSumDEEntryPoint = "LogScaleSumDensityFilterKernel"; + m_LogScaleAssignDEEntryPoint = "LogScaleAssignDensityFilterKernel"; + m_GaussianDEWithoutSsNoCacheEntryPoint = "GaussianDEWithoutSsNoCacheKernel"; + m_GaussianDESsWithScfNoCacheEntryPoint = "GaussianDESsWithScfNoCacheKernel"; + m_GaussianDESsWithoutScfNoCacheEntryPoint = "GaussianDESsWithoutScfNoCacheKernel"; + m_LogScaleSumDEKernel = CreateLogScaleSumDEKernelString(); + m_LogScaleAssignDEKernel = CreateLogScaleAssignDEKernelString(); + m_GaussianDEWithoutSsNoCacheKernel = CreateGaussianDEKernelNoLocalCache(1); + m_GaussianDESsWithScfNoCacheKernel = CreateGaussianDEKernelNoLocalCache(2); + m_GaussianDESsWithoutScfNoCacheKernel = CreateGaussianDEKernelNoLocalCache(3); +} + +/// +/// Kernel source and entry point properties, getters only. +/// + +template string DEOpenCLKernelCreator::LogScaleSumDEKernel() { return m_LogScaleSumDEKernel; } +template string DEOpenCLKernelCreator::LogScaleSumDEEntryPoint() { return m_LogScaleSumDEEntryPoint; } +template string DEOpenCLKernelCreator::LogScaleAssignDEKernel() { return m_LogScaleAssignDEKernel; } +template string DEOpenCLKernelCreator::LogScaleAssignDEEntryPoint() { return m_LogScaleAssignDEEntryPoint; } + +/// +/// Get the kernel source for the specified supersample and filterWidth. +/// +/// The supersample being used +/// Filter width +/// The kernel source +template +string DEOpenCLKernelCreator::GaussianDEKernel(unsigned int ss, unsigned int filterWidth) +{ + if ((typeid(T) == typeid(double)) || (filterWidth > MaxDEFilterSize()))//Type double does not use cache. + { + if (ss > 1) + { + if (!(ss & 1)) + return m_GaussianDESsWithScfNoCacheKernel; + else + return m_GaussianDESsWithoutScfNoCacheKernel; + } + else + return m_GaussianDEWithoutSsNoCacheKernel; + } + else + { + if (ss > 1) + { + if (!(ss & 1)) + return m_GaussianDESsWithScfKernel; + else + return m_GaussianDESsWithoutScfKernel; + } + else + return m_GaussianDEWithoutSsKernel; + } +} + +/// +/// Get the kernel entry point for the specified supersample and filterWidth. +/// +/// The supersample being used +/// Filter width +/// The name of the density estimation filtering entry point kernel function +template +string DEOpenCLKernelCreator::GaussianDEEntryPoint(unsigned int ss, unsigned int filterWidth) +{ + if ((typeid(T) == typeid(double)) || (filterWidth > MaxDEFilterSize()))//Type double does not use cache. + { + if (ss > 1) + { + if (!(ss & 1)) + return m_GaussianDESsWithScfNoCacheEntryPoint; + else + return m_GaussianDESsWithoutScfNoCacheEntryPoint; + } + else + return m_GaussianDEWithoutSsNoCacheEntryPoint; + } + else + { + if (ss > 1) + { + if (!(ss & 1)) + return m_GaussianDESsWithScfEntryPoint; + else + return m_GaussianDESsWithoutScfEntryPoint; + } + else + return m_GaussianDEWithoutSsEntryPoint; + } +} + +/// +/// Get the maximum filter size allowed for running the local memory version of density filtering +/// Filters larger than this value will run the version without local memory caching. +/// +/// The maximum filter size allowed for running the local memory version of density filtering +template +unsigned int DEOpenCLKernelCreator::MaxDEFilterSize() { return 9; }//The true max would be (maxBoxSize - 1) / 2, but that's impractical because it can give us a tiny block size. + +/// +/// Solve for the maximum filter radius. +/// The final filter width is calculated by: (unsigned int)(ceil(m_MaxRad) * (T)m_Supersample) + (m_Supersample - 1); +/// Must solve for what max rad should be in order to give a maximum final width of (maxBoxSize - 1) / 2, assuming +/// a minimum block size of 1 which processes 1 pixel. +/// Example: If a box size of 20 was allowed, a filter +/// size of up to 9: (20 - 1) / 2 == (19 / 2) == 9 could be supported. +/// This function is deprecated, the appropriate kernels take care of this problem now. +/// +/// Maximum size of the box. +/// Size of the desired filter. +/// The supersample being used +/// The maximum filter radius allowed +template +T DEOpenCLKernelCreator::SolveMaxDERad(unsigned int maxBoxSize, T desiredFilterSize, T ss) +{ + unsigned int finalFilterSize = (unsigned int)((ceil(desiredFilterSize) * ss) + (ss - 1.0)); + + //Return the desired size if the final size of it will fit. + if (finalFilterSize <= MaxDEFilterSize()) + return desiredFilterSize; + + //The final size doesn't fit, so scale the original down until it fits. + return (T)floor((MaxDEFilterSize() - (ss - 1.0)) / ss); +} + +/// +/// Determine the maximum filter box size based on the amount of local memory available +/// to each block. +/// +/// The local memory available to a block +/// The maximum filter box size allowed +template +unsigned int DEOpenCLKernelCreator::SolveMaxBoxSize(unsigned int localMem) +{ + return (unsigned int)floor(sqrt(floor((T)localMem / 16.0)));//Divide by 16 because each element is float4. +} + +/// +/// Create the log scale kernel string, using summation. +/// This means each cell will be added to, rather than just assigned. +/// Since adding is slower than assigning, this should only be used when Passes > 1, +/// otherwise use the kernel created from CreateLogScaleAssignDEKernelString(). +/// +/// The kernel string +template +string DEOpenCLKernelCreator::CreateLogScaleSumDEKernelString() +{ + ostringstream os; + + os << + ConstantDefinesString(typeid(T) == typeid(double)) << + DensityFilterCLStructString << + "__kernel void " << m_LogScaleSumDEEntryPoint << "(\n" + " const __global real4* histogram,\n" + " __global real4* accumulator,\n" + " __constant DensityFilterCL* logFilter\n" + "\t)\n" + "{\n" + " if ((GLOBAL_ID_X < logFilter->m_SuperRasW) && (GLOBAL_ID_Y < logFilter->m_SuperRasH))\n" + " {\n" + " uint index = (GLOBAL_ID_Y * logFilter->m_SuperRasW) + GLOBAL_ID_X;\n" + "\n" + " if (histogram[index].w != 0)\n" + " {\n" + " real_t logScale = (logFilter->m_K1 * log(1.0 + histogram[index].w * logFilter->m_K2)) / histogram[index].w;\n" + "\n" + " accumulator[index] += histogram[index] * logScale;\n"//Using a single real4 vector operation doubles the speed from doing each component individually. + " }\n" + "\n" + " barrier(CLK_GLOBAL_MEM_FENCE);\n"//Just to be safe. Makes no speed difference to do all of the time or only when there's a hit. + " }\n" + "}\n"; + + return os.str(); +} + +/// +/// Create the log scale kernel string, using assignment. +/// Use this when Passes == 1. +/// +/// The kernel string +template +string DEOpenCLKernelCreator::CreateLogScaleAssignDEKernelString() +{ + ostringstream os; + + os << + ConstantDefinesString(typeid(T) == typeid(double)) << + DensityFilterCLStructString << + "__kernel void " << m_LogScaleAssignDEEntryPoint << "(\n" + " const __global real4* histogram,\n" + " __global real4* accumulator,\n" + " __constant DensityFilterCL* logFilter\n" + "\t)\n" + "{\n" + " if ((GLOBAL_ID_X < logFilter->m_SuperRasW) && (GLOBAL_ID_Y < logFilter->m_SuperRasH))\n" + " {\n" + " uint index = (GLOBAL_ID_Y * logFilter->m_SuperRasW) + GLOBAL_ID_X;\n" + "\n" + " if (histogram[index].w != 0)\n" + " {\n" + " real_t logScale = (logFilter->m_K1 * log(1.0 + histogram[index].w * logFilter->m_K2)) / histogram[index].w;\n" + "\n" + " accumulator[index] = histogram[index] * logScale;\n"//Using a single real4 vector operation doubles the speed from doing each component individually. + " }\n" + "\n" + " barrier(CLK_GLOBAL_MEM_FENCE);\n"//Just to be safe. Makes no speed difference to do all of the time or only when there's a hit. + " }\n" + "}\n"; + + return os.str(); +} + +/// +/// Create the gaussian density filtering kernel string. +/// 6 different methods of processing were tried before settling on this final and fastest 7th one. +/// Each block processes a box and exits. No column or row advancements happen. +/// The block accumulates to a temporary box and writes the contents to the global density filter buffer when done. +/// Note this applies the filter from top to bottom row and not from the center outward like the CPU version does. +/// This allows the image to be filtered without suffering from pixel loss due to race conditions. +/// It is run in multiple passes that are spaced far enough apart on the image so as to not overlap. +/// This allows writing to the global buffer without ever overlapping or using atomics. +/// The supersample parameter will produce three different kernels. +/// SS = 1, SS > 1 && SS even, SS > 1 && SS odd. +/// The width of the kernl this runs in must be evenly divisible by 16 or else artifacts will occur. +/// Note that because this function uses so many variables and is so complex, OpenCL can easily run +/// out of resources in some cases. Certain variables had to be reused to condense the kernel footprint +/// down enough to be able to run a block size of 32x32. +/// For double precision, or for SS > 1, a size of 32x30 is used. +/// Box width = (BLOCK_SIZE_X + (fw * 2)). +/// Box height = (BLOCK_SIZE_Y + (fw * 2)). +/// +/// The supersample being used +/// The kernel string +template +string DEOpenCLKernelCreator::CreateGaussianDEKernel(unsigned int ss) +{ + bool doSS = ss > 1; + bool doScf = !(ss & 1); + ostringstream os; + + os << + ConstantDefinesString(typeid(T) == typeid(double)) << + DensityFilterCLStructString << + UnionCLStructString << + "__kernel void " << GaussianDEEntryPoint(ss, MaxDEFilterSize()) << "(\n" << + " const __global real4* histogram,\n" + " __global real4reals* accumulator,\n" + " __constant DensityFilterCL* densityFilter,\n" + " const __global real_t* filterCoefs,\n" + " const __global real_t* filterWidths,\n" + " const __global uint* coefIndices,\n" + " const uint chunkSizeW,\n" + " const uint chunkSizeH,\n" + " const uint rowParity,\n" + " const uint colParity\n" + "\t)\n" + "{\n" + //Parity determines if this function should execute. + " if ((GLOBAL_ID_X >= densityFilter->m_SuperRasW) ||\n" + " (GLOBAL_ID_Y >= densityFilter->m_SuperRasH) ||\n" + " ((BLOCK_ID_X % chunkSizeW) != colParity) ||\n" + " ((BLOCK_ID_Y % chunkSizeH) != rowParity)) \n" + " return;\n" + "\n"; + + if (doSS) + { + os << + " uint ss = (uint)floor((real_t)densityFilter->m_Supersample / 2.0);\n" + " int densityBoxLeftX;\n" + " int densityBoxRightX;\n" + " int densityBoxTopY;\n" + " int densityBoxBottomY;\n" + "\n"; + + if (doScf) + os << + " real_t scfact = pow(densityFilter->m_Supersample / (densityFilter->m_Supersample + 1.0), 2.0);\n"; + } + + //Compute the size of the temporary box which is the block width + 2 * filter width x block height + 2 * filter width. + //Ideally the block width and height are both 32. However, the height might be smaller if there isn't enough memory. + os << + " uint fullTempBoxWidth, fullTempBoxHeight;\n" + " uint leftBound, rightBound, topBound, botBound;\n" + " uint blockHistStartRow, blockHistEndRow, boxReadStartRow, boxReadEndRow;\n" + " uint blockHistStartCol, boxReadStartCol, boxReadEndCol;\n" + " uint accumWriteStartRow, accumWriteStartCol, colsToWrite;\n" + + //If any of the variables above end up being made __local, init them here. + //At the moment, it's slower even though it's more memory efficient. + //" if (THREAD_ID_X == 0 && THREAD_ID_Y == 0)\n" + //" {\n" + //Init local vars here. + //" }\n" + //"\n" + //" barrier(CLK_LOCAL_MEM_FENCE);\n" + "\n" + " fullTempBoxWidth = BLOCK_SIZE_X + (densityFilter->m_FilterWidth * 2);\n" + " fullTempBoxHeight = BLOCK_SIZE_Y + (densityFilter->m_FilterWidth * 2);\n" + //Compute the bounds of the area to be sampled, which is just the ends minus the super sample minus 1. + " leftBound = densityFilter->m_Supersample - 1;\n" + " rightBound = densityFilter->m_SuperRasW - (densityFilter->m_Supersample - 1);\n" + " topBound = densityFilter->m_Supersample - 1;\n" + " botBound = densityFilter->m_SuperRasH - (densityFilter->m_Supersample - 1);\n" + "\n" + //Start and end values are the indices in the histogram read from + //and written to in the accumulator. They are not the indices for the local block of data. + //Before computing local offsets, compute the global offsets first to determine if any rows or cols fall outside of the bounds. + " blockHistStartRow = min(botBound, topBound + (BLOCK_ID_Y * BLOCK_SIZE_Y));\n"//The first histogram row this block will process. + " blockHistEndRow = min(botBound, blockHistStartRow + BLOCK_SIZE_Y);\n"//The last histogram row this block will process, clamped to the last row. + " boxReadStartRow = densityFilter->m_FilterWidth - min(densityFilter->m_FilterWidth, blockHistStartRow);\n"//The first row in the local box to read from when writing back to the final accumulator for this block. + " boxReadEndRow = densityFilter->m_FilterWidth + min(densityFilter->m_FilterWidth + BLOCK_SIZE_Y, densityFilter->m_SuperRasH - blockHistStartRow);\n"//The last row in the local box to read from when writing back to the final accumulator for this block. + " blockHistStartCol = min(rightBound, leftBound + (BLOCK_ID_X * BLOCK_SIZE_X));\n"//The first histogram column this block will process. + " boxReadStartCol = densityFilter->m_FilterWidth - min(densityFilter->m_FilterWidth, blockHistStartCol);\n"//The first box row this block will read from when copying to the accumulator. + " boxReadEndCol = densityFilter->m_FilterWidth + min(densityFilter->m_FilterWidth + BLOCK_SIZE_X, densityFilter->m_SuperRasW - blockHistStartCol);\n"//The last box row this block will read from when copying to the accumulator. + "\n" + //Last, the indices in the global accumulator that the local bounds will be writing to. + " accumWriteStartRow = blockHistStartRow - min(densityFilter->m_FilterWidth, blockHistStartRow);\n"//Will be fw - 0 except for boundary columns, it will be less. + " accumWriteStartCol = blockHistStartCol - min(densityFilter->m_FilterWidth, blockHistStartCol);\n" + " colsToWrite = ceil((real_t)(boxReadEndCol - boxReadStartCol) / (real_t)BLOCK_SIZE_X);\n" + "\n" + " uint threadHistRow = blockHistStartRow + THREAD_ID_Y;\n"//The histogram row this individual thread will be reading from. + " uint threadHistCol = blockHistStartCol + THREAD_ID_X;\n"//The histogram column this individual thread will be reading from. + "\n" + + //Compute the center position in this local box to serve as the center position + //from which filter application offsets are computed. + //These are the local indices for the local data that are temporarily accumulated to before + //writing out to the global accumulator. + " uint boxRow = densityFilter->m_FilterWidth + THREAD_ID_Y;\n" + " uint boxCol = densityFilter->m_FilterWidth + THREAD_ID_X;\n" + " uint colElementsToZero = ceil((real_t)fullTempBoxWidth / (real_t)(BLOCK_SIZE_X));\n"//Usually is 2. + " int i, j, k;\n" + " uint filterSelectInt, filterCoefIndex;\n" + " real_t cacheLog;\n" + " real_t filterSelect;\n" + " real4 bucket;\n" + ; + + //This will be treated as having dimensions of (BLOCK_SIZE_X + (fw * 2)) x (BLOCK_SIZE_Y + (fw * 2)). + if (m_NVidia) + os << " __local real4reals filterBox[3000];\n"; + else + os << " __local real4reals filterBox[1200];\n"; + + os << + //Zero the temp buffers first. This splits the zeroization evenly across all threads (columns) in the first block row. + //This is a middle ground solution. Previous methods tried: + //Thread (0, 0) does all init. This works, but is the slowest. + //Init is divided among all threads. This is the fastest but exposes a severe flaw in OpenCL, + //in that it will not get executed by all threads before proceeding, despite the barrier statement + //below. As a result, strange artifacts will get left around because filtering gets executed on a temp + //box that has not been properly zeroized. + //The only way to do it and still achieve reasonable speed is to have the first row do it. This is + //most likely because the first row gets executed first, ensuring zeroization is done when the rest + //of the threads execute. + "\n"//Dummy test zeroization for debugging. + //" if (THREAD_ID_Y == 0 && THREAD_ID_X == 0)\n"//First thread of the block takes the responsibility of zeroizing. + //" {\n" + //" for (k = 0; k < 2 * 1024; k++)\n" + //" {\n" + //" filterBox[k].m_Real4 = 0;\n" + //" }\n" + //" }\n" + " if (THREAD_ID_Y == 0)\n"//First row of the block takes the responsibility of zeroizing. + " {\n" + " for (i = 0; i < fullTempBoxHeight; i++)\n"//Each column in the row iterates through all rows. + " {\n" + " for (j = 0; j < colElementsToZero && ((colElementsToZero * THREAD_ID_X) + j) < fullTempBoxWidth; j++)\n"//And zeroizes a few columns from that row. + " {\n" + " filterBox[(i * fullTempBoxWidth) + ((colElementsToZero * THREAD_ID_X) + j)].m_Real4 = 0;\n" + " }\n" + " }\n" + " }\n" + "\n" + " barrier(CLK_LOCAL_MEM_FENCE);\n" + "\n" + " if (threadHistRow < botBound && threadHistCol < rightBound)\n" + " {\n" + " bucket = histogram[(threadHistRow * densityFilter->m_SuperRasW) + threadHistCol];\n" + "\n" + " if (bucket.w != 0)\n" + " {\n" + " cacheLog = (densityFilter->m_K1 * log(1.0 + bucket.w * densityFilter->m_K2)) / bucket.w;\n"; + + if (doSS) + { + os << + " filterSelect = 0;\n" + " densityBoxLeftX = threadHistCol - min(threadHistCol, ss);\n" + " densityBoxRightX = threadHistCol + min(ss, (densityFilter->m_SuperRasW - threadHistCol) - 1);\n" + " densityBoxTopY = threadHistRow - min(threadHistRow, ss);\n" + " densityBoxBottomY = threadHistRow + min(ss, (densityFilter->m_SuperRasH - threadHistRow) - 1);\n" + "\n" + " for (j = densityBoxTopY; j <= densityBoxBottomY; j++)\n" + " {\n" + " for (i = densityBoxLeftX; i <= densityBoxRightX; i++)\n" + " {\n" + " filterSelect += histogram[i + (j * densityFilter->m_SuperRasW)].w;\n" + " }\n" + " }\n" + "\n"; + + if (doScf) + os << " filterSelect *= scfact;\n"; + } + else + { + os + << " filterSelect = bucket.w;\n"; + } + + os << + "\n" + " if (filterSelect > densityFilter->m_MaxFilteredCounts)\n" + " filterSelectInt = densityFilter->m_MaxFilterIndex;\n" + " else if (filterSelect <= DE_THRESH)\n" + " filterSelectInt = (int)ceil(filterSelect) - 1;\n" + " else\n" + " filterSelectInt = (int)DE_THRESH + (int)floor(pow((real_t)(filterSelect - DE_THRESH), densityFilter->m_Curve));\n" + "\n" + " if (filterSelectInt > densityFilter->m_MaxFilterIndex)\n" + " filterSelectInt = densityFilter->m_MaxFilterIndex;\n" + "\n" + " filterCoefIndex = filterSelectInt * densityFilter->m_KernelSize;\n" + "\n" + //With this new method, only accumulate to the temp local buffer first. Write to the final accumulator last. + //For each loop through, note that there is a local memory barrier call inside of each call to AddToAccumNoCheck(). + //If this isn't done, pixel errors occurr and even an out of resources error occurrs because too many writes are done to the same place in memory at once. + " k = (int)densityFilter->m_FilterWidth;\n"//Need a signed int to use below, really is filter width, but reusing a variable to save space. + "\n" + " for (j = -k; j <= k; j++)\n" + " {\n" + " for (i = -k; i <= k; i++)\n" + " {\n" + " filterSelectInt = filterCoefIndex + coefIndices[(abs(j) * (densityFilter->m_FilterWidth + 1)) + abs(i)];\n"//Really is filterCoeffIndexPlusOffset, but reusing a variable to save space. + "\n" + " if (filterCoefs[filterSelectInt] != 0)\n" + " {\n" + " filterBox[(i + boxCol) + ((j + boxRow) * fullTempBoxWidth)].m_Real4 += (bucket * (filterCoefs[filterSelectInt] * cacheLog));\n" + " }\n" + " }\n" + " barrier(CLK_LOCAL_MEM_FENCE);\n"//If this is the only barrier and the block size is exactly 16, it works perfectly. Otherwise, no chunks occur, but a many streaks. + " }\n" + " }\n"//bucket.w != 0. + " }\n"//In bounds. + "\n" + "\n" + " barrier(CLK_LOCAL_MEM_FENCE | CLK_GLOBAL_MEM_FENCE);\n" + "\n" + " if (THREAD_ID_Y == 0)\n" + " {\n" + //At this point, all threads in this block have applied the filter to their surrounding pixel and stored the results in the temp local box. + //Add the cells of it that are in bounds to the global accumulator. + //Compute offsets in local box to read from, and offsets into global accumulator to write to. + //Use a method here that is similar to the zeroization above: Each thread (column) in the first row iterates through all of the + //rows and adds a few columns to the accumulator. + " for (i = boxReadStartRow, j = accumWriteStartRow; i < boxReadEndRow; i++, j++)\n" + " {\n" + " for (k = 0; k < colsToWrite; k++)\n"//Write a few columns. + " {\n" + " boxCol = (colsToWrite * THREAD_ID_X) + k;\n"//Really is colOffset, but reusing a variable to save space. + "\n" + " if (boxReadStartCol + boxCol < boxReadEndCol)\n" + " accumulator[(j * densityFilter->m_SuperRasW) + (accumWriteStartCol + boxCol)].m_Real4 += filterBox[(i * fullTempBoxWidth) + (boxReadStartCol + boxCol)].m_Real4;\n" + " }\n" + " barrier(CLK_GLOBAL_MEM_FENCE);\n"//This must be here or else chunks will go missing. + " }\n" + " }\n" + "}\n"; + + return os.str(); +} + +/// +/// Create the gaussian density filtering kernel string, but use no local cache and perform +/// all writes directly to the global density filtering buffer. +/// Note this applies the filter from top to bottom row and not from the center outward like the CPU version does. +/// This allows the image to be filtered without suffering from pixel loss due to race conditions. +/// This is used for when the filter box is greater than can fit in the local cache. +/// While the cached version is incredibly fast, this version offers no real gain over doing it +/// on the CPU because the frequent global memory access brings performance to a crawl. +/// The supersample parameter will produce three different kernels. +/// SS = 1, SS > 1 && SS even, SS > 1 && SS odd. +/// The width of the kernl this runs in must be evenly divisible by 16 or else artifacts will occur. +/// Note that because this function uses so many variables and is so complex, OpenCL can easily run +/// out of resources in some cases. Certain variables had to be reused to condense the kernel footprint +/// down enough to be able to run a block size of 32x32. +/// For double precision, or for SS > 1, a size of 32x30 is used. +/// +/// The supersample being used +/// The kernel string +template +string DEOpenCLKernelCreator::CreateGaussianDEKernelNoLocalCache(unsigned int ss) +{ + bool doSS = ss > 1; + bool doScf = !(ss & 1); + ostringstream os; + + os << + ConstantDefinesString(typeid(T) == typeid(double)) << + DensityFilterCLStructString << + UnionCLStructString << + AddToAccumWithCheckFunctionString << + "__kernel void " << GaussianDEEntryPoint(ss, MaxDEFilterSize() + 1) << "(\n" << + " const __global real4* histogram,\n" + " __global real4reals* accumulator,\n" + " __constant DensityFilterCL* densityFilter,\n" + " const __global real_t* filterCoefs,\n" + " const __global real_t* filterWidths,\n" + " const __global uint* coefIndices,\n" + " const uint chunkSizeW,\n" + " const uint chunkSizeH,\n" + " const uint rowParity,\n" + " const uint colParity\n" + "\t)\n" + "{\n" + //Parity determines if this function should execute. + " if ((GLOBAL_ID_X >= densityFilter->m_SuperRasW) ||\n" + " (GLOBAL_ID_Y >= densityFilter->m_SuperRasH) ||\n" + " ((BLOCK_ID_X % chunkSizeW) != colParity) ||\n" + " ((BLOCK_ID_Y % chunkSizeH) != rowParity)) \n" + " return;\n" + "\n"; + + if (doSS) + { + os << + " uint ss = (uint)floor((real_t)densityFilter->m_Supersample / 2.0);\n" + " int densityBoxLeftX;\n" + " int densityBoxRightX;\n" + " int densityBoxTopY;\n" + " int densityBoxBottomY;\n"; + + if (doScf) + os << " real_t scfact = pow((real_t)densityFilter->m_Supersample / ((real_t)densityFilter->m_Supersample + 1.0), 2.0);\n"; + } + + os << + //Compute the bounds of the area to be sampled, which is just the ends minus the super sample minus 1. + " uint leftBound = densityFilter->m_Supersample - 1;\n" + " uint rightBound = densityFilter->m_SuperRasW - (densityFilter->m_Supersample - 1);\n" + " uint topBound = densityFilter->m_Supersample - 1;\n" + " uint botBound = densityFilter->m_SuperRasH - (densityFilter->m_Supersample - 1);\n" + "\n" + //Start and end values are the indices in the histogram read from and written to in the accumulator. + //Before computing local offsets, compute the global offsets first to determine if any rows or cols fall outside of the bounds. + " uint blockHistStartRow = min(botBound, topBound + (BLOCK_ID_Y * BLOCK_SIZE_Y));\n"//The first histogram row this block will process. + " uint threadHistRow = blockHistStartRow + THREAD_ID_Y;\n"//The histogram row this individual thread will be reading from. + "\n" + " uint blockHistStartCol = min(rightBound, leftBound + (BLOCK_ID_X * BLOCK_SIZE_X));\n"//The first histogram column this block will process. + " uint threadHistCol = blockHistStartCol + THREAD_ID_X;\n"//The histogram column this individual thread will be reading from. + "\n" + " int i, j;\n" + " uint filterSelectInt, filterCoefIndex;\n" + " real_t cacheLog;\n" + " real_t logScale;\n" + " real_t filterSelect;\n" + " real4 bucket;\n" + "\n" + " if (threadHistRow < botBound && threadHistCol < rightBound)\n" + " {\n" + " bucket = histogram[(threadHistRow * densityFilter->m_SuperRasW) + threadHistCol];\n" + "\n" + " if (bucket.w != 0)\n" + " {\n" + " cacheLog = (densityFilter->m_K1 * log(1.0 + bucket.w * densityFilter->m_K2)) / bucket.w;\n"; + + if (doSS) + { + os << + " filterSelect = 0;\n" + " densityBoxLeftX = threadHistCol - min(threadHistCol, ss);\n" + " densityBoxRightX = threadHistCol + min(ss, (densityFilter->m_SuperRasW - threadHistCol) - 1);\n" + " densityBoxTopY = threadHistRow - min(threadHistRow, ss);\n" + " densityBoxBottomY = threadHistRow + min(ss, (densityFilter->m_SuperRasH - threadHistRow) - 1);\n" + "\n" + " for (j = densityBoxTopY; j <= densityBoxBottomY; j++)\n" + " {\n" + " for (i = densityBoxLeftX; i <= densityBoxRightX; i++)\n" + " {\n" + " filterSelect += histogram[i + (j * densityFilter->m_SuperRasW)].w;\n" + " }\n" + " }\n" + "\n"; + + if (doScf) + os << " filterSelect *= scfact;\n"; + } + else + { + os + << " filterSelect = bucket.w;\n"; + } + + os << + "\n" + " if (filterSelect > densityFilter->m_MaxFilteredCounts)\n" + " filterSelectInt = densityFilter->m_MaxFilterIndex;\n" + " else if (filterSelect <= DE_THRESH)\n" + " filterSelectInt = (int)ceil(filterSelect) - 1;\n" + " else\n" + " filterSelectInt = (int)DE_THRESH + (int)floor(pow((real_t)(filterSelect - DE_THRESH), densityFilter->m_Curve));\n" + "\n" + " if (filterSelectInt > densityFilter->m_MaxFilterIndex)\n" + " filterSelectInt = densityFilter->m_MaxFilterIndex;\n" + "\n" + " filterCoefIndex = filterSelectInt * densityFilter->m_KernelSize;\n" + "\n" + " int fw = (int)densityFilter->m_FilterWidth;\n"//Need a signed int to use below. + "\n" + " for (j = -fw; j <= fw; j++)\n" + " {\n" + " for (i = -fw; i <= fw; i++)\n" + " {\n" + " if (AccumCheck(densityFilter->m_SuperRasW, densityFilter->m_SuperRasH, threadHistCol, i, threadHistRow, j))\n" + " {\n" + " filterSelectInt = filterCoefIndex + coefIndices[(abs(j) * (densityFilter->m_FilterWidth + 1)) + abs(i)];\n"//Really is filterCoeffIndexPlusOffset, but reusing a variable to save space. + "\n" + " if (filterCoefs[filterSelectInt] != 0)\n" + " {\n" + " accumulator[(i + threadHistCol) + ((j + threadHistRow) * densityFilter->m_SuperRasW)].m_Real4 += (bucket * (filterCoefs[filterSelectInt] * cacheLog));\n" + " }\n" + " }\n" + "\n" + " barrier(CLK_GLOBAL_MEM_FENCE);\n"//Required to avoid streaks. + " }\n" + " }\n" + " }\n"//bucket.w != 0. + " }\n"//In bounds. + "\n" + //" barrier(CLK_GLOBAL_MEM_FENCE);\n"//Just to be safe. + "}\n"; + + return os.str(); +} +} \ No newline at end of file diff --git a/Source/EmberCL/DEOpenCLKernelCreator.h b/Source/EmberCL/DEOpenCLKernelCreator.h new file mode 100644 index 0000000..de65e7a --- /dev/null +++ b/Source/EmberCL/DEOpenCLKernelCreator.h @@ -0,0 +1,89 @@ +#pragma once + +#include "EmberCLPch.h" +#include "EmberCLStructs.h" +#include "EmberCLFunctions.h" + +/// +/// DEOpenCLKernelCreator class. +/// + +namespace EmberCLns +{ +/// +/// Kernel creator for density filtering. +/// This implements both basic log scale filtering +/// as well as the full flam3 density estimation filtering +/// in OpenCL. +/// Several conditionals are present in the CPU version. They +/// are stripped out of the kernels and instead a separate kernel +/// is created for every possible case. +/// If the filter width is 9 or less, then the entire process can be +/// done in shared memory which is very fast. +/// However, if the filter width is greater than 9, shared memory is not +/// used and all filtering is done directly with main global VRAM. This +/// ends up being not much faster than doing it on the CPU. +/// String members are kept for the program source and entry points +/// for each version of the program. +/// Template argument expected to be float or double. +/// +template +class EMBERCL_API DEOpenCLKernelCreator +{ +public: + DEOpenCLKernelCreator(); + DEOpenCLKernelCreator(bool nVidia); + + //Accessors. + string LogScaleSumDEKernel(); + string LogScaleSumDEEntryPoint(); + string LogScaleAssignDEKernel(); + string LogScaleAssignDEEntryPoint(); + string GaussianDEKernel(unsigned int ss, unsigned int filterWidth); + string GaussianDEEntryPoint(unsigned int ss, unsigned int filterWidth); + + //Miscellaneous static functions. + static unsigned int MaxDEFilterSize(); + static T SolveMaxDERad(unsigned int maxBoxSize, T desiredFilterSize, T ss); + static unsigned int SolveMaxBoxSize(unsigned int localMem); + +private: + //Kernel creators. + string CreateLogScaleSumDEKernelString(); + string CreateLogScaleAssignDEKernelString(); + string CreateGaussianDEKernel(unsigned int ss); + string CreateGaussianDEKernelNoLocalCache(unsigned int ss); + + string m_LogScaleSumDEKernel; + string m_LogScaleSumDEEntryPoint; + + string m_LogScaleAssignDEKernel; + string m_LogScaleAssignDEEntryPoint; + + string m_GaussianDEWithoutSsKernel; + string m_GaussianDEWithoutSsEntryPoint; + + string m_GaussianDESsWithScfKernel; + string m_GaussianDESsWithScfEntryPoint; + + string m_GaussianDESsWithoutScfKernel; + string m_GaussianDESsWithoutScfEntryPoint; + + string m_GaussianDEWithoutSsNoCacheKernel; + string m_GaussianDEWithoutSsNoCacheEntryPoint; + + string m_GaussianDESsWithScfNoCacheKernel; + string m_GaussianDESsWithScfNoCacheEntryPoint; + + string m_GaussianDESsWithoutScfNoCacheKernel; + string m_GaussianDESsWithoutScfNoCacheEntryPoint; + + bool m_NVidia; +}; + +template EMBERCL_API class DEOpenCLKernelCreator; + +#ifdef DO_DOUBLE + template EMBERCL_API class DEOpenCLKernelCreator; +#endif +} diff --git a/Source/EmberCL/DllMain.cpp b/Source/EmberCL/DllMain.cpp new file mode 100644 index 0000000..3b3e62c --- /dev/null +++ b/Source/EmberCL/DllMain.cpp @@ -0,0 +1,20 @@ +#include "EmberCLPch.h" + +/// +/// Generated by Visual Studio to make the DLL run properly. +/// +BOOL APIENTRY DllMain( HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} diff --git a/Source/EmberCL/EmberCLFunctions.h b/Source/EmberCL/EmberCLFunctions.h new file mode 100644 index 0000000..735588c --- /dev/null +++ b/Source/EmberCL/EmberCLFunctions.h @@ -0,0 +1,413 @@ +#pragma once + +#include "EmberCLPch.h" +#include "EmberCLStructs.h" + +/// +/// OpenCL global function strings. +/// + +namespace EmberCLns +{ +/// +/// OpenCL equivalent of Palette::RgbToHsv(). +/// +static const char* RgbToHsvFunctionString = + //rgb 0 - 1, + //h 0 - 6, s 0 - 1, v 0 - 1 + "static inline void RgbToHsv(real4* rgb, real4* hsv)\n" + "{\n" + " real_t max, min, del, rc, gc, bc;\n" + "\n" + //Compute maximum of r, g, b. + " if ((*rgb).x >= (*rgb).y)\n" + " {\n" + " if ((*rgb).x >= (*rgb).z)\n" + " max = (*rgb).x;\n" + " else\n" + " max = (*rgb).z;\n" + " }\n" + " else\n" + " {\n" + " if ((*rgb).y >= (*rgb).z)\n" + " max = (*rgb).y;\n" + " else\n" + " max = (*rgb).z;\n" + " }\n" + "\n" + //Compute minimum of r, g, b. + " if ((*rgb).x <= (*rgb).y)\n" + " {\n" + " if ((*rgb).x <= (*rgb).z)\n" + " min = (*rgb).x;\n" + " else\n" + " min = (*rgb).z;\n" + " }\n" + " else\n" + " {\n" + " if ((*rgb).y <= (*rgb).z)\n" + " min = (*rgb).y;\n" + " else\n" + " min = (*rgb).z;\n" + " }\n" + "\n" + " del = max - min;\n" + " (*hsv).z = max;\n" + "\n" + " if (max != 0)\n" + " (*hsv).y = del / max;\n" + " else\n" + " (*hsv).y = 0;\n" + "\n" + " (*hsv).x = 0;\n" + " if ((*hsv).y != 0)\n" + " {\n" + " rc = (max - (*rgb).x) / del;\n" + " gc = (max - (*rgb).y) / del;\n" + " bc = (max - (*rgb).z) / del;\n" + "\n" + " if ((*rgb).x == max)\n" + " (*hsv).x = bc - gc;\n" + " else if ((*rgb).y == max)\n" + " (*hsv).x = 2 + rc - bc;\n" + " else if ((*rgb).z == max)\n" + " (*hsv).x = 4 + gc - rc;\n" + "\n" + " if ((*hsv).x < 0)\n" + " (*hsv).x += 6;\n" + " }\n" + "}\n" + "\n"; + +/// +/// OpenCL equivalent of Palette::HsvToRgb(). +/// +static const char* HsvToRgbFunctionString = + //h 0 - 6, s 0 - 1, v 0 - 1 + //rgb 0 - 1 + "static inline void HsvToRgb(real4* hsv, real4* rgb)\n" + "{\n" + " int j;\n" + " real_t f, p, q, t;\n" + "\n" + " while ((*hsv).x >= 6)\n" + " (*hsv).x = (*hsv).x - 6;\n" + "\n" + " while ((*hsv).x < 0)\n" + " (*hsv).x = (*hsv).x + 6;\n" + "\n" + " j = (int)floor((*hsv).x);\n" + " f = (*hsv).x - j;\n" + " p = (*hsv).z * (1 - (*hsv).y);\n" + " q = (*hsv).z * (1 - ((*hsv).y * f));\n" + " t = (*hsv).z * (1 - ((*hsv).y * (1 - f)));\n" + "\n" + " switch (j)\n" + " {\n" + " case 0: (*rgb).x = (*hsv).z; (*rgb).y = t; (*rgb).z = p; break;\n" + " case 1: (*rgb).x = q; (*rgb).y = (*hsv).z; (*rgb).z = p; break;\n" + " case 2: (*rgb).x = p; (*rgb).y = (*hsv).z; (*rgb).z = t; break;\n" + " case 3: (*rgb).x = p; (*rgb).y = q; (*rgb).z = (*hsv).z; break;\n" + " case 4: (*rgb).x = t; (*rgb).y = p; (*rgb).z = (*hsv).z; break;\n" + " case 5: (*rgb).x = (*hsv).z; (*rgb).y = p; (*rgb).z = q; break;\n" + " default: (*rgb).x = (*hsv).z; (*rgb).y = t; (*rgb).z = p; break;\n" + " }\n" + "}\n" + "\n"; + +/// +/// OpenCL equivalent of Palette::CalcAlpha(). +/// +static const char* CalcAlphaFunctionString = + "static inline real_t CalcAlpha(real_t density, real_t gamma, real_t linrange)\n"//Not the slightest clue what this is doing.//DOC + "{\n" + " real_t frac, alpha, funcval = pow(linrange, gamma);\n" + "\n" + " if (density > 0)\n" + " {\n" + " if (density < linrange)\n" + " {\n" + " frac = density / linrange;\n" + " alpha = (1.0 - frac) * density * (funcval / linrange) + frac * pow(density, gamma);\n" + " }\n" + " else\n" + " alpha = pow(density, gamma);\n" + " }\n" + " else\n" + " alpha = 0;\n" + "\n" + " return alpha;\n" + "}\n" + "\n"; + +/// +/// Use MWC 64 from David Thomas at the Imperial College of London for +/// random numbers in OpenCL, instead of ISAAC which was used +/// for CPU rendering. +/// +static const char* RandFunctionString = + "enum { MWC64X_A = 4294883355u };\n\n" + "inline uint MwcNext(uint2* s)\n" + "{\n" + " uint res = (*s).x ^ (*s).y; \n"//Calculate the result. + " uint hi = mul_hi((*s).x, MWC64X_A); \n"//Step the RNG. + " (*s).x = (*s).x * MWC64X_A + (*s).y;\n"//Pack the state back up. + " (*s).y = hi + ((*s).x < (*s).y); \n" + " return res; \n"//Return the next result. + "}\n" + "\n" + "inline uint MwcNextRange(uint2* s, uint val)\n" + "{\n" + " return (val == 0) ? MwcNext(s) : (MwcNext(s) % val);\n" + "}\n" + "\n" + "inline real_t MwcNext01(uint2* s)\n" + "{\n" + " return MwcNext(s) * (1.0 / 4294967296.0);\n" + "}\n" + "\n" + "inline real_t MwcNextNeg1Pos1(uint2* s)\n" + "{\n" + " real_t f = (real_t)MwcNext(s) / UINT_MAX;\n" + " return -1.0 + (f * (1.0 - (-1.0)));\n" + "}\n" + "\n"; + +/// +/// OpenCL equivalent of the global ClampRef(). +/// +static const char* ClampRealFunctionString = + "inline real_t Clamp(real_t val, real_t min, real_t max)\n" + "{\n" + " if (val < min)\n" + " return min;\n" + " else if (val > max)\n" + " return max;\n" + " else\n" + " return val;\n" + "}\n" + "\n" + "inline void ClampRef(real_t* val, real_t min, real_t max)\n" + "{\n" + " if (*val < min)\n" + " *val = min;\n" + " else if (*val > max)\n" + " *val = max;\n" + "}\n" + "\n" + "inline real_t ClampGte(real_t val, real_t gte)\n" + "{\n" + " return (val < gte) ? gte : val;\n" + "}\n" + "\n"; + +/// +/// OpenCL equivalent of the global LRint(). +/// +static const char* InlineMathFunctionsString = + "inline real_t LRint(real_t x)\n" + "{\n" + " intPrec temp = (x >= 0.0 ? (intPrec)(x + 0.5) : (intPrec)(x - 0.5));\n" + " return (real_t)temp;\n" + "}\n" + "\n" + "inline real_t Round(real_t r)\n" + "{\n" + " return (r > 0.0) ? floor(r + 0.5) : ceil(r - 0.5);\n" + "}\n" + "\n" + "inline real_t Sign(real_t v)\n" + "{\n" + " return (v < 0.0) ? -1 : (v > 0.0) ? 1 : 0.0;\n" + "}\n" + "\n" + "inline real_t SignNz(real_t v)\n" + "{\n" + " return (v < 0.0) ? -1.0 : 1.0;\n" + "}\n" + "\n" + "inline real_t Sqr(real_t v)\n" + "{\n" + " return v * v;\n" + "}\n" + "\n" + "inline real_t SafeSqrt(real_t x)\n" + "{\n" + " if (x <= 0.0)\n" + " return 0.0;\n" + "\n" + " return sqrt(x);\n" + "}\n" + "\n" + "inline real_t Cube(real_t v)\n" + "{\n" + " return v * v * v;\n" + "}\n" + "\n" + "inline real_t Hypot(real_t x, real_t y)\n" + "{\n" + " return sqrt(SQR(x) + SQR(y));\n" + "}\n" + "\n" + "inline real_t Spread(real_t x, real_t y)\n" + "{\n" + " return Hypot(x, y) * ((x) > 0.0 ? 1.0 : -1.0);\n" + "}\n" + "\n" + "inline real_t Powq4(real_t x, real_t y)\n" + "{\n" + " return pow(fabs(x), y) * SignNz(x);\n" + "}\n" + "\n" + "inline real_t Powq4c(real_t x, real_t y)\n" + "{\n" + " return y == 1.0 ? x : Powq4(x, y);\n" + "}\n" + "\n" + "inline real_t Zeps(real_t x)\n" + "{\n" + " return x == 0.0 ? EPS6 : x;\n" + "}\n" + "\n" + "inline real_t Lerp(real_t a, real_t b, real_t p)\n" + "{\n" + " return a + (b - a) * p;\n" + "}\n" + "\n" + "inline real_t Fabsmod(real_t v)\n" + "{\n" + " real_t dummy;\n" + "\n" + " return modf(v, &dummy);\n" + "}\n" + "\n" + "inline real_t Fosc(real_t p, real_t amp, real_t ph)\n" + "{\n" + " return 0.5 - cos(p * amp + ph) * 0.5;\n" + "}\n" + "\n" + "inline real_t Foscn(real_t p, real_t ph)\n" + "{\n" + " return 0.5 - cos(p + ph) * 0.5;\n" + "}\n" + "\n" + "inline real_t LogScale(real_t x)\n" + "{\n" + " return x == 0.0 ? 0.0 : log((fabs(x) + 1) * M_E) * SignNz(x) / M_E;\n" + "}\n" + "\n" + "inline real_t LogMap(real_t x)\n" + "{\n" + " return x == 0.0 ? 0.0 : (M_E + log(x * M_E)) * 0.25 * SignNz(x);\n" + "}\n" + "\n"; + +/// +/// OpenCL equivalent Renderer::AddToAccum(). +/// +static const char* AddToAccumWithCheckFunctionString = + "inline bool AccumCheck(int superRasW, int superRasH, int i, int ii, int j, int jj)\n" + "{\n" + " return (j + jj >= 0 && j + jj < superRasH && i + ii >= 0 && i + ii < superRasW);\n" + "}\n" + "\n"; + +/// +/// OpenCL equivalent various CarToRas member functions. +/// +static const char* CarToRasFunctionString = + "inline void CarToRasConvertPointToSingle(__constant CarToRasCL* carToRas, Point* point, unsigned int* singleBufferIndex)\n" + "{\n" + " *singleBufferIndex = (unsigned int)(carToRas->m_PixPerImageUnitW * point->m_X - carToRas->m_RasLlX) + (carToRas->m_RasWidth * (unsigned int)(carToRas->m_PixPerImageUnitH * point->m_Y - carToRas->m_RasLlY));\n" + "}\n" + "\n" + "inline bool CarToRasInBounds(__constant CarToRasCL* carToRas, Point* point)\n" + "{\n" + " return point->m_X >= carToRas->m_CarLlX &&\n" + " point->m_X < carToRas->m_CarUrX &&\n" + " point->m_Y < carToRas->m_CarUrY &&\n" + " point->m_Y >= carToRas->m_CarLlY;\n" + "}\n" + "\n"; + +static string AtomicString(bool doublePrecision, bool dp64AtomicSupport) +{ + ostringstream os; + + //If they want single precision, or if they want double precision and have dp atomic support. + if (!doublePrecision || dp64AtomicSupport) + { + os << + "void AtomicAdd(volatile __global real_t* source, const real_t operand)\n" + "{\n" + " union\n" + " {\n" + " atomi intVal;\n" + " real_t realVal;\n" + " } newVal;\n" + "\n" + " union\n" + " {\n" + " atomi intVal;\n" + " real_t realVal;\n" + " } prevVal;\n" + "\n" + " do\n" + " {\n" + " prevVal.realVal = *source;\n" + " newVal.realVal = prevVal.realVal + operand;\n" + " } while (atomic_cmpxchg((volatile __global atomi*)source, prevVal.intVal, newVal.intVal) != prevVal.intVal);\n" + "}\n"; + } + else//They want double precision and do not have dp atomic support. + { + os << + "void AtomicAdd(volatile __global real_t* source, const real_t operand)\n" + "{\n" + " union\n" + " {\n" + " uint intVal[2];\n" + " real_t realVal;\n" + " } newVal;\n" + "\n" + " union\n" + " {\n" + " uint intVal[2];\n" + " real_t realVal;\n" + " } prevVal;\n" + "\n" + " do\n" + " {\n" + " prevVal.realVal = *source;\n" + " newVal.realVal = prevVal.realVal + operand;\n" + " } while ((atomic_cmpxchg((volatile __global uint*)source, prevVal.intVal[0], newVal.intVal[0]) != prevVal.intVal[0]) ||\n" + " (atomic_cmpxchg((volatile __global uint*)source + 1, prevVal.intVal[1], newVal.intVal[1]) != prevVal.intVal[1]));\n" + "}\n"; + } + + return os.str(); +} + +#ifdef GRAVEYARD +/*"void AtomicLocalAdd(volatile __local real_t* source, const real_t operand)\n" + "{\n" + " union\n" + " {\n" + " atomi intVal;\n" + " real_t realVal;\n" + " } newVal;\n" + "\n" + " union\n" + " {\n" + " atomi intVal;\n" + " real_t realVal;\n" + " } prevVal;\n" + "\n" + " do\n" + " {\n" + " prevVal.realVal = *source;\n" + " newVal.realVal = prevVal.realVal + operand;\n" + " } while (atomic_cmpxchg((volatile __local atomi*)source, prevVal.intVal, newVal.intVal) != prevVal.intVal);\n" + "}\n"*/ +#endif +} \ No newline at end of file diff --git a/Source/EmberCL/EmberCLPch.h b/Source/EmberCL/EmberCLPch.h new file mode 100644 index 0000000..c1492b4 --- /dev/null +++ b/Source/EmberCL/EmberCLPch.h @@ -0,0 +1,39 @@ +#pragma once + +/// +/// Precompiled header file. Place all system includes here with appropriate #defines for different operating systems and compilers. +/// + +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN//Exclude rarely-used stuff from Windows headers. +#define _USE_MATH_DEFINES + +#ifdef _WIN32 + #include + #include +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Timing.h" +#include "Renderer.h" + +#if defined(BUILDING_EMBERCL) +#define EMBERCL_API __declspec(dllexport) +#else +#define EMBERCL_API __declspec(dllimport) +#endif + +using namespace std; +using namespace EmberNs; +//#define TEST_CL 1 \ No newline at end of file diff --git a/Source/EmberCL/EmberCLStructs.h b/Source/EmberCL/EmberCLStructs.h new file mode 100644 index 0000000..8d606f6 --- /dev/null +++ b/Source/EmberCL/EmberCLStructs.h @@ -0,0 +1,383 @@ +#pragma once + +#include "EmberCLPch.h" + +/// +/// Various data structures defined for the CPU and OpenCL. +/// These are stripped down versions of THE classes in Ember, for use with OpenCL. +/// Their sole purpose is to pass values from the host to the device. +/// They retain most of the member variables, but do not contain the functions. +/// Visual Studio defaults to alighment of 16, but it's made explicit in case another compiler is used. +/// This must match the alignment specified in the kernel. +/// + +namespace EmberCLns +{ +#define ALIGN __declspec(align(16))//These two must always match. +#define ALIGN_CL "((aligned (16)))"//The extra parens are necessary. + +/// +/// Various constants needed for rendering. +/// +static string ConstantDefinesString(bool doublePrecision) +{ + ostringstream os; + + if (doublePrecision) + { + os << "#if defined(cl_amd_fp64)\n"//AMD extension available? + << " #pragma OPENCL EXTENSION cl_amd_fp64 : enable\n" + << "#endif\n" + << "#if defined(cl_khr_fp64)\n"//Khronos extension available? + << " #pragma OPENCL EXTENSION cl_khr_fp64 : enable\n" + << "#endif\n" + << "#pragma OPENCL EXTENSION cl_khr_int64_base_atomics : enable\n"//Only supported on nVidia. + << "typedef long intPrec;\n" + << "typedef ulong atomi;\n" + << "typedef double real_t;\n" + << "typedef double4 real4;\n"; + } + else + { + os << "typedef int intPrec;\n" + "typedef unsigned int atomi;\n" + "typedef float real_t;\n" + "typedef float4 real4;\n"; + } + + os << + "typedef long int int64;\n" + "typedef unsigned long int uint64;\n" + "\n" + "#define EPS ((1e-10))\n"//May need to change this, it might not be enough in some cases. Maybe try 1e-9 if things look funny when close to zero. + "#define EPS6 ((1e-6))\n" + "\n" + "//The number of threads per block used in the iteration function. Don't change\n" + "//it lightly; the block size is hard coded to be exactly 32 x 8.\n" + "#define NTHREADS 256u\n" + "#define THREADS_PER_WARP 32u\n" + "#define NWARPS (NTHREADS / THREADS_PER_WARP)\n" + "#define COLORMAP_LENGTH 256u\n" + "#define COLORMAP_LENGTH_MINUS_1 255u\n" + "#define DE_THRESH 100u\n" + "#define BadVal(x) (((x) != (x)) || ((x) > 1e10) || ((x) < -1e10))\n" + "#define Rint(A) floor((A) + (((A) < 0) ? -0.5 : 0.5))\n" + "#define SQR(x) ((x) * (x))\n" + "#define CUBE(x) ((x) * (x) * (x))\n" + "#define M_2PI (M_PI * 2)\n" + "#define M_3PI (M_PI * 3)\n" + "#define SQRT5 2.2360679774997896964091736687313\n" + "#define M_PHI 1.61803398874989484820458683436563\n" + "#define DEG_2_RAD (M_PI / 180)\n" + "\n" + "//Index in each dimension of a thread within a block.\n" + "#define THREAD_ID_X (get_local_id(0))\n" + "#define THREAD_ID_Y (get_local_id(1))\n" + "#define THREAD_ID_Z (get_local_id(2))\n" + "\n" + "//Index in each dimension of a block within a grid.\n" + "#define BLOCK_ID_X (get_group_id(0))\n" + "#define BLOCK_ID_Y (get_group_id(1))\n" + "#define BLOCK_ID_Z (get_group_id(2))\n" + "\n" + "//Absolute index in each dimension of a thread within a grid.\n" + "#define GLOBAL_ID_X (get_global_id(0))\n" + "#define GLOBAL_ID_Y (get_global_id(1))\n" + "#define GLOBAL_ID_Z (get_global_id(2))\n" + "\n" + "//Dimensions of a block.\n" + "#define BLOCK_SIZE_X (get_local_size(0))\n" + "#define BLOCK_SIZE_Y (get_local_size(1))\n" + "#define BLOCK_SIZE_Z (get_local_size(2))\n" + "\n" + "//Dimensions of a grid, in terms of blocks.\n" + "#define GRID_SIZE_X (get_num_groups(0))\n" + "#define GRID_SIZE_Y (get_num_groups(1))\n" + "#define GRID_SIZE_Z (get_num_groups(2))\n" + "\n" + "//Dimensions of a grid, in terms of threads.\n" + "#define GLOBAL_SIZE_X (get_global_size(0))\n" + "#define GLOBAL_SIZE_Y (get_global_size(1))\n" + "#define GLOBAL_SIZE_Z (get_global_size(2))\n" + "\n" + "#define INDEX_IN_BLOCK_2D (THREAD_ID_Y * BLOCK_SIZE_X + THREAD_ID_X)\n" + "#define INDEX_IN_BLOCK_3D ((BLOCK_SIZE_X * BLOCK_SIZE_Y * THREAD_ID_Z) + INDEX_IN_BLOCK_2D)\n" + "\n" + "#define INDEX_IN_GRID_2D (GLOBAL_ID_Y * GLOBAL_SIZE_X + GLOBAL_ID_X)\n" + "#define INDEX_IN_GRID_3D ((GLOBAL_SIZE_X * GLOBAL_SIZE_Y * GLOBAL_ID_Z) + INDEX_IN_GRID_2D)\n" + "\n"; + + return os.str(); +} + +/// +/// A point structure on the host that maps to the one used on the device to iterate in OpenCL. +/// It might seem better to use vec4, however 2D palettes and even 3D coordinates may eventually +/// be supported, which will make it more than 4 members. +/// +template +struct ALIGN PointCL +{ + T m_X; + T m_Y; + T m_Z; + T m_ColorX; + T m_LastXfUsed; +}; + +/// +/// The point structure used to iterate in OpenCL. +/// It might seem better to use float4, however 2D palettes and even 3D coordinates may eventually +/// be supported, which will make it more than 4 members. +/// +static const char* PointCLStructString = +"typedef struct __attribute__ " ALIGN_CL " _Point\n" +"{\n" +" real_t m_X;\n" +" real_t m_Y;\n" +" real_t m_Z;\n" +" real_t m_ColorX;\n" +" uint m_LastXfUsed;\n" +"} Point;\n" +"\n"; + +#define MAX_CL_VARS 8//These must always match. +#define MAX_CL_VARS_STRING "8" + +/// +/// A structure on the host used to hold all of the needed information for an xform used on the device to iterate in OpenCL. +/// Template argument expected to be float or double. +/// +template +struct ALIGN XformCL +{ + T m_A, m_B, m_C, m_D, m_E, m_F;//24 (48) + T m_VariationWeights[MAX_CL_VARS];//56 (112) + T m_PostA, m_PostB, m_PostC, m_PostD, m_PostE, m_PostF;//80 (160) + T m_DirectColor;//84 (168) + T m_ColorSpeedCache;//88 (176) + T m_OneMinusColorCache;//92 (184) + T m_Opacity;//96 (192) + T m_VizAdjusted;//100 (200) +}; + +/// +/// The xform structure used to iterate in OpenCL. +/// +static const char* XformCLStructString = +"typedef struct __attribute__ " ALIGN_CL " _XformCL\n" +"{\n" +" real_t m_A, m_B, m_C, m_D, m_E, m_F;\n" +" real_t m_VariationWeights[" MAX_CL_VARS_STRING "];\n" +" real_t m_PostA, m_PostB, m_PostC, m_PostD, m_PostE, m_PostF;\n" +" real_t m_DirectColor;\n" +" real_t m_ColorSpeedCache;\n" +" real_t m_OneMinusColorCache;\n" +" real_t m_Opacity;\n" +" real_t m_VizAdjusted;\n" +"} XformCL;\n" +"\n"; + +#define MAX_CL_XFORM 21//These must always match. +#define MAX_CL_XFORM_STRING "21" + +/// +/// A structure on the host used to hold all of the needed information for an ember used on the device to iterate in OpenCL. +/// Template argument expected to be float or double. +/// +template +struct ALIGN EmberCL +{ + unsigned int m_FinalXformIndex; + XformCL m_Xforms[MAX_CL_XFORM]; + T m_CamZPos; + T m_CamPerspective; + T m_CamYaw; + T m_CamPitch; + T m_CamDepthBlur; + T m_BlurCoef; + m3T m_CamMat; + T m_CenterX, m_CenterY; + T m_RotA, m_RotB, m_RotD, m_RotE; +}; + +/// +/// The ember structure used to iterate in OpenCL. +/// +static const char* EmberCLStructString = +"typedef struct __attribute__ " ALIGN_CL " _EmberCL\n" +"{\n" +" uint m_FinalXformIndex;\n" +" XformCL m_Xforms[" MAX_CL_XFORM_STRING "];\n" +" real_t m_CamZPos;\n" +" real_t m_CamPerspective;\n" +" real_t m_CamYaw;\n" +" real_t m_CamPitch;\n" +" real_t m_CamDepthBlur;\n" +" real_t m_BlurCoef;\n" +" real_t m_C00;\n" +" real_t m_C01;\n" +" real_t m_C02;\n" +" real_t m_C10;\n" +" real_t m_C11;\n" +" real_t m_C12;\n" +" real_t m_C20;\n" +" real_t m_C21;\n" +" real_t m_C22;\n" +" real_t m_CenterX, m_CenterY;\n" +" real_t m_RotA, m_RotB, m_RotD, m_RotE;\n" +"} EmberCL;\n" +"\n"; + +/// +/// A structure on the host used to hold all of the needed information for cartesian to raster mapping used on the device to iterate in OpenCL. +/// Template argument expected to be float or double. +/// +template +struct ALIGN CarToRasCL +{ + T m_PixPerImageUnitW, m_RasLlX; + unsigned int m_RasWidth; + T m_PixPerImageUnitH, m_RasLlY; + T m_CarLlX, m_CarUrX, m_CarUrY, m_CarLlY; +}; + +/// +/// The cartesian to raster structure used to iterate in OpenCL. +/// +static const char* CarToRasCLStructString = +"typedef struct __attribute__ " ALIGN_CL " _CarToRasCL\n" +"{\n" +" real_t m_PixPerImageUnitW, m_RasLlX;\n" +" uint m_RasWidth;\n" +" real_t m_PixPerImageUnitH, m_RasLlY;\n" +" real_t m_CarLlX, m_CarUrX, m_CarUrY, m_CarLlY;\n" +"} CarToRasCL;\n" +"\n"; + +/// +/// A structure on the host used to hold all of the needed information for density filtering used on the device to iterate in OpenCL. +/// Note that the actual filter buffer is held elsewhere. +/// Template argument expected to be float or double. +/// +template +struct ALIGN DensityFilterCL +{ + T m_Curve; + T m_K1; + T m_K2; + unsigned int m_Supersample; + unsigned int m_SuperRasW; + unsigned int m_SuperRasH; + unsigned int m_KernelSize; + unsigned int m_MaxFilterIndex; + unsigned int m_MaxFilteredCounts; + unsigned int m_FilterWidth; +}; + +/// +/// The density filtering structure used to iterate in OpenCL. +/// Note that the actual filter buffer is held elsewhere. +/// +static const char* DensityFilterCLStructString = +"typedef struct __attribute__ " ALIGN_CL " _DensityFilterCL\n" +"{\n" +" real_t m_Curve;\n" +" real_t m_K1;\n" +" real_t m_K2;\n" +" uint m_Supersample;\n" +" uint m_SuperRasW;\n" +" uint m_SuperRasH;\n" +" uint m_KernelSize;\n" +" uint m_MaxFilterIndex;\n" +" uint m_MaxFilteredCounts;\n" +" uint m_FilterWidth;\n" +"} DensityFilterCL;\n" +"\n"; + +/// +/// A structure on the host used to hold all of the needed information for spatial filtering used on the device to iterate in OpenCL. +/// Note that the actual filter buffer is held elsewhere. +/// +template +struct ALIGN SpatialFilterCL +{ + unsigned int m_SuperRasW; + unsigned int m_SuperRasH; + unsigned int m_FinalRasW; + unsigned int m_FinalRasH; + unsigned int m_Supersample; + unsigned int m_FilterWidth; + unsigned int m_NumChannels; + unsigned int m_BytesPerChannel; + unsigned int m_DensityFilterOffset; + unsigned int m_Transparency; + T m_Vibrancy; + T m_HighlightPower; + T m_Gamma; + T m_LinRange; + Color m_Background; +}; + +/// +/// The spatial filtering structure used to iterate in OpenCL. +/// Note that the actual filter buffer is held elsewhere. +/// +static const char* SpatialFilterCLStructString = +"typedef struct __attribute__ ((aligned (16))) _SpatialFilterCL\n" +"{\n" +" uint m_SuperRasW;\n" +" uint m_SuperRasH;\n" +" uint m_FinalRasW;\n" +" uint m_FinalRasH;\n" +" uint m_Supersample;\n" +" uint m_FilterWidth;\n" +" uint m_NumChannels;\n" +" uint m_BytesPerChannel;\n" +" uint m_DensityFilterOffset;\n" +" uint m_Transparency;\n" +" real_t m_Vibrancy;\n" +" real_t m_HighlightPower;\n" +" real_t m_Gamma;\n" +" real_t m_LinRange;\n" +" real_t m_Background[4];\n"//For some reason, using float4/double4 here does not align no matter what. So just use an array of 4. +"} SpatialFilterCL;\n" +"\n"; + +/// +/// EmberCL makes extensive use of the build in vector types, however accessing +/// their members as a buffer is not natively supported. +/// Declaring them in a union with a buffer resolves this problem. +/// +static const char* UnionCLStructString = +"typedef union\n" +"{\n" +" uchar3 m_Uchar3;\n" +" uchar m_Uchars[3];\n" +"} uchar3uchars;\n" +"\n" +"typedef union\n" +"{\n" +" uchar4 m_Uchar4;\n" +" uchar m_Uchars[4];\n" +"} uchar4uchars;\n" +"\n" +"typedef union\n" +"{\n" +" uint4 m_Uint4;\n" +" uint m_Uints[4];\n" +"} uint4uints;\n" +"\n" +"typedef union\n"//Use in places where float is required. +"{\n" +" float4 m_Float4;\n" +" float m_Floats[4];\n" +"} float4floats;\n" +"\n" +"typedef union\n"//Use in places where float or double can be used depending on the template type. +"{\n" +" real4 m_Real4;\n" +" real_t m_Reals[4];\n" +"} real4reals;\n" +"\n"; +} \ No newline at end of file diff --git a/Source/EmberCL/FinalAccumOpenCLKernelCreator.cpp b/Source/EmberCL/FinalAccumOpenCLKernelCreator.cpp new file mode 100644 index 0000000..5866fd6 --- /dev/null +++ b/Source/EmberCL/FinalAccumOpenCLKernelCreator.cpp @@ -0,0 +1,517 @@ +#include "EmberCLPch.h" +#include "FinalAccumOpenCLKernelCreator.h" + +namespace EmberCLns +{ +/// +/// Constructor that creates all kernel strings. +/// The caller will access these strings through the accessor functions. +/// +template +FinalAccumOpenCLKernelCreator::FinalAccumOpenCLKernelCreator() +{ + m_GammaCorrectionWithAlphaCalcEntryPoint = "GammaCorrectionWithAlphaCalcKernel"; + m_GammaCorrectionWithoutAlphaCalcEntryPoint = "GammaCorrectionWithoutAlphaCalcKernel"; + + m_GammaCorrectionWithAlphaCalcKernel = CreateGammaCorrectionKernelString(true); + m_GammaCorrectionWithoutAlphaCalcKernel = CreateGammaCorrectionKernelString(false); + + m_FinalAccumEarlyClipEntryPoint = "FinalAccumEarlyClipKernel"; + m_FinalAccumEarlyClipWithAlphaCalcWithAlphaAccumEntryPoint = "FinalAccumEarlyClipWithAlphaCalcWithAlphaAccumKernel"; + m_FinalAccumEarlyClipWithoutAlphaCalcWithAlphaAccumEntryPoint = "FinalAccumEarlyClipWithoutAlphaCalcWithAlphaAccumKernel"; + + m_FinalAccumEarlyClipKernel = CreateFinalAccumKernelString(true, false, false); + m_FinalAccumEarlyClipWithAlphaCalcWithAlphaAccumKernel = CreateFinalAccumKernelString(true, true, true); + m_FinalAccumEarlyClipWithoutAlphaCalcWithAlphaAccumKernel = CreateFinalAccumKernelString(true, false, true); + + m_FinalAccumLateClipEntryPoint = "FinalAccumLateClipKernel"; + m_FinalAccumLateClipWithAlphaCalcWithAlphaAccumEntryPoint = "FinalAccumLateClipWithAlphaCalcWithAlphaAccumKernel"; + m_FinalAccumLateClipWithoutAlphaCalcWithAlphaAccumEntryPoint = "FinalAccumLateClipWithoutAlphaCalcWithAlphaAccumKernel"; + + m_FinalAccumLateClipKernel = CreateFinalAccumKernelString(false, false, false); + m_FinalAccumLateClipWithAlphaCalcWithAlphaAccumKernel = CreateFinalAccumKernelString(false, true, true); + m_FinalAccumLateClipWithoutAlphaCalcWithAlphaAccumKernel = CreateFinalAccumKernelString(false, false, true); +} + +/// +/// Kernel source and entry point properties, getters only. +/// + +template string FinalAccumOpenCLKernelCreator::GammaCorrectionWithAlphaCalcKernel() { return m_GammaCorrectionWithAlphaCalcKernel; } +template string FinalAccumOpenCLKernelCreator::GammaCorrectionWithAlphaCalcEntryPoint() { return m_GammaCorrectionWithAlphaCalcEntryPoint; } +template string FinalAccumOpenCLKernelCreator::GammaCorrectionWithoutAlphaCalcKernel() { return m_GammaCorrectionWithoutAlphaCalcKernel; } +template string FinalAccumOpenCLKernelCreator::GammaCorrectionWithoutAlphaCalcEntryPoint() { return m_GammaCorrectionWithoutAlphaCalcEntryPoint; } + +template string FinalAccumOpenCLKernelCreator::FinalAccumEarlyClipKernel() { return m_FinalAccumEarlyClipKernel; } +template string FinalAccumOpenCLKernelCreator::FinalAccumEarlyClipEntryPoint() { return m_FinalAccumEarlyClipEntryPoint; } +template string FinalAccumOpenCLKernelCreator::FinalAccumEarlyClipWithAlphaCalcWithAlphaAccumKernel() { return m_FinalAccumEarlyClipWithAlphaCalcWithAlphaAccumKernel; } +template string FinalAccumOpenCLKernelCreator::FinalAccumEarlyClipWithAlphaCalcWithAlphaAccumEntryPoint() { return m_FinalAccumEarlyClipWithAlphaCalcWithAlphaAccumEntryPoint; } +template string FinalAccumOpenCLKernelCreator::FinalAccumEarlyClipWithoutAlphaCalcWithAlphaAccumKernel() { return m_FinalAccumEarlyClipWithoutAlphaCalcWithAlphaAccumKernel; } +template string FinalAccumOpenCLKernelCreator::FinalAccumEarlyClipWithoutAlphaCalcWithAlphaAccumEntryPoint() { return m_FinalAccumEarlyClipWithoutAlphaCalcWithAlphaAccumEntryPoint; } + +template string FinalAccumOpenCLKernelCreator::FinalAccumLateClipKernel() { return m_FinalAccumLateClipKernel; } +template string FinalAccumOpenCLKernelCreator::FinalAccumLateClipEntryPoint() { return m_FinalAccumLateClipEntryPoint; } +template string FinalAccumOpenCLKernelCreator::FinalAccumLateClipWithAlphaCalcWithAlphaAccumKernel() { return m_FinalAccumLateClipWithAlphaCalcWithAlphaAccumKernel; } +template string FinalAccumOpenCLKernelCreator::FinalAccumLateClipWithAlphaCalcWithAlphaAccumEntryPoint() { return m_FinalAccumLateClipWithAlphaCalcWithAlphaAccumEntryPoint; } +template string FinalAccumOpenCLKernelCreator::FinalAccumLateClipWithoutAlphaCalcWithAlphaAccumKernel() { return m_FinalAccumLateClipWithoutAlphaCalcWithAlphaAccumKernel; } +template string FinalAccumOpenCLKernelCreator::FinalAccumLateClipWithoutAlphaCalcWithAlphaAccumEntryPoint() { return m_FinalAccumLateClipWithoutAlphaCalcWithAlphaAccumEntryPoint; } + +/// +/// Get the gamma correction entry point. +/// +/// The number of channels used, 3 or 4. +/// True if channels equals 4 and using transparency, else false. +/// The name of the gamma correction entry point kernel function +template +string FinalAccumOpenCLKernelCreator::GammaCorrectionEntryPoint(unsigned int channels, bool transparency) +{ + bool alphaCalc = (channels > 3 && transparency); + return alphaCalc ? m_GammaCorrectionWithAlphaCalcEntryPoint : m_GammaCorrectionWithoutAlphaCalcEntryPoint; +} + +/// +/// Get the gamma correction kernel string. +/// +/// The number of channels used, 3 or 4. +/// True if channels equals 4 and using transparency, else false. +/// The gamma correction kernel string +template +string FinalAccumOpenCLKernelCreator::GammaCorrectionKernel(unsigned int channels, bool transparency) +{ + bool alphaCalc = (channels > 3 && transparency); + return alphaCalc ? m_GammaCorrectionWithAlphaCalcKernel : m_GammaCorrectionWithoutAlphaCalcKernel; +} + +/// +/// Get the final accumulation entry point. +/// +/// True if early clip is desired, else false. +/// The number of channels used, 3 or 4. +/// True if channels equals 4 and using transparency, else false. +/// Storage for the alpha base value used in the kernel. 0 if transparency is true, else 255. +/// Storage for the alpha scale value used in the kernel. 255 if transparency is true, else 0. +/// The name of the final accumulation entry point kernel function +template +string FinalAccumOpenCLKernelCreator::FinalAccumEntryPoint(bool earlyClip, unsigned int channels, bool transparency, T& alphaBase, T& alphaScale) +{ + bool alphaCalc = (channels > 3 && transparency); + bool alphaAccum = channels > 3; + + if (alphaAccum) + { + alphaBase = transparency ? 0.0f : 255.0f;//See the table below. + alphaScale = transparency ? 255.0f : 0.0f; + } + + if (earlyClip) + { + if (!alphaCalc && !alphaAccum)//Rgb output, the most common case. + return FinalAccumEarlyClipEntryPoint(); + else if (alphaCalc && alphaAccum)//Rgba output and Transparency. + return FinalAccumEarlyClipWithAlphaCalcWithAlphaAccumEntryPoint(); + else if (!alphaCalc && alphaAccum)//Rgba output and !Transparency. + return FinalAccumEarlyClipWithoutAlphaCalcWithAlphaAccumEntryPoint(); + else + return "";//Cannot have alphaCalc and !alphaAccum, it makes no sense. + } + else + { + if (!alphaCalc && !alphaAccum)//Rgb output, the most common case. + return FinalAccumLateClipEntryPoint(); + else if (alphaCalc && alphaAccum)//Rgba output and Transparency. + return FinalAccumLateClipWithAlphaCalcWithAlphaAccumEntryPoint(); + else if (!alphaCalc && alphaAccum)//Rgba output and !Transparency. + return FinalAccumLateClipWithoutAlphaCalcWithAlphaAccumEntryPoint(); + else + return "";//Cannot have alphaCalc and !alphaAccum, it makes no sense. + } +} + +/// +/// Get the final accumulation kernel string. +/// +/// True if early clip is desired, else false. +/// The number of channels used, 3 or 4. +/// True if channels equals 4 and using transparency, else false. +/// The final accumulation kernel string +template +string FinalAccumOpenCLKernelCreator::FinalAccumKernel(bool earlyClip, unsigned int channels, bool transparency) +{ + bool alphaCalc = (channels > 3 && transparency); + bool alphaAccum = channels > 3; + + if (earlyClip) + { + if (!alphaCalc && !alphaAccum)//Rgb output, the most common case. + return FinalAccumEarlyClipKernel(); + else if (alphaCalc && alphaAccum)//Rgba output and Transparency. + return FinalAccumEarlyClipWithAlphaCalcWithAlphaAccumKernel(); + else if (!alphaCalc && alphaAccum)//Rgba output and !Transparency. + return FinalAccumEarlyClipWithoutAlphaCalcWithAlphaAccumKernel(); + else + return "";//Cannot have alphaCalc and !alphaAccum, it makes no sense. + } + else + { + if (!alphaCalc && !alphaAccum)//Rgb output, the most common case. + return FinalAccumLateClipKernel(); + else if (alphaCalc && alphaAccum)//Rgba output and Transparency. + return FinalAccumLateClipWithAlphaCalcWithAlphaAccumKernel(); + else if (!alphaCalc && alphaAccum)//Rgba output and !Transparency. + return FinalAccumLateClipWithoutAlphaCalcWithAlphaAccumKernel(); + else + return "";//Cannot have alphaCalc and !alphaAccum, it makes no sense. + } +} + +/// +/// Wrapper around CreateFinalAccumKernelString(). +/// +/// True if early clip is desired, else false. +/// The number of channels used, 3 or 4. +/// True if channels equals 4 and using transparency, else false. +/// The final accumulation kernel string +template +string FinalAccumOpenCLKernelCreator::CreateFinalAccumKernelString(bool earlyClip, unsigned int channels, bool transparency) +{ + return CreateFinalAccumKernelString(earlyClip, (channels > 3 && transparency), channels > 3); +} + +/// +/// Create the final accumulation kernel string +/// +/// True if early clip is desired, else false. +/// True if channels equals 4 and transparency is desired, else false. +/// True if channels equals 4 +/// The final accumulation kernel string +template +string FinalAccumOpenCLKernelCreator::CreateFinalAccumKernelString(bool earlyClip, bool alphaCalc, bool alphaAccum) +{ + ostringstream os; + string channels = alphaAccum ? "4" : "3"; + + os << + ConstantDefinesString(typeid(T) == typeid(double)) << + ClampRealFunctionString << + UnionCLStructString << + RgbToHsvFunctionString << + HsvToRgbFunctionString << + CalcAlphaFunctionString << + SpatialFilterCLStructString; + + if (earlyClip) + { + if (!alphaCalc && !alphaAccum)//Rgb output, the most common case. + os << "__kernel void " << m_FinalAccumEarlyClipEntryPoint << "(\n"; + else if (alphaCalc && alphaAccum)//Rgba output and Transparency. + os << "__kernel void " << m_FinalAccumEarlyClipWithAlphaCalcWithAlphaAccumEntryPoint << "(\n"; + else if (!alphaCalc && alphaAccum)//Rgba output and !Transparency. + os << "__kernel void " << m_FinalAccumEarlyClipWithoutAlphaCalcWithAlphaAccumEntryPoint << "(\n"; + else + return "";//Cannot have alphaCalc and !alphaAccum, it makes no sense. + } + else + { + os << + CreateCalcNewRgbFunctionString(false) << + CreateGammaCorrectionFunctionString(false, alphaCalc, alphaAccum, true); + + if (!alphaCalc && !alphaAccum)//Rgb output, the most common case. + os << "__kernel void " << m_FinalAccumLateClipEntryPoint << "(\n"; + else if (alphaCalc && alphaAccum)//Rgba output and Transparency. + os << "__kernel void " << m_FinalAccumLateClipWithAlphaCalcWithAlphaAccumEntryPoint << "(\n"; + else if (!alphaCalc && alphaAccum)//Rgba output and !Transparency. + os << "__kernel void " << m_FinalAccumLateClipWithoutAlphaCalcWithAlphaAccumEntryPoint << "(\n"; + else + return "";//Cannot have alphaCalc and !alphaAccum, it makes no sense. + } + + os << + " const __global real4reals* accumulator,\n" + " __write_only image2d_t pixels,\n" + " __constant SpatialFilterCL* spatialFilter,\n" + " __constant real_t* filterCoefs,\n" + " const real_t alphaBase,\n" + " const real_t alphaScale\n" + "\t)\n" + "{\n" + "\n" + " if ((GLOBAL_ID_Y >= spatialFilter->m_FinalRasH) || (GLOBAL_ID_X >= spatialFilter->m_FinalRasW))\n" + " return;\n" + "\n" + " unsigned int accumX = spatialFilter->m_DensityFilterOffset + (GLOBAL_ID_X * spatialFilter->m_Supersample);\n" + " unsigned int accumY = spatialFilter->m_DensityFilterOffset + (GLOBAL_ID_Y * spatialFilter->m_Supersample);\n" + + " int2 finalCoord;\n" + " finalCoord.x = GLOBAL_ID_X;\n" + " finalCoord.y = GLOBAL_ID_Y;\n" + " float4floats finalColor;\n" + " real_t alpha, ls;\n" + " int ii, jj;\n" + " unsigned int filterKRowIndex;\n" + " const __global real4reals* accumBucket;\n" + " real4reals newBucket;\n" + " newBucket.m_Real4 = 0;\n" + " real4reals newRgb;\n" + " newRgb.m_Real4 = 0;\n" + "\n" + " for (jj = 0; jj < spatialFilter->m_FilterWidth; jj++)\n" + " {\n" + " filterKRowIndex = jj * spatialFilter->m_FilterWidth;\n" + "\n" + " for (ii = 0; ii < spatialFilter->m_FilterWidth; ii++)\n" + " {\n" + " real_t k = filterCoefs[ii + filterKRowIndex];\n" + "\n" + " accumBucket = accumulator + (accumX + ii) + ((accumY + jj) * spatialFilter->m_SuperRasW);\n" + " newBucket.m_Real4 += (k * accumBucket->m_Real4);\n" + " }\n" + " }\n" + "\n"; + + //Not supporting 2 bytes per channel on the GPU. If the user wants it, run on the CPU. + if (earlyClip)//If early clip, simply assign values directly to the temp uint4 since they've been gamma corrected already, then write it straight to the output image below. + { + os << + " finalColor.m_Float4.x = (float)newBucket.m_Real4.x;\n"//CPU side clamps, skip here because write_imagef() does the clamping for us. + " finalColor.m_Float4.y = (float)newBucket.m_Real4.y;\n" + " finalColor.m_Float4.z = (float)newBucket.m_Real4.z;\n"; + + if (alphaAccum) + { + if (alphaCalc) + os << " finalColor.m_Float4.w = (float)newBucket.m_Real4.w * 255.0f;\n"; + else + os << " finalColor.m_Float4.w = 255;\n"; + } + } + else + { + //Late clip, so must gamma correct from the temp new bucket to temp float4. + if (typeid(T) == typeid(double)) + { + os << + " real4reals realFinal;\n" + "\n" + " GammaCorrectionFloats(&newBucket, &(spatialFilter->m_Background[0]), spatialFilter->m_Gamma, spatialFilter->m_LinRange, spatialFilter->m_Vibrancy, spatialFilter->m_HighlightPower, alphaBase, alphaScale, &(realFinal.m_Reals[0]));\n" + " finalColor.m_Float4.x = (float)realFinal.m_Real4.x;\n" + " finalColor.m_Float4.y = (float)realFinal.m_Real4.y;\n" + " finalColor.m_Float4.z = (float)realFinal.m_Real4.z;\n" + " finalColor.m_Float4.w = (float)realFinal.m_Real4.w;\n" + ; + } + else + { + os << + " GammaCorrectionFloats(&newBucket, &(spatialFilter->m_Background[0]), spatialFilter->m_Gamma, spatialFilter->m_LinRange, spatialFilter->m_Vibrancy, spatialFilter->m_HighlightPower, alphaBase, alphaScale, &(finalColor.m_Floats[0]));\n"; + } + } + + os << + " finalColor.m_Float4 /= 255.0f;\n" + " write_imagef(pixels, finalCoord, finalColor.m_Float4);\n"//Use write_imagef instead of write_imageui because only the former works when sharing with an OpenGL texture. + " barrier(CLK_GLOBAL_MEM_FENCE);\n"//Required, or else page tearing will occur during interactive rendering. + "}\n" + ; + + return os.str(); +} + +/// +/// Creates the gamma correction function string. +/// This is not a full kernel, just a function that is used in the kernels. +/// +/// True if writing to a global buffer (early clip), else false (late clip). +/// True if channels equals 4 and transparency is desired, else false. +/// True if channels equals 4 +/// True if writing to global buffer (late clip), else false (early clip). +/// The gamma correction function string +template +string FinalAccumOpenCLKernelCreator::CreateGammaCorrectionFunctionString(bool globalBucket, bool alphaCalc, bool alphaAccum, bool finalOut) +{ + ostringstream os; + string dataType; + string unionMember; + dataType = "real_t"; + + //Use real_t for all cases, early clip and final accum. + os << "void GammaCorrectionFloats(" << (globalBucket ? "__global " : "") << "real4reals* bucket, __constant real_t* background, real_t g, real_t linRange, real_t vibrancy, real_t highlightPower, real_t alphaBase, real_t alphaScale, " << (finalOut ? "" : "__global") << " real_t* correctedChannels)\n"; + + os + << "{\n" + << " real_t alpha, ls, tmp, a;\n" + << " real4reals newRgb;\n" + << "\n" + << " if (bucket->m_Reals[3] <= 0)\n" + << " {\n" + << " alpha = 0;\n" + << " ls = 0;\n" + << " }\n" + << " else\n" + << " {\n" + << " tmp = bucket->m_Reals[3];\n" + << " alpha = CalcAlpha(tmp, g, linRange);\n" + << " ls = vibrancy * 256.0 * alpha / tmp;\n" + << " ClampRef(&alpha, 0.0, 1.0);\n" + << " }\n" + << "\n" + << " CalcNewRgb(bucket, ls, highlightPower, &newRgb);\n" + << "\n" + << " for (unsigned int rgbi = 0; rgbi < 3; rgbi++)\n" + << " {\n" + << " a = newRgb.m_Reals[rgbi] + ((1.0 - vibrancy) * 256.0 * pow(bucket->m_Reals[rgbi], g));\n" + << "\n"; + + if (!alphaCalc) + { + os << + " a += ((1.0 - alpha) * background[rgbi]);\n"; + } + else + { + os + << " if (alpha > 0)\n" + << " a /= alpha;\n" + << " else\n" + << " a = 0;\n"; + } + + os << + "\n" + " correctedChannels[rgbi] = (" << dataType << ")clamp(a, 0.0, 255.0);\n" + " }\n" + "\n"; + + //The CPU code has 3 cases for assigning alpha: + //[3] = alpha.//Early clip. + //[3] = alpha * 255.//Final Rgba with transparency. + //[3] = 255.//Final Rgba without transparency. + //Putting conditionals in GPU code is to be avoided. So do base + alpha * scale which will + //work for all 3 cases without using a conditional, which should be faster on a GPU. This gives: + //Base = 0, scale = 1. [3] = (0 + (alpha * 1)). [3] = alpha. + //Base = 0, scale = 255. [3] = (0 + (alpha * 255)). [3] = alpha * 255. + //Base = 255, scale = 0. [3] = (255 + (alpha * 0)). [3] = 255. + if (alphaAccum) + { + os + << " correctedChannels[3] = (" << dataType << ")(alphaBase + (alpha * alphaScale));\n"; + } + + os << + "}\n" + "\n"; + + return os.str(); +} + +/// +/// OpenCL equivalent of Palette::CalcNewRgb(). +/// +/// True if writing the corrected value to a global buffer (early clip), else false (late clip). +/// The CalcNewRgb function string +template +string FinalAccumOpenCLKernelCreator::CreateCalcNewRgbFunctionString(bool globalBucket) +{ + ostringstream os; + + os << + "static void CalcNewRgb(" << (globalBucket ? "__global " : "") << "real4reals* oldRgb, real_t ls, real_t highPow, real4reals* newRgb)\n" + "{\n" + " int rgbi;\n" + " real_t newls, lsratio;\n" + " real4reals newHsv;\n" + " real_t maxa, maxc;\n" + " real_t adjhlp;\n" + "\n" + " if (ls == 0 || (oldRgb->m_Real4.x == 0 && oldRgb->m_Real4.y == 0 && oldRgb->m_Real4.z == 0))\n"//Can't do a vector compare to zero. + " {\n" + " newRgb->m_Real4 = 0;\n" + " return;\n" + " }\n" + "\n" + //Identify the most saturated channel. + " maxc = max(max(oldRgb->m_Reals[0], oldRgb->m_Reals[1]), oldRgb->m_Reals[2]);\n" + " maxa = ls * maxc;\n" + "\n" + //If a channel is saturated and highlight power is non-negative + //modify the color to prevent hue shift. + " if (maxa > 255 && highPow >= 0)\n" + " {\n" + " newls = 255.0 / maxc;\n" + " lsratio = pow(newls / ls, highPow);\n" + "\n" + //Calculate the max-value color (ranged 0 - 1). + " for (rgbi = 0; rgbi < 3; rgbi++)\n" + " newRgb->m_Reals[rgbi] = newls * oldRgb->m_Reals[rgbi] / 255.0;\n" + "\n" + //Reduce saturation by the lsratio. + " RgbToHsv(&(newRgb->m_Real4), &(newHsv.m_Real4));\n" + " newHsv.m_Real4.y *= lsratio;\n" + " HsvToRgb(&(newHsv.m_Real4), &(newRgb->m_Real4));\n" + "\n" + " for (rgbi = 0; rgbi < 3; rgbi++)\n"//Unrolling and vectorizing makes no difference. + " newRgb->m_Reals[rgbi] *= 255.0;\n" + " }\n" + " else\n" + " {\n" + " newls = 255.0 / maxc;\n" + " adjhlp = -highPow;\n" + "\n" + " if (adjhlp > 1)\n" + " adjhlp = 1;\n" + "\n" + " if (maxa <= 255)\n" + " adjhlp = 1;\n" + "\n" + //Calculate the max-value color (ranged 0 - 1) interpolated with the old behavior. + " for (rgbi = 0; rgbi < 3; rgbi++)\n"//Unrolling, caching and vectorizing makes no difference. + " newRgb->m_Reals[rgbi] = ((1.0 - adjhlp) * newls + adjhlp * ls) * oldRgb->m_Reals[rgbi];\n" + " }\n" + "}\n" + "\n"; + + return os.str(); +} + +/// +/// Create the gamma correction kernel string used for early clipping. +/// +/// True if channels equals 4 and transparency is desired, else false. +/// The gamma correction kernel string used for early clipping +template +string FinalAccumOpenCLKernelCreator::CreateGammaCorrectionKernelString(bool alphaCalc) +{ + ostringstream os; + string dataType; + + os << + ConstantDefinesString(typeid(T) == typeid(double)) << + ClampRealFunctionString << + UnionCLStructString << + RgbToHsvFunctionString << + HsvToRgbFunctionString << + CalcAlphaFunctionString << + CreateCalcNewRgbFunctionString(true) << + SpatialFilterCLStructString << + CreateGammaCorrectionFunctionString(true, alphaCalc, true, false);//Will only be used with float in this case, early clip. Will always alpha accum. + + os << "__kernel void " << (alphaCalc ? m_GammaCorrectionWithAlphaCalcEntryPoint : m_GammaCorrectionWithoutAlphaCalcEntryPoint) << "(\n" << + " __global real4reals* accumulator,\n" + " __constant SpatialFilterCL* spatialFilter\n" + ")\n" + "{\n" + " int testGutter = 0;\n" + "\n" + " if (GLOBAL_ID_Y >= (spatialFilter->m_SuperRasH - testGutter) || GLOBAL_ID_X >= (spatialFilter->m_SuperRasW - testGutter))\n" + " return;\n" + "\n" + " unsigned int superIndex = (GLOBAL_ID_Y * spatialFilter->m_SuperRasW) + GLOBAL_ID_X;\n" + " __global real4reals* bucket = accumulator + superIndex;\n" + //Pass in an alphaBase and alphaScale of 0, 1 which means to just directly assign the computed alpha value. + " GammaCorrectionFloats(bucket, &(spatialFilter->m_Background[0]), spatialFilter->m_Gamma, spatialFilter->m_LinRange, spatialFilter->m_Vibrancy, spatialFilter->m_HighlightPower, 0.0, 1.0, &(bucket->m_Reals[0]));\n" + "}\n" + ; + + return os.str(); +} +} \ No newline at end of file diff --git a/Source/EmberCL/FinalAccumOpenCLKernelCreator.h b/Source/EmberCL/FinalAccumOpenCLKernelCreator.h new file mode 100644 index 0000000..5ded6ec --- /dev/null +++ b/Source/EmberCL/FinalAccumOpenCLKernelCreator.h @@ -0,0 +1,87 @@ +#pragma once + +#include "EmberCLPch.h" +#include "EmberCLStructs.h" +#include "EmberCLFunctions.h" + +/// +/// FinalAccumOpenCLKernelCreator class. +/// + +namespace EmberCLns +{ +/// +/// Class for creating the final accumulation code in OpenCL. +/// There are many conditionals in the CPU code to create the +/// final output image. This class creates many different kernels +/// with all conditionals and unnecessary calculations stripped out. +/// The conditionals are: +/// Early clip/late clip +/// Alpha channel, no alpha channel +/// Alpha with/without transparency +/// Template argument expected to be float or double. +/// +template +class EMBERCL_API FinalAccumOpenCLKernelCreator +{ +public: + FinalAccumOpenCLKernelCreator(); + + string GammaCorrectionWithAlphaCalcKernel(); + string GammaCorrectionWithAlphaCalcEntryPoint(); + + string GammaCorrectionWithoutAlphaCalcKernel(); + string GammaCorrectionWithoutAlphaCalcEntryPoint(); + + string FinalAccumEarlyClipKernel(); + string FinalAccumEarlyClipEntryPoint(); + string FinalAccumEarlyClipWithAlphaCalcWithAlphaAccumKernel(); + string FinalAccumEarlyClipWithAlphaCalcWithAlphaAccumEntryPoint(); + string FinalAccumEarlyClipWithoutAlphaCalcWithAlphaAccumKernel(); + string FinalAccumEarlyClipWithoutAlphaCalcWithAlphaAccumEntryPoint(); + + string FinalAccumLateClipKernel(); + string FinalAccumLateClipEntryPoint(); + string FinalAccumLateClipWithAlphaCalcWithAlphaAccumKernel(); + string FinalAccumLateClipWithAlphaCalcWithAlphaAccumEntryPoint(); + string FinalAccumLateClipWithoutAlphaCalcWithAlphaAccumKernel(); + string FinalAccumLateClipWithoutAlphaCalcWithAlphaAccumEntryPoint(); + string GammaCorrectionEntryPoint(unsigned int channels, bool transparency); + string GammaCorrectionKernel(unsigned int channels, bool transparency); + string FinalAccumEntryPoint(bool earlyClip, unsigned int channels, bool transparency, T& alphaBase, T& alphaScale); + string FinalAccumKernel(bool earlyClip, unsigned int channels, bool transparency); + +private: + string CreateFinalAccumKernelString(bool earlyClip, unsigned int channels, bool transparency); + string CreateGammaCorrectionKernelString(bool alphaCalc); + + string CreateFinalAccumKernelString(bool earlyClip, bool alphaCalc, bool alphaAccum); + string CreateGammaCorrectionFunctionString(bool globalBucket, bool alphaCalc, bool alphaAccum, bool finalOut); + string CreateCalcNewRgbFunctionString(bool globalBucket); + string m_GammaCorrectionWithAlphaCalcKernel; + string m_GammaCorrectionWithAlphaCalcEntryPoint; + + string m_GammaCorrectionWithoutAlphaCalcKernel; + string m_GammaCorrectionWithoutAlphaCalcEntryPoint; + + string m_FinalAccumEarlyClipKernel;//False, false. + string m_FinalAccumEarlyClipEntryPoint; + string m_FinalAccumEarlyClipWithAlphaCalcWithAlphaAccumKernel;//True, true. + string m_FinalAccumEarlyClipWithAlphaCalcWithAlphaAccumEntryPoint; + string m_FinalAccumEarlyClipWithoutAlphaCalcWithAlphaAccumKernel;//False, true. + string m_FinalAccumEarlyClipWithoutAlphaCalcWithAlphaAccumEntryPoint; + + string m_FinalAccumLateClipKernel;//False, false. + string m_FinalAccumLateClipEntryPoint; + string m_FinalAccumLateClipWithAlphaCalcWithAlphaAccumKernel;//True, true. + string m_FinalAccumLateClipWithAlphaCalcWithAlphaAccumEntryPoint; + string m_FinalAccumLateClipWithoutAlphaCalcWithAlphaAccumKernel;//False, true. + string m_FinalAccumLateClipWithoutAlphaCalcWithAlphaAccumEntryPoint; +}; + +template EMBERCL_API class FinalAccumOpenCLKernelCreator; + +#ifdef DO_DOUBLE + template EMBERCL_API class FinalAccumOpenCLKernelCreator; +#endif +} \ No newline at end of file diff --git a/Source/EmberCL/IterOpenCLKernelCreator.cpp b/Source/EmberCL/IterOpenCLKernelCreator.cpp new file mode 100644 index 0000000..8f4339c --- /dev/null +++ b/Source/EmberCL/IterOpenCLKernelCreator.cpp @@ -0,0 +1,785 @@ +#include "EmberCLPch.h" +#include "IterOpenCLKernelCreator.h" + +namespace EmberCLns +{ +/// +/// Empty constructor that does nothing. The user must call the one which takes a bool +/// argument before using this class. +/// This constructor only exists so the class can be a member of a class. +/// +template +IterOpenCLKernelCreator::IterOpenCLKernelCreator() +{ +} + +/// +/// Constructor that sets up some basic entry point strings and creates +/// the zeroization kernel string since it requires no conditional inputs. +/// +template +IterOpenCLKernelCreator::IterOpenCLKernelCreator(bool nVidia) +{ + m_NVidia = nVidia; + m_IterEntryPoint = "IterateKernel"; + m_ZeroizeEntryPoint = "ZeroizeKernel"; + m_ZeroizeKernel = CreateZeroizeKernelString(); +} + +/// +/// Accessors. +/// + +template string IterOpenCLKernelCreator::ZeroizeKernel() { return m_ZeroizeKernel; } +template string IterOpenCLKernelCreator::ZeroizeEntryPoint() { return m_ZeroizeEntryPoint; } +template string IterOpenCLKernelCreator::IterEntryPoint() { return m_IterEntryPoint; } + +/// +/// Create the iteration kernel string using the Cuburn method. +/// Template argument expected to be float or double. +/// +/// The ember to create the kernel string for +/// The parametric variation #define string +/// Debugging parameter to include or omit accumulating to the histogram. Default: true. +/// The kernel string +template +string IterOpenCLKernelCreator::CreateIterKernelString(Ember& ember, string& parVarDefines, bool lockAccum, bool doAccum) +{ + bool doublePrecision = typeid(T) == typeid(double); + unsigned int i, v, varIndex, varCount, totalXformCount = ember.TotalXformCount(); + ostringstream kernelIterBody, xformFuncs, os; + vector*> variations; + + xformFuncs << "\n" << parVarDefines << endl; + ember.GetPresentVariations(variations); + std::for_each(variations.begin(), variations.end(), [&](Variation* var) { if (var) xformFuncs << var->OpenCLFuncsString(); }); + + for (i = 0; i < totalXformCount; i++) + { + Xform* xform = ember.GetTotalXform(i); + unsigned int totalVarCount = xform->TotalVariationCount(); + bool needPrecalcSumSquares = false; + bool needPrecalcSqrtSumSquares = false; + bool needPrecalcAngles = false; + bool needPrecalcAtanXY = false; + bool needPrecalcAtanYX = false; + + v = varIndex = varCount = 0; + xformFuncs << + "void Xform" << i << "(__constant XformCL* xform, __constant real_t* parVars, Point* inPoint, Point* outPoint, uint2* mwc)\n" << + "{\n" + " real_t transX, transY, transZ;\n" + " real4 vIn, vOut = 0.0;\n"; + + //Determine if any variations, regular, pre, or post need precalcs. + while (Variation* var = xform->GetVariation(v++)) + { + needPrecalcSumSquares |= var->NeedPrecalcSumSquares(); + needPrecalcSqrtSumSquares |= var->NeedPrecalcSqrtSumSquares(); + needPrecalcAngles |= var->NeedPrecalcAngles(); + needPrecalcAtanXY |= var->NeedPrecalcAtanXY(); + needPrecalcAtanYX |= var->NeedPrecalcAtanYX(); + } + + if (needPrecalcSumSquares) + xformFuncs << "\treal_t precalcSumSquares;\n"; + + if (needPrecalcSqrtSumSquares) + xformFuncs << "\treal_t precalcSqrtSumSquares;\n"; + + if (needPrecalcAngles) + { + xformFuncs << "\treal_t precalcSina;\n"; + xformFuncs << "\treal_t precalcCosa;\n"; + } + + if (needPrecalcAtanXY) + xformFuncs << "\treal_t precalcAtanxy;\n"; + + if (needPrecalcAtanYX) + xformFuncs << "\treal_t precalcAtanyx;\n"; + + xformFuncs << "\treal_t tempColor = outPoint->m_ColorX = xform->m_ColorSpeedCache + (xform->m_OneMinusColorCache * inPoint->m_ColorX);\n"; + + if (xform->PreVariationCount() + xform->VariationCount() == 0) + { + xformFuncs << + " outPoint->m_X = (xform->m_A * inPoint->m_X) + (xform->m_B * inPoint->m_Y) + xform->m_C;\n" << + " outPoint->m_Y = (xform->m_D * inPoint->m_X) + (xform->m_E * inPoint->m_Y) + xform->m_F;\n" << + " outPoint->m_Z = inPoint->m_Z;\n"; + } + else + { + xformFuncs << + " transX = (xform->m_A * inPoint->m_X) + (xform->m_B * inPoint->m_Y) + xform->m_C;\n" << + " transY = (xform->m_D * inPoint->m_X) + (xform->m_E * inPoint->m_Y) + xform->m_F;\n" << + " transZ = inPoint->m_Z;\n"; + + varCount = xform->PreVariationCount(); + + if (varCount > 0) + { + xformFuncs << "\n\t//Apply each of the " << varCount << " pre variations in this xform.\n"; + + //Output the code for each pre variation in this xform. + for (varIndex = 0; varIndex < varCount; varIndex++) + { + if (Variation* var = xform->GetVariation(varIndex)) + { + xformFuncs << "\n\t//" << var->Name() << ".\n"; + xformFuncs << var->PrecalcOpenCLString(); + xformFuncs << xform->ReadOpenCLString(VARTYPE_PRE) << endl; + xformFuncs << var->OpenCLString() << endl; + xformFuncs << xform->WriteOpenCLString(VARTYPE_PRE, var->AssignType()) << endl; + } + } + } + + if (xform->VariationCount() > 0) + { + if (xform->NeedPrecalcSumSquares()) + xformFuncs << "\tprecalcSumSquares = SQR(transX) + SQR(transY);\n"; + + if (xform->NeedPrecalcSqrtSumSquares()) + xformFuncs << "\tprecalcSqrtSumSquares = sqrt(precalcSumSquares);\n"; + + if (xform->NeedPrecalcAngles()) + { + xformFuncs << "\tprecalcSina = transX / precalcSqrtSumSquares;\n"; + xformFuncs << "\tprecalcCosa = transY / precalcSqrtSumSquares;\n"; + } + + if (xform->NeedPrecalcAtanXY()) + xformFuncs << "\tprecalcAtanxy = atan2(transX, transY);\n"; + + if (xform->NeedPrecalcAtanYX()) + xformFuncs << "\tprecalcAtanyx = atan2(transY, transX);\n"; + + xformFuncs << "\n\toutPoint->m_X = 0;"; + xformFuncs << "\n\toutPoint->m_Y = 0;"; + xformFuncs << "\n\toutPoint->m_Z = 0;\n"; + xformFuncs << "\n\t//Apply each of the " << xform->VariationCount() << " regular variations in this xform.\n\n"; + xformFuncs << xform->ReadOpenCLString(VARTYPE_REG); + + varCount += xform->VariationCount(); + + //Output the code for each regular variation in this xform. + for (; varIndex < varCount; varIndex++) + { + if (Variation* var = xform->GetVariation(varIndex)) + { + xformFuncs << "\n\t//" << var->Name() << ".\n" + << var->OpenCLString() << (varIndex == varCount - 1 ? "\n" : "\n\n") + << xform->WriteOpenCLString(VARTYPE_REG, ASSIGNTYPE_SUM); + } + } + } + else + { + xformFuncs << + " outPoint->m_X = transX;\n" + " outPoint->m_Y = transY;\n" + " outPoint->m_Z = transZ;\n"; + } + } + + if (xform->PostVariationCount() > 0) + { + varCount += xform->PostVariationCount(); + xformFuncs << "\n\t//Apply each of the " << xform->PostVariationCount() << " post variations in this xform.\n"; + + //Output the code for each post variation in this xform. + for (; varIndex < varCount; varIndex++) + { + if (Variation* var = xform->GetVariation(varIndex)) + { + xformFuncs << "\n\t//" << var->Name() << ".\n"; + xformFuncs << var->PrecalcOpenCLString(); + xformFuncs << xform->ReadOpenCLString(VARTYPE_POST) << endl; + xformFuncs << var->OpenCLString() << endl; + xformFuncs << xform->WriteOpenCLString(VARTYPE_POST, var->AssignType()) << (varIndex == varCount - 1 ? "\n" : "\n\n"); + } + } + } + + if (xform->HasPost()) + { + xformFuncs << + "\n\t//Apply post affine transform.\n" + "\treal_t tempX = outPoint->m_X;\n" + "\n" + "\toutPoint->m_X = (xform->m_PostA * tempX) + (xform->m_PostB * outPoint->m_Y) + xform->m_PostC;\n" << + "\toutPoint->m_Y = (xform->m_PostD * tempX) + (xform->m_PostE * outPoint->m_Y) + xform->m_PostF;\n"; + } + + xformFuncs << "\toutPoint->m_ColorX = outPoint->m_ColorX + xform->m_DirectColor * (tempColor - outPoint->m_ColorX);\n"; + xformFuncs << "}\n" + << "\n"; + } + + os << + ConstantDefinesString(doublePrecision) << + InlineMathFunctionsString << + ClampRealFunctionString << + RandFunctionString << + PointCLStructString << + XformCLStructString << + EmberCLStructString << + UnionCLStructString << + CarToRasCLStructString << + CarToRasFunctionString << + AtomicString(doublePrecision, m_NVidia) << + xformFuncs.str() << + "__kernel void " << m_IterEntryPoint << "(\n" << + " uint iterCount,\n" + " uint fuseCount,\n" + " uint seed,\n" + " __constant EmberCL* ember,\n" + " __constant real_t* parVars,\n" + " __global uchar* xformDistributions,\n"//Using uchar is quicker than uint. Can't be constant because the size can be too large to fit when using xaos.//FINALOPT + " __constant CarToRasCL* carToRas,\n" + " __global real4reals* histogram,\n" + " uint histSize,\n" + " __read_only image2d_t palette,\n" + " __global Point* points\n" + "\t)\n" + "{\n" + " bool fuse, ok;\n" + " uint threadIndex = INDEX_IN_BLOCK_2D;\n" + " uint i, itersToDo;\n" + " uint consec = 0;\n" + //" int badvals = 0;\n" + " uint histIndex;\n" + " real_t p00, p01;\n" + " Point firstPoint, secondPoint, tempPoint;\n" + " uint2 mwc;\n" + " float4 palColor1;\n" + " int2 iPaletteCoord;\n" + " const sampler_t paletteSampler = CLK_NORMALIZED_COORDS_FALSE |\n"//Coords from 0 to 255. + " CLK_ADDRESS_CLAMP_TO_EDGE |\n"//Clamp to edge + " CLK_FILTER_NEAREST;\n"//Don't interpolate + " uint threadXY = (THREAD_ID_X + THREAD_ID_Y);\n" + " uint threadXDivRows = (THREAD_ID_X / (NTHREADS / THREADS_PER_WARP));\n" + " uint threadsMinus1 = NTHREADS - 1;\n" + ; + + os << + "\n" + " __local Point swap[NTHREADS];\n" + " __local uint xfsel[NWARPS];\n" + "\n" + " unsigned int pointsIndex = INDEX_IN_GRID_2D;\n" + " mwc.x = (pointsIndex + 1 * seed) & 0x7FFFFFFF;\n" + " mwc.y = ((BLOCK_ID_X + 1) + (pointsIndex + 1) * seed) & 0x7FFFFFFF;\n" + " iPaletteCoord.y = 0;\n" + "\n" + " if (fuseCount > 0)\n" + " {\n" + " fuse = true;\n" + " itersToDo = fuseCount;\n" + " firstPoint.m_X = MwcNextNeg1Pos1(&mwc);\n" + " firstPoint.m_Y = MwcNextNeg1Pos1(&mwc);\n" + " firstPoint.m_Z = 0.0;\n" + " firstPoint.m_ColorX = MwcNext01(&mwc);\n" + " firstPoint.m_LastXfUsed = 0;\n" + " }\n" + " else\n" + " {\n" + " fuse = false;\n" + " itersToDo = iterCount;\n" + " firstPoint = points[pointsIndex];\n" + " }\n" + "\n"; + + //This is done once initially here and then again after each swap-sync in the main loop. + //This along with the randomness that the point shuffle provides gives sufficient randomness + //to produce results identical to those produced on the CPU. + os << + " if (THREAD_ID_Y == 0 && THREAD_ID_X < NWARPS)\n" + " xfsel[THREAD_ID_X] = MwcNext(&mwc) % " << CHOOSE_XFORM_GRAIN << ";\n"//It's faster to do the % here ahead of time than every time an xform is looked up to use inside the loop. + "\n" + " barrier(CLK_LOCAL_MEM_FENCE);\n" + "\n" + " for (i = 0; i < itersToDo; i++)\n" + " {\n"; + + os << + " consec = 0;\n" + "\n" + " do\n" + " {\n"; + if (ember.XaosPresent()) + { + os << + " secondPoint.m_LastXfUsed = xformDistributions[xfsel[THREAD_ID_Y] + (" << CHOOSE_XFORM_GRAIN << " * (firstPoint.m_LastXfUsed + 1u))];\n\n"; + } + else + { + os << + " secondPoint.m_LastXfUsed = xformDistributions[xfsel[THREAD_ID_Y]];\n\n"; + } + + for (i = 0; i < ember.XformCount(); i++) + { + if (i == 0) + os << + " if (secondPoint.m_LastXfUsed == " << i << ")\n"; + else + os << + " else if (secondPoint.m_LastXfUsed == " << i << ")\n"; + + os << + " {\n" << + " Xform" << i << "(&(ember->m_Xforms[" << i << "]), parVars, &firstPoint, &secondPoint, &mwc);\n" << + " }\n"; + } + os << + "\n" + " ok = !BadVal(secondPoint.m_X) && !BadVal(secondPoint.m_Y);\n" + //" ok = !BadVal(secondPoint.m_X) && !BadVal(secondPoint.m_Y) && !BadVal(secondPoint.m_Z);\n" + "\n" + " if (!ok)\n" + " {\n" + " firstPoint.m_X = MwcNextNeg1Pos1(&mwc);\n" + " firstPoint.m_Y = MwcNextNeg1Pos1(&mwc);\n" + " firstPoint.m_Z = 0.0;\n" + " firstPoint.m_ColorX = secondPoint.m_ColorX;\n" + " consec++;\n" + //" badvals++;\n" + " }\n" + " }\n" + " while (!ok && consec < 5);\n" + "\n" + " if (!ok)\n" + " {\n" + " secondPoint.m_X = MwcNextNeg1Pos1(&mwc);\n" + " secondPoint.m_Y = MwcNextNeg1Pos1(&mwc);\n" + " secondPoint.m_Z = 0.0;\n" + " }\n" + "\n"//Rotate points between threads. This is how randomization is achieved. + " uint swr = threadXY + ((i & 1u) * threadXDivRows);\n" + " uint sw = (swr * THREADS_PER_WARP + THREAD_ID_X) & threadsMinus1;\n" + "\n" + + //Write to another thread's location. + " swap[sw] = secondPoint;\n" + "\n" + + //Populate randomized xform index buffer with new random values. + " if (THREAD_ID_Y == 0 && THREAD_ID_X < NWARPS)\n" + " xfsel[THREAD_ID_X] = MwcNext(&mwc) % " << CHOOSE_XFORM_GRAIN << ";\n" + "\n" + " barrier(CLK_LOCAL_MEM_FENCE);\n" + "\n" + + //Another thread will have written to this thread's location, so read the new value and use it for accumulation below. + " firstPoint = swap[threadIndex];\n" + "\n" + " if (fuse)\n" + " {\n" + " if (i >= fuseCount - 1)\n" + " {\n" + " i = 0;\n" + " fuse = false;\n" + " itersToDo = iterCount;\n" + " barrier(CLK_LOCAL_MEM_FENCE);\n"//Sort of seems necessary, sort of doesn't. Makes no speed difference. + " }\n" + "\n" + " continue;\n" + " }\n" + "\n"; + + if (ember.UseFinalXform()) + { + //CPU takes an extra step here to preserve the opacity of the randomly selected xform, rather than the final xform's opacity. + //The same thing takes place here automatically because secondPoint.m_LastXfUsed is used below to retrieve the opacity when accumulating. + os << + " if ((ember->m_Xforms[ember->m_FinalXformIndex].m_Opacity == 1) || (MwcNext01(&mwc) < ember->m_Xforms[ember->m_FinalXformIndex].m_Opacity))\n" + " {\n" + " Xform" << (ember.TotalXformCount() - 1) << "(&(ember->m_Xforms[ember->m_FinalXformIndex]), parVars, &secondPoint, &tempPoint, &mwc);\n" + " secondPoint = tempPoint;\n" + " }\n" + "\n"; + } + + os << CreateProjectionString(ember); + + if (doAccum) + { + os << + " p00 = secondPoint.m_X - ember->m_CenterX;\n" + " p01 = secondPoint.m_Y - ember->m_CenterY;\n" + " tempPoint.m_X = (p00 * ember->m_RotA) + (p01 * ember->m_RotB) + ember->m_CenterX;\n" + " tempPoint.m_Y = (p00 * ember->m_RotD) + (p01 * ember->m_RotE) + ember->m_CenterY;\n" + "\n" + //Add this point to the appropriate location in the histogram. + " if (CarToRasInBounds(carToRas, &tempPoint))\n" + " {\n" + " CarToRasConvertPointToSingle(carToRas, &tempPoint, &histIndex);\n" + "\n" + " if (histIndex < histSize)\n"//Provides an extra level of safety and makes no speed difference. + " {\n"; + + //Basic texture index interoplation does not produce identical results + //to the CPU. So the code here must explicitly do the same thing and not + //rely on the GPU texture coordinate lookup. + if (ember.m_PaletteMode == PALETTE_LINEAR) + { + os << + " real_t colorIndexFrac;\n" + " real_t colorIndex = secondPoint.m_ColorX * COLORMAP_LENGTH;\n" + " int intColorIndex = (int)colorIndex;\n" + " float4 palColor2;\n" + "\n" + " if (intColorIndex < 0)\n" + " {\n" + " intColorIndex = 0;\n" + " colorIndexFrac = 0;\n" + " }\n" + " else if (intColorIndex >= COLORMAP_LENGTH_MINUS_1)\n" + " {\n" + " intColorIndex = COLORMAP_LENGTH_MINUS_1 - 1;\n" + " colorIndexFrac = 1.0;\n" + " }\n" + " else\n" + " {\n" + " colorIndexFrac = colorIndex - (real_t)intColorIndex;\n"//Interpolate between intColorIndex and intColorIndex + 1. + " }\n" + "\n" + " iPaletteCoord.x = intColorIndex;\n"//Palette operations are strictly float because OpenCL does not support dp64 textures. + " palColor1 = read_imagef(palette, paletteSampler, iPaletteCoord);\n" + " iPaletteCoord.x += 1;\n" + " palColor2 = read_imagef(palette, paletteSampler, iPaletteCoord);\n" + " palColor1 = (palColor1 * (1.0f - (float)colorIndexFrac)) + (palColor2 * (float)colorIndexFrac);\n";//The 1.0f here *must* have the 'f' suffix at the end to compile. + } + else if (ember.m_PaletteMode == PALETTE_STEP) + { + os << + " iPaletteCoord.x = (int)(secondPoint.m_ColorX * COLORMAP_LENGTH);\n" + " palColor1 = read_imagef(palette, paletteSampler, iPaletteCoord);\n"; + } + + if (lockAccum) + { + if (typeid(T) == typeid(double)) + { + os << + " AtomicAdd(&(histogram[histIndex].m_Reals[0]), (real_t)palColor1.x * ember->m_Xforms[secondPoint.m_LastXfUsed].m_VizAdjusted);\n"//Always apply opacity, even though it's usually 1. + " AtomicAdd(&(histogram[histIndex].m_Reals[1]), (real_t)palColor1.y * ember->m_Xforms[secondPoint.m_LastXfUsed].m_VizAdjusted);\n" + " AtomicAdd(&(histogram[histIndex].m_Reals[2]), (real_t)palColor1.z * ember->m_Xforms[secondPoint.m_LastXfUsed].m_VizAdjusted);\n" + " AtomicAdd(&(histogram[histIndex].m_Reals[3]), (real_t)palColor1.w * ember->m_Xforms[secondPoint.m_LastXfUsed].m_VizAdjusted);\n"; + } + else + { + os << + " AtomicAdd(&(histogram[histIndex].m_Reals[0]), palColor1.x * ember->m_Xforms[secondPoint.m_LastXfUsed].m_VizAdjusted);\n"//Always apply opacity, even though it's usually 1. + " AtomicAdd(&(histogram[histIndex].m_Reals[1]), palColor1.y * ember->m_Xforms[secondPoint.m_LastXfUsed].m_VizAdjusted);\n" + " AtomicAdd(&(histogram[histIndex].m_Reals[2]), palColor1.z * ember->m_Xforms[secondPoint.m_LastXfUsed].m_VizAdjusted);\n" + " AtomicAdd(&(histogram[histIndex].m_Reals[3]), palColor1.w * ember->m_Xforms[secondPoint.m_LastXfUsed].m_VizAdjusted);\n"; + } + } + else + { + if (typeid(T) == typeid(double)) + { + os << + " real4 realColor;\n" + "\n" + " realColor.x = (real_t)palColor1.x;\n" + " realColor.y = (real_t)palColor1.y;\n" + " realColor.z = (real_t)palColor1.z;\n" + " realColor.w = (real_t)palColor1.w;\n" + " histogram[histIndex].m_Real4 += (realColor * ember->m_Xforms[secondPoint.m_LastXfUsed].m_VizAdjusted);\n"; + } + else + { + os << + " histogram[histIndex].m_Real4 += (palColor1 * ember->m_Xforms[secondPoint.m_LastXfUsed].m_VizAdjusted);\n"; + } + } + + os << + " }\n"//histIndex < histSize. + " }\n"//CarToRasInBounds. + "\n" + " barrier(CLK_GLOBAL_MEM_FENCE);\n";//Barrier every time, whether or not the point was in bounds, else artifacts will occur when doing strips. + } + + os << + " }\n"//Main for loop. + "\n" + //At this point, iterating for this round is done, so write the final points back out + //to the global points buffer to be used as inputs for the next round. This preserves point trajectory + //between kernel calls. + " points[pointsIndex] = firstPoint;\n" + " barrier(CLK_GLOBAL_MEM_FENCE);\n" + "}\n"; + + return os.str(); +} + +/// +/// Create an OpenCL string of #defines and a corresponding host side vector for parametric variation values. +/// Parametric variations present a special problem in the iteration code. +/// The values can't be passed in with the array of other xform values because +/// the length of the parametric values is unknown. +/// This is solved by passing a separate buffer of values dedicated specifically +/// to parametric variations. +/// In OpenCL, a series of #define constants are declared which specify the indices in +/// the buffer where the various values are stored. +/// The possibility of a parametric variation type being present in multiple xforms is taken +/// into account by appending the xform index to the #define, thus making each unique. +/// The kernel creator then uses these to retrieve the values in the iteration code. +/// Example: +/// Xform1: Curl (curl_c1: 1.1, curl_c2: 2.2) +/// Xform2: Curl (curl_c1: 4.4, curl_c2: 5.5) +/// Xform3: Blob (blob_low: 1, blob_high: 2, blob_waves: 3) +/// +/// Host vector to be passed as arg to the iter kernel call: +/// [1.1][2.2][4.4][5.5][1][2][3] +/// +/// #defines in OpenCL to access the buffer: +/// +/// #define CURL_C1_1 0 +/// #define CURL_C2_1 1 +/// #define CURL_C1_2 2 +/// #define CURL_C2_2 3 +/// #define BLOB_LOW_3 4 +/// #define BLOB_HIGH_3 5 +/// #define BLOB_WAVES_ 6 +/// +/// The variations the use these #defines by first looking up the index of the +/// xform they belong to in the parent ember and generating the OpenCL string based on that +/// in their overriden OpenCLString() functions. +/// Template argument expected to be float or double. +/// +/// The ember to create the values from +/// The string,vector pair to store the values in +/// True if the vector should be populated, else false. Default: true. +/// True if the string should be populated, else false. Default: true. +template +void IterOpenCLKernelCreator::ParVarIndexDefines(Ember& ember, pair>& params, bool doVals, bool doString) +{ + unsigned int i, j, k, size = 0, xformCount = ember.TotalXformCount(); + Xform* xform; + ParametricVariation* parVar; + ostringstream os; + + if (doVals) + params.second.clear(); + + for (i = 0; i < xformCount; i++) + { + if (xform = ember.GetTotalXform(i)) + { + unsigned int varCount = xform->TotalVariationCount(); + + for (j = 0; j < varCount; j++) + { + if (parVar = dynamic_cast*>(xform->GetVariation(j))) + { + for (k = 0; k < parVar->ParamCount(); k++) + { + if (doString) + os << "#define " << ToUpper(parVar->Params()[k].Name()) << "_" << i << " " << size << endl;//Uniquely identify this param in this variation in this xform. + + if (doVals) + params.second.push_back(parVar->Params()[k].ParamVal()); + + size++; + } + } + } + } + } + + if (doString) + { + os << "\n"; + params.first = os.str(); + } +} + +/// +/// Determine whether the two embers passed in differ enough +/// to require a rebuild of the iteration code. +/// A rebuild is required if they differ in the following ways: +/// Xform count +/// Final xform presence +/// Xaos presence +/// Palette accumulation mode +/// Xform post affine presence +/// Variation count +/// Variation type +/// Template argument expected to be float or double. +/// +/// The first ember to compare +/// The second ember to compare +/// True if a rebuild is required, else false +template +bool IterOpenCLKernelCreator::IsBuildRequired(Ember& ember1, Ember& ember2) +{ + unsigned int i, j, xformCount = ember1.TotalXformCount(); + + if (xformCount != ember2.TotalXformCount()) + return true; + + if (ember1.UseFinalXform() != ember2.UseFinalXform()) + return true; + + if (ember1.XaosPresent() != ember2.XaosPresent()) + return true; + + if (ember1.m_PaletteMode != ember2.m_PaletteMode) + return true; + + if (ember1.ProjBits() != ember2.ProjBits()) + return true; + + for (i = 0; i < xformCount; i++) + { + Xform* xform1 = ember1.GetTotalXform(i); + Xform* xform2 = ember2.GetTotalXform(i); + unsigned int varCount = xform1->TotalVariationCount(); + + if (xform1->HasPost() != xform2->HasPost()) + return true; + + if (varCount != xform2->TotalVariationCount()) + return true; + + for (j = 0; j < varCount; j++) + if (xform1->GetVariation(j)->VariationId() != xform2->GetVariation(j)->VariationId()) + return true; + } + + return false; +} + +/// +/// Create the zeroize kernel string. +/// OpenCL comes with no way to zeroize a buffer like memset() +/// would do on the CPU. So a special kernel must be ran to set a range +/// of memory addresses to zero. +/// +/// The kernel string +template +string IterOpenCLKernelCreator::CreateZeroizeKernelString() +{ + ostringstream os; + + os << + ConstantDefinesString(typeid(T) == typeid(double)) <= width || GLOBAL_ID_Y >= height)\n" + " return;\n" + "\n" + " buffer[(GLOBAL_ID_Y * width) + GLOBAL_ID_X] = 0;\n"//Can't use INDEX_IN_GRID_2D here because the grid might be larger than the buffer to make even dimensions. + " barrier(CLK_GLOBAL_MEM_FENCE);\n"//Just to be safe. + "}\n" + "\n"; + + return os.str(); +} + +/// +/// Create the string for 3D projection based on the 3D values of the ember. +/// Projection is done on the second point. +/// If any of these fields toggle between 0 and nonzero between runs, a recompile is triggered. +/// +/// The ember to create the projection string for +/// The kernel string +template +string IterOpenCLKernelCreator::CreateProjectionString(Ember& ember) +{ + unsigned int projBits = ember.ProjBits(); + ostringstream os; + + if (projBits) + { + if (projBits & PROJBITS_BLUR) + { + if (projBits & PROJBITS_YAW) + { + os << + " real_t dsin, dcos;\n" + " real_t t = MwcNext01(&mwc) * M_2PI;\n" + " real_t z = secondPoint.m_Z - ember->m_CamZPos;\n" + " real_t x = ember->m_C00 * secondPoint.m_X + ember->m_C10 * secondPoint.m_Y;\n" + " real_t y = ember->m_C01 * secondPoint.m_X + ember->m_C11 * secondPoint.m_Y + ember->m_C21 * z;\n" + "\n" + " z = ember->m_C02 * secondPoint.m_X + ember->m_C12 * secondPoint.m_Y + ember->m_C22 * z;\n" + "\n" + " real_t zr = 1 - ember->m_CamPerspective * z;\n" + " real_t dr = MwcNext01(&mwc) * ember->m_BlurCoef * z;\n" + "\n" + " dsin = sin(t);\n" + " dcos = cos(t);\n" + "\n" + " secondPoint.m_X = (x + dr * dcos) / zr;\n" + " secondPoint.m_Y = (y + dr * dsin) / zr;\n" + " secondPoint.m_Z -= ember->m_CamZPos;\n"; + } + else + { + os << + " real_t y, z, zr;\n" + " real_t dsin, dcos;\n" + " real_t t = MwcNext01(&mwc) * M_2PI;\n" + "\n" + " z = secondPoint.m_Z - ember->m_CamZPos;\n" + " y = ember->m_C11 * secondPoint.m_Y + ember->m_C21 * z;\n" + " z = ember->m_C12 * secondPoint.m_Y + ember->m_C22 * z;\n" + " zr = 1 - ember->m_CamPerspective * z;\n" + "\n" + " dsin = sin(t);\n" + " dcos = cos(t);\n" + "\n" + " real_t dr = MwcNext01(&mwc) * ember->m_BlurCoef * z;\n" + "\n" + " secondPoint.m_X = (secondPoint.m_X + dr * dcos) / zr;\n" + " secondPoint.m_Y = (y + dr * dsin) / zr;\n" + " secondPoint.m_Z -= ember->m_CamZPos;\n"; + } + } + else if ((projBits & PROJBITS_PITCH) || (projBits & PROJBITS_YAW)) + { + if (projBits & PROJBITS_YAW) + { + os << + " real_t z = secondPoint.m_Z - ember->m_CamZPos;\n" + " real_t x = ember->m_C00 * secondPoint.m_X + ember->m_C10 * secondPoint.m_Y;\n" + " real_t y = ember->m_C01 * secondPoint.m_X + ember->m_C11 * secondPoint.m_Y + ember->m_C21 * z;\n" + " real_t zr = 1 - ember->m_CamPerspective * (ember->m_C02 * secondPoint.m_X + ember->m_C12 * secondPoint.m_Y + ember->m_C22 * z);\n" + "\n" + " secondPoint.m_X = x / zr;\n" + " secondPoint.m_Y = y / zr;\n" + " secondPoint.m_Z -= ember->m_CamZPos;\n"; + } + else + { + os << + " real_t z = secondPoint.m_Z - ember->m_CamZPos;\n" + " real_t y = ember->m_C11 * secondPoint.m_Y + ember->m_C21 * z;\n" + " real_t zr = 1 - ember->m_CamPerspective * (ember->m_C12 * secondPoint.m_Y + ember->m_C22 * z);\n" + "\n" + " secondPoint.m_X /= zr;\n" + " secondPoint.m_Y = y / zr;\n" + " secondPoint.m_Z -= ember->m_CamZPos;\n"; + } + } + else + { + os << + " real_t zr = 1 - ember->m_CamPerspective * (secondPoint.m_Z - ember->m_CamZPos);\n" + "\n" + " secondPoint.m_X /= zr;\n" + " secondPoint.m_Y /= zr;\n" + " secondPoint.m_Z -= ember->m_CamZPos;\n"; + } + } + + return os.str(); +} +} \ No newline at end of file diff --git a/Source/EmberCL/IterOpenCLKernelCreator.h b/Source/EmberCL/IterOpenCLKernelCreator.h new file mode 100644 index 0000000..a44ced7 --- /dev/null +++ b/Source/EmberCL/IterOpenCLKernelCreator.h @@ -0,0 +1,89 @@ +#pragma once + +#include "EmberCLPch.h" +#include "EmberCLStructs.h" +#include "EmberCLFunctions.h" + +/// +/// IterOpenCLKernelCreator class. +/// + +namespace EmberCLns +{ +/// +/// Class for creating the main iteration code in OpenCL. +/// It uses the Cuburn method of iterating where all conditionals +/// are stripped out and a specific kernel is compiled at run-time. +/// It uses a very sophisticated method for randomization that avoids +/// the problem of warp/wavefront divergence that would occur if every +/// thread selected a random xform to apply. +/// This only works with embers of type float, double is not supported. +/// +template +class EMBERCL_API IterOpenCLKernelCreator +{ +public: + IterOpenCLKernelCreator(); + IterOpenCLKernelCreator(bool nVidia); + string ZeroizeKernel(); + string ZeroizeEntryPoint(); + string IterEntryPoint(); + string CreateIterKernelString(Ember& ember, string& parVarDefines, bool lockAccum = false, bool doAccum = true); + static void ParVarIndexDefines(Ember& ember, pair>& params, bool doVals = true, bool doString = true); + static bool IsBuildRequired(Ember& ember1, Ember& ember2); + +private: + string CreateZeroizeKernelString(); + string CreateProjectionString(Ember& ember); + + string m_IterEntryPoint; + string m_ZeroizeKernel; + string m_ZeroizeEntryPoint; + bool m_NVidia; +}; + +template EMBERCL_API class IterOpenCLKernelCreator; + +#ifdef DO_DOUBLE + template EMBERCL_API class IterOpenCLKernelCreator; +#endif + +// +//template EMBERCL_API string IterOpenCLKernelCreator::CreateIterKernelString(Ember& ember, string& parVarDefines, bool lockAccum, bool doAccum); +//template EMBERCL_API string IterOpenCLKernelCreator::CreateIterKernelString(Ember& ember, string& parVarDefines, bool lockAccum, bool doAccum); +// +//template EMBERCL_API void IterOpenCLKernelCreator::ParVarIndexDefines(Ember& ember, pair>& params, bool doVals, bool doString); +//template EMBERCL_API void IterOpenCLKernelCreator::ParVarIndexDefines(Ember& ember, pair>& params, bool doVals, bool doString); +// +//template EMBERCL_API bool IterOpenCLKernelCreator::IsBuildRequired(Ember& ember1, Ember& ember2); +//template EMBERCL_API bool IterOpenCLKernelCreator::IsBuildRequired(Ember& ember1, Ember& ember2); + +#ifdef OPEN_CL_TEST_AREA +typedef void (*KernelFuncPointer) (unsigned int gridWidth, unsigned int gridHeight, unsigned int blockWidth, unsigned int blockHeight, + unsigned int BLOCK_ID_X, unsigned int BLOCK_ID_Y, unsigned int THREAD_ID_X, unsigned int THREAD_ID_Y); + +static void OpenCLSim(unsigned int gridWidth, unsigned int gridHeight, unsigned int blockWidth, unsigned int blockHeight, KernelFuncPointer func) +{ + cout << "OpenCLSim(): " << endl; + cout << " Params: " << endl; + cout << " gridW: " << gridWidth << endl; + cout << " gridH: " << gridHeight << endl; + cout << " blockW: " << blockWidth << endl; + cout << " blockH: " << blockHeight << endl; + + for (unsigned int i = 0; i < gridHeight; i += blockHeight) + { + for (unsigned int j = 0; j < gridWidth; j += blockWidth) + { + for (unsigned int k = 0; k < blockHeight; k++) + { + for (unsigned int l = 0; l < blockWidth; l++) + { + func(gridWidth, gridHeight, blockWidth, blockHeight, j / blockWidth, i / blockHeight, l, k); + } + } + } + } +} +#endif +} diff --git a/Source/EmberCL/OpenCLWrapper.cpp b/Source/EmberCL/OpenCLWrapper.cpp new file mode 100644 index 0000000..ebf28dd --- /dev/null +++ b/Source/EmberCL/OpenCLWrapper.cpp @@ -0,0 +1,1366 @@ +#include "EmberCLPch.h" +#include "OpenCLWrapper.h" + +namespace EmberCLns +{ +/// +/// Constructor that sets everything to an uninitialized state. +/// No OpenCL setup is done here, the caller must explicitly do it. +/// +OpenCLWrapper::OpenCLWrapper() +{ + m_Init = false; + m_Shared = false; + m_PlatformIndex = 0; + m_DeviceIndex = 0; + m_LocalMemSize = 0; + cl::Platform::get(&m_Platforms); + m_Devices.resize(m_Platforms.size()); + + for (size_t i = 0; i < m_Platforms.size(); i++) + m_Platforms[i].getDevices(CL_DEVICE_TYPE_ALL, &m_Devices[i]); +} + +/// +/// Determine if OpenCL is available on the system. +/// +/// True if any OpenCL platform and at least 1 device within that platform exists on the system, else false. +bool OpenCLWrapper::CheckOpenCL() +{ + for (size_t i = 0; i < m_Platforms.size(); i++) + for (size_t j = 0; j < m_Devices[i].size(); j++) + return true; + + return false; +} + +/// +/// Initialize the specified platform and device. +/// This can be shared with OpenGL. +/// +/// The index platform of the platform to use +/// The index device of the device to use +/// True if shared with OpenGL, else false. +/// True if success, else false. +bool OpenCLWrapper::Init(unsigned int platform, unsigned int device, bool shared) +{ + cl_int err; + + m_Init = false; + m_ErrorReport.clear(); + + if (m_Platforms.size() > 0) + { + if (platform < m_Platforms.size() && platform < m_Devices.size()) + { + m_PlatformIndex = platform;//Platform is ok, now do context. + + if (CreateContext(shared)) + { + //Context is ok, now do device. + if (device < m_Devices[m_PlatformIndex].size()) + { + //At least one GPU device is present, so create a command queue. + m_Queue = cl::CommandQueue(m_Context, m_Devices[m_PlatformIndex][device], 0, &err); + + if (CheckCL(err, "cl::CommandQueue()")) + { + m_DeviceIndex = device; + m_Platform = m_Platforms[m_PlatformIndex]; + m_Device = m_Devices[m_PlatformIndex][device]; + m_DeviceVec.clear(); + m_DeviceVec.push_back(m_Device); + m_LocalMemSize = (unsigned int)GetInfo(m_PlatformIndex, m_DeviceIndex, CL_DEVICE_LOCAL_MEM_SIZE); + m_Shared = shared; + m_Init = true;//Command queue is ok, it's now ok to begin building and running programs. + } + } + } + } + } + + return m_Init; +} + +/// +/// Compile and add the program, using the specified entry point. +/// If a program with the same name already exists then it will be replaced. +/// +/// The name of the program +/// The program source +/// The name of the entry point kernel function in the program +/// True if success, else false. +bool OpenCLWrapper::AddProgram(std::string name, std::string& program, std::string& entryPoint, bool doublePrecision) +{ + Spk spk; + + if (CreateSPK(name, program, entryPoint, spk, doublePrecision)) + { + for (size_t i = 0; i < m_Programs.size(); i++) + { + if (name == m_Programs[i].m_Name) + { + m_Programs[i] = spk; + return true; + } + } + + //Nothing was found, so add. + m_Programs.push_back(spk); + return true; + } + + return false; +} + +/// +/// Clear the programs. +/// +void OpenCLWrapper::ClearPrograms() +{ + m_Programs.clear(); +} + +/// +/// Add a buffer with the specified size and name. +/// Three possible actions to take: +/// Buffer didn't exist, so create and add. +/// Buffer existed, but was a different size. Replace. +/// Buffer existed with the same size, do nothing. +/// +/// The name of the buffer +/// The size in bytes of the buffer +/// The buffer flags. Default: CL_MEM_READ_WRITE. +/// True if success, else false. +bool OpenCLWrapper::AddBuffer(string name, size_t size, cl_mem_flags flags) +{ + cl_int err; + + if (m_Init) + { + int bufferIndex = FindBufferIndex(name); + + if (bufferIndex == -1)//If the buffer didn't exist, create and add. + { + cl::Buffer buff(m_Context, flags, size, NULL, &err); + + if (!CheckCL(err, "cl::Buffer()")) + return false; + + NamedBuffer nb(buff, name); + + m_Buffers.push_back(nb); + } + else if (GetBufferSize(bufferIndex) != size)//If it did exist, only create and add if the sizes were different. + { + m_Buffers[bufferIndex] = NamedBuffer(cl::Buffer(m_Context, flags, 0, NULL, &err), "emptybuffer"); + + cl::Buffer buff(m_Context, flags, size, NULL, &err); + + if (!CheckCL(err, "cl::Buffer()")) + return false; + + NamedBuffer nb(buff, name); + + m_Buffers[bufferIndex] = nb; + } + //If the buffer existed and the sizes were the same, take no action. + + return true; + } + + return false; +} + +/// +/// Add and/or write a buffer of data with the specified name to the list of buffers. +/// Three possible actions to take: +/// Buffer didn't exist, so create and add. +/// Buffer existed, but was a different size. Replace. +/// Buffer existed with the same size, copy data. +/// +/// The name of the buffer +/// A pointer to the buffer +/// The size in bytes of the buffer +/// True if success, else false. +bool OpenCLWrapper::AddAndWriteBuffer(string name, void* data, size_t size) +{ + cl_int err; + bool b = false; + + if (m_Init) + { + int bufferIndex = FindBufferIndex(name); + + //Easy case: totally new buffer, so just create and add. + if (bufferIndex == -1) + { + cl::Buffer buff(m_Context, CL_MEM_READ_WRITE, size, NULL, &err); + + if (!CheckCL(err, "cl::Buffer()")) + return b; + + NamedBuffer nb(buff, name); + + m_Buffers.push_back(nb); + b = WriteBuffer((unsigned int)m_Buffers.size() - 1, data, size); + } + else//Harder case: the buffer already exists. Replace or overwrite? + { + if (GetBufferSize(bufferIndex) == size)//Size was equal, so just copy data without creating a new buffer. + { + b = WriteBuffer(bufferIndex, data, size); + } + else//Size was not equal, so create entirely new buffer, replace, and copy data. + { + cl::Buffer buff(m_Context, CL_MEM_READ_WRITE, size, NULL, &err); + + if (!CheckCL(err, "cl::Buffer()")) + return b; + + NamedBuffer nb(buff, name); + + m_Buffers[bufferIndex] = nb; + b = WriteBuffer(bufferIndex, data, size); + } + } + } + + return b; +} + +/// +/// Write data to an existing buffer with the specified name. +/// +/// The name of the buffer +/// A pointer to the buffer +/// The size in bytes of the buffer +/// True if success, else false. +bool OpenCLWrapper::WriteBuffer(string name, void* data, size_t size) +{ + int bufferIndex = FindBufferIndex(name); + + return bufferIndex != -1 ? WriteBuffer(bufferIndex, data, size) : false; +} + +/// +/// Write data to an existing buffer at the specified index. +/// +/// The index of the buffer +/// A pointer to the buffer +/// The size in bytes of the buffer +/// True if success, else false. +bool OpenCLWrapper::WriteBuffer(unsigned int bufferIndex, void* data, size_t size) +{ + if (m_Init && (bufferIndex < m_Buffers.size()) && (GetBufferSize(bufferIndex) == size)) + { + cl::Event e; + cl_int err = m_Queue.enqueueWriteBuffer(m_Buffers[bufferIndex].m_Buffer, CL_TRUE, 0, size, data, NULL, &e); + + e.wait(); + m_Queue.finish(); + + if (CheckCL(err, "cl::CommandQueue::enqueueWriteBuffer()")) + return true; + } + + return false; +} + +/// +/// Read data from an existing buffer with the specified name. +/// +/// The name of the buffer +/// A pointer to a buffer to copy the data to +/// The size in bytes of the buffer +/// True if success, else false. +bool OpenCLWrapper::ReadBuffer(string name, void* data, size_t size) +{ + int bufferIndex = FindBufferIndex(name); + + return bufferIndex != -1 ? ReadBuffer(bufferIndex, data, size) : false; +} + +/// +/// Read data from an existing buffer at the specified index. +/// +/// The index of the buffer +/// A pointer to a buffer to copy the data to +/// The size in bytes of the buffer +/// True if success, else false. +bool OpenCLWrapper::ReadBuffer(unsigned int bufferIndex, void* data, size_t size) +{ + if (m_Init && (bufferIndex < m_Buffers.size()) && (GetBufferSize(bufferIndex) == size)) + { + cl::Event e; + cl_int err = m_Queue.enqueueReadBuffer(m_Buffers[bufferIndex].m_Buffer, CL_TRUE, 0, size, data, NULL, &e); + + e.wait(); + m_Queue.finish(); + + if (CheckCL(err, "cl::CommandQueue::enqueueReadBuffer()")) + return true; + } + + return false; +} + +/// +/// Find the index of the buffer with the specified name. +/// +/// The name of the buffer to search for +/// The index if found, else -1. +int OpenCLWrapper::FindBufferIndex(string name) +{ + for (unsigned int i = 0; i < m_Buffers.size(); i++) + if (m_Buffers[i].m_Name == name) + return (int)i; + + return -1; +} + +/// +/// Get the size of the buffer with the specified name. +/// +/// The name of the buffer to search for +/// The size of the buffer if found, else 0. +unsigned int OpenCLWrapper::GetBufferSize(string name) +{ + unsigned int bufferIndex = FindBufferIndex(name); + + return bufferIndex != -1 ? GetBufferSize(bufferIndex) : 0; +} + +/// +/// Get the size of the buffer at the specified index. +/// +/// The index of the buffer to get the size of +/// The size of the buffer if found, else 0. +unsigned int OpenCLWrapper::GetBufferSize(unsigned int bufferIndex) +{ + if (m_Init && bufferIndex < m_Buffers.size()) + return (unsigned int)m_Buffers[bufferIndex].m_Buffer.getInfo(); + + return 0; +} + +/// +/// Clear all buffers. +/// +void OpenCLWrapper::ClearBuffers() +{ + m_Buffers.clear(); +} + +/// +/// Add and/or write a new 2D image. +/// Three possible actions to take: +/// Image didn't exist, so create and add. +/// Image existed, but was a different size. Replace. +/// Image existed with the same size, copy data. +/// +/// The name of the image to add/replace +/// The memory flags +/// The image format +/// The width in pixels of the image +/// The height in pixels of the image +/// The row pitch (usually zero) +/// The image data. Default: NULL. +/// True if shared with an OpenGL texture, else false. Default: false. +/// The texture ID of the shared OpenGL texture if shared. Default: 0. +/// True if success, else false. +bool OpenCLWrapper::AddAndWriteImage(string name, cl_mem_flags flags, const cl::ImageFormat& format, ::size_t width, ::size_t height, ::size_t row_pitch, void* data, bool shared, GLuint texName) +{ + cl_int err; + + if (m_Init) + { + int imageIndex = FindImageIndex(name, shared); + + if (imageIndex == -1)//If the image didn't exist, create and add. + { + if (shared) + { + //::wglMakeCurrent(wglGetCurrentDC(), wglGetCurrentContext()); + IMAGEGL2D imageGL(m_Context, flags, GL_TEXTURE_2D, 0, texName, &err); + NamedImage2DGL namedImageGL(imageGL, name); + + if (CheckCL(err, "cl::ImageGL()")) + { + m_GLImages.push_back(namedImageGL); + + if (data) + return WriteImage2D((unsigned int)m_GLImages.size() - 1, true, width, height, row_pitch, data);//OpenGL images/textures require a separate write. + else + return true; + } + } + else + { + NamedImage2D namedImage(cl::Image2D(m_Context, flags, format, width, height, row_pitch, data, &err), name); + + if (CheckCL(err, "cl::Image2D()")) + { + m_Images.push_back(namedImage); + return true; + } + } + } + else//It did exist, so create new if sizes are different. Write if data is not NULL. + { + if (shared) + { + IMAGEGL2D imageGL = m_GLImages[imageIndex].m_Image; + + if (!CompareImageParams(imageGL, flags, format, width, height, row_pitch)) + { + NamedImage2DGL namedImageGL(IMAGEGL2D(m_Context, flags, GL_TEXTURE_2D, 0, texName, &err), name);//Sizes are different, so create new. + + if (CheckCL(err, "cl::ImageGL()")) + { + m_GLImages[imageIndex] = namedImageGL; + } + else + return false; + } + + //Write data to new image since OpenGL images/textures require a separate write, must match new size. + if (data) + return WriteImage2D(imageIndex, true, width, height, row_pitch, data); + else + return true; + } + else + { + NamedImage2D namedImage = m_Images[imageIndex]; + + if (!CompareImageParams(namedImage.m_Image, flags, format, width, height, row_pitch)) + { + NamedImage2D namedImage(cl::Image2D(m_Context, flags, format, width, height, row_pitch, data, &err), name); + + if (CheckCL(err, "cl::Image2D()")) + { + m_Images[imageIndex] = namedImage; + return true; + } + } + else if (data) + return WriteImage2D(imageIndex, false, width, height, row_pitch, data); + else//Strange case: images were same dimensions but no data was passed in, so do nothing. + return true; + } + } + } + + return false; +} + +/// +/// Write data to an existing 2D image at the specified index. +/// +/// The index of the image +/// True if shared with an OpenGL texture, else false. +/// The width in pixels of the image +/// The height in pixels of the image +/// The row pitch (usually zero) +/// The image data +/// True if success, else false. +bool OpenCLWrapper::WriteImage2D(unsigned int index, bool shared, ::size_t width, ::size_t height, ::size_t row_pitch, void* data) +{ + if (m_Init) + { + cl_int err; + cl::Event e; + cl::size_t<3> origin, region; + + origin[0] = 0; + origin[1] = 0; + origin[2] = 0; + + region[0] = width; + region[1] = height; + region[2] = 1; + + if (shared && index < m_GLImages.size()) + { + IMAGEGL2D imageGL = m_GLImages[index].m_Image; + + if (EnqueueAcquireGLObjects(imageGL)) + { + err = m_Queue.enqueueWriteImage(imageGL, CL_TRUE, origin, region, row_pitch, 0, data, NULL, &e); + e.wait(); + m_Queue.finish(); + + bool b = EnqueueReleaseGLObjects(imageGL); + return CheckCL(err, "cl::enqueueWriteImage()") && b; + } + } + else if (!shared && index < m_Images.size()) + { + err = m_Queue.enqueueWriteImage(m_Images[index].m_Image, CL_TRUE, origin, region, row_pitch, 0, data, NULL, &e); + e.wait(); + m_Queue.finish(); + return CheckCL(err, "cl::enqueueWriteImage()"); + } + } + + return false; +} + +/// +/// Read data from an existing 2D image with the specified name. +/// +/// The name of the image +/// The width in pixels of the image +/// The height in pixels of the image +/// The row pitch (usually zero) +/// True if shared with an OpenGL texture, else false. +/// A pointer to a buffer to copy the data to +/// True if success, else false. +bool OpenCLWrapper::ReadImage(string name, ::size_t width, ::size_t height, ::size_t row_pitch, bool shared, void* data) +{ + if (m_Init) + { + int imageIndex = FindImageIndex(name, shared); + + if (imageIndex != -1) + return ReadImage(imageIndex, width, height, row_pitch, shared, data); + } + + return false; +} + +/// +/// Read data from an existing 2D image at the specified index. +/// +/// The name of the image +/// The width in pixels of the image +/// The height in pixels of the image +/// The row pitch (usually zero) +/// True if shared with an OpenGL texture, else false. +/// A pointer to a buffer to copy the data to +/// True if success, else false. +bool OpenCLWrapper::ReadImage(unsigned int imageIndex, ::size_t width, ::size_t height, ::size_t row_pitch, bool shared, void* data) +{ + if (m_Init) + { + cl_int err; + cl::Event e; + cl::size_t<3> origin, region; + + origin[0] = 0; + origin[1] = 0; + origin[2] = 0; + + region[0] = width; + region[1] = height; + region[2] = 1; + + if (shared && imageIndex < m_GLImages.size()) + { + IMAGEGL2D imageGL = m_GLImages[imageIndex].m_Image; + + if (EnqueueAcquireGLObjects(imageGL)) + { + err = m_Queue.enqueueReadImage(m_GLImages[imageIndex].m_Image, true, origin, region, row_pitch, 0, data); + bool b = EnqueueReleaseGLObjects(m_GLImages[imageIndex].m_Image); + return CheckCL(err, "cl::enqueueReadImage()") && b; + } + } + else if (!shared && imageIndex < m_Images.size()) + { + err = m_Queue.enqueueReadImage(m_Images[imageIndex].m_Image, true, origin, region, row_pitch, 0, data); + return CheckCL(err, "cl::enqueueReadImage()"); + } + } + + return false; +} + +/// +/// Find the index of the 2D image with the specified name. +/// +/// The name of the image to search for +/// True if shared with an OpenGL texture, else false. +/// The index if found, else -1. +int OpenCLWrapper::FindImageIndex(string name, bool shared) +{ + if (shared) + { + for (unsigned int i = 0; i < m_GLImages.size(); i++) + if (m_GLImages[i].m_Name == name) + return i; + } + else + { + for (unsigned int i = 0; i < m_Images.size(); i++) + if (m_Images[i].m_Name == name) + return i; + } + + return -1; +} + +/// +/// Get the size of the 2D image with the specified name. +/// +/// The name of the image to search for +/// True if shared with an OpenGL texture, else false. +/// The size of the 2D image if found, else 0. +unsigned int OpenCLWrapper::GetImageSize(string name, bool shared) +{ + int imageIndex = FindImageIndex(name, shared); + return GetImageSize(imageIndex, shared); +} + +/// +/// Get the size of the 2D image at the specified index. +/// +/// Index of the image to search for +/// True if shared with an OpenGL texture, else false. +/// The size of the 2D image if found, else 0. +unsigned int OpenCLWrapper::GetImageSize(unsigned int imageIndex, bool shared) +{ + size_t size = 0; + + if (m_Init) + { + if (shared && imageIndex < m_GLImages.size()) + { + vector images; + images.push_back(m_GLImages[imageIndex].m_Image); + IMAGEGL2D image = m_GLImages[imageIndex].m_Image; + + if (EnqueueAcquireGLObjects(&images)) + size = image.getImageInfo() * image.getImageInfo() * image.getImageInfo();//Should pitch be checked here? + + EnqueueReleaseGLObjects(&images); + } + else if (!shared && imageIndex < m_Images.size()) + { + cl::Image2D image = m_Images[imageIndex].m_Image; + size = image.getImageInfo() * image.getImageInfo() * image.getImageInfo();//Should pitch be checked here? + } + } + + return (unsigned int)size; +} + +/// +/// Compare the passed in image with the specified parameters. +/// +/// The image to compare +/// The memory flags to compare (ommitted) +/// The format to compare +/// The width to compare +/// The height to compare +/// The row_pitch to compare (omitted) +/// True if all parameters matched, else false. +bool OpenCLWrapper::CompareImageParams(cl::Image& image, cl_mem_flags flags, const cl::ImageFormat& format, ::size_t width, ::size_t height, ::size_t row_pitch) +{ + cl_image_format tempFormat = image.getImageInfo(); + + return (/*image.getImageInfo() == flags &&*/ + tempFormat.image_channel_data_type == format.image_channel_data_type && + tempFormat.image_channel_order == format.image_channel_order && + image.getImageInfo() == width && + image.getImageInfo() == height/* && + image.getImageInfo() == row_pitch*/);//Pitch will be (width * bytes per pixel) + padding. +} + +/// +/// Clear all images. +/// +/// True to clear shared images, else clear regular images. +void OpenCLWrapper::ClearImages(bool shared) +{ + if (shared) + m_GLImages.clear(); + else + m_Images.clear(); +} + +/// +/// Create a 2D image and store in the image passed in. +/// +/// The 2D image to store the newly created image in +/// The memory flags to use +/// The format to use +/// The width in pixels of the image +/// The height in pixels of the image +/// The row pitch (usually zero) +/// The image data. Default: NULL. +/// True if success, else false. +bool OpenCLWrapper::CreateImage2D(cl::Image2D& image2D, cl_mem_flags flags, cl::ImageFormat format, ::size_t width, ::size_t height, ::size_t row_pitch, void* data) +{ + if (m_Init) + { + cl_int err; + + image2D = cl::Image2D(m_Context, + flags, + format, + width, + height, + row_pitch, + data, + &err); + + return CheckCL(err, "cl::Image2D()"); + } + + return false; +} + +/// +/// Create a 2D image shared with an OpenGL texture and store in the image passed in. +/// +/// The 2D image to store the newly created image in +/// The memory flags to use +/// The target +/// The mip map level +/// The texture ID of the shared OpenGL texture +/// True if success, else false. +bool OpenCLWrapper::CreateImage2DGL(IMAGEGL2D& image2DGL, cl_mem_flags flags, GLenum target, GLint miplevel, GLuint texobj) +{ + if (m_Init) + { + cl_int err; + + image2DGL = IMAGEGL2D(m_Context, + flags, + target, + miplevel, + texobj, + &err); + + return CheckCL(err, "cl::ImageGL()"); + } + + return false; +} + +/// +/// Acquire the shared 2D image with the specified name. +/// +/// The name of the image to acquire +/// True if success, else false. +bool OpenCLWrapper::EnqueueAcquireGLObjects(string name) +{ + int index = FindImageIndex(name, true); + + if (index != -1) + return EnqueueAcquireGLObjects(m_GLImages[index].m_Image); + + return false; +} + +/// +/// Acquire the shared 2D image. +/// +/// The image to acquire +/// True if success, else false. +bool OpenCLWrapper::EnqueueAcquireGLObjects(IMAGEGL2D& image) +{ + if (m_Init && m_Shared) + { + vector images; + + images.push_back(image); + cl_int err = m_Queue.enqueueAcquireGLObjects(&images); + m_Queue.finish(); + return CheckCL(err, "cl::CommandQueue::enqueueAcquireGLObjects()"); + } + + return false; +} + +/// +/// Reelease the shared 2D image with the specified name. +/// +/// The name of the image to release +/// True if success, else false. +bool OpenCLWrapper::EnqueueReleaseGLObjects(string name) +{ + int index = FindImageIndex(name, true); + + if (index != -1) + return EnqueueReleaseGLObjects(m_GLImages[index].m_Image); + + return false; +} + +/// +/// Release the shared 2D image. +/// +/// The image to release +/// True if success, else false. +bool OpenCLWrapper::EnqueueReleaseGLObjects(IMAGEGL2D& image) +{ + if (m_Init && m_Shared) + { + vector images; + + images.push_back(image); + cl_int err = m_Queue.enqueueReleaseGLObjects(&images); + m_Queue.finish(); + return CheckCL(err, "cl::CommandQueue::enqueueReleaseGLObjects()"); + } + + return false; +} + +/// +/// Acquire a vector of shared OpenGL memory objects. +/// +/// The memory objects to acquire +/// True if success, else false. +bool OpenCLWrapper::EnqueueAcquireGLObjects(const VECTOR_CLASS* memObjects) +{ + if (m_Init && m_Shared) + { + cl_int err = m_Queue.enqueueAcquireGLObjects(memObjects); + + m_Queue.finish(); + return CheckCL(err, "cl::CommandQueue::enqueueAcquireGLObjects()"); + } + + return false; +} + +/// +/// Release a vector of shared OpenGL memory objects. +/// +/// The memory objects to release +/// True if success, else false. +bool OpenCLWrapper::EnqueueReleaseGLObjects(const VECTOR_CLASS* memObjects) +{ + if (m_Init && m_Shared) + { + cl_int err = m_Queue.enqueueReleaseGLObjects(memObjects); + + m_Queue.finish(); + return CheckCL(err, "cl::CommandQueue::enqueueReleaseGLObjects()"); + } + + return false; +} + +/// +/// Create a texture sampler. +/// +/// The sampler to store the newly created sampler in +/// True to use normalized coordinates, else don't. +/// The addressing mode to use +/// The filter mode to use +/// True if success, else false. +bool OpenCLWrapper::CreateSampler(cl::Sampler& sampler, cl_bool normalizedCoords, cl_addressing_mode addressingMode, cl_filter_mode filterMode) +{ + cl_int err; + + sampler = cl::Sampler(m_Context, + normalizedCoords, + addressingMode, + filterMode, + &err); + + return CheckCL(err, "cl::Sampler()"); +} + +/// +/// Set the argument at the specified index for the kernel at the specified index to be +/// the buffer with the specified name. +/// +/// Index of the kernel +/// Index of the argument +/// The name of the buffer +/// True if success, else false. +bool OpenCLWrapper::SetBufferArg(unsigned int kernelIndex, unsigned int argIndex, string name) +{ + int bufferIndex = OpenCLWrapper::FindBufferIndex(name); + + return bufferIndex != -1 ? SetBufferArg(kernelIndex, argIndex, bufferIndex) : false; +} + +/// +/// Set the argument at the specified index for the kernel at the specified index to be +/// the buffer at the specified index. +/// +/// Index of the kernel +/// Index of the argument +/// Index of the buffer +/// True if success, else false. +bool OpenCLWrapper::SetBufferArg(unsigned int kernelIndex, unsigned int argIndex, unsigned int bufferIndex) +{ + if (m_Init && bufferIndex < m_Buffers.size()) + return SetArg(kernelIndex, argIndex, m_Buffers[bufferIndex].m_Buffer); + + return false; +} + +/// +/// Set the argument at the specified index for the kernel at the specified index to be +/// the 2D image with the specified name. +/// +/// Index of the kernel +/// Index of the argument +/// True if shared with an OpenGL texture, else false +/// The name of the 2D image +/// True if success, else false. +bool OpenCLWrapper::SetImageArg(unsigned int kernelIndex, unsigned int argIndex, bool shared, string name) +{ + if (m_Init) + { + int imageIndex = FindImageIndex(name, shared); + return SetImageArg(kernelIndex, argIndex, shared, imageIndex); + } + + return false; +} + +/// +/// Set the argument at the specified index for the kernel at the specified index to be +/// the 2D image at the specified index. +/// +/// Index of the kernel +/// Index of the argument +/// True if shared with an OpenGL texture, else false +/// Index of the 2D image +/// True if success, else false. +bool OpenCLWrapper::SetImageArg(unsigned int kernelIndex, unsigned int argIndex, bool shared, unsigned int imageIndex) +{ + cl_int err; + + if (m_Init) + { + if (shared && imageIndex < m_GLImages.size()) + { + err = m_Programs[kernelIndex].m_Kernel.setArg(argIndex, m_GLImages[imageIndex].m_Image); + return CheckCL(err, "cl::Kernel::setArg()"); + } + else if (!shared && imageIndex < m_Images.size()) + { + err = m_Programs[kernelIndex].m_Kernel.setArg(argIndex, m_Images[imageIndex].m_Image); + return CheckCL(err, "cl::Kernel::setArg()"); + } + } + + return false; +} + +/// +/// Find the index of the kernel with the specified name. +/// +/// The name of the kernel to search for +/// The index if found, else -1. +int OpenCLWrapper::FindKernelIndex(string name) +{ + for (unsigned int i = 0; i < m_Programs.size(); i++) + if (m_Programs[i].m_Name == name) + return (int)i; + + return -1; +} + +/// +/// Run the kernel at the specified index, using the specified grid and block dimensions. +/// +/// Index of the kernel to run +/// Total width of the grid +/// Total height of the grid +/// The total depth grid +/// Width of each block +/// Height of each block +/// Depth of each block +/// True if success, else false. +bool OpenCLWrapper::RunKernel(unsigned int kernelIndex, unsigned int totalGridWidth, unsigned int totalGridHeight, unsigned int totalGridDepth, + unsigned int blockWidth, unsigned int blockHeight, unsigned int blockDepth) +{ + if (m_Init && kernelIndex < m_Programs.size()) + { + cl::Event e; + cl_int err = m_Queue.enqueueNDRangeKernel(m_Programs[kernelIndex].m_Kernel, + cl::NullRange, + cl::NDRange(totalGridWidth, totalGridHeight, totalGridDepth), + cl::NDRange(blockWidth, blockHeight, blockDepth), + NULL, + &e); + + e.wait(); + m_Queue.finish(); + return CheckCL(err, "cl::CommandQueue::enqueueNDRangeKernel()"); + } + + return false; +} + +/// +/// Get device information for the specified field. +/// Template argument expected to be cl_ulong, cl_uint or cl_int; +/// +/// The device field/feature to query +/// The value of the field +template +T OpenCLWrapper::GetInfo(size_t platform, size_t device, cl_device_info name) +{ + T val; + + if (platform < m_Devices.size() && device < m_Devices[platform].size()) + m_Devices[platform][device].getInfo(name, &val); + + return val; +} + +/// +/// Get the platform name at the specified index. +/// +/// The platform index to get the name of +/// The platform name if found, else empty string +string OpenCLWrapper::PlatformName(size_t platform) +{ + if (platform < m_Platforms.size()) + return m_Platforms[platform].getInfo() + " " + m_Platforms[platform].getInfo() + " " + m_Platforms[platform].getInfo(); + else + return ""; +} + +/// +/// Get all available platform names on the system as a vector of strings. +/// +/// All available platform names on the system as a vector of strings +vector OpenCLWrapper::PlatformNames() +{ + vector platforms; + + platforms.reserve(m_Platforms.size()); + + for (unsigned int i = 0; i < m_Platforms.size(); i++) + platforms.push_back(PlatformName(i)); + + return platforms; +} + +/// +/// Get the device name at the specified index on the platform +/// at the specified index. +/// +/// The platform index of the device +/// The device index +/// The name of the device if found, else empty string +string OpenCLWrapper::DeviceName(size_t platform, size_t device) +{ + string s; + + if (platform < m_Platforms.size() && platform < m_Devices.size()) + if (device < m_Devices[platform].size()) + s = m_Devices[platform][device].getInfo() + " " + m_Devices[platform][device].getInfo();// + " " + m_Devices[platform][device].getInfo(); + + return s; +} + +/// +/// Get all available device names on the platform at the specified index as a vector of strings. +/// +/// The platform index of the devices to query +/// All available device names on the platform at the specified index as a vector of strings +vector OpenCLWrapper::DeviceNames(size_t platform) +{ + unsigned int i = 0; + string s; + vector devices; + + do + { + s = DeviceName(platform, i); + + if (s != "") + devices.push_back(s); + + i++; + } while (s != ""); + + return devices; +} + +/// +/// Get all availabe device and platform names as one contiguous string. +/// +/// A string with all available device and platform names +string OpenCLWrapper::DeviceAndPlatformNames() +{ + ostringstream os; + vector deviceNames; + + for (size_t platform = 0; platform < m_Platforms.size(); platform++) + { + os << PlatformName(platform) << endl; + + deviceNames = DeviceNames(platform); + + for (size_t device = 0; device < m_Devices[platform].size(); device++) + os << "\t" << deviceNames[device] << endl; + } + + return os.str(); +} + +/// +/// Get all information about the currently used device. +/// +/// A string with all information about the currently used device +string OpenCLWrapper::DumpInfo() +{ + ostringstream os; + vector sizes; + + os.imbue(std::locale("")); + + for (size_t platform = 0; platform < m_Platforms.size(); platform++) + { + os << "Platform " << platform << ": " << PlatformName(platform) << endl; + + for (size_t device = 0; device < m_Devices[platform].size(); device++) + { + os << "Device " << device << ": " << DeviceName(platform, device) << endl; + os << "CL_DEVICE_OPENCL_C_VERSION: " << GetInfo (platform, device, CL_DEVICE_OPENCL_C_VERSION) << endl; + os << "CL_DEVICE_LOCAL_MEM_SIZE: " << GetInfo(platform, device, CL_DEVICE_LOCAL_MEM_SIZE) << endl; + os << "CL_DEVICE_LOCAL_MEM_TYPE: " << GetInfo (platform, device, CL_DEVICE_LOCAL_MEM_TYPE) << endl; + os << "CL_DEVICE_MAX_COMPUTE_UNITS: " << GetInfo (platform, device, CL_DEVICE_MAX_COMPUTE_UNITS) << endl; + os << "CL_DEVICE_MAX_READ_IMAGE_ARGS: " << GetInfo (platform, device, CL_DEVICE_MAX_READ_IMAGE_ARGS) << endl; + os << "CL_DEVICE_MAX_WRITE_IMAGE_ARGS: " << GetInfo (platform, device, CL_DEVICE_MAX_WRITE_IMAGE_ARGS) << endl; + os << "CL_DEVICE_MAX_MEM_ALLOC_SIZE: " << GetInfo(platform, device, CL_DEVICE_MAX_MEM_ALLOC_SIZE) << endl; + + os << "CL_DEVICE_GLOBAL_MEM_CACHE_TYPE: " << GetInfo (platform, device, CL_DEVICE_GLOBAL_MEM_CACHE_TYPE) << endl; + os << "CL_DEVICE_GLOBAL_MEM_CACHELINE_SIZE: " << GetInfo (platform, device, CL_DEVICE_GLOBAL_MEM_CACHELINE_SIZE) << endl; + os << "CL_DEVICE_GLOBAL_MEM_CACHE_SIZE: " << GetInfo(platform, device, CL_DEVICE_GLOBAL_MEM_CACHE_SIZE) << endl; + os << "CL_DEVICE_GLOBAL_MEM_SIZE: " << GetInfo(platform, device, CL_DEVICE_GLOBAL_MEM_SIZE) << endl; + os << "CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE: " << GetInfo(platform, device, CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE) << endl; + + os << "CL_DEVICE_MAX_CONSTANT_ARGS: " << GetInfo (platform, device, CL_DEVICE_MAX_CONSTANT_ARGS) << endl; + os << "CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS: " << GetInfo (platform, device, CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS) << endl; + os << "CL_DEVICE_MAX_WORK_GROUP_SIZE: " << GetInfo<::size_t>(platform, device, CL_DEVICE_MAX_WORK_GROUP_SIZE) << endl; + + sizes = GetInfo>(platform, device, CL_DEVICE_MAX_WORK_ITEM_SIZES); + os << "CL_DEVICE_MAX_WORK_ITEM_SIZES: " << sizes[0] << ", " << sizes[1] << ", " << sizes[2] << endl << endl; + + if (device != m_Devices[platform].size() - 1 && platform != m_Platforms.size() - 1) + os << endl; + } + + os << endl; + } + + return os.str(); +} + +/// +/// OpenCL properties, getters only. +/// +bool OpenCLWrapper::Ok() { return m_Init; } +bool OpenCLWrapper::Shared() { return m_Shared; } +cl::Context OpenCLWrapper::Context() { return m_Context; } +unsigned int OpenCLWrapper::PlatformIndex() { return m_PlatformIndex; } +unsigned int OpenCLWrapper::DeviceIndex() { return m_DeviceIndex; } +unsigned int OpenCLWrapper::LocalMemSize() { return m_LocalMemSize; } + +/// +/// Makes the even grid dims. +/// +/// The block w. +/// The block h. +/// The grid w. +/// The grid h. +void OpenCLWrapper::MakeEvenGridDims(unsigned int blockW, unsigned int blockH, unsigned int& gridW, unsigned int& gridH) +{ + if (gridW % blockW != 0) + gridW += (blockW - (gridW % blockW)); + + if (gridH % blockH != 0) + gridH += (blockH - (gridH % blockH)); +} + +/// +/// Create a context that is optionall shared with OpenGL. +/// +/// True if shared with OpenGL, else not shared. +/// True if success, else false. +bool OpenCLWrapper::CreateContext(bool shared) +{ + cl_int err; + + if (shared) + { + //Define OS-specific context properties and create the OpenCL context. + #if defined (__APPLE__) || defined(MACOSX) + CGLContextObj kCGLContext = CGLGetCurrentContext(); + CGLShareGroupObj kCGLShareGroup = CGLGetShareGroup(kCGLContext); + cl_context_properties props[] = + { + CL_CONTEXT_PROPERTY_USE_CGL_SHAREGROUP_APPLE, (cl_context_properties)kCGLShareGroup, + 0 + }; + + m_Context = cl::Context(CL_DEVICE_TYPE_GPU, props, NULL, NULL, &err);//May need to tinker with this on Mac. + #else + #if defined WIN32 + cl_context_properties props[] = + { + CL_GL_CONTEXT_KHR, (cl_context_properties)wglGetCurrentContext(), + CL_WGL_HDC_KHR, (cl_context_properties)wglGetCurrentDC(), + CL_CONTEXT_PLATFORM, (cl_context_properties)(m_Platforms[m_PlatformIndex])(), + 0 + }; + + m_Context = cl::Context(CL_DEVICE_TYPE_GPU, props, NULL, NULL, &err); + #else + cl_context_properties props[] = + { + CL_GL_CONTEXT_KHR, (cl_context_properties)glXGetCurrentContext(), + CL_GLX_DISPLAY_KHR, (cl_context_properties)glXGetCurrentDisplay(), + CL_CONTEXT_PLATFORM, (cl_context_properties)(m_Platforms[m_Platform])(), + 0 + }; + + m_Context = cl::Context(CL_DEVICE_TYPE_GPU, props, NULL, NULL, &err); + #endif + #endif + } + else + { + cl_context_properties props[3] = + { + CL_CONTEXT_PLATFORM, + (cl_context_properties)(m_Platforms[m_PlatformIndex])(), + 0 + }; + + m_Context = cl::Context(CL_DEVICE_TYPE_ALL, props, NULL, NULL, &err); + } + + return CheckCL(err, "cl::Context()"); +} + +/// +/// Create an Spk object created by compiling the program arguments passed in. +/// +/// The name of the program +/// The source of the program +/// The name of the entry point kernel function in the program +/// The Spk object to store the resulting compiled program in +/// True if success, else false. +bool OpenCLWrapper::CreateSPK(std::string& name, std::string& program, std::string& entryPoint, Spk& spk, bool doublePrecision) +{ + if (m_Init) + { + cl_int err; + + spk.m_Name = name; + spk.m_Source = cl::Program::Sources(1, std::make_pair(program.c_str(), program.length() + 1)); + spk.m_Program = cl::Program(m_Context, spk.m_Source); + + if (doublePrecision) + err = spk.m_Program.build(m_DeviceVec, "-cl-mad-enable");//Tinker with other options later. + else + err = spk.m_Program.build(m_DeviceVec, "-cl-mad-enable -cl-no-signed-zeros -cl-single-precision-constant"); + //err = spk.m_Program.build(m_DeviceVec, "-cl-mad-enable -cl-no-signed-zeros -cl-fast-relaxed-math -cl-single-precision-constant");//This can cause some rounding. + //err = spk.m_Program.build(m_DeviceVec, "-cl-mad-enable -cl-single-precision-constant"); + + if (CheckCL(err, "cl::Program::build()")) + { + //Building of program is ok, now create kernel with the specified entry point. + spk.m_Kernel = cl::Kernel(spk.m_Program, entryPoint.c_str(), &err); + + if (CheckCL(err, "cl::Kernel()")) + return true;//Everything is ok. + } + } + + return false; +} + +/// +/// Check an OpenCL return value for errors. +/// +/// The error code to inspect +/// A description of where the value was gotten from +/// True if success, else false. +bool OpenCLWrapper::CheckCL(cl_int err, const char* name) +{ + if (err != CL_SUCCESS) + { + ostringstream ss; + ss << "ERROR: " << ErrorToStringCL(err) << " in " << name << "." << std::endl; + m_ErrorReport.push_back(ss.str()); + } + + return err == CL_SUCCESS; +} + +/// +/// Translate an OpenCL error code into a human readable string. +/// +/// The error code to translate +/// A human readable description of the error passed in +std::string OpenCLWrapper::ErrorToStringCL(cl_int err) +{ + switch (err) + { + case CL_SUCCESS: return "Success"; + case CL_DEVICE_NOT_FOUND: return "Device not found"; + case CL_DEVICE_NOT_AVAILABLE: return "Device not available"; + case CL_COMPILER_NOT_AVAILABLE: return "Compiler not available"; + case CL_MEM_OBJECT_ALLOCATION_FAILURE: return "Memory object allocation failure"; + case CL_OUT_OF_RESOURCES: return "Out of resources"; + case CL_OUT_OF_HOST_MEMORY: return "Out of host memory"; + case CL_PROFILING_INFO_NOT_AVAILABLE: return "Profiling information not available"; + case CL_MEM_COPY_OVERLAP: return "Memory copy overlap"; + case CL_IMAGE_FORMAT_MISMATCH: return "Image format mismatch"; + case CL_IMAGE_FORMAT_NOT_SUPPORTED: return "Image format not supported"; + case CL_BUILD_PROGRAM_FAILURE: return "Program build failure"; + case CL_MAP_FAILURE: return "Map failure"; + case CL_MISALIGNED_SUB_BUFFER_OFFSET: return "Misaligned sub buffer offset"; + case CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST: return "Exec status error for events in wait list"; + case CL_INVALID_VALUE: return "Invalid value"; + case CL_INVALID_DEVICE_TYPE: return "Invalid device type"; + case CL_INVALID_PLATFORM: return "Invalid platform"; + case CL_INVALID_DEVICE: return "Invalid device"; + case CL_INVALID_CONTEXT: return "Invalid context"; + case CL_INVALID_QUEUE_PROPERTIES: return "Invalid queue properties"; + case CL_INVALID_COMMAND_QUEUE: return "Invalid command queue"; + case CL_INVALID_HOST_PTR: return "Invalid host pointer"; + case CL_INVALID_MEM_OBJECT: return "Invalid memory object"; + case CL_INVALID_IMAGE_FORMAT_DESCRIPTOR: return "Invalid image format descriptor"; + case CL_INVALID_IMAGE_SIZE: return "Invalid image size"; + case CL_INVALID_SAMPLER: return "Invalid sampler"; + case CL_INVALID_BINARY: return "Invalid binary"; + case CL_INVALID_BUILD_OPTIONS: return "Invalid build options"; + case CL_INVALID_PROGRAM: return "Invalid program"; + case CL_INVALID_PROGRAM_EXECUTABLE: return "Invalid program executable"; + case CL_INVALID_KERNEL_NAME: return "Invalid kernel name"; + case CL_INVALID_KERNEL_DEFINITION: return "Invalid kernel definition"; + case CL_INVALID_KERNEL: return "Invalid kernel"; + case CL_INVALID_ARG_INDEX: return "Invalid argument index"; + case CL_INVALID_ARG_VALUE: return "Invalid argument value"; + case CL_INVALID_ARG_SIZE: return "Invalid argument size"; + case CL_INVALID_KERNEL_ARGS: return "Invalid kernel arguments"; + case CL_INVALID_WORK_DIMENSION: return "Invalid work dimension"; + case CL_INVALID_WORK_GROUP_SIZE: return "Invalid work group size"; + case CL_INVALID_WORK_ITEM_SIZE: return "Invalid work item size"; + case CL_INVALID_GLOBAL_OFFSET: return "Invalid global offset"; + case CL_INVALID_EVENT_WAIT_LIST: return "Invalid event wait list"; + case CL_INVALID_EVENT: return "Invalid event"; + case CL_INVALID_OPERATION: return "Invalid operation"; + case CL_INVALID_GL_OBJECT: return "Invalid OpenGL object"; + case CL_INVALID_BUFFER_SIZE: return "Invalid buffer size"; + case CL_INVALID_MIP_LEVEL: return "Invalid mip-map level"; + case CL_INVALID_GLOBAL_WORK_SIZE: return "Invalid global work size"; + case CL_INVALID_PROPERTY: return "Invalid property"; + default: + { + ostringstream ss; + ss << " " << err; + return ss.str(); + } + } +} +} \ No newline at end of file diff --git a/Source/EmberCL/OpenCLWrapper.h b/Source/EmberCL/OpenCLWrapper.h new file mode 100644 index 0000000..4fd438e --- /dev/null +++ b/Source/EmberCL/OpenCLWrapper.h @@ -0,0 +1,219 @@ +#pragma once + +#include "EmberCLPch.h" + +/// +/// OpenCLWrapper, Spk, NamedBuffer, NamedImage2D, NamedImage2DGL classes. +/// + +namespace EmberCLns +{ +#if CL_VERSION_1_2 +#define IMAGEGL2D cl::ImageGL +#else +#define IMAGEGL2D cl::Image2DGL +#endif + +/// +/// Class to contain all of the things needed to store an OpenCL program. +/// The name of it, the source, the compiled program object and the kernel. +/// +class EMBERCL_API Spk +{ +public: + string m_Name; + cl::Program::Sources m_Source; + cl::Program m_Program; + cl::Kernel m_Kernel; +}; + +/// +/// Class to hold an OpenCL buffer with a name to identify it by. +/// +class EMBERCL_API NamedBuffer +{ +public: + NamedBuffer() + { + } + + NamedBuffer(cl::Buffer& buff, string name) + { + m_Buffer = buff; + m_Name = name; + } + + cl::Buffer m_Buffer; + string m_Name; +}; + +/// +/// Class to hold a 2D image with a name to identify it by. +/// +class EMBERCL_API NamedImage2D +{ +public: + NamedImage2D() + { + } + + NamedImage2D(cl::Image2D& image, string name) + { + m_Image = image; + m_Name = name; + } + + cl::Image2D m_Image; + string m_Name; +}; + +/// +/// Class to hold a 2D image that is mapped to an OpenGL texture +/// and a name to identify it by. +/// +class EMBERCL_API NamedImage2DGL +{ +public: + NamedImage2DGL() + { + } + + NamedImage2DGL(IMAGEGL2D& image, string name) + { + m_Image = image; + m_Name = name; + } + + IMAGEGL2D m_Image; + string m_Name; +}; + +/// +/// Running kernels in OpenCL can require quite a bit of setup, tear down and +/// general housekeeping. This class helps shield the user from such hassles. +/// It's main utility is in holding collections of programs, buffers and images +/// all identified by names. That way, a user can access them as needed without +/// having to pollute their code. +/// In addition, writing to an existing object by name determines if the object +/// can be overwritten, or if it needs to be deleted and replaced by the new one. +/// This class derives from EmberReport, so the caller is able +/// to retrieve a text dump of error information if any errors occur. +/// +class EMBERCL_API OpenCLWrapper : public EmberReport +{ +public: + OpenCLWrapper(); + bool CheckOpenCL(); + bool Init(unsigned int platform, unsigned int device, bool shared = false); + + //Programs. + bool AddProgram(std::string name, std::string& program, std::string& entryPoint, bool doublePrecision); + void ClearPrograms(); + + //Buffers. + bool AddBuffer(string name, size_t size, cl_mem_flags flags = CL_MEM_READ_WRITE); + bool AddAndWriteBuffer(string name, void* data, size_t size); + bool WriteBuffer(string name, void* data, size_t size); + bool WriteBuffer(unsigned int bufferIndex, void* data, size_t size); + bool ReadBuffer(string name, void* data, size_t size); + bool ReadBuffer(unsigned int bufferIndex, void* data, size_t size); + int FindBufferIndex(string name); + unsigned int GetBufferSize(string name); + unsigned int GetBufferSize(unsigned int bufferIndex); + void ClearBuffers(); + + //Images. + bool AddAndWriteImage(string name, cl_mem_flags flags, const cl::ImageFormat& format, ::size_t width, ::size_t height, ::size_t row_pitch, void* data = NULL, bool shared = false, GLuint texName = 0); + bool WriteImage2D(unsigned int index, bool shared, ::size_t width, ::size_t height, ::size_t row_pitch, void* data); + bool ReadImage(string name, ::size_t width, ::size_t height, ::size_t row_pitch, bool shared, void* data); + bool ReadImage(unsigned int imageIndex, ::size_t width, ::size_t height, ::size_t row_pitch, bool shared, void* data); + int FindImageIndex(string name, bool shared); + unsigned int GetImageSize(string name, bool shared); + unsigned int GetImageSize(unsigned int imageIndex, bool shared); + bool CompareImageParams(cl::Image& image, cl_mem_flags flags, const cl::ImageFormat& format, ::size_t width, ::size_t height, ::size_t row_pitch); + void ClearImages(bool shared); + bool CreateImage2D(cl::Image2D& image2D, cl_mem_flags flags, cl::ImageFormat format, ::size_t width, ::size_t height, ::size_t row_pitch = 0, void* data = NULL); + bool CreateImage2DGL(IMAGEGL2D& image2DGL, cl_mem_flags flags, GLenum target, GLint miplevel, GLuint texobj); + bool EnqueueAcquireGLObjects(string name); + bool EnqueueAcquireGLObjects(IMAGEGL2D& image); + bool EnqueueReleaseGLObjects(string name); + bool EnqueueReleaseGLObjects(IMAGEGL2D& image); + bool EnqueueAcquireGLObjects(const VECTOR_CLASS* memObjects = NULL); + bool EnqueueReleaseGLObjects(const VECTOR_CLASS* memObjects = NULL); + bool CreateSampler(cl::Sampler& sampler, cl_bool normalizedCoords, cl_addressing_mode addressingMode, cl_filter_mode filterMode); + + //Arguments. + bool SetBufferArg(unsigned int kernelIndex, unsigned int argIndex, string name); + bool SetBufferArg(unsigned int kernelIndex, unsigned int argIndex, unsigned int bufferIndex); + bool SetImageArg(unsigned int kernelIndex, unsigned int argIndex, bool shared, string name); + bool SetImageArg(unsigned int kernelIndex, unsigned int argIndex, bool shared, unsigned int imageIndex); + + /// + /// Set an argument in the specified kernel, at the specified argument index. + /// Must keep this here in the .h because it's templated. + /// + /// Index of the kernel whose argument will be set + /// Index of the argument to set + /// The argument value to set + /// True if success, else false + template + bool SetArg(unsigned int kernelIndex, unsigned int argIndex, T arg) + { + if (m_Init && kernelIndex < m_Programs.size()) + { + cl_int err = m_Programs[kernelIndex].m_Kernel.setArg(argIndex, arg); + + return CheckCL(err, "cl::Kernel::setArg()"); + } + + return false; + } + + //Kernels. + int FindKernelIndex(string name); + bool RunKernel(unsigned int kernelIndex, unsigned int totalGridWidth, unsigned int totalGridHeight, unsigned int totalGridDepth, unsigned int blockWidth, unsigned int blockHeight, unsigned int blockDepth); + + //Info. + template + T GetInfo(size_t platform, size_t device, cl_device_info name); + string PlatformName(size_t platform); + vector PlatformNames(); + string DeviceName(size_t platform, size_t device); + vector DeviceNames(size_t platform); + string DeviceAndPlatformNames(); + string DumpInfo(); + + //Accessors. + bool Ok(); + bool Shared(); + cl::Context Context(); + unsigned int PlatformIndex(); + unsigned int DeviceIndex(); + unsigned int LocalMemSize(); + + static void MakeEvenGridDims(unsigned int blockW, unsigned int blockH, unsigned int& gridW, unsigned int& gridH); + +private: + bool CreateContext(bool shared); + bool CreateSPK(std::string& name, std::string& program, std::string& entryPoint, Spk& spk, bool doublePrecision); + bool CheckCL(cl_int err, const char* name); + std::string ErrorToStringCL(cl_int err); + + bool m_Init; + bool m_Shared; + unsigned int m_PlatformIndex; + unsigned int m_DeviceIndex; + unsigned int m_LocalMemSize; + cl::Platform m_Platform; + cl::Context m_Context; + cl::Device m_Device; + cl::CommandQueue m_Queue; + std::vector m_Platforms; + std::vector> m_Devices; + std::vector m_DeviceVec; + std::vector m_Programs; + std::vector m_Buffers; + std::vector m_Images; + std::vector m_GLImages; +}; +} \ No newline at end of file diff --git a/Source/EmberCL/RendererCL.cpp b/Source/EmberCL/RendererCL.cpp new file mode 100644 index 0000000..339e4ab --- /dev/null +++ b/Source/EmberCL/RendererCL.cpp @@ -0,0 +1,1340 @@ +#include "EmberCLPch.h" +#include "RendererCL.h" + +namespace EmberCLns +{ +/// +/// Constructor that inintializes various buffer names, block dimensions, image formats +/// and finally initializes OpenCL using the passed in parameters. +/// +/// The index platform of the platform to use. Default: 0. +/// The index device of the device to use. Default: 0. +/// True if shared with OpenGL, else false. Default: false. +/// The texture ID of the shared OpenGL texture if shared. Default: 0. +template +RendererCL::RendererCL(unsigned int platform, unsigned int device, bool shared, GLuint outputTexID) +{ + m_Init = false; + m_NVidia = false; + m_DoublePrecision = typeid(T) == typeid(double); + m_NumChannels = 4; + m_Calls = 0; + + //Buffer names. + m_EmberBufferName = "Ember"; + m_ParVarsBufferName = "ParVars"; + m_DistBufferName = "Dist"; + m_CarToRasBufferName = "CarToRas"; + m_DEFilterParamsBufferName = "DEFilterParams"; + m_SpatialFilterParamsBufferName = "SpatialFilterParams"; + m_DECoefsBufferName = "DECoefs"; + m_DEWidthsBufferName = "DEWidths"; + m_DECoefIndicesBufferName = "DECoefIndices"; + m_SpatialFilterCoefsBufferName = "SpatialFilterCoefs"; + m_HistBufferName = "Hist"; + m_AccumBufferName = "Accum"; + m_FinalImageName = "Final"; + m_PointsBufferName = "Points"; + + //It's critical that these numbers never change. They are + //based on the cuburn model of each kernel launch containing + //256 threads. 32 wide by 8 high. Everything done in the OpenCL + //iteraion kernel depends on these dimensions. + m_IterBlockWidth = 32; + m_IterBlockHeight = 8; + m_IterBlocksWide = 64; + m_IterBlocksHigh = 2; + + m_PaletteFormat.image_channel_order = CL_RGBA; + m_PaletteFormat.image_channel_data_type = CL_FLOAT; + m_FinalFormat.image_channel_order = CL_RGBA; + m_FinalFormat.image_channel_data_type = CL_UNORM_INT8;//Change if this ever supports 2BPP outputs for PNG. + + Init(platform, device, shared, outputTexID);//Init OpenCL upon construction and create programs that will not change. +} + +/// +/// Virtual destructor. +/// +template +RendererCL::~RendererCL() +{ +} + +/// +/// Ordinary member functions for OpenCL specific tasks. +/// + +/// +/// Initialize OpenCL. +/// In addition to initializing, this function will create the zeroization program, +/// as well as the basic log scale filtering programs. This is done to ensure basic +/// compilation works. Further compilation will be done later for iteration, density filtering, +/// and final accumulation. +/// +/// The index platform of the platform to use +/// The index device of the device to use +/// True if shared with OpenGL, else false. +/// The texture ID of the shared OpenGL texture if shared +/// True if success, else false. +template +bool RendererCL::Init(unsigned int platform, unsigned int device, bool shared, GLuint outputTexID) +{ + //Timing t; + m_OutputTexID = outputTexID; + const char* loc = __FUNCTION__; + + if (!m_Wrapper.Ok() || PlatformIndex() != platform || DeviceIndex() != device) + { + m_Init = false; + m_Wrapper.Init(platform, device, shared); + } + + if (m_Wrapper.Ok() && !m_Init) + { + m_NVidia = ToLower(m_Wrapper.DeviceAndPlatformNames()).find_first_of("nvidia") != string::npos && m_Wrapper.LocalMemSize() > (32 * 1024); + m_WarpSize = m_NVidia ? 32 : 64; + m_IterOpenCLKernelCreator = IterOpenCLKernelCreator(m_NVidia); + m_DEOpenCLKernelCreator = DEOpenCLKernelCreator(m_NVidia); + + string zeroizeProgram = m_IterOpenCLKernelCreator.ZeroizeKernel(); + string logAssignProgram = m_DEOpenCLKernelCreator.LogScaleAssignDEKernel(); + string logSumProgram = m_DEOpenCLKernelCreator.LogScaleSumDEKernel();//Build a couple of simple programs to ensure OpenCL is working right. + + if (!m_Wrapper.AddProgram(m_IterOpenCLKernelCreator.ZeroizeEntryPoint(), zeroizeProgram, m_IterOpenCLKernelCreator.ZeroizeEntryPoint(), m_DoublePrecision)) { m_ErrorReport.push_back(loc); return false; } + if (!m_Wrapper.AddProgram(m_DEOpenCLKernelCreator.LogScaleAssignDEEntryPoint(), logAssignProgram, m_DEOpenCLKernelCreator.LogScaleAssignDEEntryPoint(), m_DoublePrecision)) { m_ErrorReport.push_back(loc); return false; } + if (!m_Wrapper.AddProgram(m_DEOpenCLKernelCreator.LogScaleSumDEEntryPoint(), logSumProgram, m_DEOpenCLKernelCreator.LogScaleSumDEEntryPoint(), m_DoublePrecision)) { m_ErrorReport.push_back(loc); return false; } + + if (!m_Wrapper.AddAndWriteImage("Palette", CL_MEM_READ_ONLY, m_PaletteFormat, 256, 1, 0, NULL)) { m_ErrorReport.push_back(loc); return false; } + + //This is the maximum box dimension for density filtering which consists of (blockSize * blockSize) + (2 * filterWidth). + //These blocks must be square, and ideally, 32x32. + //Sadly, at the moment, Fermi runs out of resources at that block size because the DE filter function is so complex. + //The next best block size seems to be 24x24. + //AMD is further limited because of less local memory so these have to be 16 on AMD. + m_MaxDEBlockSizeW = m_NVidia ? 32 : 16;//These *must* both be divisible by 16 or else pixels will go missing. + m_MaxDEBlockSizeH = m_NVidia ? 32 : 16; + m_Init = true; + //t.Toc(loc); + } + + return m_Init; +} + +/// +/// OpenCL property accessors, getters only. +/// + +template unsigned int RendererCL::IterBlocksWide() { return m_IterBlocksWide; } +template unsigned int RendererCL::IterBlocksHigh() { return m_IterBlocksHigh; } +template unsigned int RendererCL::IterBlockWidth() { return m_IterBlockWidth; } +template unsigned int RendererCL::IterBlockHeight() { return m_IterBlockHeight; } +template unsigned int RendererCL::IterGridWidth() { return IterBlocksWide() * IterBlockWidth(); } +template unsigned int RendererCL::IterGridHeight() { return IterBlocksHigh() * IterBlockHeight(); } +template unsigned int RendererCL::TotalIterKernelCount() { return IterGridWidth() * IterGridHeight(); } +template unsigned int RendererCL::PlatformIndex() { return m_Wrapper.PlatformIndex(); } +template unsigned int RendererCL::DeviceIndex() { return m_Wrapper.DeviceIndex(); } + +/// +/// Read the histogram into the host side CPU buffer. +/// Used for debugging. +/// +/// True if success, else false. +template +bool RendererCL::ReadHist() +{ + if (Renderer::Alloc())//Allocate the memory to read into. + return m_Wrapper.ReadBuffer(m_HistBufferName, (void*)HistBuckets(), SuperSize() * sizeof(v4T)); + + return false; +} + +/// +/// Read the density filtering buffer into the host side CPU buffer. +/// Used for debugging. +/// +/// True if success, else false. +template +bool RendererCL::ReadAccum() +{ + if (Renderer::Alloc())//Allocate the memory to read into. + return m_Wrapper.ReadBuffer(m_AccumBufferName, (void*)AccumulatorBuckets(), SuperSize() * sizeof(v4T)); + + return false; +} + +/// +/// Read the temporary points buffer into a host side CPU buffer. +/// Used for debugging. +/// +/// The host side buffer to read into +/// True if success, else false. +template +bool RendererCL::ReadPoints(vector>& vec) +{ + vec.resize(TotalIterKernelCount());//Allocate the memory to read into. + + if (vec.size() >= TotalIterKernelCount()) + return m_Wrapper.ReadBuffer(m_PointsBufferName, (void*)vec.data(), TotalIterKernelCount() * sizeof(PointCL)); + + return false; +} + +/// +/// Read the final image buffer buffer into the host side CPU buffer. +/// This must be called before saving the final output image to file. +/// +/// The host side buffer to read into +/// True if success, else false. +template +bool RendererCL::ReadFinal(unsigned char* pixels) +{ + if (pixels) + return m_Wrapper.ReadImage(m_FinalImageName, FinalRasW(), FinalRasH(), 0, m_Wrapper.Shared(), pixels); + + return false; +} + +/// +/// Clear the final image output buffer with all zeroes by copying a host side buffer. +/// Slow, but never used because the final output image is always completely overwritten. +/// +/// True if success, else false. +template +bool RendererCL::ClearFinal() +{ + vector v; + unsigned int index = m_Wrapper.FindImageIndex(m_FinalImageName, m_Wrapper.Shared()); + + if (PrepFinalAccumVector(v)) + { + bool b = m_Wrapper.WriteImage2D(index, m_Wrapper.Shared(), FinalRasW(), FinalRasH(), 0, v.data()); + + if (!b) + m_ErrorReport.push_back(__FUNCTION__); + + return b; + } + else + return false; +} + +/// +/// Clear the histogram buffer with all zeroes. +/// +/// True if success, else false. +template +bool RendererCL::ClearHist() +{ + return ClearBuffer(m_HistBufferName, SuperRasW(), SuperRasH(), sizeof(v4T)); +} + +/// +/// Clear the desnity filtering buffer with all zeroes. +/// +/// True if success, else false. +template +bool RendererCL::ClearAccum() +{ + return ClearBuffer(m_AccumBufferName, SuperRasW(), SuperRasH(), sizeof(v4T)); +} + +/// +/// Write values from a host side CPU buffer into the temporary points buffer. +/// Used for debugging. +/// +/// The host side buffer whose values to write +/// True if success, else false. +template +bool RendererCL::WritePoints(vector>& vec) +{ + return m_Wrapper.WriteBuffer(m_PointsBufferName, (void*)vec.data(), vec.size() * sizeof(vec[0])); +} + +/// +/// Get the kernel string for the last built iter program. +/// +/// The string representation of the kernel for the last built iter program. +template +string RendererCL::IterKernel() { return m_IterKernel; } + +/// +/// Public virtual functions overriden from Renderer. +/// + +/// +/// The amount of video RAM available on the GPU to render with. +/// +/// An unsigned 64-bit integer specifying how much video memory is available +template +unsigned __int64 RendererCL::MemoryAvailable() +{ + return Ok() ? m_Wrapper.GetInfo(PlatformIndex(), DeviceIndex(), CL_DEVICE_GLOBAL_MEM_SIZE) : 0ULL; +} + +/// +/// Return whether OpenCL has been properly initialized. +/// +/// True if OpenCL has been properly initialized, else false. +template +bool RendererCL::Ok() const +{ + return m_Init; +} + +/// +/// Override to force num channels to be 4 because RGBA is always used for OpenCL +/// since the output is actually an image rather than just a buffer. +/// +/// The number of channels, ignored. +template +void RendererCL::NumChannels(unsigned int numChannels) +{ + m_NumChannels = 4; +} + +/// +/// Dump the error report for this class as well as the OpenCLWrapper member. +/// +template +void RendererCL::DumpErrorReport() +{ + EmberReport::DumpErrorReport(); + m_Wrapper.DumpErrorReport(); +} + +/// +/// Clear the error report for this class as well as the OpenCLWrapper member. +/// +template +void RendererCL::ClearErrorReport() +{ + EmberReport::ClearErrorReport(); + m_Wrapper.ClearErrorReport(); +} + +/// +/// The sub batch size for OpenCL will always be how many +/// iterations are ran per kernel call. The caller can't +/// change this. +/// +/// The number of iterations ran in a single kernel call +template +unsigned int RendererCL::SubBatchSize() const +{ + return m_IterBlocksWide * m_IterBlocksHigh * 256 * 256; +} + +/// +/// The thread count for OpenCL is always considered to be 1, however +/// the kernel internally runs many threads. +/// +/// 1 +template +unsigned int RendererCL::ThreadCount() const +{ + return 1; +} + +/// +/// Override to always set the thread count to 1 for OpenCL. +/// Specific seeds can't be used for OpenCL. If a repeatable trajectory +/// is needed for debugging, use the base class. +/// +/// The number of threads to use, ignored. +/// The seed string to use if threads is 1, ignored. Default: NULL. +template +void RendererCL::ThreadCount(unsigned int threads, const char* seedString) +{ + Renderer::ThreadCount(threads, seedString); +} + +/// +/// Create the density filter in the base class and copy the filter values +/// to the corresponding OpenCL buffers. +/// +/// True if a new filter instance was created, else false. +/// True if success, else false. +template +bool RendererCL::CreateDEFilter(bool& newAlloc) +{ + if (Renderer::CreateDEFilter(newAlloc)) + { + //Copy coefs and widths here. Convert and copy the other filter params right before calling the filtering kernel. + if (newAlloc) + { + DensityFilter* filter = GetDensityFilter(); + + if (!m_Wrapper.AddAndWriteBuffer(m_DECoefsBufferName, (void*)filter->Coefs(), filter->CoefsSizeBytes())) { m_ErrorReport.push_back(__FUNCTION__); return false; } + if (!m_Wrapper.AddAndWriteBuffer(m_DEWidthsBufferName, (void*)filter->Widths(), filter->WidthsSizeBytes())) { m_ErrorReport.push_back(__FUNCTION__); return false; } + if (!m_Wrapper.AddAndWriteBuffer(m_DECoefIndicesBufferName, (void*)filter->CoefIndices(), filter->CoefsIndicesSizeBytes())) { m_ErrorReport.push_back(__FUNCTION__); return false; } + } + + return true; + } + + return false; +} + +/// +/// Create the spatial filter in the base class and copy the filter values +/// to the corresponding OpenCL buffers. +/// +/// True if a new filter instance was created, else false. +/// True if success, else false. +template +bool RendererCL::CreateSpatialFilter(bool& newAlloc) +{ + if (Renderer::CreateSpatialFilter(newAlloc)) + { + if (newAlloc) + if (!m_Wrapper.AddAndWriteBuffer(m_SpatialFilterCoefsBufferName, (void*)GetSpatialFilter()->Filter(), GetSpatialFilter()->BufferSizeBytes())) { m_ErrorReport.push_back(__FUNCTION__); return false; } + + return true; + } + + return false; +} + +/// +/// Get the renderer type enum. +/// +/// OPENCL_RENDERER +template +eRendererType RendererCL::RendererType() const +{ + return OPENCL_RENDERER; +} + +/// +/// Concatenate and return the error report for this class and the +/// OpenCLWrapper member as a single string. +/// +/// The concatenated error report string +template +string RendererCL::ErrorReportString() +{ + return EmberReport::ErrorReportString() + m_Wrapper.ErrorReportString(); +} + +/// +/// Concatenate and return the error report for this class and the +/// OpenCLWrapper member as a vector of strings. +/// +/// The concatenated error report vector of strings +template +vector RendererCL::ErrorReport() +{ + vector ours = EmberReport::ErrorReport(); + vector wrappers = m_Wrapper.ErrorReport(); + + ours.insert(ours.end(), wrappers.begin(), wrappers.end()); + return ours; +} + +/// +/// Protected virtual functions overriden from Renderer. +/// + +/// +/// Make the final palette used for iteration. +/// This override differs from the base in that it does not use +/// bucketT as the output palette type. This is because OpenCL +/// only supports floats for texture images. +/// +/// The color scalar to multiply the ember's palette by +template +void RendererCL::MakeDmap(T colorScalar) +{ + m_Ember.m_Palette.MakeDmap(m_Dmap, colorScalar); +} + +/// +/// Allocate all buffers required for running as well as the final +/// 2D image. +/// +/// True if success, else false. +template +bool RendererCL::Alloc() +{ + if (!m_Wrapper.Ok()) + return false; + + EnterResize(); + + bool b = true; + size_t histLength = SuperSize() * sizeof(v4T); + size_t accumLength = SuperSize() * sizeof(v4T); + const char* loc = __FUNCTION__; + + if (!m_Wrapper.AddBuffer(m_EmberBufferName, sizeof(m_EmberCL))) { m_ErrorReport.push_back(loc); return false; } + if (!m_Wrapper.AddBuffer(m_ParVarsBufferName, 128 * sizeof(T))) { m_ErrorReport.push_back(loc); return false; } + if (!m_Wrapper.AddBuffer(m_DistBufferName, CHOOSE_XFORM_GRAIN)) { m_ErrorReport.push_back(loc); return false; }//Will be resized for xaos. + if (!m_Wrapper.AddBuffer(m_CarToRasBufferName, sizeof(m_CarToRasCL))) { m_ErrorReport.push_back(loc); return false; } + if (!m_Wrapper.AddBuffer(m_DEFilterParamsBufferName, sizeof(m_DensityFilterCL))) { m_ErrorReport.push_back(loc); return false; } + if (!m_Wrapper.AddBuffer(m_SpatialFilterParamsBufferName, sizeof(m_SpatialFilterCL))) { m_ErrorReport.push_back(loc); return false; } + + if (!m_Wrapper.AddBuffer(m_HistBufferName, histLength)) { m_ErrorReport.push_back(loc); return false; }//Histogram. Will memset to zero later. + if (!m_Wrapper.AddBuffer(m_AccumBufferName, accumLength)) { m_ErrorReport.push_back(loc); return false; }//Accum buffer. + if (!m_Wrapper.AddBuffer(m_PointsBufferName, TotalIterKernelCount() * sizeof(PointCL))) { m_ErrorReport.push_back(loc); return false; }//Points between iter calls. + + if (!m_Wrapper.AddAndWriteImage(m_FinalImageName, CL_MEM_WRITE_ONLY, m_FinalFormat, FinalRasW(), FinalRasH(), 0, NULL, m_Wrapper.Shared(), m_OutputTexID)) + { + m_ErrorReport.push_back(loc); + LeaveResize(); + return false; + } + + LeaveResize(); + return true; +} + +/// +/// Clear OpenCL histogram and/or density filtering buffers to all zeroes. +/// +/// Clear histogram if true, else don't. +/// Clear density filtering buffer if true, else don't. +/// True if success, else false. +template +bool RendererCL::ResetBuckets(bool resetHist, bool resetAccum) +{ + bool b = true; + + if (resetHist) + b &= ClearHist(); + + if (resetAccum) + b &= ClearAccum(); + + return b; +} + +/// +/// Perform log scale density filtering. +/// +/// True if success and not aborted, else false. +template +eRenderStatus RendererCL::LogScaleDensityFilter() +{ + return RunLogScaleFilter(); +} + +/// +/// Run gaussian density estimation filtering. +/// +/// True if success and not aborted, else false. +template +eRenderStatus RendererCL::GaussianDensityFilter() +{ + //This commented section is for debugging density filtering by making it run on the CPU + //then copying the results back to the GPU. + //if (ReadHist()) + //{ + // unsigned int accumLength = SuperSize() * sizeof(glm::detail::tvec4); + // const char* loc = __FUNCTION__; + // + // Renderer::ResetBuckets(false, true); + // Renderer::GaussianDensityFilter(); + // + // if (!m_Wrapper.WriteBuffer(m_AccumBufferName, AccumulatorBuckets(), accumLength)) { m_ErrorReport.push_back(loc); return RENDER_ERROR; } + // return RENDER_OK; + //} + //else + // return RENDER_ERROR; + + //Timing t(4); + + eRenderStatus status = RunDensityFilter(); + //t.Toc(__FUNCTION__ " RunKernel()"); + + return status; +} + +/// +/// Run final accumulation. +/// If pixels is NULL, the output will remain in the OpenCL 2D image. +/// However, if pixels is not NULL, the output will be copied. This is +/// useful when rendering in OpenCL, but saving the output to a file. +/// +/// The pixels to copy the final image to if not NULL +/// Offset in the buffer to store the pixels to +/// True if success and not aborted, else false. +template +eRenderStatus RendererCL::AccumulatorToFinalImage(unsigned char* pixels, size_t finalOffset) +{ + eRenderStatus status = RunFinalAccum(); + + if (status == RENDER_OK && pixels != NULL && !m_Wrapper.Shared()) + { + pixels += finalOffset; + + if (!ReadFinal(pixels)) + status = RENDER_ERROR; + } + + return status; +} + +/// +/// Run the iteration algorithm for the specified number of iterations. +/// This is only called after all other setup has been done. +/// This will recompile the OpenCL program if this ember differs significantly +/// from the previous run. +/// Note that the bad value count is not recorded when running with OpenCL. If it's +/// needed, run on the CPU. +/// +/// The number of iterations to run +/// The pass this is running for +/// The temporal sample within the current pass this is running for +/// Rendering statistics +template +EmberStats RendererCL::Iterate(unsigned __int64 iterCount, unsigned int pass, unsigned int temporalSample) +{ + bool b = true; + EmberStats stats;//Do not record bad vals with with GPU. If the user needs to investigate bad vals, use the CPU. + const char* loc = __FUNCTION__; + + IterOpenCLKernelCreator::ParVarIndexDefines(m_Ember, m_Params, true, false);//Always do this to get the values (but no string), regardless of whether a rebuild is necessary. + + //Don't know the size of the parametric varations parameters buffer until the ember is examined. + //So set it up right before the run. + if (!m_Params.second.empty()) + { + if (!m_Wrapper.AddAndWriteBuffer(m_ParVarsBufferName, m_Params.second.data(), m_Params.second.size() * sizeof(m_Params.second[0]))) + { + m_Abort = true; + m_ErrorReport.push_back(loc); + return stats; + } + } + + //Rebuilding is expensive, so only do it if it's required. + if (IterOpenCLKernelCreator::IsBuildRequired(m_Ember, m_LastBuiltEmber)) + b = BuildIterProgramForEmber(true); + + if (b) + { + if (m_ProcessState == ITER_STARTED) + m_Calls = 0; + + b = RunIter(iterCount, pass, temporalSample, stats.m_Iters); + + if (!b || stats.m_Iters == 0)//If no iters were executed, something went catastrophically wrong. + m_Abort = true; + } + else + { + m_Abort = true; + m_ErrorReport.push_back(loc); + } + + return stats; +} + +/// +/// Private functions for making and running OpenCL programs. +/// + +/// +/// Build the iteration program for the current ember. +/// +/// Whether to build in accumulation, only for debugging. Default: true. +/// True if success, else false. +template +bool RendererCL::BuildIterProgramForEmber(bool doAccum) +{ + //Timing t; + const char* loc = __FUNCTION__; + IterOpenCLKernelCreator::ParVarIndexDefines(m_Ember, m_Params, false, true);//Do with string and no vals. + m_IterKernel = m_IterOpenCLKernelCreator.CreateIterKernelString(m_Ember, m_Params.first, m_LockAccum, doAccum); + //cout << "Building: " << endl << iterProgram << endl; + + //A program build is roughly .66s which will detract from the user experience. + //Need to experiment with launching this in a thread/task and returning once it's done.//TODO + if (m_Wrapper.AddProgram(m_IterOpenCLKernelCreator.IterEntryPoint(), m_IterKernel, m_IterOpenCLKernelCreator.IterEntryPoint(), m_DoublePrecision)) + { + //t.Toc(__FUNCTION__ " program build"); + //cout << string(loc) << "():\nBuilding the following program succeeded: \n" << iterProgram << endl; + m_LastBuiltEmber = m_Ember; + } + else + { + m_ErrorReport.push_back(string(loc) + "():\nBuilding the following program failed: \n" + m_IterKernel + "\n"); + return false; + } + + return true; +} + +/// +/// Run the iteration kernel. +/// Fusing on the CPU is done once per sub batch, usually 10,000 iters, however +/// determining when to do it in OpenCL is much more difficult. +/// Currently it's done once every 4 kernel calls which seems to be a good balance +/// between quality of the final image and performance. +/// +/// The number of iterations to run +/// The pass this is running for +/// The temporal sample within the current pass this is running for +/// The storage for the number of iterations ran +/// True if success, else false. +template +bool RendererCL::RunIter(unsigned __int64 iterCount, unsigned int pass, unsigned int temporalSample, unsigned __int64& itersRan) +{ + Timing t;//, t2(4); + bool b = false; + unsigned int fuse, argIndex; + unsigned int iterCountPerKernel = 256; + unsigned int iterCountPerBlock = iterCountPerKernel * m_IterBlockWidth * m_IterBlockHeight; + unsigned int seed; + unsigned __int64 itersRemaining, localIterCount = 0; + int kernelIndex = m_Wrapper.FindKernelIndex(m_IterOpenCLKernelCreator.IterEntryPoint()); + double percent, etaMs; + const char* loc = __FUNCTION__; + + itersRan = 0; +#ifdef TEST_CL + m_Abort = false; +#endif + + if (kernelIndex != -1) + { + b = true; + m_EmberCL = ConvertEmber(m_Ember); + m_CarToRasCL = ConvertCarToRas(*CoordMap()); + + if (!m_Wrapper.WriteBuffer (m_EmberBufferName, (void*)&m_EmberCL, sizeof(m_EmberCL))) { m_ErrorReport.push_back(loc); return false; } + if (!m_Wrapper.AddAndWriteBuffer(m_DistBufferName, (void*)XformDistributions(), XformDistributionsSize())) { m_ErrorReport.push_back(loc); return false; }//Will be resized for xaos. + if (!m_Wrapper.WriteBuffer (m_CarToRasBufferName, (void*)&m_CarToRasCL, sizeof(m_CarToRasCL))) { m_ErrorReport.push_back(loc); return false; } + + if (!m_Wrapper.AddAndWriteImage("Palette", CL_MEM_READ_ONLY, m_PaletteFormat, 256, 1, 0, m_Dmap.m_Entries.data())) { m_ErrorReport.push_back(loc); return false; } + + //If animating, treat each temporal sample as a newly started render for fusing purposes. + if (temporalSample > 0) + m_Calls = 0; + + while (itersRan < iterCount && !m_Abort) + { + argIndex = 0; + seed = m_Rand[0].Rand(); +#ifdef TEST_CL + fuse = false; +#else + fuse = ((m_Calls % 4) == 0 ? 100 : 0); +#endif + //fuse = 100; + itersRemaining = iterCount - itersRan; + unsigned int gridW = (unsigned int)min(ceil((double)itersRemaining / (double)iterCountPerBlock), (double)IterBlocksWide()); + unsigned int gridH = (unsigned int)min(ceil((double)itersRemaining / ((double)gridW * iterCountPerBlock)), (double)IterBlocksHigh()); + unsigned int iterCountThisLaunch = iterCountPerBlock * gridW * gridH; + + //Similar to what's done in the base class. + //The number of iters per thread must be adjusted if they've requested less iters than is normally ran in a block (256 * 256). + if (iterCountThisLaunch > iterCount) + { + iterCountPerKernel = (unsigned int)ceil((double)iterCount / (double)(gridW * gridH * m_IterBlockWidth * m_IterBlockHeight)); + iterCountThisLaunch = iterCountPerKernel * (gridW * gridH * m_IterBlockWidth * m_IterBlockHeight); + } + + if (!m_Wrapper.SetArg (kernelIndex, argIndex, iterCountPerKernel)) { m_ErrorReport.push_back(loc); return false; } argIndex++;//Number of iters for each thread to run. + if (!m_Wrapper.SetArg (kernelIndex, argIndex, fuse)) { m_ErrorReport.push_back(loc); return false; } argIndex++;//Number of iters to fuse. + if (!m_Wrapper.SetArg (kernelIndex, argIndex, seed)) { m_ErrorReport.push_back(loc); return false; } argIndex++;//Seed. + if (!m_Wrapper.SetBufferArg(kernelIndex, argIndex, m_EmberBufferName)) { m_ErrorReport.push_back(loc); return false; } argIndex++;//Flame. + if (!m_Wrapper.SetBufferArg(kernelIndex, argIndex, m_ParVarsBufferName)) { m_ErrorReport.push_back(loc); return false; } argIndex++;//Parametric variation parameters. + if (!m_Wrapper.SetBufferArg(kernelIndex, argIndex, m_DistBufferName)) { m_ErrorReport.push_back(loc); return false; } argIndex++;//Xform distributions. + if (!m_Wrapper.SetBufferArg(kernelIndex, argIndex, m_CarToRasBufferName)) { m_ErrorReport.push_back(loc); return false; } argIndex++;//Coordinate converter. + if (!m_Wrapper.SetBufferArg(kernelIndex, argIndex, m_HistBufferName)) { m_ErrorReport.push_back(loc); return false; } argIndex++;//Histogram. + if (!m_Wrapper.SetArg (kernelIndex, argIndex, SuperSize())) { m_ErrorReport.push_back(loc); return false; } argIndex++;//Histogram size. + if (!m_Wrapper.SetImageArg (kernelIndex, argIndex, false, "Palette")) { m_ErrorReport.push_back(loc); return false; } argIndex++;//Palette. + if (!m_Wrapper.SetBufferArg(kernelIndex, argIndex, m_PointsBufferName)) { m_ErrorReport.push_back(loc); return false; } argIndex++;//Random start points. + + if (!m_Wrapper.RunKernel(kernelIndex, + gridW * IterBlockWidth(),//Total grid dims. + gridH * IterBlockHeight(), + 1, + IterBlockWidth(),//Individual block dims. + IterBlockHeight(), + 1)) + { + b = false; + m_Abort = true; + m_ErrorReport.push_back(loc); + break; + } + + itersRan += iterCountThisLaunch; + m_Calls++; + + if (m_Callback) + { + percent = 100.0 * + double + ( + double + ( + double + ( + double + ( + double(m_LastIter + itersRan) / double(ItersPerTemporalSample()) + ) + temporalSample + ) / (double)TemporalSamples() + ) + (double)pass + ) / (double)Passes(); + + double percentDiff = percent - m_LastIterPercent; + double toc = m_ProgressTimer.Toc(); + + if (percentDiff >= 10 || (toc > 1000 && percentDiff >= 1))//Call callback function if either 10% has passed, or one second (and 1%). + { + etaMs = ((100.0 - percent) / percent) * m_RenderTimer.Toc(); + + if (!m_Callback->ProgressFunc(m_Ember, m_ProgressParameter, percent, 0, etaMs)) + Abort(); + + m_LastIterPercent = percent; + m_ProgressTimer.Tic(); + } + } + } + } + else + { + m_ErrorReport.push_back(loc); + } + + //t2.Toc(__FUNCTION__); + return b; +} + +/// +/// Run the log scale filter. +/// +/// True if success, else false. +template +eRenderStatus RendererCL::RunLogScaleFilter() +{ + //Timing t(4); + int kernelIndex; + const char* loc = __FUNCTION__; + eRenderStatus status = RENDER_OK; + + if (Passes() == 1) + kernelIndex = m_Wrapper.FindKernelIndex(m_DEOpenCLKernelCreator.LogScaleAssignDEEntryPoint()); + else + kernelIndex = m_Wrapper.FindKernelIndex(m_DEOpenCLKernelCreator.LogScaleSumDEEntryPoint()); + + if (kernelIndex != -1) + { + m_DensityFilterCL = ConvertDensityFilter(); + unsigned int argIndex = 0; + unsigned int blockW = m_WarpSize; + unsigned int blockH = 4;//A height of 4 seems to run the fastest. + unsigned int gridW = m_DensityFilterCL.m_SuperRasW; + unsigned int gridH = m_DensityFilterCL.m_SuperRasH; + + OpenCLWrapper::MakeEvenGridDims(blockW, blockH, gridW, gridH); + + if (!m_Wrapper.AddAndWriteBuffer(m_DEFilterParamsBufferName, (void*)&m_DensityFilterCL, sizeof(m_DensityFilterCL))) { m_ErrorReport.push_back(loc); return RENDER_ERROR; } + + if (!m_Wrapper.SetBufferArg(kernelIndex, argIndex, m_HistBufferName)) { m_ErrorReport.push_back(loc); return RENDER_ERROR; } argIndex++;//Histogram. + if (!m_Wrapper.SetBufferArg(kernelIndex, argIndex, m_AccumBufferName)) { m_ErrorReport.push_back(loc); return RENDER_ERROR; } argIndex++;//Accumulator. + if (!m_Wrapper.SetBufferArg(kernelIndex, argIndex, m_DEFilterParamsBufferName)) { m_ErrorReport.push_back(loc); return RENDER_ERROR; } argIndex++;//DensityFilterCL. + + //t.Tic(); + if (!m_Wrapper.RunKernel(kernelIndex, gridW, gridH, 1, blockW, blockH, 1)) { m_ErrorReport.push_back(loc); return RENDER_ERROR; } + //t.Toc(loc); + } + else + { + status = RENDER_ERROR; + m_ErrorReport.push_back(loc); + } + + return status; +} + +/// +/// Run the Gaussian density filter. +/// Method 7: Each block processes a 32x32 block and exits. No column or row advancements happen. +/// +/// True if success and not aborted, else false. +template +eRenderStatus RendererCL::RunDensityFilter() +{ + Timing t(4);//, t2(4); + m_DensityFilterCL = ConvertDensityFilter(); + int kernelIndex = MakeAndGetDensityFilterProgram(Supersample(), m_DensityFilterCL.m_FilterWidth); + const char* loc = __FUNCTION__; + eRenderStatus status = RENDER_OK; + + if (kernelIndex != -1) + { + unsigned int leftBound = m_DensityFilterCL.m_Supersample - 1; + unsigned int rightBound = m_DensityFilterCL.m_SuperRasW - (m_DensityFilterCL.m_Supersample - 1); + unsigned int topBound = leftBound; + unsigned int botBound = m_DensityFilterCL.m_SuperRasH - (m_DensityFilterCL.m_Supersample - 1); + unsigned int gridW = rightBound - leftBound; + unsigned int gridH = botBound - topBound; + unsigned int blockSizeW = m_MaxDEBlockSizeW;//These *must* both be divisible by 16 or else pixels will go missing. + unsigned int blockSizeH = m_MaxDEBlockSizeH; + + //OpenCL runs out of resources when using double or a supersample of 2. + //Remedy this by reducing the height of the block by 2. + if (m_DoublePrecision || m_DensityFilterCL.m_Supersample > 1) + blockSizeH -= 2; + + //Can't just blindly pass in vals. Must adjust them first to evenly divide the block count + //into the total grid dimensions. + OpenCLWrapper::MakeEvenGridDims(blockSizeW, blockSizeH, gridW, gridH); + + //t.Tic(); + //The classic problem with performing DE on adjacent pixels is that the filter will overlap. + //This can be solved in 2 ways. One is to use atomics, which is unacceptably slow. + //The other is to proces the entire image in multiple passes, and each pass processes blocks of pixels + //that are far enough apart such that their filters do not overlap. + //Do the latter. + unsigned int gapW = (unsigned int)ceil((m_DensityFilterCL.m_FilterWidth * 2.0) / (double)blockSizeW); + unsigned int chunkSizeW = gapW + 1; + unsigned int gapH = (unsigned int)ceil((m_DensityFilterCL.m_FilterWidth * 2.0) / (double)blockSizeH); + unsigned int chunkSizeH = gapH + 1; + + double totalChunks = chunkSizeW * chunkSizeH; + + if (!m_Wrapper.AddAndWriteBuffer(m_DEFilterParamsBufferName, (void*)&m_DensityFilterCL, sizeof(m_DensityFilterCL))) { m_ErrorReport.push_back(loc); return RENDER_ERROR; } + + for (unsigned int row = 0; row < chunkSizeH; row++) + { + for (unsigned int col = 0; col < chunkSizeW; col++) + { + //t2.Tic(); + if (!RunDensityFilterPrivate(kernelIndex, gridW, gridH, blockSizeW, blockSizeH, chunkSizeW, chunkSizeH, row, col)) { m_Abort = true; m_ErrorReport.push_back(loc); return RENDER_ERROR; } + //t2.Toc(loc); + + if (m_Callback) + { + double percent = (double((row * chunkSizeW) + (col + 1)) / totalChunks) * 100.0; + double etaMs = ((100.0 - percent) / percent) * t.Toc(); + + if (!m_Callback->ProgressFunc(m_Ember, m_ProgressParameter, percent, 1, etaMs)) + Abort(); + } + + if (m_Abort) + return RENDER_ABORT; + } + } + + if (m_Callback) + m_Callback->ProgressFunc(m_Ember, m_ProgressParameter, 100.0, 1, 0.0); + + //t2.Toc(__FUNCTION__ " all passes"); + } + else + { + status = RENDER_ERROR; + m_ErrorReport.push_back(loc); + } + + return status; +} + +/// +/// Run final accumulation to the 2D output image. +/// +/// True if success and not aborted, else false. +template +eRenderStatus RendererCL::RunFinalAccum() +{ + //Timing t(4); + T alphaBase; + T alphaScale; + int accumKernelIndex = MakeAndGetFinalAccumProgram(alphaBase, alphaScale); + unsigned int argIndex; + unsigned int gridW; + unsigned int gridH; + unsigned int blockW; + unsigned int blockH; + const char* loc = __FUNCTION__; + eRenderStatus status = RENDER_OK; + + if (!m_Abort && accumKernelIndex != -1) + { + //This is needed with or without early clip. + m_SpatialFilterCL = ConvertSpatialFilter(); + + if (!m_Wrapper.AddAndWriteBuffer(m_SpatialFilterParamsBufferName, (void*)&m_SpatialFilterCL, sizeof(m_SpatialFilterCL))) { m_ErrorReport.push_back(loc); return RENDER_ERROR; } + + //Since early clip requires gamma correcting the entire accumulator first, + //it can't be done inside of the normal final accumulation kernel, so + //an additional kernel must be launched first. + if (EarlyClip()) + { + int gammaCorrectKernelIndex = MakeAndGetGammaCorrectionProgram(); + + if (gammaCorrectKernelIndex != -1) + { + argIndex = 0; + blockW = m_WarpSize; + blockH = 4;//A height of 4 seems to run the fastest. + gridW = m_SpatialFilterCL.m_SuperRasW;//Using super dimensions because this processes the density filtering bufer. + gridH = m_SpatialFilterCL.m_SuperRasH; + OpenCLWrapper::MakeEvenGridDims(blockW, blockH, gridW, gridH); + + if (!m_Wrapper.SetBufferArg(gammaCorrectKernelIndex, argIndex, m_AccumBufferName)) { m_ErrorReport.push_back(loc); return RENDER_ERROR; } argIndex++;//Accumulator. + if (!m_Wrapper.SetBufferArg(gammaCorrectKernelIndex, argIndex, m_SpatialFilterParamsBufferName)) { m_ErrorReport.push_back(loc); return RENDER_ERROR; } argIndex++;//SpatialFilterCL. + + if (!m_Wrapper.RunKernel(gammaCorrectKernelIndex, gridW, gridH, 1, blockW, blockH, 1)) { m_ErrorReport.push_back(loc); return RENDER_ERROR; } + } + else + { + m_ErrorReport.push_back(loc); + return RENDER_ERROR; + } + } + + argIndex = 0; + blockW = m_WarpSize; + blockH = 4;//A height of 4 seems to run the fastest. + gridW = m_SpatialFilterCL.m_FinalRasW; + gridH = m_SpatialFilterCL.m_FinalRasH; + OpenCLWrapper::MakeEvenGridDims(blockW, blockH, gridW, gridH); + + if (!m_Wrapper.SetBufferArg(accumKernelIndex, argIndex, m_AccumBufferName)) { m_ErrorReport.push_back(loc); return RENDER_ERROR; } argIndex++;//Accumulator. + if (!m_Wrapper.SetImageArg(accumKernelIndex, argIndex, m_Wrapper.Shared(), m_FinalImageName)) { m_ErrorReport.push_back(loc); return RENDER_ERROR; } argIndex++;//Final image. + if (!m_Wrapper.SetBufferArg(accumKernelIndex, argIndex, m_SpatialFilterParamsBufferName)) { m_ErrorReport.push_back(loc); return RENDER_ERROR; } argIndex++;//SpatialFilterCL. + if (!m_Wrapper.SetBufferArg(accumKernelIndex, argIndex, m_SpatialFilterCoefsBufferName)) { m_ErrorReport.push_back(loc); return RENDER_ERROR; } argIndex++;//Filter coefs. + if (!m_Wrapper.SetArg(accumKernelIndex, argIndex, alphaBase)) { m_ErrorReport.push_back(loc); return RENDER_ERROR; } argIndex++;//Alpha base. + if (!m_Wrapper.SetArg(accumKernelIndex, argIndex, alphaScale)) { m_ErrorReport.push_back(loc); return RENDER_ERROR; } argIndex++;//Alpha scale. + + if (m_Wrapper.Shared()) + if (!m_Wrapper.EnqueueAcquireGLObjects(m_FinalImageName)) { m_ErrorReport.push_back(loc); return RENDER_ERROR; } + + if (!m_Wrapper.RunKernel(accumKernelIndex, gridW, gridH, 1, blockW, blockH, 1)) { m_ErrorReport.push_back(loc); return RENDER_ERROR; } + + if (m_Wrapper.Shared()) + if (!m_Wrapper.EnqueueReleaseGLObjects(m_FinalImageName)) { m_ErrorReport.push_back(loc); return RENDER_ERROR; } + + //t.Toc((char*)loc); + } + else + { + status = RENDER_ERROR; + m_ErrorReport.push_back(loc); + } + + return status; +} + +/// +/// Zeroize a buffer of the specified size. +/// +/// Name of the buffer to clear +/// Width in elements +/// Height in elements +/// Size of each element +/// True if success, else false. +template +bool RendererCL::ClearBuffer(string bufferName, unsigned int width, unsigned int height, unsigned int elementSize) +{ + int kernelIndex = m_Wrapper.FindKernelIndex(m_IterOpenCLKernelCreator.ZeroizeEntryPoint()); + unsigned int argIndex = 0; + const char* loc = __FUNCTION__; + + if (kernelIndex != -1) + { + unsigned int blockW = m_NVidia ? 32 : 16;//Max work group size is 256 on AMD, which means 16x16. + unsigned int blockH = m_NVidia ? 32 : 16; + unsigned int gridW = width * elementSize; + unsigned int gridH = height; + + OpenCLWrapper::MakeEvenGridDims(blockW, blockH, gridW, gridH); + + if (!m_Wrapper.SetBufferArg(kernelIndex, argIndex, bufferName)) { m_ErrorReport.push_back(loc); return false; } argIndex++;//Buffer of unsigned char. + if (!m_Wrapper.SetArg (kernelIndex, argIndex, width * elementSize)) { m_ErrorReport.push_back(loc); return false; } argIndex++;//Width. + if (!m_Wrapper.SetArg (kernelIndex, argIndex, height)) { m_ErrorReport.push_back(loc); return false; } argIndex++;//Height. + if (!m_Wrapper.RunKernel(kernelIndex, gridW, gridH, 1, blockW, blockH, 1)) { m_ErrorReport.push_back(loc); return false; } + + return true; + } + else + m_ErrorReport.push_back(loc); + + return false; +} + +/// +/// Private wrapper around calling Gaussian density filtering kernel. +/// The parameters are very specific to how the kernel is internally implemented. +/// +/// Index of the kernel to call +/// Grid width +/// Grid height +/// Block width +/// Block height +/// Chunk size (gap + 1) +/// Row parity +/// Column parity +/// True if success, else false. +template +bool RendererCL::RunDensityFilterPrivate(unsigned int kernelIndex, unsigned int gridW, unsigned int gridH, unsigned int blockW, unsigned int blockH, unsigned int chunkSizeW, unsigned int chunkSizeH, unsigned int rowParity, unsigned int colParity) +{ + //Timing t; + bool b = true; + unsigned int argIndex = 0; + const char* loc = __FUNCTION__; + + if (!m_Wrapper.SetBufferArg(kernelIndex, argIndex, m_HistBufferName)) { m_ErrorReport.push_back(loc); return false; } argIndex++;//Histogram. + if (!m_Wrapper.SetBufferArg(kernelIndex, argIndex, m_AccumBufferName)) { m_ErrorReport.push_back(loc); return false; } argIndex++;//Accumulator. + if (!m_Wrapper.SetBufferArg(kernelIndex, argIndex, m_DEFilterParamsBufferName)) { m_ErrorReport.push_back(loc); return false; } argIndex++;//FlameDensityFilterCL. + if (!m_Wrapper.SetBufferArg(kernelIndex, argIndex, m_DECoefsBufferName)) { m_ErrorReport.push_back(loc); return false; } argIndex++;//Coefs. + if (!m_Wrapper.SetBufferArg(kernelIndex, argIndex, m_DEWidthsBufferName)) { m_ErrorReport.push_back(loc); return false; } argIndex++;//Widths. + if (!m_Wrapper.SetBufferArg(kernelIndex, argIndex, m_DECoefIndicesBufferName)) { m_ErrorReport.push_back(loc); return false; } argIndex++;//Coef indices. + if (!m_Wrapper.SetArg( kernelIndex, argIndex, chunkSizeW)) { m_ErrorReport.push_back(loc); return false; } argIndex++;//Chunk size width (gapW + 1). + if (!m_Wrapper.SetArg( kernelIndex, argIndex, chunkSizeH)) { m_ErrorReport.push_back(loc); return false; } argIndex++;//Chunk size height (gapH + 1). + if (!m_Wrapper.SetArg( kernelIndex, argIndex, rowParity)) { m_ErrorReport.push_back(loc); return false; } argIndex++;//Row parity. + if (!m_Wrapper.SetArg( kernelIndex, argIndex, colParity)) { m_ErrorReport.push_back(loc); return false; } argIndex++;//Col parity. + //t.Toc(__FUNCTION__ " set args"); + + //t.Tic(); + if (!m_Wrapper.RunKernel(kernelIndex, gridW, gridH, 1, blockW, blockH, 1)) { m_ErrorReport.push_back(loc); return false; }//Method 7, accumulating to temp box area. + //t.Toc(__FUNCTION__ " RunKernel()"); + + return true; +} + +/// +/// Make the Gaussian density filter program and return its index. +/// +/// The supersample being used for the current ember +/// Width of the gaussian filter +/// The kernel index if successful, else -1. +template +int RendererCL::MakeAndGetDensityFilterProgram(unsigned int ss, unsigned int filterWidth) +{ + string deEntryPoint = m_DEOpenCLKernelCreator.GaussianDEEntryPoint(ss, filterWidth); + int kernelIndex = m_Wrapper.FindKernelIndex(deEntryPoint); + const char* loc = __FUNCTION__; + + if (kernelIndex == -1)//Has not been built yet. + { + string kernel = m_DEOpenCLKernelCreator.GaussianDEKernel(ss, filterWidth); + bool b = m_Wrapper.AddProgram(deEntryPoint, kernel, deEntryPoint, m_DoublePrecision); + + if (b) + { + kernelIndex = m_Wrapper.FindKernelIndex(deEntryPoint);//Try to find it again, it will be present if successfully built. + } + else + { + m_ErrorReport.push_back(string(loc) + "():\nBuilding the following program failed: \n" + kernel + "\n"); + //cout << m_ErrorReport.back(); + } + } + + return kernelIndex; +} + +/// +/// Make the final accumulation program and return its index. +/// There are many different kernels for final accum, depending on early clip, alpha channel, and transparency. +/// Loading all of these in the beginning is too much, so only load the one for the current case being worked with. +/// +/// Storage for the alpha base value used in the kernel. 0 if transparency is true, else 255. +/// Storage for the alpha scale value used in the kernel. 255 if transparency is true, else 0. +/// The kernel index if successful, else -1. +template +int RendererCL::MakeAndGetFinalAccumProgram(T& alphaBase, T& alphaScale) +{ + string finalAccumEntryPoint = m_FinalAccumOpenCLKernelCreator.FinalAccumEntryPoint(EarlyClip(), Renderer::NumChannels(), Transparency(), alphaBase, alphaScale); + int kernelIndex = m_Wrapper.FindKernelIndex(finalAccumEntryPoint); + const char* loc = __FUNCTION__; + + if (kernelIndex == -1)//Has not been built yet. + { + string kernel = m_FinalAccumOpenCLKernelCreator.FinalAccumKernel(EarlyClip(), Renderer::NumChannels(), Transparency()); + bool b = m_Wrapper.AddProgram(finalAccumEntryPoint, kernel, finalAccumEntryPoint, m_DoublePrecision); + + if (b) + kernelIndex = m_Wrapper.FindKernelIndex(finalAccumEntryPoint);//Try to find it again, it will be present if successfully built. + else + m_ErrorReport.push_back(loc); + } + + return kernelIndex; +} + +/// +/// Make the gamma correction program for early clipping and return its index. +/// +/// The kernel index if successful, else -1. +template +int RendererCL::MakeAndGetGammaCorrectionProgram() +{ + string gammaEntryPoint = m_FinalAccumOpenCLKernelCreator.GammaCorrectionEntryPoint(Renderer::NumChannels(), Transparency()); + int kernelIndex = m_Wrapper.FindKernelIndex(gammaEntryPoint); + const char* loc = __FUNCTION__; + + if (kernelIndex == -1)//Has not been built yet. + { + string kernel = m_FinalAccumOpenCLKernelCreator.GammaCorrectionKernel(Renderer::NumChannels(), Transparency()); + bool b = m_Wrapper.AddProgram(gammaEntryPoint, kernel, gammaEntryPoint, m_DoublePrecision); + + if (b) + kernelIndex = m_Wrapper.FindKernelIndex(gammaEntryPoint);//Try to find it again, it will be present if successfully built. + else + m_ErrorReport.push_back(loc); + } + + return kernelIndex; +} + +/// +/// Private functions passing data to OpenCL programs. +/// + +/// +/// Convert the currently used host side DensityFilter object into a DensityFilterCL object +/// for passing to OpenCL. +/// +/// The DensityFilterCL object +template +DensityFilterCL RendererCL::ConvertDensityFilter() +{ + DensityFilterCL filterCL; + DensityFilter* densityFilter = GetDensityFilter(); + + filterCL.m_Supersample = Supersample(); + filterCL.m_SuperRasW = SuperRasW(); + filterCL.m_SuperRasH = SuperRasH(); + filterCL.m_K1 = K1(); + filterCL.m_K2 = K2(); + + if (densityFilter) + { + filterCL.m_Curve = densityFilter->Curve(); + filterCL.m_KernelSize = densityFilter->KernelSize(); + filterCL.m_MaxFilterIndex = densityFilter->MaxFilterIndex(); + filterCL.m_MaxFilteredCounts = densityFilter->MaxFilteredCounts(); + filterCL.m_FilterWidth = densityFilter->FilterWidth(); + } + + return filterCL; +} + +/// +/// Convert the currently used host side SpatialFilter object into a SpatialFilterCL object +/// for passing to OpenCL. +/// +/// The SpatialFilterCL object +template +SpatialFilterCL RendererCL::ConvertSpatialFilter() +{ + T g, linRange, vibrancy; + Color background; + SpatialFilterCL filterCL; + + PrepFinalAccumVals(background, g, linRange, vibrancy); + + filterCL.m_SuperRasW = SuperRasW(); + filterCL.m_SuperRasH = SuperRasH(); + filterCL.m_FinalRasW = FinalRasW(); + filterCL.m_FinalRasH = FinalRasH(); + filterCL.m_Supersample = Supersample(); + filterCL.m_FilterWidth = GetSpatialFilter()->FinalFilterWidth(); + filterCL.m_NumChannels = Renderer::NumChannels(); + filterCL.m_BytesPerChannel = BytesPerChannel(); + filterCL.m_DensityFilterOffset = DensityFilterOffset(); + filterCL.m_Transparency = Transparency(); + filterCL.m_Vibrancy = vibrancy; + filterCL.m_HighlightPower = HighlightPower(); + filterCL.m_Gamma = g; + filterCL.m_LinRange = linRange; + filterCL.m_Background = background; + + return filterCL; +} + +/// +/// Convert the host side Ember object into an EmberCL object +/// for passing to OpenCL. +/// +/// The Ember object to convert +/// The EmberCL object +template +EmberCL RendererCL::ConvertEmber(Ember& ember) +{ + EmberCL emberCL; + + memset(&emberCL, 0, sizeof(EmberCL));//Might not really be needed. + + emberCL.m_RotA = m_RotMat.A(); + emberCL.m_RotB = m_RotMat.B(); + emberCL.m_RotD = m_RotMat.D(); + emberCL.m_RotE = m_RotMat.E(); + emberCL.m_CamMat = ember.m_CamMat; + emberCL.m_CenterX = CenterX(); + emberCL.m_CenterY = CenterY(); + emberCL.m_CamZPos = ember.m_CamZPos; + emberCL.m_CamPerspective = ember.m_CamPerspective; + emberCL.m_CamYaw = ember.m_CamYaw; + emberCL.m_CamPitch = ember.m_CamPitch; + emberCL.m_CamDepthBlur = ember.m_CamDepthBlur; + emberCL.m_BlurCoef = ember.BlurCoef(); + emberCL.m_FinalXformIndex = ember.UseFinalXform() ? ember.TotalXformCount() - 1 : -1; + + for (unsigned int i = 0; i < ember.TotalXformCount() && i < MAX_CL_XFORM; i++)//Copy the relevant values for each xform, capped at the max. + { + Xform* xform = ember.GetTotalXform(i); + + emberCL.m_Xforms[i].m_A = xform->m_Affine.A(); + emberCL.m_Xforms[i].m_B = xform->m_Affine.B(); + emberCL.m_Xforms[i].m_C = xform->m_Affine.C(); + emberCL.m_Xforms[i].m_D = xform->m_Affine.D(); + emberCL.m_Xforms[i].m_E = xform->m_Affine.E(); + emberCL.m_Xforms[i].m_F = xform->m_Affine.F(); + + emberCL.m_Xforms[i].m_PostA = xform->m_Post.A(); + emberCL.m_Xforms[i].m_PostB = xform->m_Post.B(); + emberCL.m_Xforms[i].m_PostC = xform->m_Post.C(); + emberCL.m_Xforms[i].m_PostD = xform->m_Post.D(); + emberCL.m_Xforms[i].m_PostE = xform->m_Post.E(); + emberCL.m_Xforms[i].m_PostF = xform->m_Post.F(); + + emberCL.m_Xforms[i].m_DirectColor = xform->m_DirectColor; + emberCL.m_Xforms[i].m_ColorSpeedCache = xform->ColorSpeedCache(); + emberCL.m_Xforms[i].m_OneMinusColorCache = xform->OneMinusColorCache(); + emberCL.m_Xforms[i].m_Opacity = xform->m_Opacity; + emberCL.m_Xforms[i].m_VizAdjusted = xform->VizAdjusted(); + + for (unsigned int varIndex = 0; varIndex < xform->TotalVariationCount() && varIndex < MAX_CL_VARS; varIndex++)//Assign all variation weights for this xform, with a max of MAX_CL_VARS. + emberCL.m_Xforms[i].m_VariationWeights[varIndex] = xform->GetVariation(varIndex)->m_Weight; + } + + return emberCL; +} + +/// +/// Convert the host side CarToRas object into a CarToRasCL object +/// for passing to OpenCL. +/// +/// The CarToRas object to convert +/// The CarToRasCL object +template +CarToRasCL RendererCL::ConvertCarToRas(const CarToRas& carToRas) +{ + CarToRasCL carToRasCL; + + carToRasCL.m_RasWidth = carToRas.RasWidth(); + carToRasCL.m_PixPerImageUnitW = carToRas.PixPerImageUnitW(); + carToRasCL.m_RasLlX = carToRas.RasLlX(); + carToRasCL.m_PixPerImageUnitH = carToRas.PixPerImageUnitH(); + carToRasCL.m_RasLlY = carToRas.RasLlY(); + carToRasCL.m_CarLlX = carToRas.CarLlX(); + carToRasCL.m_CarLlY = carToRas.CarLlY(); + carToRasCL.m_CarUrX = carToRas.CarUrX(); + carToRasCL.m_CarUrY = carToRas.CarUrY(); + + return carToRasCL; +} +} \ No newline at end of file diff --git a/Source/EmberCL/RendererCL.h b/Source/EmberCL/RendererCL.h new file mode 100644 index 0000000..241695d --- /dev/null +++ b/Source/EmberCL/RendererCL.h @@ -0,0 +1,156 @@ +#pragma once + +#include "EmberCLPch.h" +#include "OpenCLWrapper.h" +#include "IterOpenCLKernelCreator.h" +#include "DEOpenCLKernelCreator.h" +#include "FinalAccumOpenCLKernelCreator.h" + +/// +/// RendererCL class. +/// + +namespace EmberCLns +{ +class EMBERCL_API RendererCLBase +{ +public: + virtual bool ReadFinal(unsigned char* pixels) { return false; } + virtual bool ClearFinal() { return false; } +}; + +/// +/// RendererCL is a derivation of the basic CPU renderer which +/// overrides various functions to render on the GPU using OpenCL. +/// Since this class derives from EmberReport and also contains an +/// OpenCLWrapper member which also derives from EmberReport, the +/// reporting functions are overridden to aggregate the errors from +/// both sources. +/// It does not support different types for T and bucketT, so it only has one template argument +/// and uses both for the base. +/// +template +class EMBERCL_API RendererCL : public RendererCLBase, public Renderer +{ +public: + RendererCL(unsigned int platform = 0, unsigned int device = 0, bool shared = false, GLuint outputTexID = 0); + ~RendererCL(); + + //Ordinary member functions for OpenCL specific tasks. + bool Init(unsigned int platform, unsigned int device, bool shared, GLuint outputTexID); + inline unsigned int IterBlocksWide(); + inline unsigned int IterBlocksHigh(); + inline unsigned int IterBlockWidth(); + inline unsigned int IterBlockHeight(); + inline unsigned int IterGridWidth(); + inline unsigned int IterGridHeight(); + inline unsigned int TotalIterKernelCount(); + unsigned int PlatformIndex(); + unsigned int DeviceIndex(); + bool ReadHist(); + bool ReadAccum(); + bool ReadPoints(vector>& vec); + virtual bool ReadFinal(unsigned char* pixels); + virtual bool ClearFinal(); + bool ClearHist(); + bool ClearAccum(); + bool WritePoints(vector>& vec); + string IterKernel(); + + //Public virtual functions overriden from Renderer. + virtual unsigned __int64 MemoryAvailable(); + virtual bool Ok() const; + virtual void NumChannels(unsigned int numChannels); + virtual void DumpErrorReport(); + virtual void ClearErrorReport(); + virtual unsigned int SubBatchSize() const; + virtual unsigned int ThreadCount() const; + virtual void ThreadCount(unsigned int threads, const char* seedString = NULL); + virtual bool CreateDEFilter(bool& newAlloc); + virtual bool CreateSpatialFilter(bool& newAlloc); + virtual eRendererType RendererType() const; + virtual string ErrorReportString(); + virtual vector ErrorReport(); + +#ifndef TEST_CL +protected: +#endif + //Protected virtual functions overriden from Renderer. + virtual void MakeDmap(T colorScalar); + virtual bool Alloc(); + virtual bool ResetBuckets(bool resetHist = true, bool resetAccum = true); + virtual eRenderStatus LogScaleDensityFilter(); + virtual eRenderStatus GaussianDensityFilter(); + virtual eRenderStatus AccumulatorToFinalImage(unsigned char* pixels, size_t finalOffset); + virtual EmberStats Iterate(unsigned __int64 iterCount, unsigned int pass, unsigned int temporalSample); + +private: + //Private functions for making and running OpenCL programs. + bool BuildIterProgramForEmber(bool doAccum = true); + bool RunIter(unsigned __int64 iterCount, unsigned int pass, unsigned int temporalSample, unsigned __int64& itersRan); + eRenderStatus RunLogScaleFilter(); + eRenderStatus RunDensityFilter(); + eRenderStatus RunFinalAccum(); + bool ClearBuffer(string bufferName, unsigned int width, unsigned int height, unsigned int elementSize); + bool RunDensityFilterPrivate(unsigned int kernelIndex, unsigned int gridW, unsigned int gridH, unsigned int blockW, unsigned int blockH, unsigned int chunkSizeW, unsigned int chunkSizeH, unsigned int rowParity, unsigned int colParity); + int MakeAndGetDensityFilterProgram(unsigned int ss, unsigned int filterWidth); + int MakeAndGetFinalAccumProgram(T& alphaBase, T& alphaScale); + int MakeAndGetGammaCorrectionProgram(); + + //Private functions passing data to OpenCL programs. + DensityFilterCL ConvertDensityFilter(); + SpatialFilterCL ConvertSpatialFilter(); + EmberCL ConvertEmber(Ember& ember); + static CarToRasCL ConvertCarToRas(const CarToRas& carToRas); + + bool m_Init; + bool m_NVidia; + bool m_DoublePrecision; + unsigned int m_IterBlocksWide, m_IterBlockWidth; + unsigned int m_IterBlocksHigh, m_IterBlockHeight; + unsigned int m_MaxDEBlockSizeW; + unsigned int m_MaxDEBlockSizeH; + unsigned int m_WarpSize; + unsigned int m_Calls; + + string m_EmberBufferName; + string m_ParVarsBufferName; + string m_DistBufferName; + string m_CarToRasBufferName; + string m_DEFilterParamsBufferName; + string m_SpatialFilterParamsBufferName; + string m_DECoefsBufferName; + string m_DEWidthsBufferName; + string m_DECoefIndicesBufferName; + string m_SpatialFilterCoefsBufferName; + string m_HistBufferName; + string m_AccumBufferName; + string m_FinalImageName; + string m_PointsBufferName; + + string m_IterKernel; + + OpenCLWrapper m_Wrapper; + cl::ImageFormat m_PaletteFormat; + cl::ImageFormat m_FinalFormat; + cl::Image2D m_Palette; + IMAGEGL2D m_AccumImage; + GLuint m_OutputTexID; + EmberCL m_EmberCL; + Palette m_Dmap;//Used instead of the base class' m_Dmap because OpenCL only supports float textures. + CarToRasCL m_CarToRasCL; + DensityFilterCL m_DensityFilterCL; + SpatialFilterCL m_SpatialFilterCL; + IterOpenCLKernelCreator m_IterOpenCLKernelCreator; + DEOpenCLKernelCreator m_DEOpenCLKernelCreator; + FinalAccumOpenCLKernelCreator m_FinalAccumOpenCLKernelCreator; + pair> m_Params; + Ember m_LastBuiltEmber; +}; + +template EMBERCL_API class RendererCL; + +#ifdef DO_DOUBLE + template EMBERCL_API class RendererCL; +#endif +} \ No newline at end of file diff --git a/Source/EmberCommon/EmberCommon.h b/Source/EmberCommon/EmberCommon.h new file mode 100644 index 0000000..e2e9cce --- /dev/null +++ b/Source/EmberCommon/EmberCommon.h @@ -0,0 +1,261 @@ +#pragma once + +#include "EmberCommonPch.h" + +/// +/// Global utility classes and functions that are common to all programs that use +/// Ember and its derivatives. +/// + +/// +/// Derivation of the RenderCallback class to do custom printing action +/// whenever the progress function is internally called inside of Ember +/// and its derivatives. +/// Template argument expected to be float or double. +/// +template +class RenderProgress : public RenderCallback +{ +public: + /// + /// Constructor that initializes the state to zero. + /// + RenderProgress() + { + Clear(); + } + + /// + /// The progress function which will be called from inside the renderer. + /// + /// The ember currently being rendered + /// An extra dummy parameter + /// The progress fraction from 0-100 + /// The stage of iteration. 1 is iterating, 2 is density filtering, 2 is final accumulation. + /// The estimated milliseconds to completion of the current stage + /// 1 since this is intended to run in an environment where the render runs to completion, unlike interactive rendering. + virtual int ProgressFunc(Ember& ember, void* foo, double fraction, int stage, double etaMs) + { + if (stage == 0 || stage == 1) + { + if (m_LastStage != stage) + cout << endl; + + cout << "\r" << string(m_S.length(), ' ');//Clear what was previously here. + m_SS.str("");//Begin new output. + m_SS << "\rStage = " << (stage ? "filtering" : "chaos"); + m_SS << ", progress = " << int(fraction) << "%"; + m_SS << ", eta = " << t.Format(etaMs); + m_S = m_SS.str(); + cout << m_S; + } + + m_LastStage = stage; + return 1; + } + + /// + /// Reset the state. + /// + void Clear() + { + m_LastStage = 0; + m_LastLength = 0; + m_SS.clear(); + m_S.clear(); + } + +private: + int m_LastStage; + int m_LastLength; + stringstream m_SS; + string m_S; + Timing t; +}; + +/// +/// Wrapper for parsing an ember Xml file, storing the embers in a vector and printing +/// any errors that occurred. +/// Template argument expected to be float or double. +/// +/// The parser to use +/// The full path and name of the file +/// Storage for the embers read from the file +/// True if success, else false. +template +static bool ParseEmberFile(XmlToEmber& parser, string filename, vector>& embers) +{ + if (!parser.Parse(filename.c_str(), embers)) + { + cout << "Error parsing flame file " << filename << ", returning without executing." << endl; + return false; + } + + if (embers.empty()) + { + cout << "Error: No data present in file " << filename << ". Aborting." << endl; + return false; + } + + return true; +} + +/// +/// Wrapper for parsing palette Xml file and initializing it's private static members, +/// and printing any errors that occurred. +/// Template argument expected to be float or double. +/// +/// The full path and name of the file +/// True if success, else false. +template +static bool InitPaletteList(string filename) +{ + PaletteList paletteList;//Even though this is local, the members are static so they will remain. + + if (!paletteList.Init(filename)) + { + cout << "Error parsing palette file " << filename << ". Reason: " << endl; + cout << paletteList.ErrorReportString() << endl << "Returning without executing." << endl; + return false; + } + + return true; +} + +/// +/// Calculate the number of strips required if the needed amount of memory +/// is greater than the system memory, or greater than what the user want to allow. +/// +/// Amount of memory required +/// The maximum amount of memory to use. Use max if 0. +/// The number of strips to use +static unsigned int CalcStrips(double mem, double memAvailable, double useMem) +{ + unsigned int strips; + double memRequired; + + if (useMem > 0) + memAvailable = useMem; + else + memAvailable *= 0.8; + + memRequired = mem; + + if (memAvailable >= memRequired) + return 1; + + strips = (unsigned int)ceil(memRequired / memAvailable); + + return strips; +} + +/// +/// Given a numerator and a denominator, find the next highest denominator that divides +/// evenly into the numerator. +/// +/// The numerator +/// The denominator +/// The next highest divisor if found, else 1. +static unsigned int NextHighestEvenDiv(unsigned int numerator, unsigned int denominator) +{ + unsigned int result = 1; + unsigned int numDiv2 = numerator / 2; + + do + { + denominator++; + + if (numerator % denominator == 0) + { + result = denominator; + break; + } + } + while (denominator <= numDiv2); + + return result; +} + +/// +/// Given a numerator and a denominator, find the next lowest denominator that divides +/// evenly into the numerator. +/// +/// The numerator +/// The denominator +/// The next lowest divisor if found, else 1. +static unsigned int NextLowestEvenDiv(unsigned int numerator, unsigned int denominator) +{ + unsigned int result = 1; + unsigned int numDiv2 = numerator / 2; + + denominator--; + + if (denominator > numDiv2) + denominator = numDiv2; + + while (denominator >= 1) + { + if (numerator % denominator == 0) + { + result = denominator; + break; + } + + denominator--; + } + + return result; +} + +/// +/// Wrapper for creating a renderer of the specified type. +/// First template argument expected to be float or double for CPU renderer, +/// Second argument expected to be float or double for CPU renderer, and only float for OpenCL renderer. +/// +/// Type of renderer to create +/// The index platform of the platform to use +/// The index device of the device to use +/// True if shared with OpenGL, else false. +/// The texture ID of the shared OpenGL texture if shared +/// The error report for holding errors if anything goes wrong +/// A pointer to the created renderer if successful, else false. +template +static Renderer* CreateRenderer(eRendererType renderType, unsigned int platform, unsigned int device, bool shared, GLuint texId, EmberReport& errorReport) +{ + string s; + auto_ptr> renderer; + + try + { + if (renderType == CPU_RENDERER) + { + s = "CPU"; + renderer = auto_ptr>(new Renderer()); + } + else if (renderType == OPENCL_RENDERER) + { + s = "OpenCL"; + renderer = auto_ptr>(new RendererCL(platform, device, shared, texId)); + + if (!renderer.get() || !renderer->Ok()) + { + if (renderer.get()) + errorReport.AddToReport(renderer->ErrorReport()); + + errorReport.AddToReport("Error initializing OpenCL renderer, using CPU renderer instead."); + renderer = auto_ptr>(new Renderer()); + } + } + } + catch (...) + { + errorReport.AddToReport("Error creating " + s + " renderer.\n"); + } + + return renderer.release(); +} + +/// +/// Simple macro to print a string if the --verbose options has been specified. +/// +#define VerbosePrint(s) if (opt.Verbose()) cout << s << endl diff --git a/Source/EmberCommon/EmberCommonPch.cpp b/Source/EmberCommon/EmberCommonPch.cpp new file mode 100644 index 0000000..4619b97 --- /dev/null +++ b/Source/EmberCommon/EmberCommonPch.cpp @@ -0,0 +1 @@ +#include "EmberCommonPch.h" diff --git a/Source/EmberCommon/EmberCommonPch.h b/Source/EmberCommon/EmberCommonPch.h new file mode 100644 index 0000000..920b281 --- /dev/null +++ b/Source/EmberCommon/EmberCommonPch.h @@ -0,0 +1,54 @@ +#pragma once + +/// +/// Precompiled header file. Place all system includes here with appropriate #defines for different operating systems and compilers. +/// + +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN//Exclude rarely-used stuff from Windows headers. +#define _USE_MATH_DEFINES + +#ifdef _WIN32 +#include +#include +#include //For htons(). +#define snprintf _snprintf +#else +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "jconfig.h" +#include "jpeglib.h" + +#include "png.h" +//#include "pnginfo.h" + +//Ember. +#include "Ember.h" +#include "Variation.h" +#include "EmberToXml.h" +#include "XmlToEmber.h" +#include "PaletteList.h" +#include "Iterator.h" +#include "Renderer.h" +#include "RendererCL.h" +#include "SheepTools.h" + +//Options. +#include "SimpleGlob.h" +#include "SimpleOpt.h" + +using namespace EmberNs; +using namespace EmberCLns; diff --git a/Source/EmberCommon/EmberOptions.h b/Source/EmberCommon/EmberOptions.h new file mode 100644 index 0000000..fc06f78 --- /dev/null +++ b/Source/EmberCommon/EmberOptions.h @@ -0,0 +1,705 @@ +#pragma once + +#include "EmberCommon.h" + +/// +/// EmberOptionEntry and EmberOptions classes. +/// + +static char* DescriptionString = "Ember - Fractal flames C++ port and enhancement with OpenCL GPU support"; + +/// +/// Enum for specifying which command line programs an option is meant to be used with. +/// If an option is used with multiple programs, their values are ORed together. +/// +enum eOptionUse +{ + OPT_USE_RENDER = 1, + OPT_USE_ANIMATE = 1 << 1, + OPT_USE_GENOME = 1 << 2, + OPT_RENDER_ANIM = OPT_USE_RENDER | OPT_USE_ANIMATE, + OPT_ANIM_GENOME = OPT_USE_ANIMATE | OPT_USE_GENOME, + OPT_USE_ALL = OPT_USE_RENDER | OPT_USE_ANIMATE | OPT_USE_GENOME +}; + +/// +/// Unique identifiers for every available option across all programs. +/// +enum eOptionIDs +{ + //Diagnostic args. + OPT_HELP, + OPT_VERSION, + OPT_VERBOSE, + OPT_DEBUG, + OPT_DUMP_ARGS, + OPT_PROGRESS, + OPT_DUMP_OPENCL_INFO, + + //Boolean args. + OPT_OPENCL, + OPT_EARLYCLIP, + OPT_TRANSPARENCY, + OPT_NAME_ENABLE, + OPT_INT_PALETTE, + OPT_HEX_PALETTE, + OPT_INSERT_PALETTE, + OPT_JPEG_COMMENTS, + OPT_PNG_COMMENTS, + OPT_WRITE_GENOME, + OPT_ENCLOSED, + OPT_NO_EDITS, + OPT_UNSMOOTH_EDGE, + OPT_LOCK_ACCUM, + OPT_DUMP_KERNEL, + + //Value args. + OPT_OPENCL_PLATFORM,//Int value args. + OPT_OPENCL_DEVICE, + OPT_SEED, + OPT_NTHREADS, + OPT_STRIPS, + OPT_BITS, + OPT_BPC, + OPT_SBS, + OPT_PRINT_EDIT_DEPTH, + OPT_JPEG, + OPT_BEGIN, + OPT_END, + OPT_FRAME, + OPT_TIME, + OPT_DTIME, + OPT_NFRAMES, + OPT_SYMMETRY, + OPT_SHEEP_GEN, + OPT_SHEEP_ID, + OPT_LOOPS, + OPT_REPEAT, + OPT_TRIES, + OPT_MAX_XFORMS, + + OPT_SS,//Float value args. + OPT_QS, + OPT_PIXEL_ASPECT, + OPT_STAGGER, + OPT_AVG_THRESH, + OPT_BLACK_THRESH, + OPT_WHITE_LIMIT, + OPT_SPEED, + OPT_OFFSETX, + OPT_OFFSETY, + OPT_USEMEM, + + OPT_ISAAC_SEED,//String value args. + OPT_IN, + OPT_OUT, + OPT_PREFIX, + OPT_SUFFIX, + OPT_FORMAT, + OPT_PALETTE_FILE, + OPT_PALETTE_IMAGE, + OPT_ID, + OPT_URL, + OPT_NICK, + OPT_COMMENT, + OPT_TEMPLATE, + OPT_CLONE, + OPT_CLONE_ALL, + OPT_CLONE_ACTION, + OPT_ANIMATE, + OPT_MUTATE, + OPT_CROSS0, + OPT_CROSS1, + OPT_METHOD, + OPT_INTER, + OPT_ROTATE, + OPT_STRIP, + OPT_SEQUENCE, + OPT_USE_VARS, + OPT_DONT_USE_VARS, + OPT_EXTRAS +}; + +class EmberOptions; + +/// +/// A single option. +/// Template argument expected to be bool, int, unsigned int, double or string. +/// +template +class EmberOptionEntry +{ +friend class EmberOptions; + +private: + /// + /// Default constructor. This should never be used, instead use the one that takes arguments. + /// + EmberOptionEntry() + { + m_OptionUse = OPT_USE_ALL; + m_Option.nArgType = SO_NONE; + m_Option.nId = 0; + m_Option.pszArg = _T("--fillmein"); + m_DocString = "Dummy doc"; + } + +public: + /// + /// Constructor that takes arguments. + /// + /// The specified program usage + /// The option identifier enum + /// The command line argument (--arg) + /// The default value to use the option was not given on the command line + /// The format the argument should be given in + /// The documentation string describing what the argument means + EmberOptionEntry(eOptionUse optUsage, eOptionIDs optId, const CharT* arg, T defaultVal, ESOArgType argType, string docString) + { + m_OptionUse = optUsage; + m_Option.nId = (int)optId; + m_Option.pszArg = arg; + m_Option.nArgType = argType; + m_DocString = docString; + m_NameWithoutDashes = Trim(string(arg), '-'); + m_Val = Arg((char*)m_NameWithoutDashes.c_str(), defaultVal); + } + + /// + /// Copy constructor. + /// + /// The EmberOptionEntry object to copy + EmberOptionEntry(const EmberOptionEntry& entry) + { + *this = entry; + } + + /// + /// Functor accessors. + /// + inline T operator() (void) { return m_Val; } + inline void operator() (T t) { m_Val = t; } + +private: + eOptionUse m_OptionUse; + CSimpleOpt::SOption m_Option; + string m_DocString; + string m_NameWithoutDashes; + T m_Val; +}; + +/// +/// Macros for setting up and parsing various option types. +/// + +//Bool. +#define Eob EmberOptionEntry +#define INITBOOLOPTION(member, option) \ + member = option; \ + m_BoolArgs.push_back(&member) + +#define PARSEBOOLOPTION(opt, member) \ + case (opt): \ + member(true); \ + break + +//Int. +#define Eoi EmberOptionEntry +#define INITINTOPTION(member, option) \ + member = option; \ + m_IntArgs.push_back(&member) + +#define PARSEINTOPTION(opt, member) \ + case (opt): \ + sscanf_s(args.OptionArg(), "%d", &member.m_Val); \ + break + +//Uint. +#define Eou EmberOptionEntry +#define INITUINTOPTION(member, option) \ + member = option; \ + m_UintArgs.push_back(&member) + +#define PARSEUINTOPTION(opt, member) \ + case (opt): \ + sscanf_s(args.OptionArg(), "%u", &member.m_Val); \ + break + +//Double. +#define Eod EmberOptionEntry +#define INITDOUBLEOPTION(member, option) \ + member = option; \ + m_DoubleArgs.push_back(&member) + +#define PARSEDOUBLEOPTION(opt, member) \ + case (opt): \ + sscanf_s(args.OptionArg(), "%lf", &member.m_Val); \ + break + +//String. +#define Eos EmberOptionEntry +#define INITSTRINGOPTION(member, option) \ + member = option; \ + m_StringArgs.push_back(&member) + +#define PARSESTRINGOPTION(opt, member) \ + case (opt): \ + member.m_Val = args.OptionArg(); \ + break + +/// +/// Class for holding all available options across all command line programs. +/// Some are used only for a single program, while others are used for more than one. +/// This prevents having to keep separate documentation strings around for different programs. +/// +class EmberOptions +{ +public: + /// + /// Constructor that populates all available options. + /// + EmberOptions() + { + m_BoolArgs.reserve(25); + m_IntArgs.reserve(25); + m_UintArgs.reserve(25); + m_DoubleArgs.reserve(25); + m_StringArgs.reserve(35); + + //Diagnostic bools. + INITBOOLOPTION(Help, Eob(OPT_USE_ALL, OPT_HELP, _T("--help"), false, SO_NONE, "\t--help Show this screen.\n")); + INITBOOLOPTION(Version, Eob(OPT_USE_ALL, OPT_VERSION, _T("--version"), false, SO_NONE, "\t--version Show version.\n")); + INITBOOLOPTION(Verbose, Eob(OPT_USE_ALL, OPT_VERBOSE, _T("--verbose"), false, SO_NONE, "\t--verbose Verbose output.\n")); + INITBOOLOPTION(Debug, Eob(OPT_USE_ALL, OPT_DEBUG, _T("--debug"), false, SO_NONE, "\t--debug Debug output.\n")); + INITBOOLOPTION(DumpArgs, Eob(OPT_USE_ALL, OPT_DUMP_ARGS, _T("--dumpargs"), false, SO_NONE, "\t--dumpargs Print all arguments entered from either the command line or environment variables.\n")); + INITBOOLOPTION(DoProgress, Eob(OPT_USE_ALL, OPT_PROGRESS, _T("--progress"), false, SO_NONE, "\t--progress Display progress. This will slow down processing by about 10%%.\n")); + INITBOOLOPTION(OpenCLInfo, Eob(OPT_USE_ALL, OPT_DUMP_OPENCL_INFO, _T("--openclinfo"), false, SO_NONE, "\t--openclinfo Display platforms and devices for OpenCL.\n")); + + //Execution bools. + INITBOOLOPTION(EmberCL, Eob(OPT_USE_ALL, OPT_OPENCL, _T("--opencl"), false, SO_NONE, "\t--opencl Use OpenCL renderer (EmberCL) for rendering [default: false].\n")); + INITBOOLOPTION(EarlyClip, Eob(OPT_USE_ALL, OPT_EARLYCLIP, _T("--earlyclip"), false, SO_NONE, "\t--earlyclip Perform clipping of RGB values before spatial filtering for better antialiasing and resizing [default: false].\n")); + INITBOOLOPTION(Transparency, Eob(OPT_RENDER_ANIM, OPT_TRANSPARENCY, _T("--transparency"), false, SO_NONE, "\t--transparency Include alpha channel in final output [default: false except for PNG].\n")); + INITBOOLOPTION(NameEnable, Eob(OPT_USE_RENDER, OPT_NAME_ENABLE, _T("--name_enable"), false, SO_NONE, "\t--name_enable Use the name attribute contained in the xml as the output filename [default: false].\n")); + INITBOOLOPTION(IntPalette, Eob(OPT_USE_ALL, OPT_INT_PALETTE, _T("--intpalette"), false, SO_NONE, "\t--intpalette Force palette RGB values to be integers [default: false (float)].\n")); + INITBOOLOPTION(HexPalette, Eob(OPT_USE_ALL, OPT_HEX_PALETTE, _T("--hex_palette"), true, SO_NONE, "\t--hex_palette Force palette RGB values to be hex [default: true].\n")); + INITBOOLOPTION(InsertPalette, Eob(OPT_RENDER_ANIM, OPT_INSERT_PALETTE, _T("--insert_palette"), false, SO_NONE, "\t--insert_palette Insert the palette into the image for debugging purposes [default: false].\n")); + INITBOOLOPTION(JpegComments, Eob(OPT_RENDER_ANIM, OPT_JPEG_COMMENTS, _T("--enable_jpeg_comments"), true, SO_NONE, "\t--enable_jpeg_comments Enables comments in the jpeg header [default: true].\n")); + INITBOOLOPTION(PngComments, Eob(OPT_RENDER_ANIM, OPT_PNG_COMMENTS, _T("--enable_png_comments"), true, SO_NONE, "\t--enable_png_comments Enables comments in the png header [default: true].\n")); + INITBOOLOPTION(WriteGenome, Eob(OPT_USE_ANIMATE, OPT_WRITE_GENOME, _T("--write_genome"), false, SO_NONE, "\t--write_genome Write out flame associated with center of motion blur window [default: false].\n")); + INITBOOLOPTION(Enclosed, Eob(OPT_USE_GENOME, OPT_ENCLOSED, _T("--enclosed"), true, SO_NONE, "\t--enclosed Use enclosing XML tags [default: false].\n")); + INITBOOLOPTION(NoEdits, Eob(OPT_USE_GENOME, OPT_NO_EDITS, _T("--noedits"), false, SO_NONE, "\t--noedits Exclude edit tags when writing Xml [default: false].\n")); + INITBOOLOPTION(UnsmoothEdge, Eob(OPT_USE_GENOME, OPT_UNSMOOTH_EDGE, _T("--unsmoother"), false, SO_NONE, "\t--unsmoother Do not use smooth blending for sheep edges [default: false].\n")); + INITBOOLOPTION(LockAccum, Eob(OPT_USE_ALL, OPT_LOCK_ACCUM, _T("--lock_accum"), false, SO_NONE, "\t--lock_accum Lock threads when accumulating to the histogram using the CPU (ignored for OpenCL). This will drop performance to that of single threading [default: false].\n")); + INITBOOLOPTION(DumpKernel, Eob(OPT_USE_RENDER, OPT_DUMP_KERNEL, _T("--dump_kernel"), false, SO_NONE, "\t--dump_kernel Print the iteration kernel string when using OpenCL (ignored for CPU) [default: false].\n")); + + //Int. + INITINTOPTION(Symmetry, Eoi(OPT_USE_GENOME, OPT_SYMMETRY, _T("--symmetry"), 0, SO_REQ_SEP, "\t--symmetry= Set symmetry of result [default: 0].\n")); + INITINTOPTION(SheepGen, Eoi(OPT_USE_GENOME, OPT_SHEEP_GEN, _T("--sheep_gen"), -1, SO_REQ_SEP, "\t--sheep_gen= Sheep generation of this flame [default: -1].\n")); + INITINTOPTION(SheepId, Eoi(OPT_USE_GENOME, OPT_SHEEP_ID, _T("--sheep_id"), -1, SO_REQ_SEP, "\t--sheep_id= Sheep ID of this flame [default: -1].\n")); + INITUINTOPTION(Platform, Eou(OPT_RENDER_ANIM, OPT_OPENCL_PLATFORM, _T("--platform"), 0, SO_REQ_SEP, "\t--platform The OpenCL platform index to use [default: 0].\n")); + INITUINTOPTION(Device, Eou(OPT_RENDER_ANIM, OPT_OPENCL_DEVICE, _T("--device"), 0, SO_REQ_SEP, "\t--device The OpenCL device index within the specified platform to use [default: 0].\n")); + INITUINTOPTION(Seed, Eou(OPT_USE_ALL, OPT_SEED, _T("--seed"), 0, SO_REQ_SEP, "\t--seed= Integer seed to use for the random number generator [default: random].\n")); + INITUINTOPTION(ThreadCount, Eou(OPT_USE_ALL, OPT_NTHREADS, _T("--nthreads"), 0, SO_REQ_SEP, "\t--nthreads= The number of threads to use [default: use all available cores].\n")); + INITUINTOPTION(Strips, Eou(OPT_USE_RENDER, OPT_STRIPS, _T("--nstrips"), 1, SO_REQ_SEP, "\t--nstrips= The number of fractions to split a single render frame into. Useful for print size renders or low memory systems [default: 1].\n")); + INITUINTOPTION(BitsPerChannel, Eou(OPT_RENDER_ANIM, OPT_BPC, _T("--bpc"), 8, SO_REQ_SEP, "\t--bpc= Bits per channel. 8 or 16 for PNG, 8 for all others [default: 8].\n")); + INITUINTOPTION(SubBatchSize, Eou(OPT_USE_ALL, OPT_SBS, _T("--sub_batch_size"), 10000, SO_REQ_SEP, "\t--sub_batch_size= The chunk size that iterating will be broken into [default: 10000].\n")); + INITUINTOPTION(Bits, Eou(OPT_USE_ALL, OPT_BITS, _T("--bits"), 33, SO_REQ_SEP, "\t--bits= Determines the types used for the histogram and accumulator [default: 33].\n" + "\t\t\t\t\t32: Histogram: float, Accumulator: float.\n" + "\t\t\t\t\t33: Histogram: float, Accumulator: float.\n"//This differs from the original which used an int hist for bits 33. + "\t\t\t\t\t64: Histogram: double, Accumulator: double.\n")); + + INITUINTOPTION(PrintEditDepth, Eou(OPT_USE_ALL, OPT_PRINT_EDIT_DEPTH, _T("--print_edit_depth"), 0, SO_REQ_SEP, "\t--print_edit_depth= Depth to truncate tag structure when converting a flame to xml. 0 prints all tags [default: 0].\n")); + INITUINTOPTION(JpegQuality, Eou(OPT_USE_ALL, OPT_JPEG, _T("--jpeg"), 95, SO_REQ_SEP, "\t--jpeg= Jpeg quality 0-100 for compression [default: 95].\n")); + INITUINTOPTION(FirstFrame, Eou(OPT_USE_ANIMATE, OPT_BEGIN, _T("--begin"), UINT_MAX, SO_REQ_SEP, "\t--begin= Time of first frame to render [default: first time specified in file].\n")); + INITUINTOPTION(LastFrame, Eou(OPT_USE_ANIMATE, OPT_END, _T("--end"), UINT_MAX, SO_REQ_SEP, "\t--end= Time of last frame to render [default: last time specified in the input file].\n")); + INITUINTOPTION(Time, Eou(OPT_ANIM_GENOME, OPT_TIME, _T("--time"), 0, SO_REQ_SEP, "\t--time= Time of first and last frame (ie do one frame).\n")); + INITUINTOPTION(Frame, Eou(OPT_ANIM_GENOME, OPT_FRAME, _T("--frame"), 0, SO_REQ_SEP, "\t--frame= Synonym for \"time\".\n")); + INITUINTOPTION(Dtime, Eou(OPT_USE_ANIMATE, OPT_DTIME, _T("--dtime"), 1, SO_REQ_SEP, "\t--dtime= Time between frames [default: 1].\n")); + INITUINTOPTION(Frames, Eou(OPT_USE_GENOME, OPT_NFRAMES, _T("--nframes"), 20, SO_REQ_SEP, "\t--nframes= Number of frames for each stage of the animation [default: 20].\n")); + INITUINTOPTION(Loops, Eou(OPT_USE_GENOME, OPT_LOOPS, _T("--loops"), 1, SO_REQ_SEP, "\t--loops= Number of times to rotate each control point in sequence [default: 1].\n")); + INITUINTOPTION(Repeat, Eou(OPT_USE_GENOME, OPT_REPEAT, _T("--repeat"), 1, SO_REQ_SEP, "\t--repeat= Number of new flames to create. Ignored if sequence, inter or rotate were specified [default: 1].\n")); + INITUINTOPTION(Tries, Eou(OPT_USE_GENOME, OPT_TRIES, _T("--tries"), 10, SO_REQ_SEP, "\t--tries= Number times to try creating a flame that meets the specified constraints. Ignored if sequence, inter or rotate were specified [default: 10].\n")); + INITUINTOPTION(MaxXforms, Eou(OPT_USE_GENOME, OPT_MAX_XFORMS, _T("--maxxforms"), UINT_MAX, SO_REQ_SEP, "\t--maxxforms= The maximum number of xforms allowed in the final output.\n")); + + //Double. + INITDOUBLEOPTION(SizeScale, Eod(OPT_USE_ALL, OPT_SS, _T("--ss"), 1, SO_REQ_SEP, "\t--ss= Size scale. All dimensions are scaled by this amount [default: 1.0].\n")); + INITDOUBLEOPTION(QualityScale, Eod(OPT_USE_ALL, OPT_QS, _T("--qs"), 1, SO_REQ_SEP, "\t--qs= Quality scale. All quality values are scaled by this amount [default: 1.0].\n")); + INITDOUBLEOPTION(AspectRatio, Eod(OPT_USE_ALL, OPT_PIXEL_ASPECT, _T("--pixel_aspect"), 1, SO_REQ_SEP, "\t--pixel_aspect= Aspect ratio of pixels (width over height), eg. 0.90909 for NTSC [default: 1.0].\n")); + INITDOUBLEOPTION(Stagger, Eod(OPT_USE_ALL, OPT_STAGGER, _T("--stagger"), 0, SO_REQ_SEP, "\t--stagger= Affects simultaneity of xform interpolation during flame interpolation.\n" + "\t Represents how 'separate' the xforms are interpolated. Set to 1 for each\n" + "\t xform to be interpolated individually, fractions control interpolation overlap [default: 0].\n")); + INITDOUBLEOPTION(AvgThresh, Eod(OPT_USE_GENOME, OPT_AVG_THRESH, _T("--avg"), 20.0, SO_REQ_SEP, "\t--avg= Minimum average pixel channel sum (r + g + b) threshold from 0 - 765. Ignored if sequence, inter or rotate were specified [default: 20].\n")); + INITDOUBLEOPTION(BlackThresh, Eod(OPT_USE_GENOME, OPT_BLACK_THRESH, _T("--black"), 0.01, SO_REQ_SEP, "\t--black= Minimum number of allowed black pixels as a percentage from 0 - 1. Ignored if sequence, inter or rotate were specified [default: 0.01].\n")); + INITDOUBLEOPTION(WhiteLimit, Eod(OPT_USE_GENOME, OPT_WHITE_LIMIT, _T("--white"), 0.05, SO_REQ_SEP, "\t--white= Maximum number of allowed white pixels as a percentage from 0 - 1. Ignored if sequence, inter or rotate were specified [default: 0.05].\n")); + INITDOUBLEOPTION(Speed, Eod(OPT_USE_GENOME, OPT_SPEED, _T("--speed"), 0.1, SO_REQ_SEP, "\t--speed= Speed as a percentage from 0 - 1 that the affine transform of an existing flame mutates with the new flame. Ignored if sequence, inter or rotate were specified [default: 0.1].\n")); + INITDOUBLEOPTION(OffsetX, Eod(OPT_USE_GENOME, OPT_OFFSETX, _T("--offsetx"), 0.0, SO_REQ_SEP, "\t--offsetx= Amount to jitter each flame horizontally when applying genome tools [default: 0].\n")); + INITDOUBLEOPTION(OffsetY, Eod(OPT_USE_GENOME, OPT_OFFSETY, _T("--offsety"), 0.0, SO_REQ_SEP, "\t--offsety= Amount to jitter each flame vertically when applying genome tools [default: 0].\n")); + INITDOUBLEOPTION(UseMem, Eod(OPT_USE_RENDER, OPT_USEMEM, _T("--use_mem"), 0.0, SO_REQ_SEP, "\t--use_mem= Number of bytes of memory to use [default: max system memory].\n")); + + //String. + INITSTRINGOPTION(IsaacSeed, Eos(OPT_USE_ALL, OPT_ISAAC_SEED, _T("--isaac_seed"), "", SO_REQ_SEP, "\t--isaac_seed= Character-based seed for the random number generator [default: random].\n")); + INITSTRINGOPTION(Input, Eos(OPT_USE_ALL, OPT_IN, _T("--in"), "", SO_REQ_SEP, "\t--in= Name of the input file.\n")); + INITSTRINGOPTION(Out, Eos(OPT_USE_ALL, OPT_OUT, _T("--out"), "", SO_REQ_SEP, "\t--out= Name of a single output file. Not recommended when rendering more than one image.\n")); + INITSTRINGOPTION(Prefix, Eos(OPT_USE_ALL, OPT_PREFIX, _T("--prefix"), "", SO_REQ_SEP, "\t--prefix= Prefix to prepend to all output files.\n")); + INITSTRINGOPTION(Suffix, Eos(OPT_USE_ALL, OPT_SUFFIX, _T("--suffix"), "", SO_REQ_SEP, "\t--suffix= Suffix to append to all output files.\n")); + INITSTRINGOPTION(Format, Eos(OPT_RENDER_ANIM, OPT_FORMAT, _T("--format"), "png", SO_REQ_SEP, "\t--format= Format of the output file. Valid values are: bmp, jpg, png, ppm [default: jpg].\n")); + INITSTRINGOPTION(PalettePath, Eos(OPT_USE_ALL, OPT_PALETTE_FILE, _T("--flam3_palettes"), "flam3-palettes.xml", SO_REQ_SEP, "\t--flam3_palettes= Path and name of the palette file [default: flam3-palettes.xml].\n")); + INITSTRINGOPTION(PaletteImage, Eos(OPT_USE_ALL, OPT_PALETTE_IMAGE, _T("--image"), "", SO_REQ_SEP, "\t--image= Replace palette with png, jpg, or ppm image.\n")); + INITSTRINGOPTION(Id, Eos(OPT_USE_ALL, OPT_ID, _T("--id"), "", SO_REQ_SEP, "\t--id= ID to use in tags.\n")); + INITSTRINGOPTION(Url, Eos(OPT_USE_ALL, OPT_URL, _T("--url"), "", SO_REQ_SEP, "\t--url= URL to use in tags / img comments.\n")); + INITSTRINGOPTION(Nick, Eos(OPT_USE_ALL, OPT_NICK, _T("--nick"), "", SO_REQ_SEP, "\t--nick= Nickname to use in tags / img comments.\n")); + INITSTRINGOPTION(Comment, Eos(OPT_USE_GENOME, OPT_COMMENT, _T("--comment"), "", SO_REQ_SEP, "\t--comment= Comment to use in tags.\n")); + + INITSTRINGOPTION(TemplateFile, Eos(OPT_USE_GENOME, OPT_TEMPLATE, _T("--template"), "", SO_REQ_SEP, "\t--template= Apply defaults based on this flame.\n")); + INITSTRINGOPTION(Clone, Eos(OPT_USE_GENOME, OPT_CLONE, _T("--clone"), "", SO_REQ_SEP, "\t--clone= Clone random flame in input.\n")); + INITSTRINGOPTION(CloneAll, Eos(OPT_USE_GENOME, OPT_CLONE_ALL, _T("--clone_all"), "", SO_REQ_SEP, "\t--clone_all= Clones all flames in the input file. Useful for applying template to all flames.\n")); + INITSTRINGOPTION(CloneAction, Eos(OPT_USE_GENOME, OPT_CLONE_ACTION, _T("--clone_action"), "", SO_REQ_SEP, "\t--clone_action= A description of the clone action taking place.\n")); + INITSTRINGOPTION(Animate, Eos(OPT_USE_GENOME, OPT_ANIMATE, _T("--animate"), "", SO_REQ_SEP, "\t--animate= Interpolates between all flames in the input file, using times specified in file.\n")); + INITSTRINGOPTION(Mutate, Eos(OPT_USE_GENOME, OPT_MUTATE, _T("--mutate"), "", SO_REQ_SEP, "\t--mutate= Randomly mutate a random flame from the input file.\n")); + INITSTRINGOPTION(Cross0, Eos(OPT_USE_GENOME, OPT_CROSS0, _T("--cross0"), "", SO_REQ_SEP, "\t--cross0= Randomly select one flame from the input file to genetically cross...\n")); + INITSTRINGOPTION(Cross1, Eos(OPT_USE_GENOME, OPT_CROSS1, _T("--cross1"), "", SO_REQ_SEP, "\t--cross1= ...with one flame from this file.\n")); + INITSTRINGOPTION(Method, Eos(OPT_USE_GENOME, OPT_METHOD, _T("--method"), "", SO_REQ_SEP, "\t--method= Method used for genetic cross: alternate, interpolate, or union. For mutate: all_vars, one_xform, add_symmetry, post_xforms, color_palette, delete_xform, all_coefs [default: random].\n"));//Original ommitted this important documentation for mutate! + INITSTRINGOPTION(Inter, Eos(OPT_USE_GENOME, OPT_INTER, _T("--inter"), "", SO_REQ_SEP, "\t--inter= Interpolate the input file.\n")); + INITSTRINGOPTION(Rotate, Eos(OPT_USE_GENOME, OPT_ROTATE, _T("--rotate"), "", SO_REQ_SEP, "\t--rotate= Rotate the input file.\n")); + INITSTRINGOPTION(Strip, Eos(OPT_USE_GENOME, OPT_STRIP, _T("--strip"), "", SO_REQ_SEP, "\t--strip= Break strip out of each flame in the input file.\n")); + INITSTRINGOPTION(Sequence, Eos(OPT_USE_GENOME, OPT_SEQUENCE, _T("--sequence"), "", SO_REQ_SEP, "\t--sequence= 360 degree rotation 'loops' times of each control point in the input file plus rotating transitions.\n")); + INITSTRINGOPTION(UseVars, Eos(OPT_USE_GENOME, OPT_USE_VARS, _T("--use_vars"), "", SO_REQ_SEP, "\t--use_vars= Comma separated list of variation #'s to use when generating a random flame.\n")); + INITSTRINGOPTION(DontUseVars, Eos(OPT_USE_GENOME, OPT_DONT_USE_VARS, _T("--dont_use_vars"), "", SO_REQ_SEP, "\t--dont_use_vars= Comma separated list of variation #'s to NOT use when generating a random flame.\n")); + INITSTRINGOPTION(Extras, Eos(OPT_USE_GENOME, OPT_EXTRAS, _T("--extras"), "", SO_REQ_SEP, "\t--extras= Extra attributes to place in the flame section of the Xml.\n")); + } + + /// + /// Parse and populate the supplied command line options for the specified program usage. + /// If --help or --version were specified, information will be printed + /// and parsing will cease. + /// + /// The number of command line arguments passed + /// The command line arguments passed + /// The program for which these options are to be parsed and used. + /// True if --help or --version specified, else false + bool Populate(int argc, _TCHAR* argv[], eOptionUse optUsage) + { + EmberOptions options; + vector sOptions = options.GetSimpleOptions(); + CSimpleOpt args(argc, argv, sOptions.data()); + + //Process args. + while (args.Next()) + { + ESOError errorCode = args.LastError(); + + if (errorCode == SO_SUCCESS) + { + switch (args.OptionId()) + { + case OPT_HELP://Bool args. + { + ShowUsage(optUsage); + return true; + } + case OPT_VERSION: + { + cout << EmberVersion() << endl; + return true; + } + PARSEBOOLOPTION(OPT_VERBOSE, Verbose); + PARSEBOOLOPTION(OPT_DEBUG, Debug); + PARSEBOOLOPTION(OPT_DUMP_ARGS, DumpArgs); + PARSEBOOLOPTION(OPT_PROGRESS, DoProgress); + PARSEBOOLOPTION(OPT_DUMP_OPENCL_INFO, OpenCLInfo); + PARSEBOOLOPTION(OPT_OPENCL, EmberCL); + PARSEBOOLOPTION(OPT_EARLYCLIP, EarlyClip); + PARSEBOOLOPTION(OPT_TRANSPARENCY, Transparency); + PARSEBOOLOPTION(OPT_NAME_ENABLE, NameEnable); + PARSEBOOLOPTION(OPT_INT_PALETTE, IntPalette); + PARSEBOOLOPTION(OPT_HEX_PALETTE, HexPalette); + PARSEBOOLOPTION(OPT_INSERT_PALETTE, InsertPalette); + PARSEBOOLOPTION(OPT_JPEG_COMMENTS, JpegComments); + PARSEBOOLOPTION(OPT_PNG_COMMENTS, PngComments); + PARSEBOOLOPTION(OPT_WRITE_GENOME, WriteGenome); + PARSEBOOLOPTION(OPT_ENCLOSED, Enclosed); + PARSEBOOLOPTION(OPT_NO_EDITS, NoEdits); + PARSEBOOLOPTION(OPT_UNSMOOTH_EDGE, UnsmoothEdge); + PARSEBOOLOPTION(OPT_LOCK_ACCUM, LockAccum); + PARSEBOOLOPTION(OPT_DUMP_KERNEL, DumpKernel); + + PARSEINTOPTION(OPT_SYMMETRY, Symmetry);//Int args + PARSEINTOPTION(OPT_SHEEP_GEN, SheepGen); + PARSEINTOPTION(OPT_SHEEP_ID, SheepId); + PARSEUINTOPTION(OPT_OPENCL_PLATFORM, Platform);//Unsigned int args. + PARSEUINTOPTION(OPT_OPENCL_DEVICE, Device); + PARSEUINTOPTION(OPT_SEED, Seed); + PARSEUINTOPTION(OPT_NTHREADS, ThreadCount); + PARSEUINTOPTION(OPT_STRIPS, Strips); + PARSEUINTOPTION(OPT_BITS, Bits); + PARSEUINTOPTION(OPT_BPC, BitsPerChannel); + PARSEUINTOPTION(OPT_SBS, SubBatchSize); + PARSEUINTOPTION(OPT_PRINT_EDIT_DEPTH, PrintEditDepth); + PARSEUINTOPTION(OPT_JPEG, JpegQuality); + PARSEUINTOPTION(OPT_BEGIN, FirstFrame); + PARSEUINTOPTION(OPT_END, LastFrame); + PARSEUINTOPTION(OPT_FRAME, Frame); + PARSEUINTOPTION(OPT_TIME, Time); + PARSEUINTOPTION(OPT_DTIME, Dtime); + PARSEUINTOPTION(OPT_NFRAMES, Frames); + PARSEUINTOPTION(OPT_LOOPS, Loops); + PARSEUINTOPTION(OPT_REPEAT, Repeat); + PARSEUINTOPTION(OPT_TRIES, Tries); + PARSEUINTOPTION(OPT_MAX_XFORMS, MaxXforms); + + PARSEDOUBLEOPTION(OPT_SS, SizeScale);//Float args. + PARSEDOUBLEOPTION(OPT_QS, QualityScale); + PARSEDOUBLEOPTION(OPT_PIXEL_ASPECT, AspectRatio); + PARSEDOUBLEOPTION(OPT_STAGGER, Stagger); + PARSEDOUBLEOPTION(OPT_AVG_THRESH, AvgThresh); + PARSEDOUBLEOPTION(OPT_BLACK_THRESH, BlackThresh); + PARSEDOUBLEOPTION(OPT_WHITE_LIMIT, WhiteLimit); + PARSEDOUBLEOPTION(OPT_SPEED, Speed); + PARSEDOUBLEOPTION(OPT_OFFSETX, OffsetX); + PARSEDOUBLEOPTION(OPT_OFFSETY, OffsetY); + PARSEDOUBLEOPTION(OPT_USEMEM, UseMem); + + PARSESTRINGOPTION(OPT_ISAAC_SEED, IsaacSeed);//String args. + PARSESTRINGOPTION(OPT_IN, Input); + PARSESTRINGOPTION(OPT_OUT, Out); + PARSESTRINGOPTION(OPT_PREFIX, Prefix); + PARSESTRINGOPTION(OPT_SUFFIX, Suffix); + PARSESTRINGOPTION(OPT_FORMAT, Format); + PARSESTRINGOPTION(OPT_PALETTE_FILE, PalettePath); + PARSESTRINGOPTION(OPT_PALETTE_IMAGE, PaletteImage); + PARSESTRINGOPTION(OPT_ID, Id); + PARSESTRINGOPTION(OPT_URL, Url); + PARSESTRINGOPTION(OPT_NICK, Nick); + PARSESTRINGOPTION(OPT_COMMENT, Comment); + PARSESTRINGOPTION(OPT_TEMPLATE, TemplateFile); + PARSESTRINGOPTION(OPT_CLONE, Clone); + PARSESTRINGOPTION(OPT_CLONE_ALL, CloneAll); + PARSESTRINGOPTION(OPT_CLONE_ACTION, CloneAction); + PARSESTRINGOPTION(OPT_ANIMATE, Animate); + PARSESTRINGOPTION(OPT_MUTATE, Mutate); + PARSESTRINGOPTION(OPT_CROSS0, Cross0); + PARSESTRINGOPTION(OPT_CROSS1, Cross1); + PARSESTRINGOPTION(OPT_METHOD, Method); + PARSESTRINGOPTION(OPT_INTER, Inter); + PARSESTRINGOPTION(OPT_ROTATE, Rotate); + PARSESTRINGOPTION(OPT_STRIP, Strip); + PARSESTRINGOPTION(OPT_SEQUENCE, Sequence); + PARSESTRINGOPTION(OPT_USE_VARS, UseVars); + PARSESTRINGOPTION(OPT_DONT_USE_VARS, DontUseVars); + PARSESTRINGOPTION(OPT_EXTRAS, Extras); + } + } + else + { + cout << "Invalid argument: " << args.OptionText() << endl; + cout << "\tReason: " << GetLastErrorText(errorCode) << endl; + } + } + + return false; + } + + /// + /// Return a vector of all available options for the specified program. + /// + /// The specified program usage + /// A vector of all available options for the specified program + vector GetSimpleOptions(eOptionUse optUsage = OPT_USE_ALL) + { + vector entries; + CSimpleOpt::SOption endOption = SO_END_OF_OPTIONS; + entries.reserve(75); + + std::for_each(m_BoolArgs.begin(), m_BoolArgs.end(), [&](Eob* entry) { if (entry->m_OptionUse & optUsage) entries.push_back(entry->m_Option); }); + std::for_each(m_IntArgs.begin(), m_IntArgs.end(), [&](Eoi* entry) { if (entry->m_OptionUse & optUsage) entries.push_back(entry->m_Option); }); + std::for_each(m_UintArgs.begin(), m_UintArgs.end(), [&](Eou* entry) { if (entry->m_OptionUse & optUsage) entries.push_back(entry->m_Option); }); + std::for_each(m_DoubleArgs.begin(), m_DoubleArgs.end(), [&](Eod* entry) { if (entry->m_OptionUse & optUsage) entries.push_back(entry->m_Option); }); + std::for_each(m_StringArgs.begin(), m_StringArgs.end(), [&](Eos* entry) { if (entry->m_OptionUse & optUsage) entries.push_back(entry->m_Option); }); + + entries.push_back(endOption); + return entries; + } + + /// + /// Return a string with the descriptions of all available options for the specified program. + /// + /// The specified program usage + /// A string with the descriptions of all available options for the specified program + string GetUsage(eOptionUse optUsage = OPT_USE_ALL) + { + ostringstream os; + + std::for_each(m_BoolArgs.begin(), m_BoolArgs.end(), [&](Eob* entry) { if (entry->m_OptionUse & optUsage) os << entry->m_DocString << endl; }); + std::for_each(m_IntArgs.begin(), m_IntArgs.end(), [&](Eoi* entry) { if (entry->m_OptionUse & optUsage) os << entry->m_DocString << endl; }); + std::for_each(m_UintArgs.begin(), m_UintArgs.end(), [&](Eou* entry) { if (entry->m_OptionUse & optUsage) os << entry->m_DocString << endl; }); + std::for_each(m_DoubleArgs.begin(), m_DoubleArgs.end(), [&](Eod* entry) { if (entry->m_OptionUse & optUsage) os << entry->m_DocString << endl; }); + std::for_each(m_StringArgs.begin(), m_StringArgs.end(), [&](Eos* entry) { if (entry->m_OptionUse & optUsage) os << entry->m_DocString << endl; }); + + return os.str(); + } + + /// + /// Return a string with all of the names and values for all available options for the specified program. + /// + /// The specified program usage + /// A string with all of the names and values for all available options for the specified program + string GetValues(eOptionUse optUsage = OPT_USE_ALL) + { + ostringstream os; + + os << std::boolalpha; + std::for_each(m_BoolArgs.begin(), m_BoolArgs.end(), [&](Eob* entry) { if (entry->m_OptionUse & optUsage) os << entry->m_NameWithoutDashes << ": " << (*entry)() << endl; }); + std::for_each(m_IntArgs.begin(), m_IntArgs.end(), [&](Eoi* entry) { if (entry->m_OptionUse & optUsage) os << entry->m_NameWithoutDashes << ": " << (*entry)() << endl; }); + std::for_each(m_UintArgs.begin(), m_UintArgs.end(), [&](Eou* entry) { if (entry->m_OptionUse & optUsage) os << entry->m_NameWithoutDashes << ": " << (*entry)() << endl; }); + std::for_each(m_DoubleArgs.begin(), m_DoubleArgs.end(), [&](Eod* entry) { if (entry->m_OptionUse & optUsage) os << entry->m_NameWithoutDashes << ": " << (*entry)() << endl; }); + std::for_each(m_StringArgs.begin(), m_StringArgs.end(), [&](Eos* entry) { if (entry->m_OptionUse & optUsage) os << entry->m_NameWithoutDashes << ": " << (*entry)() << endl; }); + + return os.str(); + } + + /// + /// Print description string, version and description of all available options for the specified program. + /// + /// The specified program usage + void ShowUsage(eOptionUse optUsage) + { + cout << DescriptionString << " version " << EmberVersion() << endl << endl; + + if (optUsage == OPT_USE_RENDER) + { + cout << "Usage:\n" + "\tEmberRender.exe --in=test.flam3 [--out=outfile --format=png --verbose --progress --opencl]\n" << endl; + } + else if (optUsage == OPT_USE_ANIMATE) + { + cout << "Usage:\n" + "\tEmberAnimate.exe --in=sequence.flam3 [--format=png --verbose --progress --opencl]\n" << endl; + } + else if (optUsage == OPT_USE_GENOME) + { + cout << "Usage:\n" + "\tEmberGenome.exe --sequence=test.flam3 > sequenceout.flam3\n" << endl; + } + + cout << GetUsage(optUsage) << endl; + } + + /// + /// Return the last option parsing error text as a string. + /// + /// The code of the last parsing error + /// The last option parsing error text as a string + string GetLastErrorText(int errorCode) + { + switch (errorCode) + { + case SO_SUCCESS: return "Success"; + case SO_OPT_INVALID: return "Unrecognized option"; + case SO_OPT_MULTIPLE: return "Option matched multiple strings"; + case SO_ARG_INVALID: return "Option does not accept argument"; + case SO_ARG_INVALID_TYPE: return "Invalid argument format"; + case SO_ARG_MISSING: return "Required argument is missing"; + case SO_ARG_INVALID_DATA: return "Invalid argument data"; + default: return "Unknown error"; + } + } + + //Break from the usual m_* notation for members here because + //each of these is a functor, so it looks nicer and is less typing + //to just say opt.Member(). + EmberOptionEntry Help;//Diagnostic bool. + EmberOptionEntry Version; + EmberOptionEntry Verbose; + EmberOptionEntry Debug; + EmberOptionEntry DumpArgs; + EmberOptionEntry DoProgress; + EmberOptionEntry OpenCLInfo; + + EmberOptionEntry EmberCL;//Value bool. + EmberOptionEntry EarlyClip; + EmberOptionEntry Transparency; + EmberOptionEntry NameEnable; + EmberOptionEntry IntPalette; + EmberOptionEntry HexPalette; + EmberOptionEntry InsertPalette; + EmberOptionEntry JpegComments; + EmberOptionEntry PngComments; + EmberOptionEntry WriteGenome; + EmberOptionEntry Enclosed; + EmberOptionEntry NoEdits; + EmberOptionEntry UnsmoothEdge; + EmberOptionEntry LockAccum; + EmberOptionEntry DumpKernel; + + EmberOptionEntry Symmetry;//Value int. + EmberOptionEntry SheepGen;//Value int. + EmberOptionEntry SheepId;//Value int. + EmberOptionEntry Platform;//Value unsigned int. + EmberOptionEntry Device; + EmberOptionEntry Seed; + EmberOptionEntry ThreadCount; + EmberOptionEntry Strips; + EmberOptionEntry BitsPerChannel; + EmberOptionEntry SubBatchSize; + EmberOptionEntry Bits; + EmberOptionEntry PrintEditDepth; + EmberOptionEntry JpegQuality; + EmberOptionEntry FirstFrame; + EmberOptionEntry LastFrame; + EmberOptionEntry Frame; + EmberOptionEntry Time; + EmberOptionEntry Dtime; + EmberOptionEntry Frames; + EmberOptionEntry Loops; + EmberOptionEntry Repeat; + EmberOptionEntry Tries; + EmberOptionEntry MaxXforms; + + EmberOptionEntry SizeScale;//Value double. + EmberOptionEntry QualityScale; + EmberOptionEntry AspectRatio; + EmberOptionEntry Stagger; + EmberOptionEntry AvgThresh; + EmberOptionEntry BlackThresh; + EmberOptionEntry WhiteLimit; + EmberOptionEntry Speed; + EmberOptionEntry OffsetX; + EmberOptionEntry OffsetY; + EmberOptionEntry UseMem; + + EmberOptionEntry IsaacSeed;//Value string. + EmberOptionEntry Input; + EmberOptionEntry Out; + EmberOptionEntry Prefix; + EmberOptionEntry Suffix; + EmberOptionEntry Format; + EmberOptionEntry PalettePath; + EmberOptionEntry PaletteImage; + EmberOptionEntry Id; + EmberOptionEntry Url; + EmberOptionEntry Nick; + EmberOptionEntry Comment; + EmberOptionEntry TemplateFile; + EmberOptionEntry Clone; + EmberOptionEntry CloneAll; + EmberOptionEntry CloneAction; + EmberOptionEntry Animate; + EmberOptionEntry Mutate; + EmberOptionEntry Cross0; + EmberOptionEntry Cross1; + EmberOptionEntry Method; + EmberOptionEntry Inter; + EmberOptionEntry Rotate; + EmberOptionEntry Strip; + EmberOptionEntry Sequence; + EmberOptionEntry UseVars; + EmberOptionEntry DontUseVars; + EmberOptionEntry Extras; + +private: + vector*> m_BoolArgs; + vector*> m_IntArgs; + vector*> m_UintArgs; + vector*> m_DoubleArgs; + vector*> m_StringArgs; +}; diff --git a/Source/EmberCommon/JpegUtils.h b/Source/EmberCommon/JpegUtils.h new file mode 100644 index 0000000..e3494cf --- /dev/null +++ b/Source/EmberCommon/JpegUtils.h @@ -0,0 +1,362 @@ +#pragma once + +#include "EmberCommonPch.h" + +#define PNG_COMMENT_MAX 8 + +/// +/// Write a PPM file. +/// +/// The full path and name of the file +/// Pointer to the image data to write +/// Width of the image in pixels +/// Height of the image in pixels +/// True if success, else false +static bool WritePpm(const char* filename, unsigned char* image, int width, int height) +{ + bool b = false; + unsigned int size = width * height * 3; + FILE* file; + + if (fopen_s(&file, filename, "wb") == 0) + { + fprintf_s(file, "P6\n"); + fprintf_s(file, "%d %d\n255\n", width, height); + + b = (size == fwrite(image, 1, size, file)); + fclose(file); + } + + return b; +} + +/// +/// Write a JPEG file. +/// +/// The full path and name of the file +/// Pointer to the image data to write +/// Width of the image in pixels +/// Height of the image in pixels +/// The quality to use +/// True to embed comments, else false +/// The comment string to embed +/// Id of the author +/// Url of the author +/// Nickname of the author +/// True if success, else false +static bool WriteJpeg(const char* filename, unsigned char* image, unsigned int width, unsigned int height, int quality, bool enableComments, EmberImageComments& comments, string id, string url, string nick) +{ + bool b = false; + FILE* file; + + if (fopen_s(&file, filename, "wb") == 0) + { + size_t i; + jpeg_error_mgr jerr; + jpeg_compress_struct info; + char nickString[64], urlString[128], idString[128]; + char bvString[64], niString[64], rtString[64]; + char genomeString[65536], verString[64]; + + //Create the mandatory comment strings. + snprintf_s(genomeString, 65536, "flam3_genome: %s", comments.m_Genome.c_str()); + snprintf_s(bvString, 64, "flam3_error_rate: %s", comments.m_Badvals.c_str()); + snprintf_s(niString, 64, "flam3_samples: %s", comments.m_NumIters); + snprintf_s(rtString, 64, "flam3_time: %s", comments.m_Runtime.c_str()); + snprintf_s(verString, 64, "flam3_version: %s", EmberVersion()); + + info.err = jpeg_std_error(&jerr); + jpeg_create_compress(&info); + jpeg_stdio_dest(&info, file); + info.in_color_space = JCS_RGB; + info.input_components = 3; + info.image_width = width; + info.image_height = height; + jpeg_set_defaults(&info); + jpeg_set_quality(&info, quality, TRUE); + jpeg_start_compress(&info, TRUE); + + //Write comments to jpeg. + if (enableComments) + { + jpeg_write_marker(&info, JPEG_COM, (unsigned char*)verString, (int)strlen(verString)); + + if (nick != "") + { + snprintf_s(nickString, 64, "flam3_nickname: %s", nick.c_str()); + jpeg_write_marker(&info, JPEG_COM, (unsigned char*)nickString, (int)strlen(nickString)); + } + + if (url != "") + { + snprintf_s(urlString, 128, "flam3_url: %s", url.c_str()); + jpeg_write_marker(&info, JPEG_COM, (unsigned char*)urlString, (int)strlen(urlString)); + } + + if (id != "") + { + snprintf_s(idString, 128, "flam3_id: %s", id.c_str()); + jpeg_write_marker(&info, JPEG_COM, (unsigned char*)idString, (int)strlen(idString)); + } + + jpeg_write_marker(&info, JPEG_COM, (unsigned char*)bvString, (int)strlen(bvString)); + jpeg_write_marker(&info, JPEG_COM, (unsigned char*)niString, (int)strlen(niString)); + jpeg_write_marker(&info, JPEG_COM, (unsigned char*)rtString, (int)strlen(rtString)); + jpeg_write_marker(&info, JPEG_COM, (unsigned char*)genomeString, (int)strlen(genomeString)); + } + + for (i = 0; i < height; i++) + { + JSAMPROW row_pointer[1]; + row_pointer[0] = (unsigned char*)image + (3 * width * i); + jpeg_write_scanlines(&info, row_pointer, 1); + } + + jpeg_finish_compress(&info); + jpeg_destroy_compress(&info); + fclose(file); + b = true; + } + + return b; +} + +/// +/// Write a PNG file. +/// +/// The full path and name of the file +/// Pointer to the image data to write +/// Width of the image in pixels +/// Height of the image in pixels +/// Bytes per channel, 1 or 2. +/// True to embed comments, else false +/// The comment string to embed +/// Id of the author +/// Url of the author +/// Nickname of the author +/// True if success, else false +static bool WritePng(const char* filename, unsigned char* image, unsigned int width, unsigned int height, int bytesPerChannel, bool enableComments, EmberImageComments& comments, string id, string url, string nick) +{ + bool b = false; + FILE* file; + + if (fopen_s(&file, filename, "wb") == 0) + { + png_structp png_ptr; + png_infop info_ptr; + png_text text[PNG_COMMENT_MAX]; + size_t i; + unsigned short testbe = 1; + vector rows(height); + + text[0].compression = PNG_TEXT_COMPRESSION_NONE; + text[0].key = "flam3_version"; + text[0].text = EmberVersion(); + + text[1].compression = PNG_TEXT_COMPRESSION_NONE; + text[1].key = "flam3_nickname"; + text[1].text = (png_charp)nick.c_str(); + + text[2].compression = PNG_TEXT_COMPRESSION_NONE; + text[2].key = "flam3_url"; + text[2].text = (png_charp)url.c_str(); + + text[3].compression = PNG_TEXT_COMPRESSION_NONE; + text[3].key = "flam3_id"; + text[3].text = (png_charp)id.c_str(); + + text[4].compression = PNG_TEXT_COMPRESSION_NONE; + text[4].key = "flam3_error_rate"; + text[4].text = (png_charp)comments.m_Badvals.c_str(); + + text[5].compression = PNG_TEXT_COMPRESSION_NONE; + text[5].key = "flam3_samples"; + text[5].text = (png_charp)comments.m_NumIters.c_str(); + + text[6].compression = PNG_TEXT_COMPRESSION_NONE; + text[6].key = "flam3_time"; + text[6].text = (png_charp)comments.m_Runtime.c_str(); + + text[7].compression = PNG_TEXT_COMPRESSION_zTXt; + text[7].key = "flam3_genome"; + text[7].text = (png_charp)comments.m_Genome.c_str(); + + for (i = 0; i < height; i++) + rows[i] = (unsigned char*)image + i * width * 4 * bytesPerChannel; + + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + info_ptr = png_create_info_struct(png_ptr); + + if (setjmp(png_jmpbuf(png_ptr))) + { + fclose(file); + png_destroy_write_struct(&png_ptr, &info_ptr); + perror("writing file"); + return false; + } + + png_init_io(png_ptr, file); + + png_set_IHDR(png_ptr, info_ptr, width, height, 8 * bytesPerChannel, + PNG_COLOR_TYPE_RGBA, + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, + PNG_FILTER_TYPE_BASE); + + if (enableComments == 1) + png_set_text(png_ptr, info_ptr, text, PNG_COMMENT_MAX); + + png_write_info(png_ptr, info_ptr); + + //Must set this after png_write_info(). + if (bytesPerChannel == 2 && testbe != htons(testbe)) + { + png_set_swap(png_ptr); + } + + png_write_image(png_ptr, (png_bytepp) rows.data()); + png_write_end(png_ptr, info_ptr); + png_destroy_write_struct(&png_ptr, &info_ptr); + fclose(file); + b = true; + } + + return b; +} + +/// +/// Convert an RGB buffer to BGR for usage with BMP. +/// +/// The buffer to convert +/// The width. +/// The height. +/// The size of the new buffer created +/// The converted buffer if successful, else NULL. +static BYTE* ConvertRGBToBMPBuffer(BYTE* buffer, int width, int height, long& newSize) +{ + if (NULL == buffer || width == 0 || height == 0) + return NULL; + + int padding = 0; + int scanlinebytes = width * 3; + while ((scanlinebytes + padding ) % 4 != 0) + padding++; + + int psw = scanlinebytes + padding; + + newSize = height * psw; + BYTE* newBuf = new BYTE[newSize]; + + if (newBuf) + { + memset (newBuf, 0, newSize); + + long bufpos = 0; + long newpos = 0; + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < 3 * width; x += 3) + { + bufpos = y * 3 * width + x; // position in original buffer + newpos = (height - y - 1) * psw + x; // position in padded buffer + newBuf[newpos] = buffer[bufpos+2]; // swap r and b + newBuf[newpos + 1] = buffer[bufpos + 1]; // g stays + newBuf[newpos + 2] = buffer[bufpos]; // swap b and r + + //No swap. + //newBuf[newpos] = buffer[bufpos]; + //newBuf[newpos + 1] = buffer[bufpos + 1]; + //newBuf[newpos + 2] = buffer[bufpos + 2]; + } + } + + return newBuf; + } + + return NULL; +} + +/// +/// Save a Bmp file. +/// +/// The full path and name of the file +/// Pointer to the image data to write +/// Width of the image in pixels +/// Height of the image in pixels +/// Padded size, greater than or equal to total image size. +/// True if success, else false +static bool SaveBmp(const char* filename, BYTE* image, int width, int height, long paddedSize) +{ + BITMAPFILEHEADER bmfh; + BITMAPINFOHEADER info; + unsigned long bwritten; + HANDLE file; + memset (&bmfh, 0, sizeof (BITMAPFILEHEADER)); + memset (&info, 0, sizeof (BITMAPINFOHEADER)); + + bmfh.bfType = 0x4d42; // 0x4d42 = 'BM' + bmfh.bfReserved1 = 0; + bmfh.bfReserved2 = 0; + bmfh.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + paddedSize; + bmfh.bfOffBits = 0x36; + + info.biSize = sizeof(BITMAPINFOHEADER); + info.biWidth = width; + info.biHeight = height; + info.biPlanes = 1; + info.biBitCount = 24; + info.biCompression = BI_RGB; + info.biSizeImage = 0; + info.biXPelsPerMeter = 0x0ec4; + info.biYPelsPerMeter = 0x0ec4; + info.biClrUsed = 0; + info.biClrImportant = 0; + + if ((file = CreateFileA(filename, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL)) == NULL) + { + CloseHandle(file); + return false; + } + + if (WriteFile(file, &bmfh, sizeof (BITMAPFILEHEADER), &bwritten, NULL) == false) + { + CloseHandle(file); + return false; + } + + if (WriteFile(file, &info, sizeof(BITMAPINFOHEADER), &bwritten, NULL) == false) + { + CloseHandle(file); + return false; + } + + if (WriteFile(file, image, paddedSize, &bwritten, NULL) == false) + { + CloseHandle(file); + return false; + } + + CloseHandle(file); + return true; +} + +/// +/// Convert a buffer from RGB to BGR and write a Bmp file. +/// +/// The full path and name of the file +/// Pointer to the image data to write +/// Width of the image in pixels +/// Height of the image in pixels +/// True if success, else false +static bool WriteBmp(const char* filename, unsigned char* image, int width, int height) +{ + bool b = false; + long newSize; + auto_ptr bgrBuf(ConvertRGBToBMPBuffer(image, width, height, newSize)); + + if (bgrBuf.get()) + b = SaveBmp(filename, bgrBuf.get(), width, height, newSize); + + return b; +} diff --git a/Source/EmberCommon/SimpleGlob.h b/Source/EmberCommon/SimpleGlob.h new file mode 100644 index 0000000..dddc8d1 --- /dev/null +++ b/Source/EmberCommon/SimpleGlob.h @@ -0,0 +1,959 @@ +/*! @file SimpleGlob.h + + @version 3.6 + + @brief A cross-platform file globbing library providing the ability to + expand wildcards in command-line arguments to a list of all matching + files. It is designed explicitly to be portable to any platform and has + been tested on Windows and Linux. See CSimpleGlobTempl for the class + definition. + + @section features FEATURES + - MIT Licence allows free use in all software (including GPL and + commercial) + - multi-platform (Windows 95/98/ME/NT/2K/XP, Linux, Unix) + - supports most of the standard linux glob() options + - recognition of a forward paths as equivalent to a backward slash + on Windows. e.g. "c:/path/foo*" is equivalent to "c:\path\foo*". + - implemented with only a single C++ header file + - char, wchar_t and Windows TCHAR in the same program + - complete working examples included + - compiles cleanly at warning level 4 (Windows/VC.NET 2003), + warning level 3 (Windows/VC6) and -Wall (Linux/gcc) + + @section usage USAGE + The SimpleGlob class is used by following these steps: +
    +
  1. Include the SimpleGlob.h header file + +
    +        \#include "SimpleGlob.h"
    +        
    + +
  2. Instantiate a CSimpleGlob object supplying the appropriate flags. + +
    +        @link CSimpleGlobTempl CSimpleGlob @endlink glob(FLAGS);
    +        
    + +
  3. Add all file specifications to the glob class. + +
    +        glob.Add("file*");
    +        glob.Add(argc, argv);
    +        
    + +
  4. Process all files with File(), Files() and FileCount() + +
    +        for (int n = 0; n < glob.FileCount(); ++n) {
    +            ProcessFile(glob.File(n));
    +        }
    +        
    + +
+ + @section licence MIT LICENCE +
+    The licence text below is the boilerplate "MIT Licence" used from:
+    http://www.opensource.org/licenses/mit-license.php
+
+    Copyright (c) 2006-2013, Brodie Thiesfield
+
+    Permission is hereby granted, free of charge, to any person obtaining a
+    copy of this software and associated documentation files (the "Software"),
+    to deal in the Software without restriction, including without limitation
+    the rights to use, copy, modify, merge, publish, distribute, sublicense,
+    and/or sell copies of the Software, and to permit persons to whom the
+    Software is furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be included
+    in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
+    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
+    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
+    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
+    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+*/ + +#ifndef INCLUDED_SimpleGlob +#define INCLUDED_SimpleGlob + +/*! @brief The operation of SimpleGlob is fine-tuned via the use of a + combination of the following flags. + + The flags may be passed at initialization of the class and used for every + filespec added, or alternatively they may optionally be specified in the + call to Add() and be different for each filespec. + + @param SG_GLOB_ERR + Return upon read error (e.g. directory does not have read permission) + + @param SG_GLOB_MARK + Append a slash (backslash in Windows) to every path which corresponds + to a directory + + @param SG_GLOB_NOSORT + By default, files are returned in sorted into string order. With this + flag, no sorting is done. This is not compatible with + SG_GLOB_FULLSORT. + + @param SG_GLOB_FULLSORT + By default, files are sorted in groups belonging to each filespec that + was added. For example if the filespec "b*" was added before the + filespec "a*" then the argv array will contain all b* files sorted in + order, followed by all a* files sorted in order. If this flag is + specified, the entire array will be sorted ignoring the filespec + groups. + + @param SG_GLOB_NOCHECK + If the pattern doesn't match anything, return the original pattern. + + @param SG_GLOB_TILDE + Tilde expansion is carried out (on Unix platforms) + + @param SG_GLOB_ONLYDIR + Return only directories which match (not compatible with + SG_GLOB_ONLYFILE) + + @param SG_GLOB_ONLYFILE + Return only files which match (not compatible with SG_GLOB_ONLYDIR) + + @param SG_GLOB_NODOT + Do not return the "." or ".." special directories. + */ +enum SG_Flags { + SG_GLOB_ERR = 1 << 0, + SG_GLOB_MARK = 1 << 1, + SG_GLOB_NOSORT = 1 << 2, + SG_GLOB_NOCHECK = 1 << 3, + SG_GLOB_TILDE = 1 << 4, + SG_GLOB_ONLYDIR = 1 << 5, + SG_GLOB_ONLYFILE = 1 << 6, + SG_GLOB_NODOT = 1 << 7, + SG_GLOB_FULLSORT = 1 << 8 +}; + +/*! @brief Error return codes */ +enum SG_Error { + SG_SUCCESS = 0, + SG_ERR_NOMATCH = 1, + SG_ERR_MEMORY = -1, + SG_ERR_FAILURE = -2 +}; + +// --------------------------------------------------------------------------- +// Platform dependent implementations + +// if we aren't on Windows and we have ICU available, then enable ICU +// by default. Define this to 0 to intentially disable it. +#ifndef SG_HAVE_ICU +# if !defined(_WIN32) && defined(USTRING_H) +# define SG_HAVE_ICU 1 +# else +# define SG_HAVE_ICU 0 +# endif +#endif + +// don't include this in documentation as it isn't relevant +#ifndef DOXYGEN + +// on Windows we want to use MBCS aware string functions and mimic the +// Unix glob functionality. On Unix we just use glob. +#ifdef _WIN32 +# include +# define sg_strchr ::_mbschr +# define sg_strrchr ::_mbsrchr +# define sg_strlen ::_mbslen +# if __STDC_WANT_SECURE_LIB__ +# define sg_strcpy_s(a,n,b) ::_mbscpy_s(a,n,b) +# else +# define sg_strcpy_s(a,n,b) ::_mbscpy(a,b) +# endif +# define sg_strcmp ::_mbscmp +# define sg_strcasecmp ::_mbsicmp +# define SOCHAR_T unsigned char +#else +# include +# include +# include +# include +# define MAX_PATH PATH_MAX +# define sg_strchr ::strchr +# define sg_strrchr ::strrchr +# define sg_strlen ::strlen +# define sg_strcpy_s(a,n,b) ::strcpy(a,b) +# define sg_strcmp ::strcmp +# define sg_strcasecmp ::strcasecmp +# define SOCHAR_T char +#endif + +#include +#include +#include + +// use assertions to test the input data +#ifdef _DEBUG +# ifdef _MSC_VER +# include +# define SG_ASSERT(b) _ASSERTE(b) +# else +# include +# define SG_ASSERT(b) assert(b) +# endif +#else +# define SG_ASSERT(b) +#endif + +/*! @brief String manipulation functions. */ +class SimpleGlobUtil +{ +public: + static const char * strchr(const char *s, char c) { + return (char *) sg_strchr((const SOCHAR_T *)s, c); + } + static const wchar_t * strchr(const wchar_t *s, wchar_t c) { + return ::wcschr(s, c); + } +#if SG_HAVE_ICU + static const UChar * strchr(const UChar *s, UChar c) { + return ::u_strchr(s, c); + } +#endif + + static const char * strrchr(const char *s, char c) { + return (char *) sg_strrchr((const SOCHAR_T *)s, c); + } + static const wchar_t * strrchr(const wchar_t *s, wchar_t c) { + return ::wcsrchr(s, c); + } +#if SG_HAVE_ICU + static const UChar * strrchr(const UChar *s, UChar c) { + return ::u_strrchr(s, c); + } +#endif + + // Note: char strlen returns number of bytes, not characters + static size_t strlen(const char *s) { return ::strlen(s); } + static size_t strlen(const wchar_t *s) { return ::wcslen(s); } +#if SG_HAVE_ICU + static size_t strlen(const UChar *s) { return ::u_strlen(s); } +#endif + + static void strcpy_s(char *dst, size_t n, const char *src) { + (void) n; + sg_strcpy_s((SOCHAR_T *)dst, n, (const SOCHAR_T *)src); + } + static void strcpy_s(wchar_t *dst, size_t n, const wchar_t *src) { +# if __STDC_WANT_SECURE_LIB__ + ::wcscpy_s(dst, n, src); +#else + (void) n; + ::wcscpy(dst, src); +#endif + } +#if SG_HAVE_ICU + static void strcpy_s(UChar *dst, size_t n, const UChar *src) { + ::u_strncpy(dst, src, n); + } +#endif + + static int strcmp(const char *s1, const char *s2) { + return sg_strcmp((const SOCHAR_T *)s1, (const SOCHAR_T *)s2); + } + static int strcmp(const wchar_t *s1, const wchar_t *s2) { + return ::wcscmp(s1, s2); + } +#if SG_HAVE_ICU + static int strcmp(const UChar *s1, const UChar *s2) { + return ::u_strcmp(s1, s2); + } +#endif + + static int strcasecmp(const char *s1, const char *s2) { + return sg_strcasecmp((const SOCHAR_T *)s1, (const SOCHAR_T *)s2); + } +#if _WIN32 + static int strcasecmp(const wchar_t *s1, const wchar_t *s2) { + return ::_wcsicmp(s1, s2); + } +#endif // _WIN32 +#if SG_HAVE_ICU + static int strcasecmp(const UChar *s1, const UChar *s2) { + return u_strcasecmp(s1, s2, 0); + } +#endif +}; + +enum SG_FileType { + SG_FILETYPE_INVALID, + SG_FILETYPE_FILE, + SG_FILETYPE_DIR +}; + +#ifdef _WIN32 + +#ifndef INVALID_FILE_ATTRIBUTES +# define INVALID_FILE_ATTRIBUTES ((DWORD)-1) +#endif + +#define SG_PATH_CHAR '\\' + +/*! @brief Windows glob implementation. */ +template +struct SimpleGlobBase +{ + SimpleGlobBase() : m_hFind(INVALID_HANDLE_VALUE) { } + + int FindFirstFileS(const char * a_pszFileSpec, unsigned int) { + m_hFind = FindFirstFileA(a_pszFileSpec, &m_oFindDataA); + if (m_hFind != INVALID_HANDLE_VALUE) { + return SG_SUCCESS; + } + DWORD dwErr = GetLastError(); + if (dwErr == ERROR_FILE_NOT_FOUND) { + return SG_ERR_NOMATCH; + } + return SG_ERR_FAILURE; + } + int FindFirstFileS(const wchar_t * a_pszFileSpec, unsigned int) { + m_hFind = FindFirstFileW(a_pszFileSpec, &m_oFindDataW); + if (m_hFind != INVALID_HANDLE_VALUE) { + return SG_SUCCESS; + } + DWORD dwErr = GetLastError(); + if (dwErr == ERROR_FILE_NOT_FOUND) { + return SG_ERR_NOMATCH; + } + return SG_ERR_FAILURE; + } + + bool FindNextFileS(char) { + return FindNextFileA(m_hFind, &m_oFindDataA) != FALSE; + } + bool FindNextFileS(wchar_t) { + return FindNextFileW(m_hFind, &m_oFindDataW) != FALSE; + } + + void FindDone() { + FindClose(m_hFind); + } + + const char * GetFileNameS(char) const { + return m_oFindDataA.cFileName; + } + const wchar_t * GetFileNameS(wchar_t) const { + return m_oFindDataW.cFileName; + } + + bool IsDirS(char) const { + return this->GetFileTypeS(m_oFindDataA.dwFileAttributes) == SG_FILETYPE_DIR; + } + bool IsDirS(wchar_t) const { + return this->GetFileTypeS(m_oFindDataW.dwFileAttributes) == SG_FILETYPE_DIR; + } + + SG_FileType GetFileTypeS(const char * a_pszPath) { + return this->GetFileTypeS(GetFileAttributesA(a_pszPath)); + } + SG_FileType GetFileTypeS(const wchar_t * a_pszPath) { + return this->GetFileTypeS(GetFileAttributesW(a_pszPath)); + } + SG_FileType GetFileTypeS(DWORD a_dwAttribs) const { + if (a_dwAttribs == INVALID_FILE_ATTRIBUTES) { + return SG_FILETYPE_INVALID; + } + if (a_dwAttribs & FILE_ATTRIBUTE_DIRECTORY) { + return SG_FILETYPE_DIR; + } + return SG_FILETYPE_FILE; + } + +private: + HANDLE m_hFind; + WIN32_FIND_DATAA m_oFindDataA; + WIN32_FIND_DATAW m_oFindDataW; +}; + +#else // !_WIN32 + +#define SG_PATH_CHAR '/' + +/*! @brief Unix glob implementation. */ +template +struct SimpleGlobBase +{ + SimpleGlobBase() { + memset(&m_glob, 0, sizeof(m_glob)); + m_uiCurr = (size_t)-1; + } + + ~SimpleGlobBase() { + globfree(&m_glob); + } + + void FilePrep() { + m_bIsDir = false; + size_t len = strlen(m_glob.gl_pathv[m_uiCurr]); + if (m_glob.gl_pathv[m_uiCurr][len-1] == '/') { + m_bIsDir = true; + m_glob.gl_pathv[m_uiCurr][len-1] = 0; + } + } + + int FindFirstFileS(const char * a_pszFileSpec, unsigned int a_uiFlags) { + int nFlags = GLOB_MARK | GLOB_NOSORT; + if (a_uiFlags & SG_GLOB_ERR) nFlags |= GLOB_ERR; + if (a_uiFlags & SG_GLOB_TILDE) nFlags |= GLOB_TILDE; + int rc = glob(a_pszFileSpec, nFlags, NULL, &m_glob); + if (rc == GLOB_NOSPACE) return SG_ERR_MEMORY; + if (rc == GLOB_ABORTED) return SG_ERR_FAILURE; + if (rc == GLOB_NOMATCH) return SG_ERR_NOMATCH; + m_uiCurr = 0; + FilePrep(); + return SG_SUCCESS; + } + +#if SG_HAVE_ICU + int FindFirstFileS(const UChar * a_pszFileSpec, unsigned int a_uiFlags) { + char buf[PATH_MAX] = { 0 }; + UErrorCode status = U_ZERO_ERROR; + u_strToUTF8(buf, sizeof(buf), NULL, a_pszFileSpec, -1, &status); + if (U_FAILURE(status)) return SG_ERR_FAILURE; + return this->FindFirstFileS(buf, a_uiFlags); + } +#endif + + bool FindNextFileS(char) { + SG_ASSERT(m_uiCurr != (size_t)-1); + if (++m_uiCurr >= m_glob.gl_pathc) { + return false; + } + FilePrep(); + return true; + } + +#if SG_HAVE_ICU + bool FindNextFileS(UChar) { + return this->FindNextFileS((char)0); + } +#endif + + void FindDone() { + globfree(&m_glob); + memset(&m_glob, 0, sizeof(m_glob)); + m_uiCurr = (size_t)-1; + } + + const char * GetFileNameS(char) const { + SG_ASSERT(m_uiCurr != (size_t)-1); + return m_glob.gl_pathv[m_uiCurr]; + } + +#if SG_HAVE_ICU + const UChar * GetFileNameS(UChar) const { + const char * pszFile = this->GetFileNameS((char)0); + if (!pszFile) return NULL; + UErrorCode status = U_ZERO_ERROR; + memset(m_szBuf, 0, sizeof(m_szBuf)); + u_strFromUTF8(m_szBuf, PATH_MAX, NULL, pszFile, -1, &status); + if (U_FAILURE(status)) return NULL; + return m_szBuf; + } +#endif + + bool IsDirS(char) const { + SG_ASSERT(m_uiCurr != (size_t)-1); + return m_bIsDir; + } + +#if SG_HAVE_ICU + bool IsDirS(UChar) const { + return this->IsDirS((char)0); + } +#endif + + SG_FileType GetFileTypeS(const char * a_pszPath) const { + struct stat sb; + if (0 != stat(a_pszPath, &sb)) { + return SG_FILETYPE_INVALID; + } + if (S_ISDIR(sb.st_mode)) { + return SG_FILETYPE_DIR; + } + if (S_ISREG(sb.st_mode)) { + return SG_FILETYPE_FILE; + } + return SG_FILETYPE_INVALID; + } + +#if SG_HAVE_ICU + SG_FileType GetFileTypeS(const UChar * a_pszPath) const { + char buf[PATH_MAX] = { 0 }; + UErrorCode status = U_ZERO_ERROR; + u_strToUTF8(buf, sizeof(buf), NULL, a_pszPath, -1, &status); + if (U_FAILURE(status)) return SG_FILETYPE_INVALID; + return this->GetFileTypeS(buf); + } +#endif + +private: + glob_t m_glob; + size_t m_uiCurr; + bool m_bIsDir; +#if SG_HAVE_ICU + mutable UChar m_szBuf[PATH_MAX]; +#endif +}; + +#endif // _WIN32 + +#endif // DOXYGEN + +// --------------------------------------------------------------------------- +// MAIN TEMPLATE CLASS +// --------------------------------------------------------------------------- + +/*! @brief Implementation of the SimpleGlob class */ +template +class CSimpleGlobTempl : private SimpleGlobBase +{ +public: + /*! @brief Initialize the class. + + @param a_uiFlags Combination of SG_GLOB flags. + @param a_nReservedSlots Number of slots in the argv array that + should be reserved. In the returned array these slots + argv[0] ... argv[a_nReservedSlots-1] will be left empty for + the caller to fill in. + */ + CSimpleGlobTempl(unsigned int a_uiFlags = 0, int a_nReservedSlots = 0); + + /*! @brief Deallocate all memory buffers. */ + ~CSimpleGlobTempl(); + + /*! @brief Initialize (or re-initialize) the class in preparation for + adding new filespecs. + + All existing files are cleared. Note that allocated memory is only + deallocated at object destruction. + + @param a_uiFlags Combination of SG_GLOB flags. + @param a_nReservedSlots Number of slots in the argv array that + should be reserved. In the returned array these slots + argv[0] ... argv[a_nReservedSlots-1] will be left empty for + the caller to fill in. + */ + int Init(unsigned int a_uiFlags = 0, int a_nReservedSlots = 0); + + /*! @brief Add a new filespec to the glob. + + The filesystem will be immediately scanned for all matching files and + directories and they will be added to the glob. + + @param a_pszFileSpec Filespec to add to the glob. + + @return SG_SUCCESS Matching files were added to the glob. + @return SG_ERR_NOMATCH Nothing matched the pattern. To ignore this + error compare return value to >= SG_SUCCESS. + @return SG_ERR_MEMORY Out of memory failure. + @return SG_ERR_FAILURE General failure. + */ + int Add(const SOCHAR *a_pszFileSpec); + + /*! @brief Add an array of filespec to the glob. + + The filesystem will be immediately scanned for all matching files and + directories in each filespec and they will be added to the glob. + + @param a_nCount Number of filespec in the array. + @param a_rgpszFileSpec Array of filespec to add to the glob. + + @return SG_SUCCESS Matching files were added to the glob. + @return SG_ERR_NOMATCH Nothing matched the pattern. To ignore this + error compare return value to >= SG_SUCCESS. + @return SG_ERR_MEMORY Out of memory failure. + @return SG_ERR_FAILURE General failure. + */ + int Add(int a_nCount, const SOCHAR * const * a_rgpszFileSpec); + + /*! @brief Return the number of files in the argv array. + */ + inline int FileCount() const { return m_nArgsLen; } + + /*! @brief Return the full argv array. */ + inline SOCHAR ** Files() { + SetArgvArrayType(POINTERS); + return m_rgpArgs; + } + + /*! @brief Return the a single file. */ + inline SOCHAR * File(int n) { + SG_ASSERT(n >= 0 && n < m_nArgsLen); + return Files()[n]; + } + +private: + CSimpleGlobTempl(const CSimpleGlobTempl &); // disabled + CSimpleGlobTempl & operator=(const CSimpleGlobTempl &); // disabled + + /*! @brief The argv array has it's members stored as either an offset into + the string buffer, or as pointers to their string in the buffer. The + offsets are used because if the string buffer is dynamically resized, + all pointers into that buffer would become invalid. + */ + enum ARG_ARRAY_TYPE { OFFSETS, POINTERS }; + + /*! @brief Change the type of data stored in the argv array. */ + void SetArgvArrayType(ARG_ARRAY_TYPE a_nNewType); + + /*! @brief Add a filename to the array if it passes all requirements. */ + int AppendName(const SOCHAR *a_pszFileName, bool a_bIsDir); + + /*! @brief Grow the argv array to the required size. */ + bool GrowArgvArray(int a_nNewLen); + + /*! @brief Grow the string buffer to the required size. */ + bool GrowStringBuffer(size_t a_uiMinSize); + + /*! @brief Compare two (possible NULL) strings */ + static int fileSortCompare(const void *a1, const void *a2); + +private: + unsigned int m_uiFlags; + ARG_ARRAY_TYPE m_nArgArrayType; //!< argv is indexes or pointers + SOCHAR ** m_rgpArgs; //!< argv + int m_nReservedSlots; //!< # client slots in argv array + int m_nArgsSize; //!< allocated size of array + int m_nArgsLen; //!< used length + SOCHAR * m_pBuffer; //!< argv string buffer + size_t m_uiBufferSize; //!< allocated size of buffer + size_t m_uiBufferLen; //!< used length of buffer + SOCHAR m_szPathPrefix[MAX_PATH]; //!< wildcard path prefix +}; + +// --------------------------------------------------------------------------- +// IMPLEMENTATION +// --------------------------------------------------------------------------- + +template +CSimpleGlobTempl::CSimpleGlobTempl( + unsigned int a_uiFlags, + int a_nReservedSlots + ) +{ + m_rgpArgs = NULL; + m_nArgsSize = 0; + m_pBuffer = NULL; + m_uiBufferSize = 0; + + Init(a_uiFlags, a_nReservedSlots); +} + +template +CSimpleGlobTempl::~CSimpleGlobTempl() +{ + if (m_rgpArgs) free(m_rgpArgs); + if (m_pBuffer) free(m_pBuffer); +} + +template +int +CSimpleGlobTempl::Init( + unsigned int a_uiFlags, + int a_nReservedSlots + ) +{ + m_nArgArrayType = POINTERS; + m_uiFlags = a_uiFlags; + m_nArgsLen = a_nReservedSlots; + m_nReservedSlots = a_nReservedSlots; + m_uiBufferLen = 0; + + if (m_nReservedSlots > 0) { + if (!GrowArgvArray(m_nReservedSlots)) { + return SG_ERR_MEMORY; + } + for (int n = 0; n < m_nReservedSlots; ++n) { + m_rgpArgs[n] = NULL; + } + } + + return SG_SUCCESS; +} + +template +int +CSimpleGlobTempl::Add( + const SOCHAR *a_pszFileSpec + ) +{ +#ifdef _WIN32 + // Windows FindFirst/FindNext recognizes forward slash as the same as + // backward slash and follows the directories. We need to do the same + // when calculating the prefix and when we have no wildcards. + SOCHAR szFileSpec[MAX_PATH]; + SimpleGlobUtil::strcpy_s(szFileSpec, MAX_PATH, a_pszFileSpec); + const SOCHAR * pszPath = SimpleGlobUtil::strchr(szFileSpec, '/'); + while (pszPath) { + szFileSpec[pszPath - szFileSpec] = SG_PATH_CHAR; + pszPath = SimpleGlobUtil::strchr(pszPath + 1, '/'); + } + a_pszFileSpec = szFileSpec; +#endif + + // if this doesn't contain wildcards then we can just add it directly + m_szPathPrefix[0] = 0; + if (!SimpleGlobUtil::strchr(a_pszFileSpec, '*') && + !SimpleGlobUtil::strchr(a_pszFileSpec, '?')) + { + SG_FileType nType = this->GetFileTypeS(a_pszFileSpec); + if (nType == SG_FILETYPE_INVALID) { + if (m_uiFlags & SG_GLOB_NOCHECK) { + return AppendName(a_pszFileSpec, false); + } + return SG_ERR_NOMATCH; + } + return AppendName(a_pszFileSpec, nType == SG_FILETYPE_DIR); + } + +#ifdef _WIN32 + // Windows doesn't return the directory with the filename, so we need to + // extract the path from the search string ourselves and prefix it to the + // filename we get back. + const SOCHAR * pszFilename = + SimpleGlobUtil::strrchr(a_pszFileSpec, SG_PATH_CHAR); + if (pszFilename) { + SimpleGlobUtil::strcpy_s(m_szPathPrefix, MAX_PATH, a_pszFileSpec); + m_szPathPrefix[pszFilename - a_pszFileSpec + 1] = 0; + } +#endif + + // search for the first match on the file + int rc = this->FindFirstFileS(a_pszFileSpec, m_uiFlags); + if (rc != SG_SUCCESS) { + if (rc == SG_ERR_NOMATCH && (m_uiFlags & SG_GLOB_NOCHECK)) { + int ok = AppendName(a_pszFileSpec, false); + if (ok != SG_SUCCESS) rc = ok; + } + return rc; + } + + // add it and find all subsequent matches + int nError, nStartLen = m_nArgsLen; + bool bSuccess; + do { + nError = AppendName(this->GetFileNameS((SOCHAR)0), this->IsDirS((SOCHAR)0)); + bSuccess = this->FindNextFileS((SOCHAR)0); + } + while (nError == SG_SUCCESS && bSuccess); + SimpleGlobBase::FindDone(); + + // sort these files if required + if (m_nArgsLen > nStartLen && !(m_uiFlags & SG_GLOB_NOSORT)) { + if (m_uiFlags & SG_GLOB_FULLSORT) { + nStartLen = m_nReservedSlots; + } + SetArgvArrayType(POINTERS); + qsort( + m_rgpArgs + nStartLen, + m_nArgsLen - nStartLen, + sizeof(m_rgpArgs[0]), fileSortCompare); + } + + return nError; +} + +template +int +CSimpleGlobTempl::Add( + int a_nCount, + const SOCHAR * const * a_rgpszFileSpec + ) +{ + int nResult; + for (int n = 0; n < a_nCount; ++n) { + nResult = Add(a_rgpszFileSpec[n]); + if (nResult != SG_SUCCESS) { + return nResult; + } + } + return SG_SUCCESS; +} + +template +int +CSimpleGlobTempl::AppendName( + const SOCHAR * a_pszFileName, + bool a_bIsDir + ) +{ + // we need the argv array as offsets in case we resize it + SetArgvArrayType(OFFSETS); + + // check for special cases which cause us to ignore this entry + if ((m_uiFlags & SG_GLOB_ONLYDIR) && !a_bIsDir) { + return SG_SUCCESS; + } + if ((m_uiFlags & SG_GLOB_ONLYFILE) && a_bIsDir) { + return SG_SUCCESS; + } + if ((m_uiFlags & SG_GLOB_NODOT) && a_bIsDir) { + if (a_pszFileName[0] == '.') { + if (a_pszFileName[1] == '\0') { + return SG_SUCCESS; + } + if (a_pszFileName[1] == '.' && a_pszFileName[2] == '\0') { + return SG_SUCCESS; + } + } + } + + // ensure that we have enough room in the argv array + if (!GrowArgvArray(m_nArgsLen + 1)) { + return SG_ERR_MEMORY; + } + + // ensure that we have enough room in the string buffer (+1 for null) + size_t uiPrefixLen = SimpleGlobUtil::strlen(m_szPathPrefix); + size_t uiLen = uiPrefixLen + SimpleGlobUtil::strlen(a_pszFileName) + 1; + if (a_bIsDir && (m_uiFlags & SG_GLOB_MARK) == SG_GLOB_MARK) { + ++uiLen; // need space for the backslash + } + if (!GrowStringBuffer(m_uiBufferLen + uiLen)) { + return SG_ERR_MEMORY; + } + + // add this entry. m_uiBufferLen is offset from beginning of buffer. + m_rgpArgs[m_nArgsLen++] = (SOCHAR*)m_uiBufferLen; + SimpleGlobUtil::strcpy_s(m_pBuffer + m_uiBufferLen, + m_uiBufferSize - m_uiBufferLen, m_szPathPrefix); + SimpleGlobUtil::strcpy_s(m_pBuffer + m_uiBufferLen + uiPrefixLen, + m_uiBufferSize - m_uiBufferLen - uiPrefixLen, a_pszFileName); + m_uiBufferLen += uiLen; + + // add the directory slash if desired + if (a_bIsDir && (m_uiFlags & SG_GLOB_MARK) == SG_GLOB_MARK) { + const static SOCHAR szDirSlash[] = { SG_PATH_CHAR, 0 }; + SimpleGlobUtil::strcpy_s(m_pBuffer + m_uiBufferLen - 2, + m_uiBufferSize - (m_uiBufferLen - 2), szDirSlash); + } + + return SG_SUCCESS; +} + +template +void +CSimpleGlobTempl::SetArgvArrayType( + ARG_ARRAY_TYPE a_nNewType + ) +{ + if (m_nArgArrayType == a_nNewType) return; + if (a_nNewType == POINTERS) { + SG_ASSERT(m_nArgArrayType == OFFSETS); + for (int n = 0; n < m_nArgsLen; ++n) { + m_rgpArgs[n] = (m_rgpArgs[n] == (SOCHAR*)-1) ? + NULL : m_pBuffer + (size_t) m_rgpArgs[n]; + } + } + else { + SG_ASSERT(a_nNewType == OFFSETS); + SG_ASSERT(m_nArgArrayType == POINTERS); + for (int n = 0; n < m_nArgsLen; ++n) { + m_rgpArgs[n] = (m_rgpArgs[n] == NULL) ? + (SOCHAR*) -1 : (SOCHAR*) (m_rgpArgs[n] - m_pBuffer); + } + } + m_nArgArrayType = a_nNewType; +} + +template +bool +CSimpleGlobTempl::GrowArgvArray( + int a_nNewLen + ) +{ + if (a_nNewLen >= m_nArgsSize) { + static const int SG_ARGV_INITIAL_SIZE = 32; + int nNewSize = (m_nArgsSize > 0) ? + m_nArgsSize * 2 : SG_ARGV_INITIAL_SIZE; + while (a_nNewLen >= nNewSize) { + nNewSize *= 2; + } + void * pNewBuffer = realloc(m_rgpArgs, nNewSize * sizeof(SOCHAR*)); + if (!pNewBuffer) return false; + m_nArgsSize = nNewSize; + m_rgpArgs = (SOCHAR**) pNewBuffer; + } + return true; +} + +template +bool +CSimpleGlobTempl::GrowStringBuffer( + size_t a_uiMinSize + ) +{ + if (a_uiMinSize >= m_uiBufferSize) { + static const int SG_BUFFER_INITIAL_SIZE = 1024; + size_t uiNewSize = (m_uiBufferSize > 0) ? + m_uiBufferSize * 2 : SG_BUFFER_INITIAL_SIZE; + while (a_uiMinSize >= uiNewSize) { + uiNewSize *= 2; + } + void * pNewBuffer = realloc(m_pBuffer, uiNewSize * sizeof(SOCHAR)); + if (!pNewBuffer) return false; + m_uiBufferSize = uiNewSize; + m_pBuffer = (SOCHAR*) pNewBuffer; + } + return true; +} + +template +int +CSimpleGlobTempl::fileSortCompare( + const void *a1, + const void *a2 + ) +{ + const SOCHAR * s1 = *(const SOCHAR **)a1; + const SOCHAR * s2 = *(const SOCHAR **)a2; + if (s1 && s2) { + return SimpleGlobUtil::strcasecmp(s1, s2); + } + // NULL sorts first + return s1 == s2 ? 0 : (s1 ? 1 : -1); +} + +// --------------------------------------------------------------------------- +// TYPE DEFINITIONS +// --------------------------------------------------------------------------- + +/*! @brief ASCII/MBCS version of CSimpleGlob */ +typedef CSimpleGlobTempl CSimpleGlobA; + +/*! @brief wchar_t version of CSimpleGlob */ +typedef CSimpleGlobTempl CSimpleGlobW; + +#if SG_HAVE_ICU +/*! @brief UChar version of CSimpleGlob */ +typedef CSimpleGlobTempl CSimpleGlobU; +#endif + +#ifdef _UNICODE +/*! @brief TCHAR version dependent on if _UNICODE is defined */ +# if SG_HAVE_ICU +# define CSimpleGlob CSimpleGlobU +# else +# define CSimpleGlob CSimpleGlobW +# endif +#else +/*! @brief TCHAR version dependent on if _UNICODE is defined */ +# define CSimpleGlob CSimpleGlobA +#endif + +#endif // INCLUDED_SimpleGlob diff --git a/Source/EmberCommon/SimpleOpt.h b/Source/EmberCommon/SimpleOpt.h new file mode 100644 index 0000000..6c566ed --- /dev/null +++ b/Source/EmberCommon/SimpleOpt.h @@ -0,0 +1,1063 @@ +/*! @file SimpleOpt.h + + @version 3.6 + + @brief A cross-platform command line library which can parse almost any + of the standard command line formats in use today. It is designed + explicitly to be portable to any platform and has been tested on Windows + and Linux. See CSimpleOptTempl for the class definition. + + @section features FEATURES + - MIT Licence allows free use in all software (including GPL + and commercial) + - multi-platform (Windows 95/98/ME/NT/2K/XP, Linux, Unix) + - supports all lengths of option names: + +
- + switch character only (e.g. use stdin for input) +
-o + short (single character) +
-long + long (multiple character, single switch character) +
--longer + long (multiple character, multiple switch characters) +
+ - supports all types of arguments for options: + +
--option + short/long option flag (no argument) +
--option ARG + short/long option with separate required argument +
--option=ARG + short/long option with combined required argument +
--option[=ARG] + short/long option with combined optional argument +
-oARG + short option with combined required argument +
-o[ARG] + short option with combined optional argument +
+ - supports options with multiple or variable numbers of arguments: + +
--multi ARG1 ARG2 + Multiple arguments +
--multi N ARG-1 ARG-2 ... ARG-N + Variable number of arguments +
+ - supports case-insensitive option matching on short, long and/or + word arguments. + - supports options which do not use a switch character. i.e. a special + word which is construed as an option. + e.g. "foo.exe open /directory/file.txt" + - supports clumping of multiple short options (no arguments) in a string + e.g. "foo.exe -abcdef file1" <==> "foo.exe -a -b -c -d -e -f file1" + - automatic recognition of a single slash as equivalent to a single + hyphen on Windows, e.g. "/f FILE" is equivalent to "-f FILE". + - file arguments can appear anywhere in the argument list: + "foo.exe file1.txt -a ARG file2.txt --flag file3.txt file4.txt" + files will be returned to the application in the same order they were + supplied on the command line + - short-circuit option matching: "--man" will match "--mandate" + invalid options can be handled while continuing to parse the command + line valid options list can be changed dynamically during command line + processing, i.e. accept different options depending on an option + supplied earlier in the command line. + - implemented with only a single C++ header file + - optionally use no C runtime or OS functions + - char, wchar_t and Windows TCHAR in the same program + - complete working examples included + - compiles cleanly at warning level 4 (Windows/VC.NET 2003), warning + level 3 (Windows/VC6) and -Wall (Linux/gcc) + + @section usage USAGE + The SimpleOpt class is used by following these steps: +
    +
  1. Include the SimpleOpt.h header file +
    +		\#include "SimpleOpt.h"
    +		
    +
  2. Define an array of valid options for your program. +
    +@link CSimpleOptTempl::SOption CSimpleOpt::SOption @endlink g_rgOptions[] = {
    +	{ OPT_FLAG, _T("-a"),     SO_NONE    }, // "-a"
    +	{ OPT_FLAG, _T("-b"),     SO_NONE    }, // "-b"
    +	{ OPT_ARG,  _T("-f"),     SO_REQ_SEP }, // "-f ARG"
    +	{ OPT_HELP, _T("-?"),     SO_NONE    }, // "-?"
    +	{ OPT_HELP, _T("--help"), SO_NONE    }, // "--help"
    +	SO_END_OF_OPTIONS                       // END
    +};
    +
    + Note that all options must start with a hyphen even if the slash will + be accepted. This is because the slash character is automatically + converted into a hyphen to test against the list of options. + For example, the following line matches both "-?" and "/?" + (on Windows). +
    +	{ OPT_HELP, _T("-?"),     SO_NONE    }, // "-?"
    +
    +
  3. Instantiate a CSimpleOpt object supplying argc, argv and the option + table +
    +@link CSimpleOptTempl CSimpleOpt @endlink args(argc, argv, g_rgOptions);
    +
    +
  4. Process the arguments by calling Next() until it returns false. + On each call, first check for an error by calling LastError(), then + either handle the error or process the argument. +
    +while (args.Next()) {
    +	if (args.LastError() == SO_SUCCESS) {
    +		handle option: use OptionId(), OptionText() and OptionArg()
    +	}
    +	else {
    +		handle error: see ESOError enums
    +	}
    +}
    +
    +
  5. Process all non-option arguments with File(), Files() and FileCount() +
    +ShowFiles(args.FileCount(), args.Files());
    +
    +
+ + @section notes NOTES + - In MBCS mode, this library is guaranteed to work correctly only when + all option names use only ASCII characters. + - Note that if case-insensitive matching is being used then the first + matching option in the argument list will be returned. + + @section licence MIT LICENCE +
+	The licence text below is the boilerplate "MIT Licence" used from:
+	http://www.opensource.org/licenses/mit-license.php
+
+	Copyright (c) 2006-2013, Brodie Thiesfield
+
+	Permission is hereby granted, free of charge, to any person obtaining a
+	copy of this software and associated documentation files (the "Software"),
+	to deal in the Software without restriction, including without limitation
+	the rights to use, copy, modify, merge, publish, distribute, sublicense,
+	and/or sell copies of the Software, and to permit persons to whom the
+	Software is furnished to do so, subject to the following conditions:
+
+	The above copyright notice and this permission notice shall be included
+	in all copies or substantial portions of the Software.
+
+	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
+	OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
+	MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
+	IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 
+	CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
+	TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
+	SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+*/ + +/*! @mainpage + + +
Library SimpleOpt +
Author Brodie Thiesfield [code at jellycan dot com] +
Source http://code.jellycan.com/simpleopt/ +
+ + @section SimpleOpt SimpleOpt + + A cross-platform library providing a simple method to parse almost any of + the standard command-line formats in use today. + + See the @link SimpleOpt.h SimpleOpt @endlink documentation for full + details. + + @section SimpleGlob SimpleGlob + + A cross-platform file globbing library providing the ability to + expand wildcards in command-line arguments to a list of all matching + files. + + See the @link SimpleGlob.h SimpleGlob @endlink documentation for full + details. +*/ + +#ifndef INCLUDED_SimpleOpt +#define INCLUDED_SimpleOpt + +// Default the max arguments to a fixed value. If you want to be able to +// handle any number of arguments, then predefine this to 0 and it will +// use an internal dynamically allocated buffer instead. +#ifdef SO_MAX_ARGS +# define SO_STATICBUF SO_MAX_ARGS +#else +# include // malloc, free +# include // memcpy +# define SO_STATICBUF 50 +#endif + +//! Error values +typedef enum _ESOError +{ + //! No error + SO_SUCCESS = 0, + + /*! It looks like an option (it starts with a switch character), but + it isn't registered in the option table. */ + SO_OPT_INVALID = -1, + + /*! Multiple options matched the supplied option text. + Only returned when NOT using SO_O_EXACT. */ + SO_OPT_MULTIPLE = -2, + + /*! Option doesn't take an argument, but a combined argument was + supplied. */ + SO_ARG_INVALID = -3, + + /*! SO_REQ_CMB style-argument was supplied to a SO_REQ_SEP option + Only returned when using SO_O_PEDANTIC. */ + SO_ARG_INVALID_TYPE = -4, + + //! Required argument was not supplied + SO_ARG_MISSING = -5, + + /*! Option argument looks like another option. + Only returned when NOT using SO_O_NOERR. */ + SO_ARG_INVALID_DATA = -6 +} ESOError; + +//! Option flags +enum _ESOFlags +{ + /*! Disallow partial matching of option names */ + SO_O_EXACT = 0x0001, + + /*! Disallow use of slash as an option marker on Windows. + Un*x only ever recognizes a hyphen. */ + SO_O_NOSLASH = 0x0002, + + /*! Permit arguments on single letter options with no equals sign. + e.g. -oARG or -o[ARG] */ + SO_O_SHORTARG = 0x0004, + + /*! Permit single character options to be clumped into a single + option string. e.g. "-a -b -c" <==> "-abc" */ + SO_O_CLUMP = 0x0008, + + /*! Process the entire argv array for options, including the + argv[0] entry. */ + SO_O_USEALL = 0x0010, + + /*! Do not generate an error for invalid options. errors for missing + arguments will still be generated. invalid options will be + treated as files. invalid options in clumps will be silently + ignored. */ + SO_O_NOERR = 0x0020, + + /*! Validate argument type pedantically. Return an error when a + separated argument "-opt arg" is supplied by the user as a + combined argument "-opt=arg". By default this is not considered + an error. */ + SO_O_PEDANTIC = 0x0040, + + /*! Case-insensitive comparisons for short arguments */ + SO_O_ICASE_SHORT = 0x0100, + + /*! Case-insensitive comparisons for long arguments */ + SO_O_ICASE_LONG = 0x0200, + + /*! Case-insensitive comparisons for word arguments + i.e. arguments without any hyphens at the start. */ + SO_O_ICASE_WORD = 0x0400, + + /*! Case-insensitive comparisons for all arg types */ + SO_O_ICASE = 0x0700 +}; + +/*! Types of arguments that options may have. Note that some of the _ESOFlags + are not compatible with all argument types. SO_O_SHORTARG requires that + relevant options use either SO_REQ_CMB or SO_OPT. SO_O_CLUMP requires + that relevant options use only SO_NONE. + */ +typedef enum _ESOArgType { + /*! No argument. Just the option flags. + e.g. -o --opt */ + SO_NONE, + + /*! Required separate argument. + e.g. -o ARG --opt ARG */ + SO_REQ_SEP, + + /*! Required combined argument. + e.g. -oARG -o=ARG --opt=ARG */ + SO_REQ_CMB, + + /*! Optional combined argument. + e.g. -o[ARG] -o[=ARG] --opt[=ARG] */ + SO_OPT, + + /*! Multiple separate arguments. The actual number of arguments is + determined programatically at the time the argument is processed. + e.g. -o N ARG1 ARG2 ... ARGN --opt N ARG1 ARG2 ... ARGN */ + SO_MULTI +} ESOArgType; + +//! this option definition must be the last entry in the table +#define SO_END_OF_OPTIONS { -1, NULL, SO_NONE } + +#ifdef _DEBUG +# ifdef _MSC_VER +# include +# define SO_ASSERT(b) _ASSERTE(b) +# else +# include +# define SO_ASSERT(b) assert(b) +# endif +#else +# define SO_ASSERT(b) //!< assertion used to test input data +#endif + +// --------------------------------------------------------------------------- +// MAIN TEMPLATE CLASS +// --------------------------------------------------------------------------- + +/*! @brief Implementation of the SimpleOpt class */ +template +class CSimpleOptTempl +{ +public: + /*! @brief Structure used to define all known options. */ + struct SOption { + + //SOption()//Added for compatibility with fractal flames. + //{ + // nId = 0; + // pszArg = NULL; + // nArgType = SO_NONE; + //} + + //SOption(int id, const SOCHAR* arg, ESOArgType argType)//Added for compatibility with fractal flames. + //{ + // nId = id; + // pszArg = arg; + // nArgType = argType; + //} + + /*! ID to return for this flag. Optional but must be >= 0 */ + int nId; + + /*! arg string to search for, e.g. "open", "-", "-f", "--file" + Note that on Windows the slash option marker will be converted + to a hyphen so that "-f" will also match "/f". */ + const SOCHAR * pszArg; + + /*! type of argument accepted by this option */ + ESOArgType nArgType; + }; + + /*! @brief Initialize the class. Init() must be called later. */ + CSimpleOptTempl() + : m_rgShuffleBuf(NULL) + { + Init(0, NULL, NULL, 0); + } + + /*! @brief Initialize the class in preparation for use. */ + CSimpleOptTempl( + int argc, + SOCHAR * argv[], + const SOption * a_rgOptions, + int a_nFlags = 0 + ) + : m_rgShuffleBuf(NULL) + { + Init(argc, argv, a_rgOptions, a_nFlags); + } + +#ifndef SO_MAX_ARGS + /*! @brief Deallocate any allocated memory. */ + ~CSimpleOptTempl() { if (m_rgShuffleBuf) free(m_rgShuffleBuf); } +#endif + + /*! @brief Initialize the class in preparation for calling Next. + + The table of options pointed to by a_rgOptions does not need to be + valid at the time that Init() is called. However on every call to + Next() the table pointed to must be a valid options table with the + last valid entry set to SO_END_OF_OPTIONS. + + NOTE: the array pointed to by a_argv will be modified by this + class and must not be used or modified outside of member calls to + this class. + + @param a_argc Argument array size + @param a_argv Argument array + @param a_rgOptions Valid option array + @param a_nFlags Optional flags to modify the processing of + the arguments + + @return true Successful + @return false if SO_MAX_ARGC > 0: Too many arguments + if SO_MAX_ARGC == 0: Memory allocation failure + */ + bool Init( + int a_argc, + SOCHAR * a_argv[], + const SOption * a_rgOptions, + int a_nFlags = 0 + ); + + /*! @brief Change the current options table during option parsing. + + @param a_rgOptions Valid option array + */ + inline void SetOptions(const SOption * a_rgOptions) { + m_rgOptions = a_rgOptions; + } + + /*! @brief Change the current flags during option parsing. + + Note that changing the SO_O_USEALL flag here will have no affect. + It must be set using Init() or the constructor. + + @param a_nFlags Flags to modify the processing of the arguments + */ + inline void SetFlags(int a_nFlags) { m_nFlags = a_nFlags; } + + /*! @brief Query if a particular flag is set */ + inline bool HasFlag(int a_nFlag) const { + return (m_nFlags & a_nFlag) == a_nFlag; + } + + /*! @brief Advance to the next option if available. + + When all options have been processed it will return false. When true + has been returned, you must check for an invalid or unrecognized + option using the LastError() method. This will be return an error + value other than SO_SUCCESS on an error. All standard data + (e.g. OptionText(), OptionArg(), OptionId(), etc) will be available + depending on the error. + + After all options have been processed, the remaining files from the + command line can be processed in same order as they were passed to + the program. + + @return true option or error available for processing + @return false all options have been processed + */ + bool Next(); + + /*! Stops processing of the command line and returns all remaining + arguments as files. The next call to Next() will return false. + */ + void Stop(); + + /*! @brief Return the last error that occurred. + + This function must always be called before processing the current + option. This function is available only when Next() has returned true. + */ + inline ESOError LastError() const { return m_nLastError; } + + /*! @brief Return the nId value from the options array for the current + option. + + This function is available only when Next() has returned true. + */ + inline int OptionId() const { return m_nOptionId; } + + /*! @brief Return the pszArg from the options array for the current + option. + + This function is available only when Next() has returned true. + */ + inline const SOCHAR * OptionText() const { return m_pszOptionText; } + + /*! @brief Return the argument for the current option where one exists. + + If there is no argument for the option, this will return NULL. + This function is available only when Next() has returned true. + */ + inline SOCHAR * OptionArg() const { return m_pszOptionArg; } + + /*! @brief Validate and return the desired number of arguments. + + This is only valid when OptionId() has return the ID of an option + that is registered as SO_MULTI. It may be called multiple times + each time returning the desired number of arguments. Previously + returned argument pointers are remain valid. + + If an error occurs during processing, NULL will be returned and + the error will be available via LastError(). + + @param n Number of arguments to return. + */ + SOCHAR ** MultiArg(int n); + + /*! @brief Returned the number of entries in the Files() array. + + After Next() has returned false, this will be the list of files (or + otherwise unprocessed arguments). + */ + inline int FileCount() const { return m_argc - m_nLastArg; } + + /*! @brief Return the specified file argument. + + @param n Index of the file to return. This must be between 0 + and FileCount() - 1; + */ + inline SOCHAR * File(int n) const { + SO_ASSERT(n >= 0 && n < FileCount()); + return m_argv[m_nLastArg + n]; + } + + /*! @brief Return the array of files. */ + inline SOCHAR ** Files() const { return &m_argv[m_nLastArg]; } + +private: + CSimpleOptTempl(const CSimpleOptTempl &); // disabled + CSimpleOptTempl & operator=(const CSimpleOptTempl &); // disabled + + SOCHAR PrepareArg(SOCHAR * a_pszString) const; + bool NextClumped(); + void ShuffleArg(int a_nStartIdx, int a_nCount); + int LookupOption(const SOCHAR * a_pszOption) const; + int CalcMatch(const SOCHAR *a_pszSource, const SOCHAR *a_pszTest) const; + + // Find the '=' character within a string. + inline SOCHAR * FindEquals(SOCHAR *s) const { + while (*s && *s != (SOCHAR)'=') ++s; + return *s ? s : NULL; + } + bool IsEqual(SOCHAR a_cLeft, SOCHAR a_cRight, int a_nArgType) const; + + inline void Copy(SOCHAR ** ppDst, SOCHAR ** ppSrc, int nCount) const { +#ifdef SO_MAX_ARGS + // keep our promise of no CLIB usage + while (nCount-- > 0) *ppDst++ = *ppSrc++; +#else + memcpy(ppDst, ppSrc, nCount * sizeof(SOCHAR*)); +#endif + } + +private: + const SOption * m_rgOptions; //!< pointer to options table + int m_nFlags; //!< flags + int m_nOptionIdx; //!< current argv option index + int m_nOptionId; //!< id of current option (-1 = invalid) + int m_nNextOption; //!< index of next option + int m_nLastArg; //!< last argument, after this are files + int m_argc; //!< argc to process + SOCHAR ** m_argv; //!< argv + const SOCHAR * m_pszOptionText; //!< curr option text, e.g. "-f" + SOCHAR * m_pszOptionArg; //!< curr option arg, e.g. "c:\file.txt" + SOCHAR * m_pszClump; //!< clumped single character options + SOCHAR m_szShort[3]; //!< temp for clump and combined args + ESOError m_nLastError; //!< error status from the last call + SOCHAR ** m_rgShuffleBuf; //!< shuffle buffer for large argc +}; + +// --------------------------------------------------------------------------- +// IMPLEMENTATION +// --------------------------------------------------------------------------- + +template +bool +CSimpleOptTempl::Init( + int a_argc, + SOCHAR * a_argv[], + const SOption * a_rgOptions, + int a_nFlags + ) +{ + m_argc = a_argc; + m_nLastArg = a_argc; + m_argv = a_argv; + m_rgOptions = a_rgOptions; + m_nLastError = SO_SUCCESS; + m_nOptionIdx = 0; + m_nOptionId = -1; + m_pszOptionText = NULL; + m_pszOptionArg = NULL; + m_nNextOption = (a_nFlags & SO_O_USEALL) ? 0 : 1; + m_szShort[0] = (SOCHAR)'-'; + m_szShort[2] = (SOCHAR)'\0'; + m_nFlags = a_nFlags; + m_pszClump = NULL; + +#ifdef SO_MAX_ARGS + if (m_argc > SO_MAX_ARGS) { + m_nLastError = SO_ARG_INVALID_DATA; + m_nLastArg = 0; + return false; + } +#else + if (m_rgShuffleBuf) { + free(m_rgShuffleBuf); + } + if (m_argc > SO_STATICBUF) { + m_rgShuffleBuf = (SOCHAR**) malloc(sizeof(SOCHAR*) * m_argc); + if (!m_rgShuffleBuf) { + return false; + } + } +#endif + + return true; +} + +template +bool +CSimpleOptTempl::Next() +{ +#ifdef SO_MAX_ARGS + if (m_argc > SO_MAX_ARGS) { + SO_ASSERT(!"Too many args! Check the return value of Init()!"); + return false; + } +#endif + + // process a clumped option string if appropriate + if (m_pszClump && *m_pszClump) { + // silently discard invalid clumped option + bool bIsValid = NextClumped(); + while (*m_pszClump && !bIsValid && HasFlag(SO_O_NOERR)) { + bIsValid = NextClumped(); + } + + // return this option if valid or we are returning errors + if (bIsValid || !HasFlag(SO_O_NOERR)) { + return true; + } + } + SO_ASSERT(!m_pszClump || !*m_pszClump); + m_pszClump = NULL; + + // init for the next option + m_nOptionIdx = m_nNextOption; + m_nOptionId = -1; + m_pszOptionText = NULL; + m_pszOptionArg = NULL; + m_nLastError = SO_SUCCESS; + + // find the next option + SOCHAR cFirst; + int nTableIdx = -1; + int nOptIdx = m_nOptionIdx; + while (nTableIdx < 0 && nOptIdx < m_nLastArg) { + SOCHAR * pszArg = m_argv[nOptIdx]; + m_pszOptionArg = NULL; + + // find this option in the options table + cFirst = PrepareArg(pszArg); + if (pszArg[0] == (SOCHAR)'-') { + // find any combined argument string and remove equals sign + m_pszOptionArg = FindEquals(pszArg); + if (m_pszOptionArg) { + *m_pszOptionArg++ = (SOCHAR)'\0'; + } + } + nTableIdx = LookupOption(pszArg); + + // if we didn't find this option but if it is a short form + // option then we try the alternative forms + if (nTableIdx < 0 + && !m_pszOptionArg + && pszArg[0] == (SOCHAR)'-' + && pszArg[1] + && pszArg[1] != (SOCHAR)'-' + && pszArg[2]) + { + // test for a short-form with argument if appropriate + if (HasFlag(SO_O_SHORTARG)) { + m_szShort[1] = pszArg[1]; + int nIdx = LookupOption(m_szShort); + if (nIdx >= 0 + && (m_rgOptions[nIdx].nArgType == SO_REQ_CMB + || m_rgOptions[nIdx].nArgType == SO_OPT)) + { + m_pszOptionArg = &pszArg[2]; + pszArg = m_szShort; + nTableIdx = nIdx; + } + } + + // test for a clumped short-form option string and we didn't + // match on the short-form argument above + if (nTableIdx < 0 && HasFlag(SO_O_CLUMP)) { + m_pszClump = &pszArg[1]; + ++m_nNextOption; + if (nOptIdx > m_nOptionIdx) { + ShuffleArg(m_nOptionIdx, nOptIdx - m_nOptionIdx); + } + return Next(); + } + } + + // The option wasn't found. If it starts with a switch character + // and we are not suppressing errors for invalid options then it + // is reported as an error, otherwise it is data. + if (nTableIdx < 0) { + if (!HasFlag(SO_O_NOERR) && pszArg[0] == (SOCHAR)'-') { + m_pszOptionText = pszArg; + break; + } + + pszArg[0] = cFirst; + ++nOptIdx; + if (m_pszOptionArg) { + *(--m_pszOptionArg) = (SOCHAR)'='; + } + } + } + + // end of options + if (nOptIdx >= m_nLastArg) { + if (nOptIdx > m_nOptionIdx) { + ShuffleArg(m_nOptionIdx, nOptIdx - m_nOptionIdx); + } + return false; + } + ++m_nNextOption; + + // get the option id + ESOArgType nArgType = SO_NONE; + if (nTableIdx < 0) { + m_nLastError = (ESOError) nTableIdx; // error code + } + else { + m_nOptionId = m_rgOptions[nTableIdx].nId; + m_pszOptionText = m_rgOptions[nTableIdx].pszArg; + + // ensure that the arg type is valid + nArgType = m_rgOptions[nTableIdx].nArgType; + switch (nArgType) { + case SO_NONE: + if (m_pszOptionArg) { + m_nLastError = SO_ARG_INVALID; + } + break; + + case SO_REQ_SEP: + if (m_pszOptionArg) { + // they wanted separate args, but we got a combined one, + // unless we are pedantic, just accept it. + if (HasFlag(SO_O_PEDANTIC)) { + m_nLastError = SO_ARG_INVALID_TYPE; + } + } + // more processing after we shuffle + break; + + case SO_REQ_CMB: + if (!m_pszOptionArg) { + m_nLastError = SO_ARG_MISSING; + } + break; + + case SO_OPT: + // nothing to do + break; + + case SO_MULTI: + // nothing to do. Caller must now check for valid arguments + // using GetMultiArg() + break; + } + } + + // shuffle the files out of the way + if (nOptIdx > m_nOptionIdx) { + ShuffleArg(m_nOptionIdx, nOptIdx - m_nOptionIdx); + } + + // we need to return the separate arg if required, just re-use the + // multi-arg code because it all does the same thing + if ( nArgType == SO_REQ_SEP + && !m_pszOptionArg + && m_nLastError == SO_SUCCESS) + { + SOCHAR ** ppArgs = MultiArg(1); + if (ppArgs) { + m_pszOptionArg = *ppArgs; + } + } + + return true; +} + +template +void +CSimpleOptTempl::Stop() +{ + if (m_nNextOption < m_nLastArg) { + ShuffleArg(m_nNextOption, m_nLastArg - m_nNextOption); + } +} + +template +SOCHAR +CSimpleOptTempl::PrepareArg( + SOCHAR * a_pszString + ) const +{ +#ifdef _WIN32 + // On Windows we can accept the forward slash as a single character + // option delimiter, but it cannot replace the '-' option used to + // denote stdin. On Un*x paths may start with slash so it may not + // be used to start an option. + if (!HasFlag(SO_O_NOSLASH) + && a_pszString[0] == (SOCHAR)'/' + && a_pszString[1] + && a_pszString[1] != (SOCHAR)'-') + { + a_pszString[0] = (SOCHAR)'-'; + return (SOCHAR)'/'; + } +#endif + return a_pszString[0]; +} + +template +bool +CSimpleOptTempl::NextClumped() +{ + // prepare for the next clumped option + m_szShort[1] = *m_pszClump++; + m_nOptionId = -1; + m_pszOptionText = NULL; + m_pszOptionArg = NULL; + m_nLastError = SO_SUCCESS; + + // lookup this option, ensure that we are using exact matching + int nSavedFlags = m_nFlags; + m_nFlags = SO_O_EXACT; + int nTableIdx = LookupOption(m_szShort); + m_nFlags = nSavedFlags; + + // unknown option + if (nTableIdx < 0) { + m_pszOptionText = m_szShort; // invalid option + m_nLastError = (ESOError) nTableIdx; // error code + return false; + } + + // valid option + m_pszOptionText = m_rgOptions[nTableIdx].pszArg; + ESOArgType nArgType = m_rgOptions[nTableIdx].nArgType; + if (nArgType == SO_NONE) { + m_nOptionId = m_rgOptions[nTableIdx].nId; + return true; + } + + if (nArgType == SO_REQ_CMB && *m_pszClump) { + m_nOptionId = m_rgOptions[nTableIdx].nId; + m_pszOptionArg = m_pszClump; + while (*m_pszClump) ++m_pszClump; // must point to an empty string + return true; + } + + // invalid option as it requires an argument + m_nLastError = SO_ARG_MISSING; + return true; +} + +// Shuffle arguments to the end of the argv array. +// +// For example: +// argv[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8" }; +// +// ShuffleArg(1, 1) = { "0", "2", "3", "4", "5", "6", "7", "8", "1" }; +// ShuffleArg(5, 2) = { "0", "1", "2", "3", "4", "7", "8", "5", "6" }; +// ShuffleArg(2, 4) = { "0", "1", "6", "7", "8", "2", "3", "4", "5" }; +template +void +CSimpleOptTempl::ShuffleArg( + int a_nStartIdx, + int a_nCount + ) +{ + SOCHAR * staticBuf[SO_STATICBUF]; + SOCHAR ** buf = m_rgShuffleBuf ? m_rgShuffleBuf : staticBuf; + int nTail = m_argc - a_nStartIdx - a_nCount; + + // make a copy of the elements to be moved + Copy(buf, m_argv + a_nStartIdx, a_nCount); + + // move the tail down + Copy(m_argv + a_nStartIdx, m_argv + a_nStartIdx + a_nCount, nTail); + + // append the moved elements to the tail + Copy(m_argv + a_nStartIdx + nTail, buf, a_nCount); + + // update the index of the last unshuffled arg + m_nLastArg -= a_nCount; +} + +// match on the long format strings. partial matches will be +// accepted only if that feature is enabled. +template +int +CSimpleOptTempl::LookupOption( + const SOCHAR * a_pszOption + ) const +{ + int nBestMatch = -1; // index of best match so far + int nBestMatchLen = 0; // matching characters of best match + int nLastMatchLen = 0; // matching characters of last best match + + for (int n = 0; m_rgOptions[n].nId >= 0; ++n) { + // the option table must use hyphens as the option character, + // the slash character is converted to a hyphen for testing. + SO_ASSERT(m_rgOptions[n].pszArg[0] != (SOCHAR)'/'); + + int nMatchLen = CalcMatch(m_rgOptions[n].pszArg, a_pszOption); + if (nMatchLen == -1) { + return n; + } + if (nMatchLen > 0 && nMatchLen >= nBestMatchLen) { + nLastMatchLen = nBestMatchLen; + nBestMatchLen = nMatchLen; + nBestMatch = n; + } + } + + // only partial matches or no match gets to here, ensure that we + // don't return a partial match unless it is a clear winner + if (HasFlag(SO_O_EXACT) || nBestMatch == -1) { + return SO_OPT_INVALID; + } + return (nBestMatchLen > nLastMatchLen) ? nBestMatch : SO_OPT_MULTIPLE; +} + +// calculate the number of characters that match (case-sensitive) +// 0 = no match, > 0 == number of characters, -1 == perfect match +template +int +CSimpleOptTempl::CalcMatch( + const SOCHAR * a_pszSource, + const SOCHAR * a_pszTest + ) const +{ + if (!a_pszSource || !a_pszTest) { + return 0; + } + + // determine the argument type + int nArgType = SO_O_ICASE_LONG; + if (a_pszSource[0] != '-') { + nArgType = SO_O_ICASE_WORD; + } + else if (a_pszSource[1] != '-' && !a_pszSource[2]) { + nArgType = SO_O_ICASE_SHORT; + } + + // match and skip leading hyphens + while (*a_pszSource == (SOCHAR)'-' && *a_pszSource == *a_pszTest) { + ++a_pszSource; + ++a_pszTest; + } + if (*a_pszSource == (SOCHAR)'-' || *a_pszTest == (SOCHAR)'-') { + return 0; + } + + // find matching number of characters in the strings + int nLen = 0; + while (*a_pszSource && IsEqual(*a_pszSource, *a_pszTest, nArgType)) { + ++a_pszSource; + ++a_pszTest; + ++nLen; + } + + // if we have exhausted the source... + if (!*a_pszSource) { + // and the test strings, then it's a perfect match + if (!*a_pszTest) { + return -1; + } + + // otherwise the match failed as the test is longer than + // the source. i.e. "--mant" will not match the option "--man". + return 0; + } + + // if we haven't exhausted the test string then it is not a match + // i.e. "--mantle" will not best-fit match to "--mandate" at all. + if (*a_pszTest) { + return 0; + } + + // partial match to the current length of the test string + return nLen; +} + +template +bool +CSimpleOptTempl::IsEqual( + SOCHAR a_cLeft, + SOCHAR a_cRight, + int a_nArgType + ) const +{ + // if this matches then we are doing case-insensitive matching + if (m_nFlags & a_nArgType) { + if (a_cLeft >= 'A' && a_cLeft <= 'Z') a_cLeft += 'a' - 'A'; + if (a_cRight >= 'A' && a_cRight <= 'Z') a_cRight += 'a' - 'A'; + } + return a_cLeft == a_cRight; +} + +// calculate the number of characters that match (case-sensitive) +// 0 = no match, > 0 == number of characters, -1 == perfect match +template +SOCHAR ** +CSimpleOptTempl::MultiArg( + int a_nCount + ) +{ + // ensure we have enough arguments + if (m_nNextOption + a_nCount > m_nLastArg) { + m_nLastError = SO_ARG_MISSING; + return NULL; + } + + // our argument array + SOCHAR ** rgpszArg = &m_argv[m_nNextOption]; + + // Ensure that each of the following don't start with an switch character. + // Only make this check if we are returning errors for unknown arguments. + if (!HasFlag(SO_O_NOERR)) { + for (int n = 0; n < a_nCount; ++n) { + SOCHAR ch = PrepareArg(rgpszArg[n]); + if (rgpszArg[n][0] == (SOCHAR)'-') { + rgpszArg[n][0] = ch; + m_nLastError = SO_ARG_INVALID_DATA; + return NULL; + } + rgpszArg[n][0] = ch; + } + } + + // all good + m_nNextOption += a_nCount; + return rgpszArg; +} + + +// --------------------------------------------------------------------------- +// TYPE DEFINITIONS +// --------------------------------------------------------------------------- + +/*! @brief ASCII/MBCS version of CSimpleOpt */ +typedef CSimpleOptTempl CSimpleOptA; + +/*! @brief wchar_t version of CSimpleOpt */ +typedef CSimpleOptTempl CSimpleOptW; + +#if defined(_UNICODE) +/*! @brief TCHAR version dependent on if _UNICODE is defined */ +# define CSimpleOpt CSimpleOptW +# define CharT wchar_t +#else +/*! @brief TCHAR version dependent on if _UNICODE is defined */ +# define CSimpleOpt CSimpleOptA +# define CharT char +#endif + +#endif // INCLUDED_SimpleOpt diff --git a/Source/EmberGenome/EmberGenome.cpp b/Source/EmberGenome/EmberGenome.cpp new file mode 100644 index 0000000..ea03e5e --- /dev/null +++ b/Source/EmberGenome/EmberGenome.cpp @@ -0,0 +1,776 @@ +#include "EmberCommonPch.h" +#include "EmberGenome.h" +#include "JpegUtils.h" + +/// +/// Set various default test values on the passed in ember. +/// +/// The ember to test +template +void SetDefaultTestValues(Ember& ember) +{ + ember.m_Time = 0.0; + ember.m_Interp = EMBER_INTERP_LINEAR; + ember.m_PaletteInterp = INTERP_HSV; + ember.m_Background[0] = 0; + ember.m_Background[1] = 0; + ember.m_Background[2] = 0; + ember.m_Background[3] = 255; + ember.m_CenterX = 0; + ember.m_CenterY = 0; + ember.m_Rotate = 0; + ember.m_PixelsPerUnit = 64; + ember.m_FinalRasW = 128; + ember.m_FinalRasH = 128; + ember.m_Supersample = 1; + ember.m_SpatialFilterRadius = T(0.5); + ember.m_SpatialFilterType = GAUSSIAN_SPATIAL_FILTER; + ember.m_Zoom = 0; + ember.m_Quality = 1; + ember.m_Passes = 1; + ember.m_TemporalSamples = 1; + ember.m_MaxRadDE = 0; + ember.m_MinRadDE = 0; + ember.m_CurveDE = T(0.6); +} + +/// +/// The core of the EmberGenome.exe program. +/// Template argument expected to be float or double. +/// +/// A populated EmberOptions object which specifies all program options to be used +/// True if success, else false. +template +bool EmberGenome(EmberOptions& opt) +{ + OpenCLWrapper wrapper; + std::cout.imbue(std::locale("")); + + if (opt.DumpArgs()) + cout << opt.GetValues(OPT_USE_GENOME) << endl; + + if (opt.OpenCLInfo()) + { + cout << "\nOpenCL Info: " << endl; + cout << wrapper.DumpInfo(); + return true; + } + + //Regular variables. + Timing t; + bool exactTimeMatch, randomMode, didColor, seqFlag; + unsigned int i, j, i0, i1, rep, val, frame, frameCount, count = 0; + unsigned int ftime, firstFrame, lastFrame; + unsigned int n, tot, totb, totw; + T avgPix, fractionBlack, fractionWhite, blend, spread, mix0, mix1; + string token, filename; + ostringstream os, os2; + vector> embers, embers2, templateEmbers; + vector vars, noVars; + vector finalImage; + eCrossMode crossMeth; + eMutateMode mutMeth; + Ember orig, save, selp0, selp1, parent0, parent1; + Ember result, result1, result2, result3, interpolated; + Ember* aselp0, *aselp1, *pTemplate = NULL; + XmlToEmber parser; + EmberToXml emberToXml; + VariationList varList; + EmberReport emberReport, emberReport2; + auto_ptr> progress(new RenderProgress()); + auto_ptr> renderer(CreateRenderer(opt.EmberCL() ? OPENCL_RENDERER : CPU_RENDERER, opt.Platform(), opt.Device(), false, 0, emberReport)); + QTIsaac rand(ISAAC_INT(t.Tic()), ISAAC_INT(t.Tic() * 2), ISAAC_INT(t.Tic() * 3)); + vector errorReport = emberReport.ErrorReport(); + + os.imbue(std::locale("")); + os2.imbue(std::locale("")); + + if (!errorReport.empty()) + emberReport.DumpErrorReport(); + + if (!renderer.get()) + { + cout << "Renderer creation failed, exiting." << endl; + return false; + } + + if (!InitPaletteList(opt.PalettePath())) + return false; + + if (!opt.EmberCL()) + { + if (opt.ThreadCount() != 0) + renderer->ThreadCount(opt.ThreadCount(), opt.IsaacSeed() != "" ? opt.IsaacSeed().c_str() : NULL); + + renderer->LockAccum(opt.LockAccum()); + } + else + { + cout << "Using OpenCL to render." << endl; + + if (opt.Verbose()) + { + cout << "Platform: " << wrapper.PlatformName(opt.Platform()) << endl; + cout << "Device: " << wrapper.DeviceName(opt.Platform(), opt.Device()) << endl; + } + } + + //SheepTools will own the created renderer and will take care of cleaning it up. + SheepTools tools(opt.PalettePath(), CreateRenderer(opt.EmberCL() ? OPENCL_RENDERER : CPU_RENDERER, opt.Platform(), opt.Device(), false, 0, emberReport2)); + + tools.SetSpinParams(!opt.UnsmoothEdge(), + T(opt.Stagger()), + T(opt.OffsetX()), + T(opt.OffsetY()), + opt.Nick(), + opt.Url(), + opt.Id(), + opt.Comment(), + opt.SheepGen(), + opt.SheepId()); + + if (opt.UseVars() != "" && opt.DontUseVars() != "") + { + cout << "use_vars and dont_use_vars cannot both be specified. Returning without executing." << endl; + return false; + } + + //Specify reasonable defaults if nothing is specified. + if (opt.UseVars() == "" && opt.DontUseVars() == "") + { + noVars.push_back(VAR_NOISE); + noVars.push_back(VAR_BLUR); + noVars.push_back(VAR_GAUSSIAN_BLUR); + noVars.push_back(VAR_RADIAL_BLUR); + noVars.push_back(VAR_NGON); + noVars.push_back(VAR_SQUARE); + noVars.push_back(VAR_RAYS); + noVars.push_back(VAR_CROSS); + noVars.push_back(VAR_PRE_BLUR); + noVars.push_back(VAR_SEPARATION); + noVars.push_back(VAR_SPLIT); + noVars.push_back(VAR_SPLITS); + + //Loop over the novars and set ivars to the complement. + for (i = 0; i < varList.Size(); i++) + { + for (j = 0; j < noVars.size(); j++) + { + if (noVars[j] == varList.GetVariation(i)->VariationId()) + break; + } + + if (j == noVars.size()) + vars.push_back(varList.GetVariation(i)->VariationId()); + } + } + else + { + if (opt.UseVars() != "")//Parse comma-separated list of variations to use. + { + istringstream iss(opt.UseVars()); + + while (std::getline(iss, token, ',')) + { + if (parser.Atoi((char*)token.c_str(), val)) + { + if (val < varList.Size()) + vars.push_back((eVariationId)val); + } + } + } + else if (opt.DontUseVars() != "") + { + istringstream iss(opt.DontUseVars()); + + while (std::getline(iss, token, ',')) + { + if (parser.Atoi((char*)token.c_str(), val)) + { + if (val < varList.Size()) + noVars.push_back((eVariationId)val); + } + } + + //Loop over the novars and set ivars to the complement. + for (i = 0; i < varList.Size(); i++) + { + for (j = 0; j < noVars.size(); j++) + { + if (noVars[j] == varList.GetVariation(i)->VariationId()) + break; + } + + if (j == noVars.size()) + vars.push_back(varList.GetVariation(i)->VariationId()); + } + } + } + + bool doMutate = opt.Mutate() != ""; + bool doInter = opt.Inter() != ""; + bool doRotate = opt.Rotate() != ""; + bool doClone = opt.Clone() != ""; + bool doStrip = opt.Strip() != ""; + bool doCross0 = opt.Cross0() != ""; + bool doCross1 = opt.Cross1() != ""; + + count += (doMutate ? 1 : 0); + count += (doInter ? 1 : 0); + count += (doRotate ? 1 : 0); + count += (doClone ? 1 : 0); + count += (doStrip ? 1 : 0); + count += ((doCross0 || doCross1) ? 1 : 0); + + if (count > 1) + { + cout << "Can only specify one of mutate, clone, cross, rotate, strip, or inter. Returning without executing." << endl; + return false; + } + + if ((!doCross0) ^ (!doCross1)) + { + cout << "Must specify both crossover arguments. Returning without executing." << endl; + return false; + } + + if (opt.Method() != "" && (!doCross0 && !doMutate)) + { + cout << "Cannot specify method unless doing crossover or mutate. Returning without executing." << endl; + return false; + } + + if (opt.TemplateFile() != "") + { + if (!ParseEmberFile(parser, opt.TemplateFile(), templateEmbers)) + return false; + + if (templateEmbers.size() > 1) + cout << "More than one control point in template, ignoring all but first." << endl; + + pTemplate = &templateEmbers[0]; + } + + //Methods for genetic manipulation begin here. + if (doMutate) filename = opt.Mutate(); + else if (doInter) filename = opt.Inter(); + else if (doRotate) filename = opt.Rotate(); + else if (doClone) filename = opt.Clone(); + else if (doStrip) filename = opt.Strip(); + else if (doCross0) filename = opt.Cross0(); + else if (opt.CloneAll() != "") filename = opt.CloneAll(); + else if (opt.Animate() != "") filename = opt.Animate(); + else if (opt.Sequence() != "") filename = opt.Sequence(); + else if (opt.Inter() != "") filename = opt.Inter(); + else if (opt.Rotate() != "") filename = opt.Rotate(); + else if (opt.Strip() != "") filename = opt.Strip(); + else if (opt.Clone() != "") filename = opt.Clone(); + else if (opt.Mutate() != "") filename = opt.Mutate(); + + if (!ParseEmberFile(parser, filename, embers)) + return false; + + if (doCross1 && !ParseEmberFile(parser, opt.Cross1(), embers2)) + return false; + + if (opt.CloneAll() != "") + { + cout << "" << endl; + + for (i = 0; i < embers.size(); i++) + { + if (pTemplate) + tools.ApplyTemplate(embers[i], *pTemplate); + + tools.Offset(embers[i], (T)opt.OffsetX(), (T)opt.OffsetY()); + cout << emberToXml.ToString(embers[i], opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), false, opt.HexPalette()); + } + + cout << "" << endl; + return true; + } + + if (opt.Animate() != "") + { + for (i = 0; i < embers.size(); i++) + { + if (i > 0 && embers[i].m_Time <= embers[i - 1].m_Time) + { + cout << "Error: control points must be sorted by time, but " << embers[i].m_Time << " <= " << embers[i - 1].m_Time << ", index " << i << "." << endl; + return false; + } + + embers[i].DeleteMotionElements(); + } + + firstFrame = (unsigned int)(opt.FirstFrame() == UINT_MAX ? embers[0].m_Time : opt.FirstFrame()); + lastFrame = (unsigned int)(opt.LastFrame() == UINT_MAX ? embers.back().m_Time : opt.LastFrame()); + + if (lastFrame < firstFrame) + lastFrame = firstFrame; + + cout << "" << endl; + + for (ftime = firstFrame; ftime <= lastFrame; ftime++) + { + exactTimeMatch = false; + + for (i = 0; i < embers.size(); i++) + { + if (ftime == (unsigned int)embers[i].m_Time) + { + interpolated = embers[i]; + exactTimeMatch = true; + break; + } + } + + if (!exactTimeMatch) + { + Interpolater::Interpolate(embers, T(ftime), T(opt.Stagger()), interpolated); + + for (i = 0; i < embers.size(); i++) + { + if (ftime == (unsigned int)(embers[i].m_Time - 1)) + { + exactTimeMatch = true; + break; + } + } + + if (!exactTimeMatch) + interpolated.m_AffineInterp = INTERP_LINEAR; + } + + if (pTemplate) + tools.ApplyTemplate(interpolated, *pTemplate); + + cout << emberToXml.ToString(interpolated, opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), false, opt.HexPalette()); + } + + cout << "" << endl; + return true; + } + + if (opt.Sequence() != "") + { + frame = std::max(opt.Frame(), opt.Time()); + + if (opt.Frames() == 0) + { + cout << "nframes must be positive and non-zero, not " << opt.Frames() << "." << endl; + return false; + } + + if (opt.Enclosed()) + cout << "" << endl; + + spread = 1 / T(opt.Frames()); + frameCount = 0; + + for (i = 0; i < embers.size(); i++) + { + if (opt.Loops()) + { + for (frame = 0; frame < opt.Frames(); frame++) + { + blend = (T)frame / (T)opt.Frames(); + tools.Spin(embers[i], pTemplate, result, frameCount++, blend);//Result is cleared and reassigned each time inside of Spin(). + cout << emberToXml.ToString(result, opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), false, opt.HexPalette()); + } + } + + if (i < embers.size() - 1) + { + for (frame = 0; frame < opt.Frames(); frame++) + { + seqFlag = (frame == 0 || frame == opt.Frames() - 1); + blend = frame / (T)opt.Frames(); + result.Clear(); + tools.SpinInter(&embers[i], pTemplate, result, frameCount++, seqFlag, blend); + cout << emberToXml.ToString(result, opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), false, opt.HexPalette()); + } + } + } + + result = embers.back(); + tools.Spin(embers.back(), pTemplate, result, frameCount, 0); + cout << emberToXml.ToString(result, opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), false, opt.HexPalette()); + + if (opt.Enclosed()) + cout << "" << endl; + + return true; + } + + if (doInter || doRotate) + { + frame = std::max(opt.Frame(), opt.Time()); + + if (opt.Frames() == 0) + { + cout << "nframes must be positive and non-zero, not " << opt.Frames() << "." << endl; + return false; + } + + blend = frame / T(opt.Frames()); + spread = 1 / T(opt.Frames()); + + if (opt.Enclosed()) + cout << "" << endl; + + if (doRotate) + { + if (embers.size() != 1) + { + cout << "rotation requires one control point, not " << embers.size() << "." << endl; + return false; + } + + tools.Spin(embers[0], pTemplate, result1, frame - 1, blend - spread); + tools.Spin(embers[0], pTemplate, result2, frame , blend ); + tools.Spin(embers[0], pTemplate, result3, frame + 1, blend + spread); + + cout << emberToXml.ToString(result1, opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), false, opt.HexPalette()); + cout << emberToXml.ToString(result2, opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), false, opt.HexPalette()); + cout << emberToXml.ToString(result3, opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), false, opt.HexPalette()); + } + else + { + if (embers.size() != 2) + { + cout << "interpolation requires two control points, not " << embers.size() << "." << endl; + return false; + } + + tools.SpinInter(embers.data(), pTemplate, result1, frame - 1, 0, blend - spread); + tools.SpinInter(embers.data(), pTemplate, result2, frame , 0, blend ); + tools.SpinInter(embers.data(), pTemplate, result3, frame + 1, 0, blend + spread); + + cout << emberToXml.ToString(result1, opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), false, opt.HexPalette()); + cout << emberToXml.ToString(result2, opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), false, opt.HexPalette()); + cout << emberToXml.ToString(result3, opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), false, opt.HexPalette()); + } + + if (opt.Enclosed()) + cout << "" << endl; + + return true; + } + + if (doStrip) + { + if (opt.Enclosed()) + cout << "" << endl; + + for (i = 0; i < embers.size(); i++) + { + T oldX, oldY; + + embers[i].DeleteMotionElements(); + + oldX = embers[i].m_CenterX; + oldY = embers[i].m_CenterY; + embers[i].m_FinalRasH = (unsigned int)((T)embers[i].m_FinalRasH / (T)opt.Frames()); + + embers[i].m_CenterY = embers[i].m_CenterY - ((opt.Frames() - 1) * embers[i].m_FinalRasH) / + (2 * embers[i].m_PixelsPerUnit * pow(T(2.0), embers[i].m_Zoom)); + embers[i].m_CenterY += embers[i].m_FinalRasH * opt.Frame() / (embers[i].m_PixelsPerUnit * pow(T(2.0), embers[i].m_Zoom)); + + tools.RotateOldCenterBy(embers[i].m_CenterX, embers[i].m_CenterY, oldX, oldY, embers[i].m_Rotate); + + if (pTemplate) + tools.ApplyTemplate(embers[i], *pTemplate); + + tools.Offset(embers[i], T(opt.OffsetX()), T(opt.OffsetY())); + cout << emberToXml.ToString(embers[i], opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), false, opt.HexPalette()); + } + + if (opt.Enclosed()) + cout << "" << endl; + + return true; + } + + //Repeat. + renderer->EarlyClip(opt.EarlyClip()); + renderer->SubBatchSize(opt.SubBatchSize()); + renderer->PixelAspectRatio(T(opt.AspectRatio())); + + if (opt.Repeat() == 0) + { + cout << "Repeat must be positive, not " << opt.Repeat() << endl; + return false; + } + + if (opt.Enclosed()) + cout << "" << endl; + + for (rep = 0; rep < opt.Repeat(); rep++) + { + count = 0; + os.str(""); + save.Clear(); + VerbosePrint("Flame = " << rep + 1 << "/" << opt.Repeat() << ".."); + + if (opt.Clone() != "") + { + os << "clone";//Action is 'clone' with trunc vars concat. + + if (opt.CloneAction() != "") + os << " " << opt.CloneAction(); + + selp0 = embers[rand.Rand() % embers.size()]; + save = selp0; + aselp0 = &selp0; + aselp1 = NULL; + os << tools.TruncateVariations(save, 5); + save.m_Edits = emberToXml.CreateNewEditdoc(aselp0, aselp1, os.str(), opt.Nick(), opt.Url(), opt.Id(), opt.Comment(), opt.SheepGen(), opt.SheepId()); + } + else + { + do + { + randomMode = false; + didColor = false; + os.str(""); + VerbosePrint("."); + + if (doMutate) + { + selp0 = embers[rand.Rand() % embers.size()]; + orig = selp0; + aselp0 = &selp0; + aselp1 = NULL; + + if (opt.Method() == "") + mutMeth = MUTATE_NOT_SPECIFIED; + else if (opt.Method() == "all_vars") + mutMeth = MUTATE_ALL_VARIATIONS; + else if (opt.Method() == "one_xform") + mutMeth = MUTATE_ONE_XFORM_COEFS; + else if (opt.Method() == "add_symmetry") + mutMeth = MUTATE_ADD_SYMMETRY; + else if (opt.Method() == "post_xforms") + mutMeth = MUTATE_POST_XFORMS; + else if (opt.Method() == "color_palette") + mutMeth = MUTATE_COLOR_PALETTE; + else if (opt.Method() == "delete_xform") + mutMeth = MUTATE_DELETE_XFORM; + else if (opt.Method() == "all_coefs") + mutMeth = MUTATE_ALL_COEFS; + else + { + cout << "method " << opt.Method() << " not defined for mutate. Defaulting to random." << endl; + mutMeth = MUTATE_NOT_SPECIFIED; + } + + os << tools.Mutate(orig, mutMeth, vars, opt.Symmetry(), T(opt.Speed())); + + //Scan string returned for 'mutate color'. + if (strstr(os.str().c_str(), "mutate color")) + didColor = true; + + if (orig.m_Name != "") + { + os2.str(""); + os2 << "mutation " << rep << " of " << orig.m_Name; + orig.m_Name = os2.str(); + } + } + else if (doCross0) + { + i0 = rand.Rand() % embers.size(); + i1 = rand.Rand() % embers2.size(); + + selp0 = embers[i0]; + selp1 = embers2[i1]; + + aselp0 = &selp0; + aselp1 = &selp1; + + if (opt.Method() == "") + crossMeth = CROSS_NOT_SPECIFIED; + else if (opt.Method() == "union") + crossMeth = CROSS_UNION; + else if (opt.Method() == "interpolate") + crossMeth = CROSS_INTERPOLATE; + else if (opt.Method() == "alternate") + crossMeth = CROSS_ALTERNATE; + else + { + cout << "method '" << opt.Method() << "' not defined for cross. Defaulting to random." << endl; + crossMeth = CROSS_NOT_SPECIFIED; + } + + tools.Cross(embers[i0], embers2[i1], orig, crossMeth); + + if (embers[i0].m_Name != "" || embers2[i1].m_Name != "") + { + os2.str(""); + os2 << rep << " of " << embers[i0].m_Name << " x " << embers2[i1].m_Name; + orig.m_Name = os2.str(); + } + } + else + { + os << "random"; + randomMode = true; + tools.Random(orig, vars, opt.Symmetry(), 0); + aselp0 = NULL; + aselp1 = NULL; + } + + //Adjust bounding box half the time. + if (rand.RandBit() || randomMode) + { + T bmin[2], bmax[2]; + tools.EstimateBoundingBox(orig, T(0.01), 100000, bmin, bmax); + + if (rand.Frand01() < T(0.3)) + { + orig.m_CenterX = (bmin[0] + bmax[0]) / 2; + orig.m_CenterY = (bmin[1] + bmax[1]) / 2; + os << " recentered"; + } + else + { + if (rand.RandBit()) + { + mix0 = rand.GoldenBit() + rand.Frand11() / 5; + mix1 = rand.GoldenBit(); + os << " reframed0"; + } + else if (rand.RandBit()) + { + mix0 = rand.GoldenBit(); + mix1 = rand.GoldenBit() + rand.Frand11() / 5; + os << " reframed1"; + } + else + { + mix0 = rand.GoldenBit() + rand.Frand11() / 5; + mix1 = rand.GoldenBit() + rand.Frand11() / 5; + os << " reframed2"; + } + + orig.m_CenterX = mix0 * bmin[0] + (1 - mix0) * bmax[0]; + orig.m_CenterY = mix1 * bmin[1] + (1 - mix1) * bmax[1]; + } + + orig.m_PixelsPerUnit = orig.m_FinalRasW / (bmax[0] - bmin[0]); + } + + os << tools.TruncateVariations(orig, 5); + + if (!didColor && rand.RandBit()) + { + if (opt.Debug()) + cout << "improving colors..." << endl; + + tools.ImproveColors(orig, 100, false, 10); + os << " improved colors"; + } + + orig.m_Edits = emberToXml.CreateNewEditdoc(aselp0, aselp1, os.str(), opt.Nick(), opt.Url(), opt.Id(), opt.Comment(), opt.SheepGen(), opt.SheepId()); + save = orig; + SetDefaultTestValues(orig); + renderer->SetEmber(orig); + + if (renderer->Run(finalImage) != RENDER_OK) + { + cout << "Error: test image rendering failed, aborting." << endl; + return false; + } + + tot = totb = totw = 0; + n = orig.m_FinalRasW * orig.m_FinalRasH; + + for (i = 0; i < 3 * n; i += 3) + { + tot += (finalImage[i] + finalImage[i + 1] + finalImage[i + 2]); + + if (0 == finalImage[i] && 0 == finalImage[i + 1] && 0 == finalImage[i + 2]) totb++; + if (255 == finalImage[i] && 255 == finalImage[i + 1] && 255 == finalImage[i + 2]) totw++; + } + + avgPix = (tot / T(3 * n)); + fractionBlack = totb / T(n); + fractionWhite = totw / T(n); + + if (opt.Debug()) + cout << "avgPix = " << avgPix << " fractionBlack = " << fractionBlack << " fractionWhite = " << fractionWhite << " n = " << n << endl; + + orig.Clear(); + count++; + } while ((avgPix < opt.AvgThresh() || + fractionBlack < opt.BlackThresh() || + fractionWhite > opt.WhiteLimit()) && + count < opt.Tries()); + + if (count == opt.Tries()) + cout << "Warning: reached maximum attempts, giving up." << endl; + } + + if (pTemplate) + tools.ApplyTemplate(save, *pTemplate); + + save.m_Time = T(rep); + + if (opt.MaxXforms() != UINT_MAX) + { + save.m_Symmetry = 0; + + while (save.TotalXformCount() > opt.MaxXforms()) + save.DeleteTotalXform(save.TotalXformCount() - 1); + } + + cout << emberToXml.ToString(save, opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), false, opt.HexPalette()); + VerbosePrint("\nDone. Action = " << os.str() << "\n"); + cout.flush(); + save.Clear(); + } + + if (opt.Enclosed()) + cout << "\n"; + + return true; +} + +/// +/// Main program entry point for EmberGenome.exe. +/// +/// The number of command line arguments passed +/// The command line arguments passed +/// 0 if successful, else 1. +int _tmain(int argc, _TCHAR* argv[]) +{ + bool b, d = true; + EmberOptions opt; + + //Required for large allocs, else GPU memory usage will be severely limited to small sizes. + //This must be done in the application and not in the EmberCL DLL. + _putenv_s("GPU_MAX_ALLOC_PERCENT", "100"); + + if (opt.Populate(argc, argv, OPT_USE_GENOME)) + return 0; + +#ifdef DO_DOUBLE + if (opt.Bits() == 64) + { + b = EmberGenome(opt); + } + else +#endif + if (opt.Bits() == 33) + { + b = EmberGenome(opt); + } + else if (opt.Bits() == 32) + { + cout << "Bits 32/int histogram no longer supported. Using bits == 33 (float)." << endl; + b = EmberGenome(opt); + } + + return b ? 0 : 1; +} \ No newline at end of file diff --git a/Source/EmberGenome/EmberGenome.h b/Source/EmberGenome/EmberGenome.h new file mode 100644 index 0000000..2117e36 --- /dev/null +++ b/Source/EmberGenome/EmberGenome.h @@ -0,0 +1,23 @@ +#pragma once + +#include "EmberOptions.h" + +/// +/// Declaration for the EmberGenome() and SetDefaultTestValues() functions. +/// + +/// +/// Set various default test values on the passed in ember. +/// +/// The ember to test +template +static void SetDefaultTestValues(Ember& ember); + +/// +/// The core of the EmberGenome.exe program. +/// Template argument expected to be float or double. +/// +/// A populated EmberOptions object which specifies all program options to be used +/// True if success, else false. +template +static bool EmberGenome(EmberOptions& opt); \ No newline at end of file diff --git a/Source/EmberGenome/EmberGenome.rc b/Source/EmberGenome/EmberGenome.rc new file mode 100644 index 0000000..d547f3a --- /dev/null +++ b/Source/EmberGenome/EmberGenome.rc @@ -0,0 +1,98 @@ +// Microsoft Visual C++ generated resource script. +// +#include +#include "resource.h" +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_ICON1 ICON "..\\Fractorium\\Icons\\\\Fractorium.ico" + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 0,4,0,2 + PRODUCTVERSION 0,4,0,2 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x0L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "Open Source" + VALUE "FileDescription", "Manipulates fractal flames parameter files" + VALUE "FileVersion", "0.4.0.2" + VALUE "InternalName", "EmberGenome.rc" + VALUE "LegalCopyright", "Copyright (C) Matt Feemster 2013, GPL v3" + VALUE "OriginalFilename", "EmberGenome.rc" + VALUE "ProductName", "Ember Genome" + VALUE "ProductVersion", "0.4.0.2" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/Source/EmberGenome/resource.h b/Source/EmberGenome/resource.h new file mode 100644 index 0000000..3cb79f7 --- /dev/null +++ b/Source/EmberGenome/resource.h @@ -0,0 +1,15 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by EmberGenome.rc +// + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/Source/EmberRender/EmberRender.cpp b/Source/EmberRender/EmberRender.cpp new file mode 100644 index 0000000..84040b5 --- /dev/null +++ b/Source/EmberRender/EmberRender.cpp @@ -0,0 +1,386 @@ +#include "EmberCommonPch.h" +#include "EmberRender.h" +#include "JpegUtils.h" + +/// +/// The core of the EmberRender.exe program. +/// Template argument expected to be float or double. +/// +/// A populated EmberOptions object which specifies all program options to be used +/// True if success, else false. +template +bool EmberRender(EmberOptions& opt) +{ + OpenCLWrapper wrapper; + + std::cout.imbue(std::locale("")); + + if (opt.DumpArgs()) + cout << opt.GetValues(OPT_USE_RENDER) << endl; + + if (opt.OpenCLInfo()) + { + cout << "\nOpenCL Info: " << endl; + cout << wrapper.DumpInfo(); + return true; + } + + Timing t; + bool writeSuccess = false; + unsigned char* finalImagep; + unsigned int i, channels, strip, strips, realHeight, origHeight; + size_t stripOffset; + T centerY, centerBase, zoomScale, floatStripH; + string filename; + ostringstream os; + vector> embers; + vector finalImage, vecRgb; + EmberStats stats; + EmberReport emberReport; + EmberImageComments comments; + XmlToEmber parser; + EmberToXml emberToXml; + vector> randVec; + auto_ptr> progress(new RenderProgress()); + auto_ptr> renderer(CreateRenderer(opt.EmberCL() ? OPENCL_RENDERER : CPU_RENDERER, opt.Platform(), opt.Device(), false, 0, emberReport)); + vector errorReport = emberReport.ErrorReport(); + + if (!errorReport.empty()) + emberReport.DumpErrorReport(); + + if (!renderer.get()) + { + cout << "Renderer creation failed, exiting." << endl; + return false; + } + + if (opt.EmberCL() && renderer->RendererType() != OPENCL_RENDERER)//OpenCL init failed, so fall back to CPU. + opt.EmberCL(false); + + if (!InitPaletteList(opt.PalettePath())) + return false; + + if (!ParseEmberFile(parser, opt.Input(), embers)) + return false; + + if (!opt.EmberCL()) + { + if (opt.ThreadCount() == 0) + { + cout << "Using " << Timing::ProcessorCount() << " automatically detected threads." << endl; + opt.ThreadCount(Timing::ProcessorCount()); + } + else + { + cout << "Using " << opt.ThreadCount() << " manually specified threads." << endl; + } + + renderer->ThreadCount(opt.ThreadCount(), opt.IsaacSeed() != "" ? opt.IsaacSeed().c_str() : NULL); + } + else + { + cout << "Using OpenCL to render." << endl; + + if (opt.Verbose()) + { + cout << "Platform: " << wrapper.PlatformName(opt.Platform()) << endl; + cout << "Device: " << wrapper.DeviceName(opt.Platform(), opt.Device()) << endl; + } + + if (opt.ThreadCount() > 1) + cout << "Cannot specify threads with OpenCL, using 1 thread." << endl; + + opt.ThreadCount(1); + renderer->ThreadCount(opt.ThreadCount(), opt.IsaacSeed() != "" ? opt.IsaacSeed().c_str() : NULL); + + if (opt.BitsPerChannel() != 8) + { + cout << "Bits per channel cannot be anything other than 8 with OpenCL, setting to 8." << endl; + opt.BitsPerChannel(8); + } + } + + if (opt.Format() != "jpg" && + opt.Format() != "png" && + opt.Format() != "ppm" && + opt.Format() != "bmp") + { + cout << "Format must be jpg, png, ppm, or bmp not " << opt.Format() << ". Setting to jpg." << endl; + } + + channels = opt.Format() == "png" ? 4 : 3; + + if (opt.BitsPerChannel() == 16 && opt.Format() != "png") + { + cout << "Support for 16 bits per channel images is only present for the png format. Setting to 8." << endl; + opt.BitsPerChannel(8); + } + else if (opt.BitsPerChannel() != 8 && opt.BitsPerChannel() != 16) + { + cout << "Unexpected bits per channel specified " << opt.BitsPerChannel() << ". Setting to 8." << endl; + opt.BitsPerChannel(8); + } + + if (opt.InsertPalette() && opt.BitsPerChannel() != 8) + { + cout << "Inserting palette only supported with 8 bits per channel, insertion will not take place." << endl; + opt.InsertPalette(false); + } + + if (opt.AspectRatio() < 0) + { + cout << "Invalid pixel aspect ratio " << opt.AspectRatio() << endl << ". Must be positive, setting to 1." << endl; + opt.AspectRatio(1); + } + + if (!opt.Out().empty() && (embers.size() > 1)) + { + cout << "Single output file " << opt.Out() << " specified for multiple images. Changing to use prefix of badname-changethis instead. Always specify prefixes when reading a file with multiple embers." << endl; + opt.Out(""); + opt.Prefix("badname-changethis"); + } + + //Final setup steps before running. + os.imbue(std::locale("")); + renderer->EarlyClip(opt.EarlyClip()); + renderer->LockAccum(opt.LockAccum()); + renderer->InsertPalette(opt.InsertPalette()); + renderer->SubBatchSize(opt.SubBatchSize()); + renderer->PixelAspectRatio(T(opt.AspectRatio())); + renderer->Transparency(opt.Transparency()); + renderer->NumChannels(channels); + renderer->BytesPerChannel(opt.BitsPerChannel() / 8); + renderer->Callback(opt.DoProgress() ? progress.get() : NULL); + + for (i = 0; i < embers.size(); i++) + { + if (opt.Verbose() && embers.size() > 1) + cout << "\nFlame = " << i + 1 << "/" << embers.size() << endl; + else + cout << endl; + + embers[i].m_TemporalSamples = 1;//Force temporal samples to 1 for render. + embers[i].m_Quality *= T(opt.QualityScale()); + embers[i].m_FinalRasW = (unsigned int)((T)embers[i].m_FinalRasW * opt.SizeScale()); + embers[i].m_FinalRasH = (unsigned int)((T)embers[i].m_FinalRasH * opt.SizeScale()); + embers[i].m_PixelsPerUnit *= T(opt.SizeScale()); + + if (embers[i].m_FinalRasW == 0 || embers[i].m_FinalRasH == 0) + { + cout << "Output image " << i << " has dimension 0: " << embers[i].m_FinalRasW << ", " << embers[i].m_FinalRasH << ". Setting to 1920 x 1080." << endl; + embers[i].m_FinalRasW = 1920; + embers[i].m_FinalRasH = 1080; + } + + //Cast to double in case the value exceeds 2^32. + double imageMem = (double)renderer->NumChannels() * (double)embers[i].m_FinalRasW + * (double)embers[i].m_FinalRasH * (double)renderer->BytesPerChannel(); + double maxMem = pow(2.0, double((sizeof(void*) * 8) - 1)); + + if (imageMem > maxMem)//Ensure the max amount of memory for a process is not exceeded. + { + cout << "Image " << i << " size > " << maxMem << ". Setting to 1920 x 1080." << endl; + embers[i].m_FinalRasW = 1920; + embers[i].m_FinalRasH = 1080; + } + + renderer->SetEmber(embers[i]); + renderer->PrepFinalAccumVector(finalImage);//Must manually call this first because it could be erroneously made smaller due to strips if called inside Renderer::Run(). + + if (opt.Strips() > 1) + { + strips = opt.Strips(); + } + else + { + strips = CalcStrips((double)renderer->MemoryRequired(false), (double)renderer->MemoryAvailable(), opt.UseMem()); + + if (strips > 1) + VerbosePrint("Setting strips to " << strips << " with specified memory usage of " << opt.UseMem()); + } + + if (strips > embers[i].m_FinalRasH) + { + cout << "Cannot have more strips than rows: " << opt.Strips() << " > " << embers[i].m_FinalRasH << ". Setting strips = rows." << endl; + opt.Strips(strips = embers[i].m_FinalRasH); + } + + if (embers[i].m_FinalRasH % strips != 0) + { + cout << "A strips value of " << strips << " does not divide evenly into a height of " << embers[i].m_FinalRasH; + + strips = NextHighestEvenDiv(embers[i].m_FinalRasH, strips); + + if (strips == 1)//No higher divisor, check for a lower one. + strips = NextLowestEvenDiv(embers[i].m_FinalRasH, strips); + + cout << ". Setting strips to " << strips << "." << endl; + } + + embers[i].m_Quality *= strips; + realHeight = embers[i].m_FinalRasH; + floatStripH = T(embers[i].m_FinalRasH) / T(strips); + embers[i].m_FinalRasH = (unsigned int)ceil(floatStripH); + centerY = embers[i].m_CenterY; + zoomScale = pow(T(2), embers[i].m_Zoom); + centerBase = centerY - ((strips - 1) * floatStripH) / (2 * embers[i].m_PixelsPerUnit * zoomScale); + + if (strips > 1) + randVec = renderer->RandVec(); + //For testing incremental renderer. + //int sb = 1; + //bool resume = false, success = false; + //do + //{ + // success = renderer->Run(finalImage, 0, sb, false/*resume == false*/) == RENDER_OK; + // sb++; + // resume = true; + //} + //while (success && renderer->ProcessState() != ACCUM_DONE); + + for (strip = 0; strip < strips; strip++) + { + stripOffset = (size_t)embers[i].m_FinalRasH * strip * renderer->FinalRowSize(); + embers[i].m_CenterY = centerBase + embers[i].m_FinalRasH * T(strip) / (embers[i].m_PixelsPerUnit * zoomScale); + + if ((embers[i].m_FinalRasH * (strip + 1)) > realHeight) + { + origHeight = embers[i].m_FinalRasH; + embers[i].m_FinalRasH = realHeight - origHeight * strip; + embers[i].m_CenterY -= (origHeight - embers[i].m_FinalRasH) * T(0.5) / (embers[i].m_PixelsPerUnit * zoomScale); + } + + if (strips > 1) + { + renderer->RandVec(randVec);//Use the same vector of ISAAC rands for each strip. + renderer->SetEmber(embers[i]);//Set one final time after modifications for strips. + + if (opt.Verbose() && (strips > 1) && strip > 0) + cout << endl; + + VerbosePrint("Strip = " << (strip + 1) << "/" << strips); + } + + if ((renderer->Run(finalImage, 0, 0, false, stripOffset) != RENDER_OK) || renderer->Aborted() || finalImage.empty()) + { + cout << "Error: image rendering failed, skipping to next image." << endl; + renderer->DumpErrorReport();//Something went wrong, print errors. + break;//Exit strips loop, resume next iter in embers loop. + } + + progress->Clear(); + + //Original wrote every strip as a full image which could be very slow with many large images. + //Only write once all strips for this image are finished. + if (strip == strips - 1) + { + if (!opt.Out().empty()) + { + filename = opt.Out(); + } + else if (opt.NameEnable() && !embers[i].m_Name.empty()) + { + filename = opt.Prefix() + embers[i].m_Name + opt.Suffix() + "." + opt.Format(); + } + else + { + ostringstream ssLocal; + + ssLocal << opt.Prefix() << setfill('0') << setw(5) << i << opt.Suffix() << "." << opt.Format(); + filename = ssLocal.str(); + } + + writeSuccess = false; + comments = renderer->ImageComments(opt.PrintEditDepth(), opt.IntPalette(), opt.HexPalette()); + stats = renderer->Stats(); + os.str(""); + os << comments.m_NumIters << "/" << renderer->TotalIterCount() << " (" << std::fixed << std::setprecision(2) << ((double)stats.m_Iters/(double)renderer->TotalIterCount() * 100) << "%)"; + + VerbosePrint("\nIters ran/requested: " + os.str()); + VerbosePrint("Bad values: " << stats.m_Badvals); + VerbosePrint("Render time: " + t.Format(stats.m_RenderSeconds * 1000)); + VerbosePrint("Writing " + filename); + + if ((opt.Format() == "jpg" || opt.Format() == "bmp") && renderer->NumChannels() == 4) + { + EmberNs::RgbaToRgb(finalImage, vecRgb, renderer->FinalRasW(), realHeight); + + finalImagep = vecRgb.data(); + } + else + { + finalImagep = finalImage.data(); + } + + if (opt.Format() == "png") + writeSuccess = WritePng(filename.c_str(), finalImagep, renderer->FinalRasW(), realHeight, opt.BitsPerChannel() / 8, opt.PngComments(), comments, opt.Id(), opt.Url(), opt.Nick()); + else if (opt.Format() == "jpg") + writeSuccess = WriteJpeg(filename.c_str(), finalImagep, renderer->FinalRasW(), realHeight, opt.JpegQuality(), opt.JpegComments(), comments, opt.Id(), opt.Url(), opt.Nick()); + else if (opt.Format() == "ppm") + writeSuccess = WritePpm(filename.c_str(), finalImagep, renderer->FinalRasW(), realHeight); + else if (opt.Format() == "bmp") + writeSuccess = WriteBmp(filename.c_str(), finalImagep, renderer->FinalRasW(), realHeight); + + if (!writeSuccess) + cout << "Error writing " << filename << endl; + } + } + + //Restore the ember values to their original values. + if (strips > 1) + { + embers[i].m_Quality /= strips; + embers[i].m_FinalRasH = realHeight; + embers[i].m_CenterY = centerY; + memset(finalImage.data(), 0, finalImage.size()); + } + + if (opt.EmberCL() && opt.DumpKernel()) + cout << "Iteration kernel: \n" << ((RendererCL*)renderer.get())->IterKernel() << endl; + + VerbosePrint("Done."); + } + + if (opt.Verbose()) + t.Toc("\nTotal time: ", true); + + return true; +} + +/// +/// Main program entry point for EmberRender.exe. +/// +/// The number of command line arguments passed +/// The command line arguments passed +/// 0 if successful, else 1. +int _tmain(int argc, _TCHAR* argv[]) +{ + bool b, d = true; + EmberOptions opt; + + //Required for large allocs, else GPU memory usage will be severely limited to small sizes. + //This must be done in the application and not in the EmberCL DLL. + _putenv_s("GPU_MAX_ALLOC_PERCENT", "100"); + + if (opt.Populate(argc, argv, OPT_USE_RENDER)) + return 0; + +#ifdef DO_DOUBLE + if (opt.Bits() == 64) + { + b = EmberRender(opt); + } + else +#endif + if (opt.Bits() == 33) + { + b = EmberRender(opt); + } + else if (opt.Bits() == 32) + { + cout << "Bits 32/int histogram no longer supported. Using bits == 33 (float)." << endl; + b = EmberRender(opt); + } + + return b ? 0 : 1; +} \ No newline at end of file diff --git a/Source/EmberRender/EmberRender.h b/Source/EmberRender/EmberRender.h new file mode 100644 index 0000000..8ba8efb --- /dev/null +++ b/Source/EmberRender/EmberRender.h @@ -0,0 +1,16 @@ +#pragma once + +#include "EmberOptions.h" + +/// +/// Declaration for the EmberRender() function. +/// + +/// +/// The core of the EmberRender.exe program. +/// Template argument expected to be float or double. +/// +/// A populated EmberOptions object which specifies all program options to be used +/// True if success, else false. +template +static bool EmberRender(EmberOptions& opt); \ No newline at end of file diff --git a/Source/EmberRender/EmberRender.rc b/Source/EmberRender/EmberRender.rc new file mode 100644 index 0000000..5615211 --- /dev/null +++ b/Source/EmberRender/EmberRender.rc @@ -0,0 +1,98 @@ +// Microsoft Visual C++ generated resource script. +// +#include +#include "resource.h" +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_ICON1 ICON "..\\Fractorium\\Icons\\\\Fractorium.ico" + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 0,4,0,2 + PRODUCTVERSION 0,4,0,2 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x0L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "Open Source" + VALUE "FileDescription", "Renders fractal flames as single images" + VALUE "FileVersion", "0.4.0.2" + VALUE "InternalName", "EmberRender.rc" + VALUE "LegalCopyright", "Copyright (C) Matt Feemster 2013, GPL v3" + VALUE "OriginalFilename", "EmberRender.rc" + VALUE "ProductName", "Ember Render" + VALUE "ProductVersion", "0.4.0.2" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/Source/EmberRender/resource.h b/Source/EmberRender/resource.h new file mode 100644 index 0000000..f79ca08 --- /dev/null +++ b/Source/EmberRender/resource.h @@ -0,0 +1,15 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by EmberRender.rc +// + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/Source/EmberTester/EmberTester.cpp b/Source/EmberTester/EmberTester.cpp new file mode 100644 index 0000000..9097533 --- /dev/null +++ b/Source/EmberTester/EmberTester.cpp @@ -0,0 +1,1671 @@ +#include "EmberCommonPch.h" +#include "EmberTester.h" +#include "JpegUtils.h" +#include +#include +#include + +/// +/// EmberTester is a scratch area used for on the fly testing. +/// It may become a more formalized automated testing system +/// in the future. At the moment it isn't expected to build or +/// give any useful insight into the workings of Ember. +/// + +using namespace EmberNs; +//#define TEST_CL 1 + +template +void SaveFinalImage(Renderer& renderer, vector& pixels, char* suffix) +{ + Timing t; + //renderer.AccumulatorToFinalImage(pixels); + //t.Toc("AccumulatorToFinalImage()"); + + long newSize; + char ch[50]; + sprintf_s(ch, 50, ".\\BasicFlame_%d_%s.bmp", sizeof(T), suffix); + BYTE* bgrBuf = ConvertRGBToBMPBuffer(pixels.data(), renderer.FinalRasW(), renderer.FinalRasH(), newSize); + SaveBMP(ch, bgrBuf, renderer.FinalRasW(), renderer.FinalRasH(), newSize); + delete [] bgrBuf; +} + +template +Ember CreateBasicEmber(unsigned int width, unsigned int height, unsigned int ss, T quality, T centerX, T centerY, T rotate) +{ + Timing t; + QTIsaac rand; + + //t.Tic(); + Ember ember1; + //t.Toc("TestBasicFlame() : Constructor()"); + //t.Tic(); + + ember1.m_FinalRasW = width; + ember1.m_FinalRasH = height; + ember1.m_Supersample = ss; + ember1.m_Quality = quality; + ember1.m_CenterX = centerX; + ember1.m_CenterY = centerY; + ember1.m_Rotate = rotate; + Xform xform1(T(0.25), rand.Frand01(), rand.Frand11(), T(1), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11()); + Xform xform2(T(0.25), rand.Frand01(), rand.Frand11(), T(1), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11()); + Xform xform3(T(0.25), rand.Frand01(), rand.Frand11(), T(1), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11()); + Xform xform4(T(0.25), rand.Frand01(), rand.Frand11(), T(1), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11()); + + xform1.AddVariation(new SphericalVariation()); + xform2.AddVariation(new SphericalVariation()); + xform3.AddVariation(new SphericalVariation()); + xform4.AddVariation(new SphericalVariation()); + xform4.AddVariation(new BlobVariation()); + + ember1.AddXform(xform1); + ember1.AddXform(xform2); + ember1.AddXform(xform3); + ember1.AddXform(xform4); + + //ember1.SetFinalXform(xform4); + + return ember1; +} + +string GetEmberCLKernelString(Ember& ember, bool iter, bool log, bool de, unsigned int ss, bool accum) +{ + ostringstream os; + IterOpenCLKernelCreator iterCreator; + DEOpenCLKernelCreator deCreator; + FinalAccumOpenCLKernelCreator accumCreator; + pair> pair; + + iterCreator.ParVarIndexDefines(ember, pair); + + if (iter) + os << "Iter kernel: \n" << iterCreator.CreateIterKernelString(ember, pair.first, true); + + if (log) + os << "Log scale de kernel: \n" << deCreator.LogScaleAssignDEKernel(); + + //if (de) + // os << "Gaussian DE kernel: \n" << deCreator.GaussianDEKernel(ss); + + //if (accum) + // os << "Accum kernel: \n" << accumCreator.FinalAccumKernelLateClipWithoutAlpha(); + + return os.str(); +} + +void MakeTestAllVarsRegPrePostComboFile(string filename) +{ + EmberToXml writer; + vector> embers; + VariationList varList; + unsigned int index = 0; + PaletteList paletteList; + ostringstream ss; + QTIsaac rand; + + paletteList.Init("flam3-palettes.xml"); + + Timing t; + + Ember emberNoVars; + + emberNoVars.m_FinalRasW = 640; + emberNoVars.m_FinalRasH = 480; + emberNoVars.m_Quality = 100; + + Xform xform1(0.25f, rand.Frand01(), rand.Frand11(), 1, rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11()); + Xform xform2(0.25f, rand.Frand01(), rand.Frand11(), 1, rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11()); + Xform xform3(0.25f, rand.Frand01(), rand.Frand11(), 1, rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11()); + Xform xform4(0.25f, rand.Frand01(), rand.Frand11(), 1, rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11()); + Xform xform5(0.25f, rand.Frand01(), rand.Frand11(), 1, rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11()); + Xform xform6(0.25f, rand.Frand01(), rand.Frand11(), 1, rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11()); + Xform xform7(0.25f, rand.Frand01(), rand.Frand11(), 1, rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11()); + + emberNoVars.AddXform(xform1); + emberNoVars.AddXform(xform2); + emberNoVars.AddXform(xform3); + emberNoVars.AddXform(xform4); + emberNoVars.AddXform(xform5); + emberNoVars.AddXform(xform6); + emberNoVars.AddXform(xform7); + + ss << "NoVars"; + emberNoVars.m_Name = ss.str(); + ss.str(""); + emberNoVars.m_Palette = *paletteList.GetPalette(0); + embers.push_back(emberNoVars); + + while (index < varList.RegSize()) + { + Ember ember1; + auto_ptr> regVar (varList.GetVariationCopy(index, VARTYPE_REG)); + auto_ptr> preVar (varList.GetVariationCopy("pre_" + regVar->Name())); + auto_ptr> postVar(varList.GetVariationCopy("post_" + regVar->Name())); + + ember1.m_FinalRasW = 640; + ember1.m_FinalRasH = 480; + ember1.m_Quality = 100; + + Xform xform1(0.25f, rand.Frand01(), rand.Frand11(), 1, rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11()); + Xform xform2(0.25f, rand.Frand01(), rand.Frand11(), 1, rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11()); + Xform xform3(0.25f, rand.Frand01(), rand.Frand11(), 1, rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11()); + Xform xform4(0.25f, rand.Frand01(), rand.Frand11(), 1, rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11()); + Xform xform5(0.25f, rand.Frand01(), rand.Frand11(), 1, rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11()); + Xform xform6(0.25f, rand.Frand01(), rand.Frand11(), 1, rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11()); + Xform xform7(0.25f, rand.Frand01(), rand.Frand11(), 1, rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11(), rand.Frand11()); + + if (preVar.get() && postVar.get()) + { + xform1.AddVariation(preVar->Copy()); + xform2.AddVariation(regVar->Copy()); + xform3.AddVariation(postVar->Copy()); + + xform4.AddVariation(preVar->Copy()); + xform4.AddVariation(regVar->Copy()); + + xform5.AddVariation(preVar->Copy()); + xform5.AddVariation(postVar->Copy()); + + xform6.AddVariation(regVar->Copy()); + xform6.AddVariation(postVar->Copy()); + + xform7.AddVariation(preVar->Copy()); + xform7.AddVariation(regVar->Copy()); + xform7.AddVariation(postVar->Copy()); + + ember1.AddXform(xform1); + ember1.AddXform(xform2); + ember1.AddXform(xform3); + ember1.AddXform(xform4); + ember1.AddXform(xform5); + ember1.AddXform(xform6); + ember1.AddXform(xform7); + } + else + { + xform1.AddVariation(regVar->Copy()); + xform2.AddVariation(regVar->Copy()); + xform3.AddVariation(regVar->Copy()); + xform4.AddVariation(regVar->Copy()); + + ember1.AddXform(xform1); + ember1.AddXform(xform2); + ember1.AddXform(xform3); + ember1.AddXform(xform4); + } + + ss << index << "_" << regVar->Name(); + ember1.m_Name = ss.str(); + ss.str(""); + ember1.m_Palette = *paletteList.GetPalette(index % paletteList.Count()); + index++; + embers.push_back(ember1); + } + + t.Toc("Creating embers for all possible variations"); + + writer.Save(filename, embers, 0, true, false, true); +} + +void TestAtomicAdd() +{ + size_t i; + ostringstream os; + OpenCLWrapper wrapper; + vector vec(32); + + os << ConstantDefinesString(false) << UnionCLStructString << endl; + os << + "void AtomicAdd(volatile __global float* source, const float operand)\n" + "{\n" + " union\n" + " {\n" + " unsigned int intVal;\n" + " float floatVal;\n" + " } newVal;\n" + "\n" + " union\n" + " {\n" + " unsigned int intVal;\n" + " float floatVal;\n" + " } prevVal;\n" + "\n" + " do\n" + " {\n" + " prevVal.floatVal = *source;\n" + " newVal.floatVal = prevVal.floatVal + operand;\n" + " } while (atomic_cmpxchg((volatile __global unsigned int*)source, prevVal.intVal, newVal.intVal) != prevVal.intVal);\n" + "}\n" + "\n" + "__kernel void MyKernel(\n" + " __global float* buff,\n" + " unsigned int lockit\n" + "\t)\n" + "{\n" + " unsigned int index = THREAD_ID_X;\n" + "\n" + " if (lockit)\n" + " {\n" + " AtomicAdd(&(buff[index]), (float)index * 0.54321);\n" + " }\n" + " else\n" + " {\n" + " buff[index] += (float)index * 0.54321;\n" + " }\n" + "}\n"; + + string program = os.str(); + string entry = "MyKernel"; + + if (wrapper.Init(0, 0)) + { + for (i = 0; i < vec.size(); i++) + vec[i] = (i * 10.2234f); + + if (wrapper.AddAndWriteBuffer("buff", (void*)vec.data(), (unsigned int)vec.size() * sizeof(vec[0]))) + { + if (wrapper.AddProgram(entry, program, entry, false)) + { + wrapper.SetBufferArg(0, 0, 0); + wrapper.SetArg(0, 1, 0); + + if (wrapper.RunKernel(0, + 32,//Total grid dims. + 1, + 1, + 1,//Individual block dims. + 1, + 1)) + { + wrapper.ReadBuffer(0, vec.data(), (unsigned int)vec.size() * sizeof(vec[0])); + cout << "Vector after unlocked add: " << endl; + + for (i = 0; i < vec.size(); i++) + { + cout << "vec[" << i << "] = " << vec[i] << endl; + } + + for (i = 0; i < vec.size(); i++) + vec[i] = (i * 10.2234f); + + wrapper.AddAndWriteBuffer("buff", (void*)vec.data(), (unsigned int)vec.size() * sizeof(vec[0])); + wrapper.SetBufferArg(0, 0, 0); + wrapper.SetArg(0, 1, 1); + + if (wrapper.RunKernel(0, + 32,//Total grid dims. + 1, + 1, + 1,//Individual block dims. + 1, + 1)) + { + wrapper.ReadBuffer(0, vec.data(), (unsigned int)vec.size() * sizeof(vec[0])); + cout << "\n\nVector after locked add: " << endl; + + for (i = 0; i < vec.size(); i++) + { + cout << "vec[" << i << "] = " << vec[i] << endl; + } + } + } + } + } + } +} + +template +bool SearchVar(Variation* var, vector& stringVec, bool matchAll) +{ + bool ret = false; + size_t i; + string cl = var->OpenCLString(); + + if (matchAll) + { + for (i = 0; i < stringVec.size(); i++) + { + if (cl.find(stringVec[i]) == std::string::npos) + { + break; + } + } + + ret = (i == stringVec.size()); + } + else + { + for (i = 0; i < stringVec.size(); i++) + { + if (cl.find(stringVec[i]) != std::string::npos) + { + ret = true; + break; + } + } + } + + return ret; +} + +template +static vector*> FindVarsWith(vector& stringVec, bool findAll = true) +{ + int index = 0; + VariationList vl; + vector*> vec; + + while (index < vl.RegSize()) + { + Variation* regVar = vl.GetVariation(index, VARTYPE_REG); + + if (SearchVar(regVar, stringVec, false)) + { + vec.push_back(regVar->Copy()); + + if (!findAll) + break; + } + + index++; + } + + return vec; +} + +bool TestVarCounts() +{ + VariationList vlf; +#ifdef DO_DOUBLE + VariationList vld; + bool success ((vlf.Size() == vld.Size()) && (vlf.Size() == LAST_VAR)); +#else + bool success = true; +#endif + unsigned int start = (unsigned int)VAR_ARCH; + + if (!success) + { + cout << "Variation list size " << vlf.Size() << " does not equal the max var ID enum " << (unsigned int)LAST_VAR << "." << endl; + } + + for (; start < (unsigned int)LAST_VAR; start++) + { + Variation* var = vlf.GetVariation((eVariationId)start); + + if (!var) + { + cout << "Variation " << start << " was not found." << endl; + success = false; + } + } + + return success; +} + +template +bool TestVarUnique() +{ + bool success = true; + VariationList vl; + vector ids; + vector names; + + ids.reserve(vl.Size()); + names.reserve(vl.Size()); + + for (size_t i = 0; i < vl.Size(); i++) + { + Variation* var = vl.GetVariation(i); + + if (std::find(ids.begin(), ids.end(), var->VariationId()) != ids.end()) + { + cout << "Variation " << var->Name() << " was a duplicate ID entry." << endl; + success = false; + } + else + { + ids.push_back(var->VariationId()); + } + + if (std::find(names.begin(), names.end(), var->Name()) != names.end()) + { + cout << "Variation " << var->Name() << " was a duplicate name entry." << endl; + success = false; + } + else + { + names.push_back(var->Name()); + } + } + + return success; +} + +template +bool TestVarPrecalcEqual(Variation* var1, Variation