psychopy 2024.2.5__py3-none-any.whl → 2025.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of psychopy might be problematic. Click here for more details.
- psychopy/CHANGELOG.txt +4 -4
- psychopy/GIT_SHA +1 -1
- psychopy/LICENSE.txt +1 -1
- psychopy/VERSION +1 -1
- psychopy/__init__.py +10 -7
- psychopy/alerts/__init__.py +1 -1
- psychopy/alerts/_alerts.py +53 -17
- psychopy/alerts/_errorHandler.py +3 -4
- psychopy/alerts/alertsCatalogue/3210.yaml +27 -0
- psychopy/alerts/alertsCatalogue/3610.yaml +19 -0
- psychopy/alerts/alertsCatalogue/4130.yaml +19 -0
- psychopy/alerts/alertsCatalogue/alertCategories.yaml +8 -1
- psychopy/alerts/alertsCatalogue/alertmsg.py +1 -1
- psychopy/alerts/alerttools.py +0 -16
- psychopy/app/Resources/betasplash.png +0 -0
- psychopy/app/Resources/betasplash@2x.png +0 -0
- psychopy/app/Resources/classic/case.png +0 -0
- psychopy/app/Resources/classic/case@2x.png +0 -0
- psychopy/app/Resources/classic/fileaudio.png +0 -0
- psychopy/app/Resources/classic/fileaudio@2x.png +0 -0
- psychopy/app/Resources/classic/filecss.png +0 -0
- psychopy/app/Resources/classic/filecss@2x.png +0 -0
- psychopy/app/Resources/classic/filecsv.png +0 -0
- psychopy/app/Resources/classic/filecsv@2x.png +0 -0
- psychopy/app/Resources/classic/filedesign.png +0 -0
- psychopy/app/Resources/classic/filedesign@2x.png +0 -0
- psychopy/app/Resources/classic/filefont.png +0 -0
- psychopy/app/Resources/classic/filefont@2x.png +0 -0
- psychopy/app/Resources/classic/filegit.png +0 -0
- psychopy/app/Resources/classic/filegit@2x.png +0 -0
- psychopy/app/Resources/classic/filehtml.png +0 -0
- psychopy/app/Resources/classic/filehtml@2x.png +0 -0
- psychopy/app/Resources/classic/fileimage.png +0 -0
- psychopy/app/Resources/classic/fileimage@2x.png +0 -0
- psychopy/app/Resources/classic/fileinfo.png +0 -0
- psychopy/app/Resources/classic/fileinfo@2x.png +0 -0
- psychopy/app/Resources/classic/filejs.png +0 -0
- psychopy/app/Resources/classic/filejs@2x.png +0 -0
- psychopy/app/Resources/classic/filejson.png +0 -0
- psychopy/app/Resources/classic/filejson@2x.png +0 -0
- psychopy/app/Resources/classic/filepkg.png +0 -0
- psychopy/app/Resources/classic/filepkg@2x.png +0 -0
- psychopy/app/Resources/classic/filepsyexp.png +0 -0
- psychopy/app/Resources/classic/filepsyexp@2x.png +0 -0
- psychopy/app/Resources/classic/filepy.png +0 -0
- psychopy/app/Resources/classic/filepy@2x.png +0 -0
- psychopy/app/Resources/classic/filetxt.png +0 -0
- psychopy/app/Resources/classic/filetxt@2x.png +0 -0
- psychopy/app/Resources/classic/fileunknown.png +0 -0
- psychopy/app/Resources/classic/fileunknown@2x.png +0 -0
- psychopy/app/Resources/classic/filevideo.png +0 -0
- psychopy/app/Resources/classic/filevideo@2x.png +0 -0
- psychopy/app/Resources/classic/find.png +0 -0
- psychopy/app/Resources/classic/find@2x.png +0 -0
- psychopy/app/Resources/classic/loop.png +0 -0
- psychopy/app/Resources/classic/loop@2x.png +0 -0
- psychopy/app/Resources/classic/regex.png +0 -0
- psychopy/app/Resources/classic/regex@2x.png +0 -0
- psychopy/app/Resources/dark/case.png +0 -0
- psychopy/app/Resources/dark/case@2x.png +0 -0
- psychopy/app/Resources/dark/fileaudio.png +0 -0
- psychopy/app/Resources/dark/fileaudio@2x.png +0 -0
- psychopy/app/Resources/dark/filecss.png +0 -0
- psychopy/app/Resources/dark/filecss@2x.png +0 -0
- psychopy/app/Resources/dark/filecsv.png +0 -0
- psychopy/app/Resources/dark/filecsv@2x.png +0 -0
- psychopy/app/Resources/dark/filedesign.png +0 -0
- psychopy/app/Resources/dark/filedesign@2x.png +0 -0
- psychopy/app/Resources/dark/filefont.png +0 -0
- psychopy/app/Resources/dark/filefont@2x.png +0 -0
- psychopy/app/Resources/dark/filegit.png +0 -0
- psychopy/app/Resources/dark/filegit@2x.png +0 -0
- psychopy/app/Resources/dark/filehtml.png +0 -0
- psychopy/app/Resources/dark/filehtml@2x.png +0 -0
- psychopy/app/Resources/dark/fileimage.png +0 -0
- psychopy/app/Resources/dark/fileimage@2x.png +0 -0
- psychopy/app/Resources/dark/fileinfo.png +0 -0
- psychopy/app/Resources/dark/fileinfo@2x.png +0 -0
- psychopy/app/Resources/dark/filejs.png +0 -0
- psychopy/app/Resources/dark/filejs@2x.png +0 -0
- psychopy/app/Resources/dark/filejson.png +0 -0
- psychopy/app/Resources/dark/filejson@2x.png +0 -0
- psychopy/app/Resources/dark/filepkg.png +0 -0
- psychopy/app/Resources/dark/filepkg@2x.png +0 -0
- psychopy/app/Resources/dark/filepsyexp.png +0 -0
- psychopy/app/Resources/dark/filepsyexp@2x.png +0 -0
- psychopy/app/Resources/dark/filepy.png +0 -0
- psychopy/app/Resources/dark/filepy@2x.png +0 -0
- psychopy/app/Resources/dark/filetxt.png +0 -0
- psychopy/app/Resources/dark/filetxt@2x.png +0 -0
- psychopy/app/Resources/dark/fileunknown.png +0 -0
- psychopy/app/Resources/dark/fileunknown@2x.png +0 -0
- psychopy/app/Resources/dark/filevideo.png +0 -0
- psychopy/app/Resources/dark/filevideo@2x.png +0 -0
- psychopy/app/Resources/dark/find.png +0 -0
- psychopy/app/Resources/dark/find@2x.png +0 -0
- psychopy/app/Resources/dark/loop.png +0 -0
- psychopy/app/Resources/dark/loop@2x.png +0 -0
- psychopy/app/Resources/dark/regex.png +0 -0
- psychopy/app/Resources/dark/regex@2x.png +0 -0
- psychopy/app/Resources/light/case.png +0 -0
- psychopy/app/Resources/light/case@2x.png +0 -0
- psychopy/app/Resources/light/fileaudio.png +0 -0
- psychopy/app/Resources/light/fileaudio@2x.png +0 -0
- psychopy/app/Resources/light/filecss.png +0 -0
- psychopy/app/Resources/light/filecss@2x.png +0 -0
- psychopy/app/Resources/light/filecsv.png +0 -0
- psychopy/app/Resources/light/filecsv@2x.png +0 -0
- psychopy/app/Resources/light/filedesign.png +0 -0
- psychopy/app/Resources/light/filedesign@2x.png +0 -0
- psychopy/app/Resources/light/filefont.png +0 -0
- psychopy/app/Resources/light/filefont@2x.png +0 -0
- psychopy/app/Resources/light/filegit.png +0 -0
- psychopy/app/Resources/light/filegit@2x.png +0 -0
- psychopy/app/Resources/light/filehtml.png +0 -0
- psychopy/app/Resources/light/filehtml@2x.png +0 -0
- psychopy/app/Resources/light/fileimage.png +0 -0
- psychopy/app/Resources/light/fileimage@2x.png +0 -0
- psychopy/app/Resources/light/fileinfo.png +0 -0
- psychopy/app/Resources/light/fileinfo@2x.png +0 -0
- psychopy/app/Resources/light/filejs.png +0 -0
- psychopy/app/Resources/light/filejs@2x.png +0 -0
- psychopy/app/Resources/light/filejson.png +0 -0
- psychopy/app/Resources/light/filejson@2x.png +0 -0
- psychopy/app/Resources/light/filepkg.png +0 -0
- psychopy/app/Resources/light/filepkg@2x.png +0 -0
- psychopy/app/Resources/light/filepsyexp.png +0 -0
- psychopy/app/Resources/light/filepsyexp@2x.png +0 -0
- psychopy/app/Resources/light/filepy.png +0 -0
- psychopy/app/Resources/light/filepy@2x.png +0 -0
- psychopy/app/Resources/light/filetxt.png +0 -0
- psychopy/app/Resources/light/filetxt@2x.png +0 -0
- psychopy/app/Resources/light/fileunknown.png +0 -0
- psychopy/app/Resources/light/fileunknown@2x.png +0 -0
- psychopy/app/Resources/light/filevideo.png +0 -0
- psychopy/app/Resources/light/filevideo@2x.png +0 -0
- psychopy/app/Resources/light/find.png +0 -0
- psychopy/app/Resources/light/find@2x.png +0 -0
- psychopy/app/Resources/light/loop.png +0 -0
- psychopy/app/Resources/light/loop@2x.png +0 -0
- psychopy/app/Resources/light/regex.png +0 -0
- psychopy/app/Resources/light/regex@2x.png +0 -0
- psychopy/app/Resources/routine_templates/Basic.psyexp +0 -1
- psychopy/app/Resources/routine_templates/Misc.psyexp +0 -1
- psychopy/app/Resources/routine_templates/Online.psyexp +0 -2
- psychopy/app/Resources/routine_templates/Trials.psyexp +0 -1
- psychopy/app/Resources/splash.png +0 -0
- psychopy/app/Resources/splash@2x.png +0 -0
- psychopy/app/__init__.py +49 -8
- psychopy/app/__main__.py +3 -0
- psychopy/app/_psychopyApp.py +134 -125
- psychopy/app/builder/builder.py +42 -22
- psychopy/app/builder/dialogs/__init__.py +44 -11
- psychopy/app/builder/dialogs/dlgsCode.py +1 -1
- psychopy/app/builder/dialogs/dlgsConditions.py +1 -1
- psychopy/app/builder/dialogs/findDlg.py +106 -20
- psychopy/app/builder/dialogs/paramCtrls.py +42 -1
- psychopy/app/builder/validators.py +1 -1
- psychopy/app/coder/codeEditorBase.py +8 -8
- psychopy/app/coder/coder.py +32 -29
- psychopy/app/coder/fileBrowser.py +68 -22
- psychopy/app/coder/folding.py +1 -1
- psychopy/app/coder/psychoParser.py +1 -1
- psychopy/app/coder/repl.py +1 -1
- psychopy/app/coder/sourceTree.py +1 -1
- psychopy/app/connections/__init__.py +1 -1
- psychopy/app/connections/news.py +1 -1
- psychopy/app/connections/sendusage.py +1 -1
- psychopy/app/connections/updates.py +1 -1
- psychopy/app/console.py +1 -1
- psychopy/app/errorDlg.py +1 -1
- psychopy/app/frametracker.py +1 -1
- psychopy/app/idle.py +1 -1
- psychopy/app/jobs.py +5 -2
- psychopy/app/linuxconfig/__init__.py +1 -1
- psychopy/app/locale/ar_001/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ar_001/LC_MESSAGE/messages.po +4 -4
- psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/da_DK/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/de_DE/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/de_DE/LC_MESSAGE/messages.po +3 -3
- psychopy/app/locale/el_GR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/en_NZ/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/en_US/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_CO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_CO/LC_MESSAGE/messages.po +4 -4
- psychopy/app/locale/es_ES/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_ES/LC_MESSAGE/messages.po +4 -4
- psychopy/app/locale/es_US/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_US/LC_MESSAGE/messages.po +4 -4
- psychopy/app/locale/et_EE/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/et_EE/LC_MESSAGE/messages.po +2 -2
- psychopy/app/locale/fa_IR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/fa_IR/LC_MESSAGE/messages.po +1 -1
- psychopy/app/locale/fi_FI/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/fr_FR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/fr_FR/LC_MESSAGE/messages.po +2 -2
- psychopy/app/locale/he_IL/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/he_IL/LC_MESSAGE/messages.po +2 -2
- psychopy/app/locale/hi_IN/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/hi_IN/LC_MESSAGE/messages.po +2 -2
- psychopy/app/locale/hu_HU/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/it_IT/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/it_IT/LC_MESSAGE/messages.po +2 -2
- psychopy/app/locale/ja_JP/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ja_JP/LC_MESSAGE/messages.po +3421 -2396
- psychopy/app/locale/ko_KR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ms_MY/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ms_MY/LC_MESSAGE/messages.po +2 -2
- psychopy/app/locale/nl_NL/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/nn_NO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/pl_PL/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/pt_PT/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/pt_PT/LC_MESSAGE/messages.po +4 -4
- psychopy/app/locale/ro_RO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ru_RU/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/sv_SE/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/sv_SE/LC_MESSAGE/messages.po +2 -2
- psychopy/app/locale/tr_TR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/tr_TR/LC_MESSAGE/messages.po +2 -2
- psychopy/app/locale/zh_CN/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/zh_CN/LC_MESSAGE/messages.po +3 -3
- psychopy/app/locale/zh_TW/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/zh_TW/LC_MESSAGE/messages.po +2 -2
- psychopy/app/{builder/localizedStrings.py → localizedStrings.py} +173 -26
- psychopy/app/pavlovia_ui/__init__.py +1 -1
- psychopy/app/pavlovia_ui/_base.py +1 -1
- psychopy/app/pavlovia_ui/functions.py +1 -1
- psychopy/app/pavlovia_ui/menu.py +1 -1
- psychopy/app/pavlovia_ui/project.py +1 -1
- psychopy/app/pavlovia_ui/search.py +1 -1
- psychopy/app/pavlovia_ui/sync.py +1 -1
- psychopy/app/pavlovia_ui/user.py +1 -1
- psychopy/app/plugin_manager/dialog.py +34 -96
- psychopy/app/plugin_manager/packages.py +10 -14
- psychopy/app/plugin_manager/plugins.py +64 -4
- psychopy/app/preferencesDlg.py +12 -37
- psychopy/app/psychopyApp.py +130 -44
- psychopy/app/ribbon.py +1 -0
- psychopy/app/runner/runner.py +19 -7
- psychopy/app/runner/scriptProcess.py +11 -6
- psychopy/app/stdout/stdOutRich.py +9 -2
- psychopy/app/themes/fonts.py +1 -1
- psychopy/app/themes/icons.py +2 -38
- psychopy/app/ui/__init__.py +1 -1
- psychopy/app/utils.py +3 -3
- psychopy/assets/default.mp3 +0 -0
- psychopy/assets/fonts/NotoSans-Bold.ttf +0 -0
- psychopy/assets/fonts/NotoSans-BoldItalic.ttf +0 -0
- psychopy/assets/fonts/NotoSans-Italic.ttf +0 -0
- psychopy/assets/fonts/NotoSans-Regular.ttf +0 -0
- psychopy/assets/voicekeyThresholdStim.wav +0 -0
- psychopy/clock.py +4 -1
- psychopy/colors.py +2 -0
- psychopy/core.py +1 -1
- psychopy/data/base.py +1 -1
- psychopy/data/experiment.py +151 -46
- psychopy/data/routine.py +27 -1
- psychopy/data/staircase.py +2 -1
- psychopy/data/trial.py +62 -14
- psychopy/data/utils.py +1 -1
- psychopy/demos/builder/Design Templates/branchedExperiment/branchedExperiment.psyexp +333 -218
- psychopy/demos/builder/Design Templates/psychophysicsStaircase/psychophysicsStaircase.psyexp +261 -239
- psychopy/demos/builder/Design Templates/psychophysicsStairsInterleaved/psychophysicsStaircaseInterleaved.psyexp +319 -180
- psychopy/demos/builder/Design Templates/randomisedBlocks/randomisedBlocks.psyexp +204 -116
- psychopy/demos/builder/Experiments/BART/assets/background.jpg +0 -0
- psychopy/demos/builder/Experiments/BART/assets/blueBalloon.png +0 -0
- psychopy/demos/builder/Experiments/BART/assets/greenBalloon.png +0 -0
- psychopy/demos/builder/Experiments/BART/assets/redBalloon.png +0 -0
- psychopy/demos/builder/Experiments/BART/bart.psyexp +779 -866
- psychopy/demos/builder/Experiments/BigFiveInventory/BFI.psyexp +242 -180
- psychopy/demos/builder/Experiments/GoNoGo/gng.psyexp +419 -406
- psychopy/demos/builder/Experiments/dragAndDrop/README.md +2 -37
- psychopy/demos/builder/Experiments/dragAndDrop/drag_and_drop.psyexp +460 -1204
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/blank_grid.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/make_shapes.psyexp +221 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/readme.md +4 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_1.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_10.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_2.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_3.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_4.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_5.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_6.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_7.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_8.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solution_9.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solutions.xlsx +0 -0
- psychopy/demos/builder/Experiments/mentalRotation/MentalRotation.psyexp +583 -542
- psychopy/demos/builder/Experiments/navon/NavonTask.psyexp +458 -427
- psychopy/demos/builder/Experiments/sternberg/sternberg.psyexp +588 -550
- psychopy/demos/builder/Experiments/stroop/stroop.psyexp +303 -207
- psychopy/demos/builder/Experiments/stroopExtended/stroop.psyexp +390 -215
- psychopy/demos/builder/Experiments/stroopExtended/stroopReverse.psyexp +390 -215
- psychopy/demos/builder/Experiments/stroopVoice/stroopVoice.psyexp +357 -331
- psychopy/demos/builder/Feature Demos/buttonBox/buttonBoxDemo.psyexp +3 -2
- psychopy/demos/builder/Feature Demos/counterbalance/counterbalance.psyexp +287 -277
- psychopy/demos/builder/Feature Demos/gratings/gratings.psyexp +370 -320
- psychopy/demos/builder/Feature Demos/noise/noise.psyexp +452 -399
- psychopy/demos/builder/Feature Demos/panorama/panorama.psyexp +168 -133
- psychopy/demos/builder/Feature Demos/pilotMode/pilotMode.psyexp +4 -3
- psychopy/demos/builder/Feature Demos/progress/progressBar.psyexp +420 -392
- psychopy/demos/builder/Feature Demos/sliders/sliders.psyexp +917 -871
- psychopy/demos/builder/Feature Demos/visualValidator/readme.md +7 -0
- psychopy/demos/builder/Feature Demos/visualValidator/visualValidator.psyexp +200 -0
- psychopy/demos/builder/Hardware/EEG_parallel_component/EEG_triggers_parallel_comp.psyexp +25 -9
- psychopy/demos/builder/Hardware/EEG_serial_code/EEG_triggers_serial_code.psyexp +372 -361
- psychopy/demos/builder/Hardware/EEG_serial_component/EEG_triggers_serial_comp.psyexp +25 -9
- psychopy/demos/builder/Hardware/EGI_netstation/stroop.psyexp +320 -235
- psychopy/demos/builder/Hardware/Eyetracking_visual_search/visualSearch.psyexp +790 -651
- psychopy/demos/builder/Hardware/camera/camera.psyexp +326 -246
- psychopy/demos/builder/Hardware/eyetracking/eyetracking.psyexp +432 -327
- psychopy/demos/builder/Hardware/eyetracking_custom_cal/eyetracking_custom_cal.psyexp +440 -321
- psychopy/demos/builder/Hardware/fMRI/fMRI_demo.psyexp +190 -181
- psychopy/demos/builder/Hardware/lab_streaming_layer/lsl_triggers_demo.psyexp +323 -312
- psychopy/demos/builder/Hardware/lab_streaming_layer/lsl_triggers_demo_legacy.psyexp +360 -0
- psychopy/demos/builder/Hardware/lab_streaming_layer/lsl_triggers_demo_legacy_legacy.psyexp +312 -0
- psychopy/demos/builder/Hardware/lab_streaming_layer_legacy/lsl_triggers_demo_legacy.psyexp +329 -273
- psychopy/demos/builder/Hardware/lab_streaming_layer_legacy/lsl_triggers_demo_legacy_legacy.psyexp +360 -0
- psychopy/demos/builder/Hardware/lab_streaming_layer_legacy/lsl_triggers_demo_legacy_legacy_legacy.psyexp +312 -0
- psychopy/demos/builder/Hardware/microphone/microphone.psyexp +450 -404
- psychopy/demos/builder/Hardware/pump/pump.psyexp +593 -329
- psychopy/demos/builder/Helper Tools/achorVSalignment/FlowCircular-Regular.ttf +0 -0
- psychopy/demos/builder/Helper Tools/achorVSalignment/anchorAlignment.psyexp +336 -274
- psychopy/demos/builder/Helper Tools/clockFace/clockFace.psyexp +200 -149
- psychopy/demos/builder/Helper Tools/colors/colors.psyexp +300 -279
- psychopy/demos/builder/Helper Tools/drawPolygon/drawPolygon.psyexp +677 -564
- psychopy/demos/builder/Helper Tools/keyNameFinder/keyNameFinder.psyexp +214 -158
- psychopy/demos/builder/Helper Tools/spatialUnits/unitDemo.psyexp +195 -146
- psychopy/demos/coder/experiment control/runtimeInfo.py +1 -1
- psychopy/devices/__init__.py +1 -1
- psychopy/event.py +1 -1
- psychopy/exceptions.py +1 -1
- psychopy/experiment/__init__.py +1 -1
- psychopy/experiment/_experiment.py +30 -9
- psychopy/experiment/components/__init__.py +1 -6
- psychopy/experiment/components/_base.py +44 -19
- psychopy/experiment/components/aperture/__init__.py +1 -1
- psychopy/experiment/components/brush/__init__.py +2 -2
- psychopy/experiment/components/button/__init__.py +24 -28
- psychopy/experiment/components/camera/__init__.py +38 -39
- psychopy/experiment/components/code/__init__.py +1 -1
- psychopy/experiment/components/dots/__init__.py +1 -1
- psychopy/experiment/components/eyetracker_record/__init__.py +7 -3
- psychopy/experiment/components/form/__init__.py +3 -3
- psychopy/experiment/components/grating/__init__.py +1 -1
- psychopy/experiment/components/image/__init__.py +1 -1
- psychopy/experiment/components/joyButtons/__init__.py +2 -2
- psychopy/experiment/components/joyButtons/virtualJoyButtons.py +1 -1
- psychopy/experiment/components/joystick/__init__.py +3 -3
- psychopy/experiment/components/joystick/virtualJoystick.py +1 -1
- psychopy/experiment/components/keyboard/__init__.py +98 -122
- psychopy/experiment/components/microphone/__init__.py +54 -98
- psychopy/experiment/components/mouse/__init__.py +92 -93
- psychopy/experiment/components/movie/__init__.py +28 -50
- psychopy/experiment/components/parallelOut/__init__.py +3 -3
- psychopy/experiment/components/polygon/__init__.py +2 -3
- psychopy/experiment/components/roi/__init__.py +2 -2
- psychopy/experiment/components/routineSettings/__init__.py +2 -0
- psychopy/experiment/components/serialOut/__init__.py +7 -7
- psychopy/experiment/components/settings/__init__.py +317 -313
- psychopy/experiment/components/settings/eyetracking.py +108 -0
- psychopy/experiment/components/slider/__init__.py +5 -5
- psychopy/experiment/components/sound/__init__.py +168 -78
- psychopy/experiment/components/soundsensor/__init__.py +361 -0
- psychopy/experiment/components/soundsensor/classic/soundsensor.png +0 -0
- psychopy/experiment/components/soundsensor/classic/soundsensor@2x.png +0 -0
- psychopy/experiment/components/soundsensor/dark/soundsensor.png +0 -0
- psychopy/experiment/components/soundsensor/dark/soundsensor@2x.png +0 -0
- psychopy/experiment/components/soundsensor/light/soundsensor.png +0 -0
- psychopy/experiment/components/soundsensor/light/soundsensor@2x.png +0 -0
- psychopy/experiment/components/static/__init__.py +59 -33
- psychopy/experiment/components/text/__init__.py +2 -6
- psychopy/experiment/components/textbox/__init__.py +3 -7
- psychopy/experiment/components/unknown/__init__.py +2 -0
- psychopy/experiment/components/unknownPlugin/__init__.py +2 -0
- psychopy/experiment/exports.py +1 -1
- psychopy/experiment/flow.py +2 -2
- psychopy/experiment/localization.py +1 -1
- psychopy/experiment/loops.py +43 -10
- psychopy/experiment/params.py +6 -4
- psychopy/experiment/plugins.py +8 -1
- psychopy/experiment/py2js.py +1 -1
- psychopy/experiment/py2js_transpiler.py +1 -1
- psychopy/experiment/routines/__init__.py +1 -1
- psychopy/experiment/routines/_base.py +23 -24
- psychopy/experiment/routines/audioValidator/__init__.py +343 -0
- psychopy/experiment/routines/audioValidator/classic/audio_validator.png +0 -0
- psychopy/experiment/routines/audioValidator/classic/audio_validator@2x.png +0 -0
- psychopy/experiment/routines/audioValidator/dark/audio_validator.png +0 -0
- psychopy/experiment/routines/audioValidator/dark/audio_validator@2x.png +0 -0
- psychopy/experiment/routines/audioValidator/light/audio_validator.png +0 -0
- psychopy/experiment/routines/audioValidator/light/audio_validator@2x.png +0 -0
- psychopy/experiment/routines/eyetracker_calibrate/__init__.py +76 -17
- psychopy/experiment/routines/eyetracker_validate/__init__.py +1 -1
- psychopy/experiment/routines/unknown/__init__.py +2 -0
- psychopy/experiment/routines/{photodiodeValidator → visualValidator}/__init__.py +89 -120
- psychopy/experiment/routines/visualValidator/classic/visual_validator.png +0 -0
- psychopy/experiment/routines/visualValidator/classic/visual_validator@2x.png +0 -0
- psychopy/experiment/routines/visualValidator/dark/visual_validator.png +0 -0
- psychopy/experiment/routines/visualValidator/dark/visual_validator@2x.png +0 -0
- psychopy/experiment/routines/visualValidator/light/visual_validator.png +0 -0
- psychopy/experiment/routines/visualValidator/light/visual_validator@2x.png +0 -0
- psychopy/experiment/utils.py +1 -1
- psychopy/gui/__init__.py +1 -1
- psychopy/gui/qtgui.py +15 -6
- psychopy/gui/wxgui.py +1 -1
- psychopy/hardware/__init__.py +0 -1
- psychopy/hardware/base.py +147 -16
- psychopy/hardware/bbtk/__init__.py +10 -16
- psychopy/hardware/brainproducts.py +7 -13
- psychopy/hardware/button.py +21 -2
- psychopy/hardware/buttonbox/__init__.py +1 -1
- psychopy/hardware/camera/__init__.py +16 -3
- psychopy/hardware/cedrus.py +5 -16
- psychopy/hardware/crs/__init__.py +1 -1
- psychopy/hardware/crs/bits.py +36 -33
- psychopy/hardware/crs/colorcal.py +8 -11
- psychopy/hardware/crs/optical.py +7 -10
- psychopy/hardware/crs/shaders.py +18 -5
- psychopy/hardware/emotiv.py +1 -1
- psychopy/hardware/emulator.py +11 -5
- psychopy/hardware/exceptions.py +86 -0
- psychopy/hardware/forp.py +18 -23
- psychopy/hardware/gammasci.py +8 -3
- psychopy/hardware/iolab.py +8 -19
- psychopy/hardware/joystick/__init__.py +865 -266
- psychopy/hardware/joystick/_base.py +251 -0
- psychopy/hardware/joystick/backend_glfw.py +306 -0
- psychopy/hardware/joystick/backend_pyglet.py +309 -0
- psychopy/hardware/joystick/mappings.py +287 -0
- psychopy/hardware/keyboard.py +4 -2
- psychopy/hardware/labhackers.py +1 -1
- psychopy/hardware/labjacks.py +9 -13
- psychopy/hardware/{photodiode.py → lightsensor.py} +146 -203
- psychopy/hardware/listener.py +9 -8
- psychopy/hardware/manager.py +24 -35
- psychopy/hardware/microphone.py +534 -154
- psychopy/hardware/minolta.py +14 -4
- psychopy/hardware/mouse/__init__.py +1 -1
- psychopy/hardware/photometer/__init__.py +2 -2
- psychopy/hardware/pr.py +14 -4
- psychopy/hardware/qmix.py +18 -27
- psychopy/hardware/serialdevice.py +43 -12
- psychopy/hardware/soundsensor.py +473 -0
- psychopy/hardware/spatial/__init__.py +231 -0
- psychopy/hardware/speaker.py +298 -36
- psychopy/hardware/triggerbox/__init__.py +1 -1
- psychopy/hardware/triggerbox/base.py +1 -1
- psychopy/hardware/triggerbox/parallel.py +1 -1
- psychopy/info.py +1 -1
- psychopy/iohub/devices/eyetracker/__init__.py +10 -18
- psychopy/iohub/devices/eyetracker/calibration/procedure.py +15 -33
- psychopy/iohub/devices/eyetracker/hw/pupil_labs/neon/__init__.py +1 -0
- psychopy/iohub/devices/mouse/linux2.py +2 -1
- psychopy/layout.py +1 -1
- psychopy/liaison.py +91 -39
- psychopy/locale_setup.py +11 -1
- psychopy/localization/__init__.py +1 -1
- psychopy/localization/_localization.py +1 -1
- psychopy/localization/messages.pot +2 -2
- psychopy/logging.py +14 -12
- psychopy/microphone.py +4 -3
- psychopy/misc.py +1 -1
- psychopy/monitors/MonitorCenter.py +3 -3
- psychopy/monitors/__init__.py +1 -1
- psychopy/monitors/calibData.py +1 -1
- psychopy/monitors/calibTools.py +3 -2
- psychopy/platform_specific/__init__.py +1 -1
- psychopy/platform_specific/darwin.py +1 -1
- psychopy/platform_specific/linux.py +1 -1
- psychopy/platform_specific/win32.py +1 -1
- psychopy/plugins/__init__.py +26 -17
- psychopy/plugins/util.py +65 -0
- psychopy/preferences/Darwin.spec +11 -3
- psychopy/preferences/FreeBSD.spec +11 -3
- psychopy/preferences/Linux.spec +11 -3
- psychopy/preferences/Windows.spec +11 -3
- psychopy/preferences/__init__.py +1 -1
- psychopy/preferences/baseNoArch.spec +11 -3
- psychopy/preferences/hints.py +78 -61
- psychopy/preferences/preferences.py +82 -13
- psychopy/projects/__init__.py +1 -1
- psychopy/projects/pavlovia.py +29 -13
- psychopy/scripts/psyexpCompile.py +1 -1
- psychopy/session.py +81 -8
- psychopy/sound/__init__.py +25 -10
- psychopy/sound/_base.py +134 -34
- psychopy/sound/audioclip.py +38 -15
- psychopy/sound/audiodevice.py +1 -1
- psychopy/sound/backend_ptb.py +53 -321
- psychopy/sound/backend_pygame.py +7 -3
- psychopy/sound/backend_pyo.py +53 -22
- psychopy/sound/backend_pysound.py +10 -27
- psychopy/sound/backend_sounddevice.py +33 -21
- psychopy/sound/exceptions.py +1 -1
- psychopy/sound/microphone.py +83 -5
- psychopy/sound/transcribe.py +3 -3
- psychopy/tests/data/test_basic_run.py +1 -0
- psychopy/tests/data/test_sounds/default_16000.wav +0 -0
- psychopy/tests/data/test_sounds/default_192000.wav +0 -0
- psychopy/tests/data/test_sounds/default_22050.wav +0 -0
- psychopy/tests/data/test_sounds/default_32000.wav +0 -0
- psychopy/tests/data/test_sounds/default_44100.wav +0 -0
- psychopy/tests/data/test_sounds/default_48000.wav +0 -0
- psychopy/tests/data/test_sounds/default_8000.wav +0 -0
- psychopy/tests/data/test_sounds/default_96000.wav +0 -0
- psychopy/tests/test_alerts/test_alerttools.py +2 -1
- psychopy/tests/test_app/test_command_line.py +65 -0
- psychopy/tests/test_data/test_TrialHandler2.py +18 -7
- psychopy/tests/test_demos/test_builder_demos.py +1 -1
- psychopy/tests/test_experiment/needs_wx/componsTemplate.txt +5238 -4188
- psychopy/tests/test_experiment/needs_wx/test_components.py +1 -1
- psychopy/tests/test_experiment/test_components/test_RoutineSettingsComponent.py +16 -0
- psychopy/tests/test_experiment/test_components/test_all_components.py +1 -1
- psychopy/tests/test_experiment/test_components/test_base_components.py +26 -17
- psychopy/tests/test_experiment/test_loops.py +12 -4
- psychopy/tests/test_experiment/test_params.py +17 -4
- psychopy/tests/test_experiment/test_routines/test_PhotodiodeValidationRoutine.py +2 -2
- psychopy/tests/test_hardware/test_photodiode.py +66 -0
- psychopy/tests/test_liaison/test_Liaison.py +4 -4
- psychopy/tests/test_misc/test_clock.py +2 -0
- psychopy/tests/test_misc/test_color.py +2 -0
- psychopy/tests/test_misc/test_event.py +1 -0
- psychopy/tests/test_plugins/__init__.py +0 -0
- psychopy/tests/test_plugins/test_plugin_stubs.py +125 -0
- psychopy/tests/test_sound/test_sound.py +111 -40
- psychopy/tests/test_tools/test_colorspacetools.py +24 -23
- psychopy/tests/test_tools/test_mathtools.py +9 -9
- psychopy/tests/test_tools/test_viewtools.py +101 -0
- psychopy/tests/test_validators/__init__.py +0 -0
- psychopy/tests/test_validators/test_voicekeyValidator.py +95 -0
- psychopy/tests/test_visual/test_all_stimuli.py +2 -2
- psychopy/tests/test_visual/test_basevisual.py +37 -7
- psychopy/tests/test_visual/test_button.py +2 -2
- psychopy/tests/test_visual/test_circle.py +10 -2
- psychopy/tests/test_visual/test_dots.py +1 -1
- psychopy/tests/test_visual/test_form.py +13 -13
- psychopy/tests/test_visual/test_gamma.py +3 -3
- psychopy/tests/test_visual/test_image.py +2 -2
- psychopy/tests/test_visual/test_progress.py +2 -2
- psychopy/tests/test_visual/test_roi.py +8 -2
- psychopy/tests/test_visual/test_shape.py +2 -2
- psychopy/tests/test_visual/test_slider.py +2 -2
- psychopy/tests/test_visual/test_target.py +2 -2
- psychopy/tests/test_visual/test_textbox.py +6 -8
- psychopy/tests/test_visual/test_winScalePos.py +6 -5
- psychopy/tests/test_visual/test_window.py +17 -0
- psychopy/tests/utils.py +51 -1
- psychopy/tools/__init__.py +1 -1
- psychopy/tools/arraytools.py +2 -2
- psychopy/tools/attributetools.py +52 -1
- psychopy/tools/audiotools.py +4 -1
- psychopy/tools/colorspacetools.py +6 -4
- psychopy/tools/coordinatetools.py +1 -1
- psychopy/tools/fileerrortools.py +21 -9
- psychopy/tools/filetools.py +2 -2
- psychopy/tools/fontmanager.py +47 -28
- psychopy/tools/gltools.py +2964 -558
- psychopy/tools/imagetools.py +1 -1
- psychopy/tools/mathtools.py +997 -127
- psychopy/tools/monitorunittools.py +7 -1
- psychopy/tools/movietools.py +1 -2
- psychopy/tools/pkgtools.py +157 -127
- psychopy/tools/plottools.py +1 -1
- psychopy/tools/rifttools.py +1 -1
- psychopy/tools/stereotools.py +1 -1
- psychopy/tools/stimulustools.py +172 -2
- psychopy/tools/stringtools.py +22 -2
- psychopy/tools/systemtools.py +1 -1
- psychopy/tools/typetools.py +1 -1
- psychopy/tools/unittools.py +1 -1
- psychopy/tools/versionchooser.py +3 -1
- psychopy/tools/viewtools.py +54 -70
- psychopy/tools/wizard.py +2 -2
- psychopy/validation/__init__.py +6 -0
- psychopy/validation/audio.py +74 -0
- psychopy/validation/visual.py +115 -0
- psychopy/visual/__init__.py +1 -4
- psychopy/visual/aperture.py +9 -6
- psychopy/visual/backends/__init__.py +1 -1
- psychopy/visual/backends/_base.py +1 -1
- psychopy/visual/backends/gamma.py +1 -1
- psychopy/visual/backends/glfwbackend.py +8 -12
- psychopy/visual/backends/pygamebackend.py +1 -1
- psychopy/visual/backends/pygletbackend.py +32 -11
- psychopy/visual/basevisual.py +93 -13
- psychopy/visual/brush.py +1 -1
- psychopy/visual/bufferimage.py +90 -10
- psychopy/visual/button.py +1 -1
- psychopy/visual/circle.py +12 -20
- psychopy/visual/custommouse.py +1 -1
- psychopy/visual/dot.py +80 -14
- psychopy/visual/elementarray.py +84 -10
- psychopy/visual/filters.py +1 -1
- psychopy/visual/form.py +43 -28
- psychopy/visual/grating.py +105 -25
- psychopy/visual/helpers.py +1 -1
- psychopy/visual/image.py +98 -20
- psychopy/visual/line.py +15 -25
- psychopy/visual/movie.py +1 -1
- psychopy/visual/movie2.py +4 -3
- psychopy/visual/movie3.py +4 -3
- psychopy/visual/movies/__init__.py +1 -1
- psychopy/visual/movies/frame.py +1 -1
- psychopy/visual/movies/metadata.py +1 -1
- psychopy/visual/movies/players/__init__.py +1 -1
- psychopy/visual/movies/players/_base.py +1 -1
- psychopy/visual/movies/players/ffpyplayer_player.py +2 -3
- psychopy/visual/nnlvs.py +9 -6
- psychopy/visual/noise.py +4 -16
- psychopy/visual/patch.py +4 -3
- psychopy/visual/pie.py +3 -14
- psychopy/visual/polygon.py +21 -28
- psychopy/visual/radial.py +4 -18
- psychopy/visual/ratingscale.py +4 -3
- psychopy/visual/rect.py +16 -25
- psychopy/visual/rift.py +8 -2
- psychopy/visual/secondorder.py +4 -16
- psychopy/visual/shaders.py +620 -286
- psychopy/visual/shape.py +281 -90
- psychopy/visual/simpleimage.py +1 -1
- psychopy/visual/slider.py +78 -25
- psychopy/visual/stim3d.py +5 -608
- psychopy/visual/text.py +13 -3
- psychopy/visual/textbox2/textbox2.py +188 -56
- psychopy/visual/vlcmoviestim.py +1 -1
- psychopy/visual/window.py +373 -200
- psychopy/web.py +1 -1
- {psychopy-2024.2.5.dist-info → psychopy-2025.1.0.dist-info}/METADATA +9 -9
- {psychopy-2024.2.5.dist-info → psychopy-2025.1.0.dist-info}/RECORD +644 -621
- psychopy/.DS_Store +0 -0
- psychopy/__init__.py.orig +0 -65
- psychopy/app/.DS_Store +0 -0
- psychopy/app/Resources/.DS_Store +0 -0
- psychopy/app/Resources/classic/filecsv16.png +0 -0
- psychopy/app/Resources/classic/fileimage16.png +0 -0
- psychopy/app/Resources/classic/fileunknown16.png +0 -0
- psychopy/app/Resources/dark/filecsv16.png +0 -0
- psychopy/app/Resources/dark/filecsv16@2x.png +0 -0
- psychopy/app/Resources/dark/fileimage16.png +0 -0
- psychopy/app/Resources/dark/fileimage16@2x.png +0 -0
- psychopy/app/Resources/dark/fileunknown16.png +0 -0
- psychopy/app/Resources/dark/fileunknown16@2x.png +0 -0
- psychopy/app/Resources/fonts/OpenSans-Bold.ttf +0 -0
- psychopy/app/Resources/fonts/OpenSans-BoldItalic.ttf +0 -0
- psychopy/app/Resources/fonts/OpenSans-ExtraBold.ttf +0 -0
- psychopy/app/Resources/fonts/OpenSans-ExtraBoldItalic.ttf +0 -0
- psychopy/app/Resources/fonts/OpenSans-Italic.ttf +0 -0
- psychopy/app/Resources/fonts/OpenSans-Light.ttf +0 -0
- psychopy/app/Resources/fonts/OpenSans-LightItalic.ttf +0 -0
- psychopy/app/Resources/fonts/OpenSans-Regular.ttf +0 -0
- psychopy/app/Resources/fonts/OpenSans-SemiBold.ttf +0 -0
- psychopy/app/Resources/fonts/OpenSans-SemiBoldItalic.ttf +0 -0
- psychopy/app/Resources/light/filecsv16.png +0 -0
- psychopy/app/Resources/light/filecsv16@2x.png +0 -0
- psychopy/app/Resources/light/fileimage16.png +0 -0
- psychopy/app/Resources/light/fileimage16@2x.png +0 -0
- psychopy/app/Resources/light/fileunknown16.png +0 -0
- psychopy/app/Resources/light/fileunknown16@2x.png +0 -0
- psychopy/app/Resources/psychopySplash.png +0 -0
- psychopy/app/Resources/psychopySplash@2x.png +0 -0
- psychopy/app/builder/builder.py.orig +0 -3932
- psychopy/app/builder/dialogs/__init__.py.orig +0 -1679
- psychopy/app/builder/dialogs/paramCtrls.py.orig +0 -713
- psychopy/app/colorpicker/__init__.py.orig +0 -411
- psychopy/app/locale/ar_001/.DS_Store +0 -0
- psychopy/app/locale/ar_001/LC_MESSAGE/.DS_Store +0 -0
- psychopy/core.py.orig +0 -169
- psychopy/demos/builder/.DS_Store +0 -0
- psychopy/demos/builder/Design Templates/randomisedBlocks/randomisedBlocks_lastrun.py +0 -330
- psychopy/demos/builder/Experiments/.DS_Store +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/archived_conditions.xlsx +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/draw grid stim.py +0 -61
- psychopy/demos/builder/Experiments/dragAndDrop/shapeMaker.psyexp +0 -91
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_1.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_10.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_2.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_3.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_4.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_5.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_6.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_7.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_8.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/grid_image_9.png +0 -0
- psychopy/demos/builder/Experiments/dragAndDrop/updated_conditions.xlsx +0 -0
- psychopy/demos/builder/Tools/.DS_Store +0 -0
- psychopy/demos/builder/Tools/gammaCalibration/.DS_Store +0 -0
- psychopy/demos/builder/Tools/gammaCalibration/data/.DS_Store +0 -0
- psychopy/demos/builder/Tools/gammaCalibration/data/_gamma_correction_visual_2022-05-18_14h18.29.439.csv +0 -38
- psychopy/demos/builder/Tools/gammaCalibration/data/_gamma_correction_visual_2022-05-18_14h18.29.439.log +0 -3418
- psychopy/demos/builder/Tools/gammaCalibration/data/_gamma_correction_visual_2022-05-18_14h18.29.439.psydat +0 -0
- psychopy/demos/builder/Tools/gammaCalibration/data/x1_gamma_correction_visual_2022-05-17_13h59.42.928.csv +0 -2
- psychopy/demos/builder/Tools/gammaCalibration/data/x1_gamma_correction_visual_2022-05-17_13h59.42.928.log +0 -15
- psychopy/demos/builder/Tools/gammaCalibration/data/x1_gamma_correction_visual_2022-05-17_13h59.42.928.psydat +0 -0
- psychopy/demos/builder/Tools/gammaCalibration/gamma_correction_visual_lastrun.py +0 -562
- psychopy/demos/coder/.DS_Store +0 -0
- psychopy/demos/coder/experiment control/info_gamma.pickle +0 -0
- psychopy/demos/coder/iohub/.iohpid +0 -1
- psychopy/demos/coder/iohub/eyetracking/.iohpid +0 -1
- psychopy/demos/coder/iohub/wintab/.DS_Store +0 -0
- psychopy/demos/coder/stimuli/.DS_Store +0 -0
- psychopy/experiment/_experiment.py.orig +0 -1032
- psychopy/experiment/components/.DS_Store +0 -0
- psychopy/experiment/components/_base.py.orig +0 -823
- psychopy/experiment/components/form/.DS_Store +0 -0
- psychopy/experiment/components/microphone/__init__.py.orig +0 -490
- psychopy/experiment/components/settings/__init__.py.orig +0 -1337
- psychopy/experiment/components/textbox/__init__.py.orig +0 -310
- psychopy/experiment/components/webcam/.DS_Store +0 -0
- psychopy/experiment/components/webcam/light/.DS_Store +0 -0
- psychopy/experiment/loops.py.orig +0 -829
- psychopy/experiment/params.py.orig +0 -408
- psychopy/experiment/routine.py.orig +0 -503
- psychopy/experiment/routines/photodiodeValidator/classic/photodiode_validator.png +0 -0
- psychopy/experiment/routines/photodiodeValidator/classic/photodiode_validator@2x.png +0 -0
- psychopy/experiment/routines/photodiodeValidator/dark/photodiode_validator.png +0 -0
- psychopy/experiment/routines/photodiodeValidator/dark/photodiode_validator@2x.png +0 -0
- psychopy/experiment/routines/photodiodeValidator/light/photodiode_validator.png +0 -0
- psychopy/experiment/routines/photodiodeValidator/light/photodiode_validator@2x.png +0 -0
- psychopy/hardware/.DS_Store +0 -0
- psychopy/hardware/brainproducts.py.orig +0 -680
- psychopy/hardware/iolab.py.orig +0 -238
- psychopy/iohub/datastore/__init__.py.orig +0 -443
- psychopy/iohub/datastore/util.py.orig +0 -692
- psychopy/iohub/devices/mouse/darwin.py.orig +0 -427
- psychopy/iohub/devices/mouse/linux2.py.orig +0 -198
- psychopy/preferences/.DS_Store +0 -0
- psychopy/projects/pavlovia.py.orig +0 -1295
- psychopy/tests/.DS_Store +0 -0
- psychopy/tests/data/.DS_Store +0 -0
- psychopy/tests/data/TestCircle_fill_local.png +0 -0
- psychopy/tests/data/aperture1_normHexbackground_local.png +0 -0
- psychopy/tests/data/aperture1_norm_local.png +0 -0
- psychopy/tests/data/aperture2_normHexbackground_local.png +0 -0
- psychopy/tests/data/beatandrcos_height_local.png +0 -0
- psychopy/tests/data/beatandrcos_normAddBlend_local.png +0 -0
- psychopy/tests/data/beatandrcos_normHexbackground_local.png +0 -0
- psychopy/tests/data/beatandrcos_norm_local.png +0 -0
- psychopy/tests/data/beatandrcos_stencil_local.png +0 -0
- psychopy/tests/data/blend_add_height_local.png +0 -0
- psychopy/tests/data/blend_add_normAddBlend_local.png +0 -0
- psychopy/tests/data/blend_add_normHexbackground_local.png +0 -0
- psychopy/tests/data/blend_add_normNoShade_local.png +0 -0
- psychopy/tests/data/blend_add_norm_local.png +0 -0
- psychopy/tests/data/blend_add_stencil_local.png +0 -0
- psychopy/tests/data/bufferimg_gabor_height_local.png +0 -0
- psychopy/tests/data/bufferimg_gabor_normAddBlend_local.png +0 -0
- psychopy/tests/data/bufferimg_gabor_normHexbackground_local.png +0 -0
- psychopy/tests/data/bufferimg_gabor_normNoShade_local.png +0 -0
- psychopy/tests/data/bufferimg_gabor_norm_local.png +0 -0
- psychopy/tests/data/bufferimg_gabor_stencil_local.png +0 -0
- psychopy/tests/data/circleHex_height_local.png +0 -0
- psychopy/tests/data/circleHex_normAddBlend_local.png +0 -0
- psychopy/tests/data/circleHex_normHexbackground_local.png +0 -0
- psychopy/tests/data/circleHex_normNoShade_local.png +0 -0
- psychopy/tests/data/circleHex_norm_local.png +0 -0
- psychopy/tests/data/circleHex_stencil_local.png +0 -0
- psychopy/tests/data/color_comparison_local.png +0 -0
- psychopy/tests/data/correctScript/.DS_Store +0 -0
- psychopy/tests/data/dots_height_local.png +0 -0
- psychopy/tests/data/dots_normAddBlend_local.png +0 -0
- psychopy/tests/data/dots_normHexbackground_local.png +0 -0
- psychopy/tests/data/dots_normNoShade_local.png +0 -0
- psychopy/tests/data/dots_norm_local.png +0 -0
- psychopy/tests/data/dots_stencil_local.png +0 -0
- psychopy/tests/data/elarray1_height_local.png +0 -0
- psychopy/tests/data/elarray1_normAddBlend_local.png +0 -0
- psychopy/tests/data/elarray1_normHexbackground_local.png +0 -0
- psychopy/tests/data/elarray1_norm_local.png +0 -0
- psychopy/tests/data/elarray1_stencil_local.png +0 -0
- psychopy/tests/data/envelopeandrcos_height_local.png +0 -0
- psychopy/tests/data/envelopeandrcos_normAddBlend_local.png +0 -0
- psychopy/tests/data/envelopeandrcos_normHexbackground_local.png +0 -0
- psychopy/tests/data/envelopeandrcos_norm_local.png +0 -0
- psychopy/tests/data/envelopeandrcos_stencil_local.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_height_local.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_normAddBlend_local.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_normHexbackground_local.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_norm_local.png +0 -0
- psychopy/tests/data/envelopepowerandrcos_stencil_local.png +0 -0
- psychopy/tests/data/gabor1_height_local.png +0 -0
- psychopy/tests/data/gabor1_normAddBlend_local.png +0 -0
- psychopy/tests/data/gabor1_normHexbackground_local.png +0 -0
- psychopy/tests/data/gabor1_normNoShade_local.png +0 -0
- psychopy/tests/data/gabor1_norm_local.png +0 -0
- psychopy/tests/data/gabor1_stencil_local.png +0 -0
- psychopy/tests/data/greyscale_normHexbackground_local.png +0 -0
- psychopy/tests/data/imageAndGauss_height_local.png +0 -0
- psychopy/tests/data/imageAndGauss_normAddBlend_local.png +0 -0
- psychopy/tests/data/imageAndGauss_normHexbackground_local.png +0 -0
- psychopy/tests/data/imageAndGauss_normNoShade_local.png +0 -0
- psychopy/tests/data/imageAndGauss_norm_local.png +0 -0
- psychopy/tests/data/imageAndGauss_stencil_local.png +0 -0
- psychopy/tests/data/movFrame1_stencil_local.png +0 -0
- psychopy/tests/data/noiseAndRcos_height_local.png +0 -0
- psychopy/tests/data/noiseAndRcos_normAddBlend_local.png +0 -0
- psychopy/tests/data/noiseAndRcos_normHexbackground_local.png +0 -0
- psychopy/tests/data/noiseAndRcos_normNoShade_local.png +0 -0
- psychopy/tests/data/noiseAndRcos_norm_local.png +0 -0
- psychopy/tests/data/noiseAndRcos_stencil_local.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_height_local.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_normAddBlend_local.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_normHexbackground_local.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_normNoShade_local.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_norm_local.png +0 -0
- psychopy/tests/data/noiseFiltersAndRcos_stencil_local.png +0 -0
- psychopy/tests/data/numpyImage_height_local.png +0 -0
- psychopy/tests/data/numpyImage_normAddBlend_local.png +0 -0
- psychopy/tests/data/numpyImage_normHexbackground_local.png +0 -0
- psychopy/tests/data/numpyImage_normNoShade_local.png +0 -0
- psychopy/tests/data/numpyImage_norm_local.png +0 -0
- psychopy/tests/data/numpyImage_stencil_local.png +0 -0
- psychopy/tests/data/shape2_1_normAddBlend_local.png +0 -0
- psychopy/tests/data/shape2_1_normHexbackground_local.png +0 -0
- psychopy/tests/data/shape2_1_normNoShade_local.png +0 -0
- psychopy/tests/data/shape2_1_norm_local.png +0 -0
- psychopy/tests/data/shape2_1_stencil_local.png +0 -0
- psychopy/tests/data/text1_height_local.png +0 -0
- psychopy/tests/data/text1_normAddBlend_local.png +0 -0
- psychopy/tests/data/text1_normHexbackground_local.png +0 -0
- psychopy/tests/data/text1_norm_local.png +0 -0
- psychopy/tests/data/text1_stencil_local.png +0 -0
- psychopy/tests/data/wedge1_height_local.png +0 -0
- psychopy/tests/data/wedge1_normAddBlend_local.png +0 -0
- psychopy/tests/data/wedge1_normHexbackground_local.png +0 -0
- psychopy/tests/data/wedge1_normNoShade_local.png +0 -0
- psychopy/tests/data/wedge1_norm_local.png +0 -0
- psychopy/tests/data/wedge1_stencil_local.png +0 -0
- psychopy/tests/test_app/.DS_Store +0 -0
- psychopy/tests/test_app/test_builder/.DS_Store +0 -0
- psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1206.log +0 -177
- psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1206.psydat +0 -0
- psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1206.xlsx +0 -0
- psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1324.log +0 -168
- psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1324.psydat +0 -0
- psychopy/tests/test_app/test_builder/data/_2021_ 5_03_1324.xlsx +0 -0
- psychopy/tests/test_data/.DS_Store +0 -0
- psychopy/tests/test_hardware/test_CRS_BitsSharp.py +0 -67
- psychopy/tests/test_hardware/test_CRS_BitsSharp.py.orig +0 -68
- psychopy/tests/test_hardware/test_CRS_bitsShaders.py +0 -110
- psychopy/tests/test_visual/test_image.py.orig +0 -219
- psychopy/visual/basevisual.py.orig +0 -1723
- psychopy/visual/form.py.orig +0 -1181
- psychopy/visual/text.py.orig +0 -752
- psychopy/visual/textbox2/textbox2.py.orig +0 -1315
- psychopy/visual/windowwarp.py.orig +0 -463
- /psychopy/{app/cortex.log → alerts/alertsCatalogue/3600.yaml} +0 -0
- /psychopy/{app/Resources → assets}/fonts/Arvo-Bold.ttf +0 -0
- /psychopy/{app/Resources → assets}/fonts/Arvo-BoldItalic.ttf +0 -0
- /psychopy/{app/Resources → assets}/fonts/Arvo-Italic.ttf +0 -0
- /psychopy/{app/Resources → assets}/fonts/Arvo-Regular.ttf +0 -0
- /psychopy/{app/Resources → assets}/fonts/DejaVuSerif.ttf +0 -0
- /psychopy/{app/Resources → assets}/fonts/IndieFlower-Regular.ttf +0 -0
- /psychopy/{app/Resources → assets}/fonts/JetBrainsMono-Italic-VariableFont_wght.ttf +0 -0
- /psychopy/{app/Resources → assets}/fonts/JetBrainsMono-VariableFont_wght.ttf +0 -0
- /psychopy/demos/builder/Experiments/{GoNoGo → goNoGo}/readme.md +0 -0
- {psychopy-2024.2.5.dist-info → psychopy-2025.1.0.dist-info}/WHEEL +0 -0
- {psychopy-2024.2.5.dist-info → psychopy-2025.1.0.dist-info}/entry_points.txt +0 -0
- {psychopy-2024.2.5.dist-info → psychopy-2025.1.0.dist-info}/licenses/LICENSE +0 -0
psychopy/tools/gltools.py
CHANGED
|
@@ -5,15 +5,31 @@
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
# Part of the PsychoPy library
|
|
8
|
-
# Copyright (C) 2002-2018 Jonathan Peirce (C) 2019-
|
|
8
|
+
# Copyright (C) 2002-2018 Jonathan Peirce (C) 2019-2025 Open Science Tools Ltd.
|
|
9
9
|
# Distributed under the terms of the GNU General Public License (GPL).
|
|
10
10
|
|
|
11
11
|
__all__ = [
|
|
12
|
+
'VERTEX_ATTRIB_POSITION',
|
|
13
|
+
'VERTEX_ATTRIB_NORMAL',
|
|
14
|
+
'VERTEX_ATTRIB_COLOR',
|
|
15
|
+
'VERTEX_ATTRIB_SECONADRY_COLOR',
|
|
16
|
+
'VERTEX_ATTRIB_COLOR2',
|
|
17
|
+
'VERTEX_ATTRIB_FOGCOORD',
|
|
18
|
+
'VERTEX_ATTRIB_MULTITEXCOORD0',
|
|
19
|
+
'VERTEX_ATTRIB_MULTITEXCOORD1',
|
|
20
|
+
'VERTEX_ATTRIB_MULTITEXCOORD2',
|
|
21
|
+
'VERTEX_ATTRIB_MULTITEXCOORD3',
|
|
22
|
+
'VERTEX_ATTRIB_MULTITEXCOORD4',
|
|
23
|
+
'VERTEX_ATTRIB_MULTITEXCOORD5',
|
|
24
|
+
'VERTEX_ATTRIB_MULTITEXCOORD6',
|
|
25
|
+
'VERTEX_ATTRIB_MULTITEXCOORD7',
|
|
12
26
|
'createProgram',
|
|
13
27
|
'createProgramObjectARB',
|
|
14
28
|
'compileShader',
|
|
15
29
|
'compileShaderObjectARB',
|
|
16
30
|
'embedShaderSourceDefs',
|
|
31
|
+
'deleteShader',
|
|
32
|
+
'deleteProgram',
|
|
17
33
|
'deleteObject',
|
|
18
34
|
'deleteObjectARB',
|
|
19
35
|
'attachShader',
|
|
@@ -27,6 +43,9 @@ __all__ = [
|
|
|
27
43
|
'useProgram',
|
|
28
44
|
'useProgramObjectARB',
|
|
29
45
|
'getInfoLog',
|
|
46
|
+
'setUniformValue',
|
|
47
|
+
'setUniformMatrix',
|
|
48
|
+
'getUniformLocation',
|
|
30
49
|
'getUniformLocations',
|
|
31
50
|
'getAttribLocations',
|
|
32
51
|
'createQueryObject',
|
|
@@ -35,12 +54,20 @@ __all__ = [
|
|
|
35
54
|
'endQuery',
|
|
36
55
|
'getQuery',
|
|
37
56
|
'getAbsTimeGPU',
|
|
57
|
+
'FramebufferInfo',
|
|
38
58
|
'createFBO',
|
|
39
|
-
'
|
|
40
|
-
'
|
|
59
|
+
'attachImage',
|
|
60
|
+
'isFramebufferComplete',
|
|
41
61
|
'deleteFBO',
|
|
42
62
|
'blitFBO',
|
|
43
63
|
'useFBO',
|
|
64
|
+
'bindFBO',
|
|
65
|
+
'unbindFBO',
|
|
66
|
+
'deleteFBO',
|
|
67
|
+
'clearFramebuffer',
|
|
68
|
+
'setDrawBuffer',
|
|
69
|
+
'setReadBuffer',
|
|
70
|
+
'RenderbufferInfo',
|
|
44
71
|
'createRenderbuffer',
|
|
45
72
|
'deleteRenderbuffer',
|
|
46
73
|
'createTexImage2D',
|
|
@@ -50,12 +77,15 @@ __all__ = [
|
|
|
50
77
|
'createVAO',
|
|
51
78
|
'drawVAO',
|
|
52
79
|
'deleteVAO',
|
|
80
|
+
'drawClientArrays',
|
|
53
81
|
'VertexBufferInfo',
|
|
54
82
|
'createVBO',
|
|
55
83
|
'bindVBO',
|
|
56
84
|
'unbindVBO',
|
|
57
85
|
'mapBuffer',
|
|
58
86
|
'unmapBuffer',
|
|
87
|
+
'mappedBuffer',
|
|
88
|
+
'updateVBO',
|
|
59
89
|
'deleteVBO',
|
|
60
90
|
'setVertexAttribPointer',
|
|
61
91
|
'enableVertexAttribArray',
|
|
@@ -73,8 +103,17 @@ __all__ = [
|
|
|
73
103
|
'createMeshGridFromArrays',
|
|
74
104
|
'createMeshGrid',
|
|
75
105
|
'createBox',
|
|
106
|
+
'createDisc',
|
|
107
|
+
'createAnnulus',
|
|
108
|
+
'createCylinder',
|
|
76
109
|
'transformMeshPosOri',
|
|
77
110
|
'calculateVertexNormals',
|
|
111
|
+
'mergeVerticies',
|
|
112
|
+
'smoothCreases',
|
|
113
|
+
'flipFaces',
|
|
114
|
+
'interleaveAttributes',
|
|
115
|
+
'generateTexCoords',
|
|
116
|
+
'tesselate',
|
|
78
117
|
'getIntegerv',
|
|
79
118
|
'getFloatv',
|
|
80
119
|
'getString',
|
|
@@ -106,33 +145,269 @@ from psychopy.visual.helpers import setColor, findImageFile
|
|
|
106
145
|
_thisPlatform = platform.system()
|
|
107
146
|
|
|
108
147
|
# create a query counter to get absolute GPU time
|
|
109
|
-
|
|
110
148
|
QUERY_COUNTER = None # prevent genQueries from being called
|
|
111
149
|
|
|
150
|
+
# vertex attribute locations
|
|
151
|
+
VERTEX_ATTRIB_POSITION = 0 # gl_Vertex
|
|
152
|
+
VERTEX_ATTRIB_NORMAL = 2 # gl_Normal
|
|
153
|
+
VERTEX_ATTRIB_COLOR = 3 # gl_Color
|
|
154
|
+
VERTEX_ATTRIB_SECONADRY_COLOR = VERTEX_ATTRIB_COLOR2 = 4 # gl_SecondaryColor
|
|
155
|
+
VERTEX_ATTRIB_FOGCOORD = 5 # gl_FogCoord
|
|
156
|
+
VERTEX_ATTRIB_MULTITEXCOORD0 = 8 # gl_MultiTexCoord0
|
|
157
|
+
VERTEX_ATTRIB_MULTITEXCOORD1 = 9 # gl_MultiTexCoord1
|
|
158
|
+
VERTEX_ATTRIB_MULTITEXCOORD2 = 10 # gl_MultiTexCoord2
|
|
159
|
+
VERTEX_ATTRIB_MULTITEXCOORD3 = 11 # gl_MultiTexCoord3
|
|
160
|
+
VERTEX_ATTRIB_MULTITEXCOORD4 = 12 # gl_MultiTexCoord4
|
|
161
|
+
VERTEX_ATTRIB_MULTITEXCOORD5 = 13 # gl_MultiTexCoord5
|
|
162
|
+
VERTEX_ATTRIB_MULTITEXCOORD6 = 14 # gl_MultiTexCoord6
|
|
163
|
+
VERTEX_ATTRIB_MULTITEXCOORD7 = 15 # gl_MultiTexCoord7
|
|
164
|
+
|
|
165
|
+
# mapping human-readable names to the typical attribute locations
|
|
166
|
+
VERTEX_ATTRIBS = {
|
|
167
|
+
'gl_Vertex': VERTEX_ATTRIB_POSITION,
|
|
168
|
+
'gl_Normal': VERTEX_ATTRIB_NORMAL,
|
|
169
|
+
'gl_Color': VERTEX_ATTRIB_COLOR,
|
|
170
|
+
'gl_SecondaryColor': VERTEX_ATTRIB_COLOR2,
|
|
171
|
+
'gl_FogCoord': VERTEX_ATTRIB_FOGCOORD,
|
|
172
|
+
'gl_MultiTexCoord0': VERTEX_ATTRIB_MULTITEXCOORD0,
|
|
173
|
+
'gl_MultiTexCoord1': VERTEX_ATTRIB_MULTITEXCOORD1,
|
|
174
|
+
'gl_MultiTexCoord2': VERTEX_ATTRIB_MULTITEXCOORD2,
|
|
175
|
+
'gl_MultiTexCoord3': VERTEX_ATTRIB_MULTITEXCOORD3,
|
|
176
|
+
'gl_MultiTexCoord4': VERTEX_ATTRIB_MULTITEXCOORD4,
|
|
177
|
+
'gl_MultiTexCoord5': VERTEX_ATTRIB_MULTITEXCOORD5,
|
|
178
|
+
'gl_MultiTexCoord6': VERTEX_ATTRIB_MULTITEXCOORD6,
|
|
179
|
+
'gl_MultiTexCoord7': VERTEX_ATTRIB_MULTITEXCOORD7
|
|
180
|
+
}
|
|
112
181
|
|
|
113
|
-
#
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
182
|
+
# Mappings between Python/Numpy and OpenGL data types for arrays. Duplication
|
|
183
|
+
# simplifies the lookup process when used in functions. Some types are not
|
|
184
|
+
# supported by OpenGL, so they are remapped to the closest compatible type.
|
|
185
|
+
ARRAY_TYPES = {
|
|
186
|
+
'float32': (GL.GL_FLOAT, GL.GLfloat, np.float32),
|
|
187
|
+
'float': (GL.GL_FLOAT, GL.GLfloat, np.float32),
|
|
188
|
+
'double': (GL.GL_DOUBLE, GL.GLdouble, float),
|
|
189
|
+
'float64': (GL.GL_DOUBLE, GL.GLdouble, float),
|
|
190
|
+
'uint16': (GL.GL_UNSIGNED_SHORT, GL.GLushort, np.uint16),
|
|
191
|
+
'unsigned_short': (GL.GL_UNSIGNED_SHORT, GL.GLushort, np.uint16),
|
|
192
|
+
'uint32': (GL.GL_UNSIGNED_INT, GL.GLuint, np.uint32),
|
|
193
|
+
'unsigned_int': (GL.GL_UNSIGNED_INT, GL.GLuint, np.uint32),
|
|
194
|
+
'int': (GL.GL_INT, GL.GLint, np.int32), # remapped to int32 from int64
|
|
195
|
+
'int32': (GL.GL_INT, GL.GLint, np.int32),
|
|
196
|
+
'int16': (GL.GL_SHORT, GL.GLshort, np.int16),
|
|
197
|
+
'short': (GL.GL_SHORT, GL.GLshort, np.int16),
|
|
198
|
+
'uint8': (GL.GL_UNSIGNED_BYTE, GL.GLubyte, np.uint8),
|
|
199
|
+
'unsigned_byte': (GL.GL_UNSIGNED_BYTE, GL.GLubyte, np.uint8),
|
|
200
|
+
'int8': (GL.GL_BYTE, GL.GLbyte, np.int8),
|
|
201
|
+
'byte': (GL.GL_BYTE, GL.GLbyte, np.int8),
|
|
202
|
+
np.float32: (GL.GL_FLOAT, GL.GLfloat, np.float32),
|
|
203
|
+
float: (GL.GL_DOUBLE, GL.GLdouble, float),
|
|
204
|
+
np.float64: (GL.GL_DOUBLE, GL.GLdouble, np.float64),
|
|
205
|
+
np.uint16: (GL.GL_UNSIGNED_SHORT, GL.GLushort, np.uint16),
|
|
206
|
+
np.uint32: (GL.GL_UNSIGNED_INT, GL.GLuint, np.uint32),
|
|
207
|
+
int: (GL.GL_INT, GL.GLint, np.int32), # remapped to int32
|
|
208
|
+
np.int32: (GL.GL_INT, GL.GLint, np.int32),
|
|
209
|
+
np.int16: (GL.GL_SHORT, GL.GLshort, np.int16),
|
|
210
|
+
np.uint8: (GL.GL_UNSIGNED_BYTE, GL.GLubyte, np.uint8),
|
|
211
|
+
np.int8: (GL.GL_BYTE, GL.GLbyte, np.int8),
|
|
212
|
+
GL.GL_FLOAT: (GL.GL_FLOAT, GL.GLfloat, np.float32),
|
|
213
|
+
GL.GL_DOUBLE: (GL.GL_DOUBLE, GL.GLdouble, float), # python float is 64-bit
|
|
214
|
+
GL.GL_UNSIGNED_SHORT: (GL.GL_UNSIGNED_SHORT, GL.GLushort, np.uint16),
|
|
215
|
+
GL.GL_UNSIGNED_INT: (GL.GL_UNSIGNED_INT, GL.GLuint, np.uint32),
|
|
216
|
+
GL.GL_INT: (GL.GL_INT, GL.GLint, np.int32),
|
|
217
|
+
GL.GL_SHORT: (GL.GL_SHORT, GL.GLshort, np.int16),
|
|
218
|
+
GL.GL_UNSIGNED_BYTE: (GL.GL_UNSIGNED_BYTE, GL.GLubyte, np.uint8),
|
|
219
|
+
GL.GL_BYTE: (GL.GL_BYTE, GL.GLbyte, np.int8),
|
|
133
220
|
}
|
|
134
221
|
|
|
135
222
|
|
|
223
|
+
def _getGLEnum(*args):
|
|
224
|
+
"""Get the OpenGL enum value from a string or GLEnum.
|
|
225
|
+
|
|
226
|
+
Parameters
|
|
227
|
+
----------
|
|
228
|
+
args : str, GLEnum, int or None
|
|
229
|
+
OpenGL enum value(s) to retrieve. If `None`, `None` is returned.
|
|
230
|
+
|
|
231
|
+
Returns
|
|
232
|
+
-------
|
|
233
|
+
int or list
|
|
234
|
+
OpenGL enum value(s) in the order they were passed.
|
|
235
|
+
|
|
236
|
+
Examples
|
|
237
|
+
--------
|
|
238
|
+
Get the OpenGL enum value for a single string::
|
|
239
|
+
|
|
240
|
+
_getGLEnum('points') # returns GL.GL_POINTS
|
|
241
|
+
|
|
242
|
+
"""
|
|
243
|
+
if args is None:
|
|
244
|
+
return None
|
|
245
|
+
elif len(args) == 1:
|
|
246
|
+
return getattr(GL, args[0]) if isinstance(args[0], str) else args[0]
|
|
247
|
+
else:
|
|
248
|
+
return [getattr(GL, i) if isinstance(i, str) else i for i in args]
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def getIntegerv(parName):
|
|
252
|
+
"""Get a single integer parameter value, return it as a Python integer.
|
|
253
|
+
|
|
254
|
+
Parameters
|
|
255
|
+
----------
|
|
256
|
+
pName : int
|
|
257
|
+
OpenGL property enum to query (e.g. GL_MAJOR_VERSION).
|
|
258
|
+
|
|
259
|
+
Returns
|
|
260
|
+
-------
|
|
261
|
+
int
|
|
262
|
+
|
|
263
|
+
"""
|
|
264
|
+
val = GL.GLint()
|
|
265
|
+
GL.glGetIntegerv(parName, val)
|
|
266
|
+
|
|
267
|
+
return int(val.value)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def getFloatv(parName):
|
|
271
|
+
"""Get a single float parameter value, return it as a Python float.
|
|
272
|
+
|
|
273
|
+
Parameters
|
|
274
|
+
----------
|
|
275
|
+
pName : float
|
|
276
|
+
OpenGL property enum to query.
|
|
277
|
+
|
|
278
|
+
Returns
|
|
279
|
+
-------
|
|
280
|
+
float
|
|
281
|
+
|
|
282
|
+
"""
|
|
283
|
+
val = GL.GLfloat()
|
|
284
|
+
GL.glGetFloatv(parName, val)
|
|
285
|
+
|
|
286
|
+
return float(val.value)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def getString(parName):
|
|
290
|
+
"""Get a single string parameter value, return it as a Python UTF-8 string.
|
|
291
|
+
|
|
292
|
+
Parameters
|
|
293
|
+
----------
|
|
294
|
+
pName : int
|
|
295
|
+
OpenGL property enum to query (e.g. GL_VENDOR).
|
|
296
|
+
|
|
297
|
+
Returns
|
|
298
|
+
-------
|
|
299
|
+
str
|
|
300
|
+
|
|
301
|
+
"""
|
|
302
|
+
val = ctypes.cast(GL.glGetString(parName), ctypes.c_char_p).value
|
|
303
|
+
return val.decode('UTF-8')
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
class OpenGLInfo:
|
|
307
|
+
"""OpenGL information class.
|
|
308
|
+
|
|
309
|
+
This class is used to store information about the OpenGL implementation on
|
|
310
|
+
the current machine. It provides a consistent means of querying the OpenGL
|
|
311
|
+
implementation regardless of the OpenGL interface being used.
|
|
312
|
+
|
|
313
|
+
Attributes
|
|
314
|
+
----------
|
|
315
|
+
vendor : str
|
|
316
|
+
The name of the company responsible for the OpenGL implementation.
|
|
317
|
+
renderer : str
|
|
318
|
+
The name of the renderer.
|
|
319
|
+
version : str
|
|
320
|
+
The version of the OpenGL implementation.
|
|
321
|
+
majorVersion : int
|
|
322
|
+
The major version number of the OpenGL implementation.
|
|
323
|
+
minorVersion : int
|
|
324
|
+
The minor version number of the OpenGL implementation.
|
|
325
|
+
shaderVersion : str
|
|
326
|
+
The version of the GLSL implementation.
|
|
327
|
+
doubleBuffer : int
|
|
328
|
+
Indicates if the OpenGL implementation is double buffered.
|
|
329
|
+
maxTextureSize : int
|
|
330
|
+
The maximum texture size supported by the OpenGL implementation.
|
|
331
|
+
stereo : int
|
|
332
|
+
Indicates if the OpenGL implementation supports stereo rendering.
|
|
333
|
+
maxSamples : int
|
|
334
|
+
The maximum number of samples supported by the OpenGL implementation.
|
|
335
|
+
extensions : list
|
|
336
|
+
A list of supported OpenGL extensions.
|
|
337
|
+
|
|
338
|
+
"""
|
|
339
|
+
__slots__ = [
|
|
340
|
+
'vendor', 'renderer', 'version', 'shaderVersion', 'doubleBuffer',
|
|
341
|
+
'maxTextureSize', 'maxTextureUnits', 'stereo', 'maxSamples',
|
|
342
|
+
'extensions']
|
|
343
|
+
|
|
344
|
+
# singleton
|
|
345
|
+
_instance = None
|
|
346
|
+
|
|
347
|
+
def __init__(self):
|
|
348
|
+
self.vendor = getString(GL.GL_VENDOR)
|
|
349
|
+
self.renderer = getString(GL.GL_RENDERER)
|
|
350
|
+
self.version = getString(GL.GL_VERSION)
|
|
351
|
+
self.shaderVersion = getString(GL.GL_SHADING_LANGUAGE_VERSION)
|
|
352
|
+
# self.majorVersion = getIntegerv(GL.GL_MAJOR_VERSION)
|
|
353
|
+
# self.minorVersion = getIntegerv(GL.GL_MINOR_VERSION)
|
|
354
|
+
self.doubleBuffer = getIntegerv(GL.GL_DOUBLEBUFFER)
|
|
355
|
+
self.maxTextureSize = getIntegerv(GL.GL_MAX_TEXTURE_SIZE)
|
|
356
|
+
self.maxTextureUnits = getIntegerv(GL.GL_MAX_TEXTURE_IMAGE_UNITS)
|
|
357
|
+
self.stereo = getIntegerv(GL.GL_STEREO)
|
|
358
|
+
self.maxSamples = getIntegerv(GL.GL_MAX_SAMPLES)
|
|
359
|
+
self.extensions = \
|
|
360
|
+
[i for i in getString(GL.GL_EXTENSIONS).split(' ') if i not in ('', ' ')]
|
|
361
|
+
|
|
362
|
+
def __new__(cls):
|
|
363
|
+
if cls._instance is None:
|
|
364
|
+
cls._instance = super(OpenGLInfo, cls).__new__(cls)
|
|
365
|
+
|
|
366
|
+
return cls._instance
|
|
367
|
+
|
|
368
|
+
def hasExtension(self, extName):
|
|
369
|
+
"""Check if the OpenGL implementation supports an extension.
|
|
370
|
+
|
|
371
|
+
Parameters
|
|
372
|
+
----------
|
|
373
|
+
extName : str
|
|
374
|
+
The name of the extension to check for.
|
|
375
|
+
|
|
376
|
+
Returns
|
|
377
|
+
-------
|
|
378
|
+
bool
|
|
379
|
+
True if the extension is supported, False otherwise.
|
|
380
|
+
|
|
381
|
+
"""
|
|
382
|
+
return extName in self.extensions
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def getOpenGLInfo():
|
|
386
|
+
"""Get general information about the OpenGL implementation on this machine.
|
|
387
|
+
This should provide a consistent means of doing so regardless of the OpenGL
|
|
388
|
+
interface we are using.
|
|
389
|
+
|
|
390
|
+
Returns are dictionary with the following fields::
|
|
391
|
+
|
|
392
|
+
vendor, renderer, version, majorVersion, minorVersion, doubleBuffer,
|
|
393
|
+
maxTextureSize, stereo, maxSamples, extensions
|
|
394
|
+
|
|
395
|
+
Supported extensions are returned as a list in the 'extensions' field. You
|
|
396
|
+
can check if a platform supports an extension by checking the membership of
|
|
397
|
+
the extension name in that list.
|
|
398
|
+
|
|
399
|
+
Returns
|
|
400
|
+
-------
|
|
401
|
+
OpenGLInfo
|
|
402
|
+
|
|
403
|
+
"""
|
|
404
|
+
return OpenGLInfo()
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
# OpenGL limits for this system
|
|
408
|
+
MAX_TEXTURE_UNITS = getOpenGLInfo().maxTextureUnits
|
|
409
|
+
|
|
410
|
+
|
|
136
411
|
# -------------------------------
|
|
137
412
|
# Shader Program Helper Functions
|
|
138
413
|
# -------------------------------
|
|
@@ -302,7 +577,7 @@ def compileShader(shaderSrc, shaderType):
|
|
|
302
577
|
shaderId, GL.GL_COMPILE_STATUS, ctypes.byref(result))
|
|
303
578
|
|
|
304
579
|
if result.value == GL.GL_FALSE: # failed to compile for whatever reason
|
|
305
|
-
|
|
580
|
+
print(getInfoLog(shaderId) + '\n')
|
|
306
581
|
deleteObject(shaderId)
|
|
307
582
|
raise RuntimeError("Shader compilation failed, check log output.")
|
|
308
583
|
|
|
@@ -477,6 +752,32 @@ def embedShaderSourceDefs(shaderSrc, defs):
|
|
|
477
752
|
return srcOut
|
|
478
753
|
|
|
479
754
|
|
|
755
|
+
def deleteShader(shader):
|
|
756
|
+
"""Delete a shader object.
|
|
757
|
+
|
|
758
|
+
Parameters
|
|
759
|
+
----------
|
|
760
|
+
shader : int
|
|
761
|
+
Shader object handle to delete. Must have originated from a
|
|
762
|
+
:func:`compileShader` or `glCreateShader` call.
|
|
763
|
+
|
|
764
|
+
"""
|
|
765
|
+
GL.glDeleteShader(shader)
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
def deleteProgram(program):
|
|
769
|
+
"""Delete a shader program object.
|
|
770
|
+
|
|
771
|
+
Parameters
|
|
772
|
+
----------
|
|
773
|
+
program : int
|
|
774
|
+
Program object handle to delete. Must have originated from a
|
|
775
|
+
:func:`createProgram` or `glCreateProgram` call.
|
|
776
|
+
|
|
777
|
+
"""
|
|
778
|
+
GL.glDeleteProgram(program)
|
|
779
|
+
|
|
780
|
+
|
|
480
781
|
def deleteObject(obj):
|
|
481
782
|
"""Delete a shader or program object.
|
|
482
783
|
|
|
@@ -828,6 +1129,322 @@ def getInfoLog(obj):
|
|
|
828
1129
|
return logBuffer.value.decode('UTF-8')
|
|
829
1130
|
|
|
830
1131
|
|
|
1132
|
+
def getUniformLocation(program, name, error=True):
|
|
1133
|
+
"""Get the location of a uniform variable in a shader program.
|
|
1134
|
+
|
|
1135
|
+
Parameters
|
|
1136
|
+
----------
|
|
1137
|
+
program : int
|
|
1138
|
+
Handle of program to retrieve uniform location. Must have originated
|
|
1139
|
+
from a :func:`createProgram`, :func:`createProgramObjectARB`,
|
|
1140
|
+
`glCreateProgram` or `glCreateProgramObjectARB` call.
|
|
1141
|
+
name : str
|
|
1142
|
+
Name of the uniform variable to retrieve the location of.
|
|
1143
|
+
error : bool, optional
|
|
1144
|
+
Raise an error if the uniform is not found. Default is `True`.
|
|
1145
|
+
|
|
1146
|
+
Returns
|
|
1147
|
+
-------
|
|
1148
|
+
int
|
|
1149
|
+
Location of the uniform variable in the program. If the uniform is not
|
|
1150
|
+
found, `-1` is returned.
|
|
1151
|
+
|
|
1152
|
+
"""
|
|
1153
|
+
if not GL.glIsProgram(program):
|
|
1154
|
+
raise ValueError(
|
|
1155
|
+
"Specified value of `program` is not a program object handle.")
|
|
1156
|
+
|
|
1157
|
+
if type(name) is not bytes:
|
|
1158
|
+
name = bytes(name, 'utf-8')
|
|
1159
|
+
|
|
1160
|
+
loc = GL.glGetUniformLocation(program, name)
|
|
1161
|
+
|
|
1162
|
+
if error:
|
|
1163
|
+
if loc == -1:
|
|
1164
|
+
raise ValueError(
|
|
1165
|
+
"Uniform not found in program, it may not be defined or has "
|
|
1166
|
+
"been optimized out by the GLSL compiler.")
|
|
1167
|
+
raise ValueError("Uniform '{}' not found in program.".format(name))
|
|
1168
|
+
|
|
1169
|
+
return loc
|
|
1170
|
+
|
|
1171
|
+
|
|
1172
|
+
# functions for setting uniform values
|
|
1173
|
+
_unifValueFuncs = {
|
|
1174
|
+
'float': {
|
|
1175
|
+
1: GL.glUniform1f,
|
|
1176
|
+
2: GL.glUniform2f,
|
|
1177
|
+
3: GL.glUniform3f,
|
|
1178
|
+
4: GL.glUniform4f
|
|
1179
|
+
},
|
|
1180
|
+
'int': {
|
|
1181
|
+
1: GL.glUniform1i,
|
|
1182
|
+
2: GL.glUniform2i,
|
|
1183
|
+
3: GL.glUniform3i,
|
|
1184
|
+
4: GL.glUniform4i
|
|
1185
|
+
},
|
|
1186
|
+
'uint': {
|
|
1187
|
+
1: GL.glUniform1ui,
|
|
1188
|
+
2: GL.glUniform2ui,
|
|
1189
|
+
3: GL.glUniform3ui,
|
|
1190
|
+
4: GL.glUniform4ui
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
|
|
1195
|
+
def setUniformValue(program, loc, value, unifType='float',
|
|
1196
|
+
ignoreNotDefined=False):
|
|
1197
|
+
"""Set a uniform variable value in a shader program.
|
|
1198
|
+
|
|
1199
|
+
This function sets the value of a uniform variable in a shader program to
|
|
1200
|
+
the specified value. The type of the value must match the type of the
|
|
1201
|
+
uniform variable in the shader program. The location of the uniform variable
|
|
1202
|
+
can be specified as an integer or string name. If the uniform variable is
|
|
1203
|
+
not found in the program, a `ValueError` is raised.
|
|
1204
|
+
|
|
1205
|
+
Parameters
|
|
1206
|
+
----------
|
|
1207
|
+
program : int
|
|
1208
|
+
Handle of program to set the uniform value. Must have originated from a
|
|
1209
|
+
:func:`createProgram`, :func:`createProgramObjectARB`, `glCreateProgram`
|
|
1210
|
+
or `glCreateProgramObjectARB` call.
|
|
1211
|
+
loc : str or int
|
|
1212
|
+
Location of the uniform variable in the program obtained from a
|
|
1213
|
+
:func:`getUniformLocation` call. You may also specify the name of the
|
|
1214
|
+
uniform variable as a string to look-up the location before setting.
|
|
1215
|
+
value : int, float, list, tuple, numpy.ndarray
|
|
1216
|
+
Value to set the uniform to. The type of the value must match the type
|
|
1217
|
+
of the uniform variable in the shader program.
|
|
1218
|
+
unifType : str, optional
|
|
1219
|
+
Type of the uniform value elements. This is used to determine the
|
|
1220
|
+
appropritate setter function. Must be one of 'float', 'int' or 'uint'.
|
|
1221
|
+
ignoreNotDefined : bool, optional
|
|
1222
|
+
Do not raise an error if the uniform is not found in the program.
|
|
1223
|
+
Default is `False`.
|
|
1224
|
+
|
|
1225
|
+
Notes
|
|
1226
|
+
-----
|
|
1227
|
+
* If you get a "Invalid operation. The specified operation is not allowed in
|
|
1228
|
+
the current state" error, it is likely that the shader program is not
|
|
1229
|
+
currently in use or the data is not the correct type or length. Make sure
|
|
1230
|
+
the `unifType` matches the type of the uniform variable in the shader and
|
|
1231
|
+
the length of the data matches the dimensions of the uniform variable.
|
|
1232
|
+
|
|
1233
|
+
Examples
|
|
1234
|
+
--------
|
|
1235
|
+
Set a single float uniform value::
|
|
1236
|
+
|
|
1237
|
+
# define uniform in shader as `uniform float myFloat;`
|
|
1238
|
+
setUniformValue(myProgram, 'myFloat', 0.5)
|
|
1239
|
+
|
|
1240
|
+
Set a 3-element float vector uniform value::
|
|
1241
|
+
|
|
1242
|
+
# define uniform in shader as `uniform vec3 myVec3;`
|
|
1243
|
+
setUniformValue(myProgram, 'myVec3', [0.5, 0.2, 0.8])
|
|
1244
|
+
|
|
1245
|
+
Set an integer uniform value (eg. texture unit index)::
|
|
1246
|
+
|
|
1247
|
+
# define uniform in shader as `uniform sampler2d colorTexture;`
|
|
1248
|
+
setUniformValue(myProgram, 'colorTexture', 0, unifType='int')
|
|
1249
|
+
|
|
1250
|
+
"""
|
|
1251
|
+
if not GL.glIsProgram(program):
|
|
1252
|
+
raise ValueError(
|
|
1253
|
+
"Specified value of `program` is not a program object handle.")
|
|
1254
|
+
|
|
1255
|
+
if isinstance(loc, bytes):
|
|
1256
|
+
loc = GL.glGetUniformLocation(program, loc)
|
|
1257
|
+
elif isinstance(loc, str):
|
|
1258
|
+
loc = GL.glGetUniformLocation(program, bytes(loc, 'utf-8'))
|
|
1259
|
+
else:
|
|
1260
|
+
if not isinstance(loc, int):
|
|
1261
|
+
raise ValueError("Invalid type for uniform location.")
|
|
1262
|
+
|
|
1263
|
+
if loc == -1:
|
|
1264
|
+
if ignoreNotDefined:
|
|
1265
|
+
return # ignore if not found
|
|
1266
|
+
raise ValueError("Uniform '{}' not found in program.".format(loc))
|
|
1267
|
+
|
|
1268
|
+
# handle scalar values
|
|
1269
|
+
if isinstance(value, (float, int)):
|
|
1270
|
+
if unifType == 'float':
|
|
1271
|
+
GL.glUniform1f(loc, float(value))
|
|
1272
|
+
elif unifType == 'int':
|
|
1273
|
+
GL.glUniform1i(loc, int(value))
|
|
1274
|
+
elif unifType == 'uint':
|
|
1275
|
+
if value < 0:
|
|
1276
|
+
raise ValueError(
|
|
1277
|
+
"Invalid unsigned integer value, must be >= 0.")
|
|
1278
|
+
GL.glUniform1ui(loc, int(value))
|
|
1279
|
+
else:
|
|
1280
|
+
raise ValueError("Invalid uniform value type.")
|
|
1281
|
+
return
|
|
1282
|
+
|
|
1283
|
+
# passed as list, tuple or numpy array
|
|
1284
|
+
unifLen = len(value)
|
|
1285
|
+
if unifLen > 4:
|
|
1286
|
+
raise ValueError("Invalid uniform data length, must be 1-4 elements.")
|
|
1287
|
+
|
|
1288
|
+
unifSetFunc = _unifValueFuncs[unifType].get(unifLen, None)
|
|
1289
|
+
if unifSetFunc is None:
|
|
1290
|
+
raise ValueError("Invalid uniform data length.")
|
|
1291
|
+
|
|
1292
|
+
unifSetFunc(loc, *value)
|
|
1293
|
+
|
|
1294
|
+
|
|
1295
|
+
def setUniformSampler2D(program, loc, unit, ignoreNotDefined=False):
|
|
1296
|
+
"""Set a 2D texture sampler uniform value in a shader program.
|
|
1297
|
+
|
|
1298
|
+
Parameters
|
|
1299
|
+
----------
|
|
1300
|
+
program : int
|
|
1301
|
+
Handle of program to set the uniform value. Must have originated from a
|
|
1302
|
+
:func:`createProgram`, :func:`createProgramObjectARB`, `glCreateProgram`
|
|
1303
|
+
or `glCreateProgramObjectARB` call.
|
|
1304
|
+
loc : str or int
|
|
1305
|
+
Location of the uniform variable in the program obtained from a
|
|
1306
|
+
:func:`getUniformLocation` call. You may also specify the name of the
|
|
1307
|
+
uniform variable as a string to look-up the location before setting.
|
|
1308
|
+
unit : int
|
|
1309
|
+
Texture unit index to bind to the sampler uniform.
|
|
1310
|
+
ignoreNotDefined : bool, optional
|
|
1311
|
+
Do not raise an error if the uniform is not found in the program.
|
|
1312
|
+
Default is `False`.
|
|
1313
|
+
|
|
1314
|
+
Examples
|
|
1315
|
+
--------
|
|
1316
|
+
Set a 2D texture sampler uniform value::
|
|
1317
|
+
|
|
1318
|
+
# define uniform in shader as `uniform sampler2D colorTexture;`
|
|
1319
|
+
setUniformSampler2D(myProgram, 'colorTexture', 0)
|
|
1320
|
+
|
|
1321
|
+
Use the enum value for the texture unit::
|
|
1322
|
+
|
|
1323
|
+
setUniformSampler2D(myProgram, 'colorTexture', GL.GL_TEXTURE0)
|
|
1324
|
+
# or ...
|
|
1325
|
+
setUniformSampler2D(myProgram, 'colorTexture', 'GL_TEXTURE0')
|
|
1326
|
+
|
|
1327
|
+
"""
|
|
1328
|
+
if not GL.glIsProgram(program):
|
|
1329
|
+
raise ValueError(
|
|
1330
|
+
"Specified value of `program` is not a program object handle.")
|
|
1331
|
+
|
|
1332
|
+
if isinstance(loc, bytes):
|
|
1333
|
+
loc = GL.glGetUniformLocation(program, loc)
|
|
1334
|
+
elif isinstance(loc, str):
|
|
1335
|
+
loc = GL.glGetUniformLocation(program, bytes(loc, 'utf-8'))
|
|
1336
|
+
else:
|
|
1337
|
+
if not isinstance(loc, int):
|
|
1338
|
+
raise ValueError("Invalid type for uniform location.")
|
|
1339
|
+
|
|
1340
|
+
if loc == -1:
|
|
1341
|
+
if ignoreNotDefined:
|
|
1342
|
+
return # ignore if not found
|
|
1343
|
+
raise ValueError("Uniform '{}' not found in program.".format(loc))
|
|
1344
|
+
|
|
1345
|
+
if isinstance(unit, str):
|
|
1346
|
+
unit = getattr(GL, unit)
|
|
1347
|
+
|
|
1348
|
+
if unit >= GL.GL_TEXTURE0: # got enum value
|
|
1349
|
+
unit = unit - GL.GL_TEXTURE0
|
|
1350
|
+
|
|
1351
|
+
GL.glUniform1i(loc, unit)
|
|
1352
|
+
|
|
1353
|
+
|
|
1354
|
+
# lookup table for matrix uniform setter functions, the keys are hashable
|
|
1355
|
+
# tuples of the matrix dimensions
|
|
1356
|
+
_unifMatrixFuncs = {
|
|
1357
|
+
(2, 2): GL.glUniformMatrix2fv,
|
|
1358
|
+
(3, 3): GL.glUniformMatrix3fv,
|
|
1359
|
+
(4, 4): GL.glUniformMatrix4fv,
|
|
1360
|
+
(2, 3): GL.glUniformMatrix2x3fv,
|
|
1361
|
+
(3, 2): GL.glUniformMatrix3x2fv,
|
|
1362
|
+
(2, 4): GL.glUniformMatrix2x4fv,
|
|
1363
|
+
(4, 2): GL.glUniformMatrix4x2fv,
|
|
1364
|
+
(3, 4): GL.glUniformMatrix3x4fv,
|
|
1365
|
+
(4, 3): GL.glUniformMatrix4x3fv
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
|
|
1369
|
+
def setUniformMatrix(program, loc, value, transpose=False,
|
|
1370
|
+
ignoreNotDefined=False):
|
|
1371
|
+
"""Set the value of a matrix uniform variable in a shader program.
|
|
1372
|
+
|
|
1373
|
+
Arrays are converted to contiguous arrays with 'float32' data type before
|
|
1374
|
+
setting.
|
|
1375
|
+
|
|
1376
|
+
Parameters
|
|
1377
|
+
----------
|
|
1378
|
+
program : int
|
|
1379
|
+
Handle of program to set the uniform value. Must have originated from a
|
|
1380
|
+
:func:`createProgram`, :func:`createProgramObjectARB`, `glCreateProgram`
|
|
1381
|
+
or `glCreateProgramObjectARB` call.
|
|
1382
|
+
loc : str or int
|
|
1383
|
+
Location of the uniform variable in the program obtained from a
|
|
1384
|
+
:func:`getUniformLocation` call. You may also specify the name of the
|
|
1385
|
+
uniform variable as a string to look-up the location before setting.
|
|
1386
|
+
value : numpy.ndarray
|
|
1387
|
+
Matrix value to set the uniform to. The shape of the matrix must match
|
|
1388
|
+
the dimensions of the uniform variable in the shader program. The data
|
|
1389
|
+
type of the matrix must be `float32` or else it will be cast to
|
|
1390
|
+
`float32`.
|
|
1391
|
+
transpose : bool, optional
|
|
1392
|
+
Transpose the matrix before setting. Default is `False`.
|
|
1393
|
+
ignoreNotDefined : bool, optional
|
|
1394
|
+
Do not raise an error if the uniform is not found in the program.
|
|
1395
|
+
Default is `False`.
|
|
1396
|
+
|
|
1397
|
+
Notes
|
|
1398
|
+
-----
|
|
1399
|
+
* If you get a "Invalid operation. The specified operation is not allowed in
|
|
1400
|
+
the current state" error, it is likely that the shader program is not
|
|
1401
|
+
currently in use or the data is not the correct type or length. Make sure
|
|
1402
|
+
the `unifType` matches the type of the uniform variable in the shader and
|
|
1403
|
+
the length of the data matches the dimensions of the uniform variable.
|
|
1404
|
+
|
|
1405
|
+
Examples
|
|
1406
|
+
--------
|
|
1407
|
+
Set a 4x4 matrix uniform value::
|
|
1408
|
+
|
|
1409
|
+
# define uniform in shader as `uniform mat4 projectionMatrix;`
|
|
1410
|
+
setUniformMatrix(myProgram, 'projectionMatrix', np.eye(4))
|
|
1411
|
+
|
|
1412
|
+
"""
|
|
1413
|
+
if not GL.glIsProgram(program):
|
|
1414
|
+
raise ValueError(
|
|
1415
|
+
"Specified value of `program` is not a program object handle.")
|
|
1416
|
+
|
|
1417
|
+
locInput = loc # for error message
|
|
1418
|
+
if isinstance(loc, bytes):
|
|
1419
|
+
loc = GL.glGetUniformLocation(program, loc)
|
|
1420
|
+
elif isinstance(loc, str):
|
|
1421
|
+
loc = GL.glGetUniformLocation(program, bytes(loc, 'utf-8'))
|
|
1422
|
+
else:
|
|
1423
|
+
if not isinstance(loc, int):
|
|
1424
|
+
raise ValueError("Invalid type for uniform location.")
|
|
1425
|
+
|
|
1426
|
+
if loc == -1:
|
|
1427
|
+
if ignoreNotDefined:
|
|
1428
|
+
return # ignore if not found
|
|
1429
|
+
raise ValueError((
|
|
1430
|
+
"Uniform '{}' not found in program. It is either not defined "
|
|
1431
|
+
"or it is unused.").format(locInput))
|
|
1432
|
+
|
|
1433
|
+
# convert to contiguous array
|
|
1434
|
+
value = np.ascontiguousarray(value, dtype=np.float32)
|
|
1435
|
+
|
|
1436
|
+
# handle scalar values
|
|
1437
|
+
matrixFunc = _unifMatrixFuncs.get(value.shape, None)
|
|
1438
|
+
if matrixFunc is None:
|
|
1439
|
+
raise ValueError("Invalid matrix dimensions")
|
|
1440
|
+
|
|
1441
|
+
transpose = GL.GL_TRUE if transpose else GL.GL_FALSE
|
|
1442
|
+
|
|
1443
|
+
# recast as pointer
|
|
1444
|
+
matrixFunc(loc, 1, transpose, value.ctypes.data_as(
|
|
1445
|
+
ctypes.POINTER(GL.GLfloat)))
|
|
1446
|
+
|
|
1447
|
+
|
|
831
1448
|
def getUniformLocations(program, builtins=False):
|
|
832
1449
|
"""Get uniform names and locations from a given shader program object.
|
|
833
1450
|
|
|
@@ -1139,28 +1756,92 @@ Framebuffer = namedtuple(
|
|
|
1139
1756
|
)
|
|
1140
1757
|
|
|
1141
1758
|
|
|
1142
|
-
|
|
1143
|
-
"""
|
|
1759
|
+
class FramebufferInfo:
|
|
1760
|
+
"""Framebuffer object (VBO) descriptor.
|
|
1761
|
+
|
|
1762
|
+
This class only stores information about the FBO it refers to, it does not
|
|
1763
|
+
contain any actual array data associated with the FBO. Calling
|
|
1764
|
+
:func:`createFBO` returns instances of this class.
|
|
1765
|
+
|
|
1766
|
+
It is recommended to use `gltools` functions :func:`bindFBO`,
|
|
1767
|
+
:func:`unbindFBO` and :func:`deleteFBO` to manage FBOs.
|
|
1144
1768
|
|
|
1145
1769
|
Parameters
|
|
1146
1770
|
----------
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1771
|
+
name : int
|
|
1772
|
+
Handle of the FBO.
|
|
1773
|
+
target : int
|
|
1774
|
+
Target of the FBO.
|
|
1775
|
+
attachments : dict, optional
|
|
1776
|
+
Dictionary of attachments associated with the FBO. The keys are
|
|
1777
|
+
attachment points (e.g. GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT, etc.)
|
|
1778
|
+
and the values are buffer descriptors (`RenderbufferInfo` or
|
|
1779
|
+
`TexImage2DInfo`).
|
|
1780
|
+
sizeHint : tuple, optional
|
|
1781
|
+
Size hint for the FBO. This is used to specify the dimensions of logical
|
|
1782
|
+
buffers attached to the FBO. The size hint is a tuple of two integers
|
|
1783
|
+
(width, height).
|
|
1784
|
+
userData : dict, optional
|
|
1785
|
+
User-defined data associated with the FBO.
|
|
1786
|
+
|
|
1787
|
+
"""
|
|
1788
|
+
__slots__ = [
|
|
1789
|
+
'name', 'target', '_attachments', 'sizeHint', 'userData', '_lastBound']
|
|
1790
|
+
|
|
1791
|
+
def __init__(self, name, target=GL.GL_FRAMEBUFFER, attachments=None,
|
|
1792
|
+
sizeHint=None, userData=None):
|
|
1793
|
+
self.name = name
|
|
1794
|
+
self.target = target
|
|
1795
|
+
self._attachments = {} if attachments is None else attachments
|
|
1796
|
+
self.sizeHint = sizeHint
|
|
1797
|
+
self.userData = userData
|
|
1798
|
+
|
|
1799
|
+
self._lastBound = GL.GLint()
|
|
1800
|
+
|
|
1801
|
+
def __enter__(self):
|
|
1802
|
+
GL.glGetIntegerv(GL.GL_FRAMEBUFFER_BINDING, ctypes.byref(self.lastBound))
|
|
1803
|
+
GL.glBindFramebuffer(self.fbo.target, self.fbo.id)
|
|
1804
|
+
|
|
1805
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
1806
|
+
GL.glBindFramebuffer(self.fbo.target, self.lastBound.value)
|
|
1807
|
+
|
|
1808
|
+
@property
|
|
1809
|
+
def attachments(self):
|
|
1810
|
+
"""Image buffer attachments associated with the FBO (`dict`).
|
|
1811
|
+
|
|
1812
|
+
Do not modify this dictionary directly. Use :func:`attachImage` to
|
|
1813
|
+
attach images to the FBO.
|
|
1814
|
+
|
|
1815
|
+
"""
|
|
1816
|
+
return self._attachments
|
|
1817
|
+
|
|
1818
|
+
|
|
1819
|
+
def createFBO(attachments=(), sizeHint=None):
|
|
1820
|
+
"""Create a Framebuffer Object.
|
|
1821
|
+
|
|
1822
|
+
Parameters
|
|
1823
|
+
----------
|
|
1824
|
+
attachments : :obj:`list` or :obj:`tuple` of :obj:`tuple`
|
|
1825
|
+
Optional attachments to initialize the Framebuffer with. Attachments are
|
|
1826
|
+
specified as a list of tuples. Each tuple must contain an attachment
|
|
1827
|
+
point (e.g. GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT, etc.) and a
|
|
1828
|
+
buffer descriptor type (RenderbufferInfo or TexImage2DInfo). If using a
|
|
1829
|
+
combined depth/stencil format such as GL_DEPTH24_STENCIL8,
|
|
1830
|
+
GL_DEPTH_ATTACHMENT and GL_STENCIL_ATTACHMENT must be passed the same
|
|
1831
|
+
buffer. Alternatively, one can use GL_DEPTH_STENCIL_ATTACHMENT instead.
|
|
1832
|
+
If using multisample buffers, all attachment images must use the same
|
|
1833
|
+
number of samples!. As an example, one may specify attachments as
|
|
1834
|
+
'attachments=((GL.GL_COLOR_ATTACHMENT0, frameTexture),
|
|
1835
|
+
(GL.GL_DEPTH_STENCIL_ATTACHMENT, depthRenderBuffer))'.
|
|
1836
|
+
sizeHint : :obj:`tuple`, optional
|
|
1837
|
+
Size hint for the FBO. This is used to specify the dimensions of logical
|
|
1838
|
+
buffers attached to the FBO. The size hint is a tuple of two integers
|
|
1839
|
+
(width, height).
|
|
1840
|
+
|
|
1841
|
+
Returns
|
|
1842
|
+
-------
|
|
1843
|
+
FramebufferInfo
|
|
1844
|
+
Framebuffer descriptor.
|
|
1164
1845
|
|
|
1165
1846
|
Notes
|
|
1166
1847
|
-----
|
|
@@ -1177,22 +1858,18 @@ def createFBO(attachments=()):
|
|
|
1177
1858
|
|
|
1178
1859
|
Create a render target with multiple color texture attachments::
|
|
1179
1860
|
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
with useFBO(fbo):
|
|
1193
|
-
attach(GL.GL_COLOR_ATTACHMENT0, colorTex)
|
|
1194
|
-
attach(GL.GL_DEPTH_ATTACHMENT, depthRb)
|
|
1195
|
-
attach(GL.GL_STENCIL_ATTACHMENT, depthRb)
|
|
1861
|
+
fbo = gt.createFBO(sizeHint=(512, 512))
|
|
1862
|
+
gt.bindFBO(fbo)
|
|
1863
|
+
gt.attachImage( # color
|
|
1864
|
+
fbo, GL.GL_COLOR_ATTACHMENT0, gt.createTexImage2D(512, 512))
|
|
1865
|
+
gt.attachImage( # normal map
|
|
1866
|
+
fbo, GL.GL_COLOR_ATTACHMENT1, gt.createTexImage2D(512, 512))
|
|
1867
|
+
gt.attachImage( # depth/stencil
|
|
1868
|
+
fbo,
|
|
1869
|
+
GL.GL_DEPTH_STENCIL_ATTACHMENT,
|
|
1870
|
+
gt.createRenderbuffer(512, 512, GL.GL_DEPTH24_STENCIL8))
|
|
1871
|
+
print(gt.isFramebufferComplete(fbo)) # True
|
|
1872
|
+
gt.unbindFBO(None)
|
|
1196
1873
|
|
|
1197
1874
|
Examples of userData some custom function might access::
|
|
1198
1875
|
|
|
@@ -1213,26 +1890,34 @@ def createFBO(attachments=()):
|
|
|
1213
1890
|
GL.glGenFramebuffers(1, ctypes.byref(fboId))
|
|
1214
1891
|
|
|
1215
1892
|
# create a framebuffer descriptor
|
|
1216
|
-
fboDesc =
|
|
1893
|
+
fboDesc = FramebufferInfo(
|
|
1894
|
+
fboId,
|
|
1895
|
+
GL.GL_FRAMEBUFFER,
|
|
1896
|
+
attachments=None, # set later
|
|
1897
|
+
sizeHint=sizeHint,
|
|
1898
|
+
userData=dict())
|
|
1217
1899
|
|
|
1218
1900
|
# initial attachments for this framebuffer
|
|
1219
1901
|
if attachments:
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1902
|
+
bindFBO(fboDesc)
|
|
1903
|
+
for attachPoint, imageBuffer in attachments:
|
|
1904
|
+
attachImage(fboDesc, attachPoint, imageBuffer)
|
|
1905
|
+
unbindFBO()
|
|
1223
1906
|
|
|
1224
1907
|
return fboDesc
|
|
1225
1908
|
|
|
1226
1909
|
|
|
1227
|
-
def
|
|
1910
|
+
def attachImage(fbo, attachPoint, imageBuffer):
|
|
1228
1911
|
"""Attach an image to a specified attachment point on the presently bound
|
|
1229
1912
|
FBO.
|
|
1230
1913
|
|
|
1231
1914
|
Parameters
|
|
1232
1915
|
----------
|
|
1916
|
+
fbo : :obj:`FramebufferInfo`
|
|
1917
|
+
Framebuffer descriptor to attach buffer to.
|
|
1233
1918
|
attachPoint :obj:`int`
|
|
1234
1919
|
Attachment point for 'imageBuffer' (e.g. GL.GL_COLOR_ATTACHMENT0).
|
|
1235
|
-
imageBuffer : :obj:`TexImage2D` or :obj:`
|
|
1920
|
+
imageBuffer : :obj:`TexImage2D` or :obj:`RenderbufferInfo`
|
|
1236
1921
|
Framebuffer-attachable buffer descriptor.
|
|
1237
1922
|
|
|
1238
1923
|
Examples
|
|
@@ -1240,55 +1925,131 @@ def attach(attachPoint, imageBuffer):
|
|
|
1240
1925
|
Attach an image to attachment points on the framebuffer::
|
|
1241
1926
|
|
|
1242
1927
|
GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, fbo)
|
|
1243
|
-
|
|
1244
|
-
|
|
1928
|
+
attachImage(GL.GL_COLOR_ATTACHMENT0, colorTex)
|
|
1929
|
+
attachImage(GL.GL_DEPTH_STENCIL_ATTACHMENT, depthRb)
|
|
1245
1930
|
GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, lastBoundFbo)
|
|
1246
1931
|
|
|
1247
1932
|
# same as above, but using a context manager
|
|
1248
1933
|
with useFBO(fbo):
|
|
1249
|
-
|
|
1250
|
-
|
|
1934
|
+
attachImage(GL.GL_COLOR_ATTACHMENT0, colorTex)
|
|
1935
|
+
attachImage(GL.GL_DEPTH_STENCIL_ATTACHMENT, depthRb)
|
|
1251
1936
|
|
|
1252
1937
|
"""
|
|
1938
|
+
if not isinstance(fbo, FramebufferInfo):
|
|
1939
|
+
raise ValueError("Invalid type for `fbo`, must be `FramebufferInfo`.")
|
|
1940
|
+
|
|
1941
|
+
if isinstance(attachPoint, str):
|
|
1942
|
+
attachPoint = getattr(GL, attachPoint)
|
|
1943
|
+
|
|
1944
|
+
# get the last framebuffer bound
|
|
1945
|
+
lastBound = GL.GLint()
|
|
1946
|
+
GL.glGetIntegerv(GL.GL_FRAMEBUFFER_BINDING, ctypes.byref(lastBound))
|
|
1947
|
+
|
|
1948
|
+
# bind the framebuffer
|
|
1949
|
+
changeBinding = fbo.name != lastBound.value
|
|
1950
|
+
if changeBinding:
|
|
1951
|
+
bindFBO(fbo)
|
|
1952
|
+
|
|
1953
|
+
if fbo.sizeHint is not None:
|
|
1954
|
+
if (fbo.sizeHint[0] != imageBuffer.width or
|
|
1955
|
+
fbo.sizeHint[1] != imageBuffer.height):
|
|
1956
|
+
raise ValueError(
|
|
1957
|
+
"Imagebuffer dimensions do not match FBO size hint. Expected "
|
|
1958
|
+
"({}, {}), got ({}, {}).".format(
|
|
1959
|
+
fbo.sizeHint[0], fbo.sizeHint[1],
|
|
1960
|
+
imageBuffer.width, imageBuffer.height))
|
|
1961
|
+
|
|
1253
1962
|
# We should also support binding GL names specified as integers. Right now
|
|
1254
1963
|
# you need as descriptor which contains the target and name for the buffer.
|
|
1255
1964
|
#
|
|
1256
|
-
if isinstance(imageBuffer, (
|
|
1965
|
+
if isinstance(imageBuffer, (TexImage2DInfo, TexImage2DMultisampleInfo)):
|
|
1257
1966
|
GL.glFramebufferTexture2D(
|
|
1258
|
-
|
|
1967
|
+
fbo.target,
|
|
1259
1968
|
attachPoint,
|
|
1260
1969
|
imageBuffer.target,
|
|
1261
|
-
imageBuffer.
|
|
1262
|
-
elif isinstance(imageBuffer,
|
|
1970
|
+
imageBuffer.name, 0)
|
|
1971
|
+
elif isinstance(imageBuffer, RenderbufferInfo):
|
|
1263
1972
|
GL.glFramebufferRenderbuffer(
|
|
1264
|
-
|
|
1973
|
+
fbo.target,
|
|
1265
1974
|
attachPoint,
|
|
1266
1975
|
imageBuffer.target,
|
|
1267
|
-
imageBuffer.
|
|
1976
|
+
imageBuffer.name)
|
|
1977
|
+
|
|
1978
|
+
fbo.attachments[attachPoint] = imageBuffer
|
|
1979
|
+
|
|
1980
|
+
# restore the last framebuffer
|
|
1981
|
+
if changeBinding:
|
|
1982
|
+
GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, lastBound.value)
|
|
1983
|
+
|
|
1984
|
+
|
|
1985
|
+
# legacy
|
|
1986
|
+
attach = attachImage
|
|
1268
1987
|
|
|
1269
1988
|
|
|
1270
|
-
def
|
|
1989
|
+
def isFramebufferComplete(fbo):
|
|
1271
1990
|
"""Check if the currently bound framebuffer is complete.
|
|
1272
1991
|
|
|
1992
|
+
Parameters
|
|
1993
|
+
----------
|
|
1994
|
+
fbo : :obj:`FramebufferInfo`
|
|
1995
|
+
Framebuffer descriptor to check for completeness.
|
|
1996
|
+
|
|
1273
1997
|
Returns
|
|
1274
1998
|
-------
|
|
1275
1999
|
bool
|
|
1276
2000
|
`True` if the presently bound FBO is complete.
|
|
1277
2001
|
|
|
1278
2002
|
"""
|
|
1279
|
-
|
|
2003
|
+
# get the last framebuffer bound
|
|
2004
|
+
lastBound = GL.GLint()
|
|
2005
|
+
GL.glGetIntegerv(GL.GL_FRAMEBUFFER_BINDING, ctypes.byref(lastBound))
|
|
2006
|
+
|
|
2007
|
+
# bind the framebuffer
|
|
2008
|
+
changeBinding = fbo.name != lastBound.value
|
|
2009
|
+
if changeBinding:
|
|
2010
|
+
bindFBO(fbo)
|
|
2011
|
+
|
|
2012
|
+
status = GL.glCheckFramebufferStatus(fbo.target) == \
|
|
1280
2013
|
GL.GL_FRAMEBUFFER_COMPLETE
|
|
2014
|
+
|
|
2015
|
+
# restore the last framebuffer
|
|
2016
|
+
if changeBinding:
|
|
2017
|
+
GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, lastBound.value)
|
|
2018
|
+
|
|
2019
|
+
return status
|
|
1281
2020
|
|
|
1282
2021
|
|
|
1283
|
-
def deleteFBO(fbo):
|
|
2022
|
+
def deleteFBO(fbo, deleteAttachments=True):
|
|
1284
2023
|
"""Delete a framebuffer.
|
|
1285
2024
|
|
|
2025
|
+
Parameters
|
|
2026
|
+
----------
|
|
2027
|
+
fbo : :obj:`FramebufferInfo` or :obj:`int`
|
|
2028
|
+
Framebuffer descriptor or name to delete.
|
|
2029
|
+
deleteAttachments : bool, optional
|
|
2030
|
+
Delete attachments associated with the framebuffer. Default is `True`.
|
|
2031
|
+
|
|
1286
2032
|
"""
|
|
1287
|
-
|
|
1288
|
-
|
|
2033
|
+
if not isinstance(fbo, FramebufferInfo):
|
|
2034
|
+
raise ValueError("Invalid type for `fbo`, must be `FramebufferInfo`.")
|
|
2035
|
+
|
|
2036
|
+
if deleteAttachments:
|
|
2037
|
+
for attachment in fbo.attachments.values():
|
|
2038
|
+
if isinstance(attachment, RenderbufferInfo):
|
|
2039
|
+
deleteRenderbuffer(attachment)
|
|
2040
|
+
elif isinstance(attachment,
|
|
2041
|
+
(TexImage2DInfo, TexImage2DMultisampleInfo)):
|
|
2042
|
+
deleteTexture(attachment)
|
|
2043
|
+
|
|
2044
|
+
GL.glDeleteFramebuffers(1, fbo.name)
|
|
1289
2045
|
|
|
2046
|
+
# invalidate the descriptor
|
|
2047
|
+
fbo.name = 0
|
|
2048
|
+
fbo.target = 0
|
|
2049
|
+
fbo.attachments.clear()
|
|
1290
2050
|
|
|
1291
|
-
|
|
2051
|
+
|
|
2052
|
+
def blitFBO(srcRect, dstRect=None, filter=GL.GL_LINEAR, mask=GL.GL_COLOR_BUFFER_BIT):
|
|
1292
2053
|
"""Copy a block of pixels between framebuffers via blitting. Read and draw
|
|
1293
2054
|
framebuffers must be bound prior to calling this function. Beware, the
|
|
1294
2055
|
scissor box and viewport are changed when this is called to dstRect.
|
|
@@ -1302,43 +2063,181 @@ def blitFBO(srcRect, dstRect=None, filter=GL.GL_LINEAR):
|
|
|
1302
2063
|
List specifying the top-left and bottom-right coordinates of the region
|
|
1303
2064
|
to copy to (<X0>, <Y0>, <X1>, <Y1>). If None, srcRect is used for
|
|
1304
2065
|
dstRect.
|
|
1305
|
-
filter : :obj:`int`
|
|
2066
|
+
filter : :obj:`int` or :obj:`str`
|
|
1306
2067
|
Interpolation method to use if the image is stretched, default is
|
|
1307
2068
|
GL_LINEAR, but can also be GL_NEAREST.
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
-------
|
|
1311
|
-
None
|
|
2069
|
+
mask : :obj:`int` or :obj:`str`
|
|
2070
|
+
Bitmask specifying which buffers to copy. Default is GL_COLOR_BUFFER_BIT.
|
|
1312
2071
|
|
|
1313
2072
|
Examples
|
|
1314
2073
|
--------
|
|
1315
2074
|
Blitting pixels from on FBO to another::
|
|
1316
2075
|
|
|
1317
|
-
#
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
2076
|
+
# set buffers for reading and drawing
|
|
2077
|
+
gt.setReadBuffer(fbo, 'GL_COLOR_ATTACHMENT0')
|
|
2078
|
+
gt.setDrawBuffer(None, 'GL_BACK') # default back buffer for window
|
|
2079
|
+
gt.blitFBO((0 ,0, 512, 512), (0, 0, 512, 512))
|
|
2080
|
+
|
|
2081
|
+
"""
|
|
2082
|
+
if isinstance(mask, str):
|
|
2083
|
+
mask = getattr(GL, mask)
|
|
1324
2084
|
|
|
1325
|
-
|
|
1326
|
-
|
|
2085
|
+
if isinstance(filter, str):
|
|
2086
|
+
filter = getattr(GL, filter)
|
|
1327
2087
|
|
|
1328
|
-
"""
|
|
1329
2088
|
# in most cases srcRect and dstRect will be the same.
|
|
1330
2089
|
if dstRect is None:
|
|
1331
2090
|
dstRect = srcRect
|
|
1332
2091
|
|
|
1333
|
-
# GL.glViewport(*dstRect)
|
|
1334
|
-
# GL.glEnable(GL.GL_SCISSOR_TEST)
|
|
1335
|
-
# GL.glScissor(*dstRect)
|
|
1336
2092
|
GL.glBlitFramebuffer(srcRect[0], srcRect[1], srcRect[2], srcRect[3],
|
|
1337
2093
|
dstRect[0], dstRect[1], dstRect[2], dstRect[3],
|
|
1338
|
-
|
|
2094
|
+
mask, # colors only for now
|
|
1339
2095
|
filter)
|
|
2096
|
+
|
|
2097
|
+
|
|
2098
|
+
def setReadBuffer(fbo, mode):
|
|
2099
|
+
"""Set the read buffer for the framebuffer.
|
|
1340
2100
|
|
|
1341
|
-
|
|
2101
|
+
Parameters
|
|
2102
|
+
----------
|
|
2103
|
+
fbo : :obj:`FramebufferInfo` or `None`
|
|
2104
|
+
Framebuffer descriptor. If `None`, the framebuffer with target
|
|
2105
|
+
`GL_FRAMEBUFFER` is bound to `0` (default framebuffer).
|
|
2106
|
+
mode : :obj:`int`, :obj:`GLenum` or :obj:`str`
|
|
2107
|
+
Buffer mode to set as the read buffer. If `fbo` is not `None`, this
|
|
2108
|
+
value may be `GL_COLOR_ATTACHMENT(i)` where `i` is the color attachment
|
|
2109
|
+
index. If `fbo` is `None`, this value may be one of `GL_FRONT_LEFT`,
|
|
2110
|
+
`GL_FRONT_RIGHT`, `GL_BACK_LEFT`, `GL_BACK_RIGHT`, `GL_FRONT`,
|
|
2111
|
+
`GL_BACK`, `GL_LEFT`, `GL_RIGHT`.
|
|
2112
|
+
|
|
2113
|
+
Examples
|
|
2114
|
+
--------
|
|
2115
|
+
Set the read buffer for a framebuffer::
|
|
2116
|
+
|
|
2117
|
+
setReadBuffer(fbo, GL_COLOR_ATTACHMENT0)
|
|
2118
|
+
|
|
2119
|
+
"""
|
|
2120
|
+
if isinstance(mode, str):
|
|
2121
|
+
mode = getattr(GL, mode)
|
|
2122
|
+
|
|
2123
|
+
if fbo is None:
|
|
2124
|
+
GL.glBindFramebuffer(GL.GL_READ_FRAMEBUFFER, 0)
|
|
2125
|
+
GL.glReadBuffer(mode)
|
|
2126
|
+
return
|
|
2127
|
+
|
|
2128
|
+
if not isinstance(fbo, FramebufferInfo):
|
|
2129
|
+
raise ValueError(
|
|
2130
|
+
"Invalid type for `fbo`, must be `FramebufferInfo`.")
|
|
2131
|
+
|
|
2132
|
+
GL.glBindFramebuffer(GL.GL_READ_FRAMEBUFFER, fbo.name)
|
|
2133
|
+
GL.glReadBuffer(mode)
|
|
2134
|
+
|
|
2135
|
+
|
|
2136
|
+
def setDrawBuffer(fbo, mode):
|
|
2137
|
+
"""Set the draw buffer for the framebuffer.
|
|
2138
|
+
|
|
2139
|
+
Parameters
|
|
2140
|
+
----------
|
|
2141
|
+
fbo : :obj:`FramebufferInfo` or `None`
|
|
2142
|
+
Framebuffer descriptor. If `None`, the framebuffer with target
|
|
2143
|
+
`GL_FRAMEBUFFER` is bound to `0` (default framebuffer).
|
|
2144
|
+
mode : :obj:`int`, :obj:`GLenum` or :obj:`str`
|
|
2145
|
+
Buffer mode to set as the draw buffer. If `fbo` is not `None`, this
|
|
2146
|
+
value may be `GL_COLOR_ATTACHMENT(i)` where `i` is the color attachment
|
|
2147
|
+
index. If `fbo` is `None`, this value may be one of `GL_FRONT_LEFT`,
|
|
2148
|
+
`GL_FRONT_RIGHT`, `GL_BACK_LEFT`, `GL_BACK_RIGHT`, `GL_FRONT`,
|
|
2149
|
+
`GL_BACK`, `GL_LEFT`, `GL_RIGHT`.
|
|
2150
|
+
|
|
2151
|
+
"""
|
|
2152
|
+
if isinstance(mode, str):
|
|
2153
|
+
mode = getattr(GL, mode)
|
|
2154
|
+
|
|
2155
|
+
if fbo is None:
|
|
2156
|
+
GL.glBindFramebuffer(GL.GL_DRAW_FRAMEBUFFER, 0)
|
|
2157
|
+
GL.glDrawBuffer(mode)
|
|
2158
|
+
return
|
|
2159
|
+
|
|
2160
|
+
if not isinstance(fbo, FramebufferInfo):
|
|
2161
|
+
raise ValueError(
|
|
2162
|
+
"Invalid type for `fbo`, must be `FramebufferInfo`.")
|
|
2163
|
+
|
|
2164
|
+
GL.glBindFramebuffer(GL.GL_DRAW_FRAMEBUFFER, fbo.name)
|
|
2165
|
+
GL.glDrawBuffer(mode)
|
|
2166
|
+
|
|
2167
|
+
|
|
2168
|
+
def clearFramebuffer(color=(0.0, 0.0, 0.0, 1.0), depth=None, stencil=None):
|
|
2169
|
+
"""Clear the presently bound draw buffer.
|
|
2170
|
+
|
|
2171
|
+
Parameters
|
|
2172
|
+
----------
|
|
2173
|
+
color : :obj:`tuple`, optional
|
|
2174
|
+
Color to clear the framebuffer to. Default is (0.0, 0.0, 0.0, 1.0).
|
|
2175
|
+
depth : :obj:`float`, optional
|
|
2176
|
+
Depth value to clear the framebuffer to. Default is 1.0.
|
|
2177
|
+
stencil : :obj:`int`, optional
|
|
2178
|
+
Stencil value to clear the framebuffer to. Default is 0.
|
|
2179
|
+
|
|
2180
|
+
Examples
|
|
2181
|
+
--------
|
|
2182
|
+
Clear the framebuffer to green::
|
|
2183
|
+
|
|
2184
|
+
setDrawBuffer(fbo, GL_COLOR_ATTACHMENT0)
|
|
2185
|
+
clearFramebuffer(color=(0.0, 1.0, 0.0, 1.0))
|
|
2186
|
+
|
|
2187
|
+
Clear the window back buffer to black::
|
|
2188
|
+
|
|
2189
|
+
setDrawBuffer(None, GL_BACK)
|
|
2190
|
+
clearFramebuffer()
|
|
2191
|
+
|
|
2192
|
+
"""
|
|
2193
|
+
clearFlags = 0
|
|
2194
|
+
if color is not None:
|
|
2195
|
+
GL.glClearColor(*color)
|
|
2196
|
+
clearFlags |= GL.GL_COLOR_BUFFER_BIT
|
|
2197
|
+
if depth is not None:
|
|
2198
|
+
GL.glClearDepth(depth)
|
|
2199
|
+
clearFlags |= GL.GL_DEPTH_BUFFER_BIT
|
|
2200
|
+
if stencil is not None:
|
|
2201
|
+
GL.glClearStencil(stencil)
|
|
2202
|
+
clearFlags |= GL.GL_STENCIL_BUFFER_BIT
|
|
2203
|
+
|
|
2204
|
+
GL.glClear(clearFlags)
|
|
2205
|
+
|
|
2206
|
+
|
|
2207
|
+
def setViewport(x, y, width, height):
|
|
2208
|
+
"""Set the viewport for the current render target.
|
|
2209
|
+
|
|
2210
|
+
Parameters
|
|
2211
|
+
----------
|
|
2212
|
+
x : :obj:`int`
|
|
2213
|
+
X-coordinate of the lower-left corner of the viewport.
|
|
2214
|
+
y : :obj:`int`
|
|
2215
|
+
Y-coordinate of the lower-left corner of the viewport.
|
|
2216
|
+
width : :obj:`int`
|
|
2217
|
+
Width of the viewport.
|
|
2218
|
+
height : :obj:`int`
|
|
2219
|
+
Height of the viewport.
|
|
2220
|
+
|
|
2221
|
+
"""
|
|
2222
|
+
GL.glViewport(int(x), int(y), int(width), int(height))
|
|
2223
|
+
|
|
2224
|
+
|
|
2225
|
+
def setScissor(x, y, width, height):
|
|
2226
|
+
"""Set the scissor box for the current render target.
|
|
2227
|
+
|
|
2228
|
+
Parameters
|
|
2229
|
+
----------
|
|
2230
|
+
x : :obj:`int`
|
|
2231
|
+
X-coordinate of the lower-left corner of the scissor box.
|
|
2232
|
+
y : :obj:`int`
|
|
2233
|
+
Y-coordinate of the lower-left corner of the scissor box.
|
|
2234
|
+
width : :obj:`int`
|
|
2235
|
+
Width of the scissor box.
|
|
2236
|
+
height : :obj:`int`
|
|
2237
|
+
Height of the scissor box.
|
|
2238
|
+
|
|
2239
|
+
"""
|
|
2240
|
+
GL.glScissor(int(x), int(y), int(width), int(height))
|
|
1342
2241
|
|
|
1343
2242
|
|
|
1344
2243
|
@contextmanager
|
|
@@ -1380,7 +2279,7 @@ def useFBO(fbo):
|
|
|
1380
2279
|
"""
|
|
1381
2280
|
prevFBO = GL.GLint()
|
|
1382
2281
|
GL.glGetIntegerv(GL.GL_FRAMEBUFFER_BINDING, ctypes.byref(prevFBO))
|
|
1383
|
-
toBind = fbo.id if isinstance(fbo,
|
|
2282
|
+
toBind = fbo.id if isinstance(fbo, FramebufferInfo) else int(fbo)
|
|
1384
2283
|
GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, toBind)
|
|
1385
2284
|
try:
|
|
1386
2285
|
yield toBind
|
|
@@ -1388,6 +2287,80 @@ def useFBO(fbo):
|
|
|
1388
2287
|
GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, prevFBO.value)
|
|
1389
2288
|
|
|
1390
2289
|
|
|
2290
|
+
def bindFBO(fbo, target=None):
|
|
2291
|
+
"""Bind a Framebuffer Object (FBO).
|
|
2292
|
+
|
|
2293
|
+
Parameters
|
|
2294
|
+
----------
|
|
2295
|
+
fbo :obj:`FramebufferInfo`
|
|
2296
|
+
OpenGL Framebuffer Object name/ID or descriptor.
|
|
2297
|
+
target :obj:`int`, :obj:`GLenum`, :obj:`str` or `None`
|
|
2298
|
+
Target to bind the framebuffer to. If `None`, the target of the
|
|
2299
|
+
framebuffer descriptor is used. Valid targets are `GL_FRAMEBUFFER`,
|
|
2300
|
+
`GL_DRAW_FRAMEBUFFER` and `GL_READ_FRAMEBUFFER`.
|
|
2301
|
+
|
|
2302
|
+
Examples
|
|
2303
|
+
--------
|
|
2304
|
+
Bind a framebuffer::
|
|
2305
|
+
|
|
2306
|
+
bindFBO(fbo)
|
|
2307
|
+
|
|
2308
|
+
"""
|
|
2309
|
+
if target is None:
|
|
2310
|
+
target = fbo.target if isinstance(fbo, FramebufferInfo) else GL.GL_FRAMEBUFFER
|
|
2311
|
+
|
|
2312
|
+
if isinstance(target, str):
|
|
2313
|
+
target = getattr(GL, target)
|
|
2314
|
+
|
|
2315
|
+
if fbo is None:
|
|
2316
|
+
GL.glBindFramebuffer(target, 0)
|
|
2317
|
+
return
|
|
2318
|
+
|
|
2319
|
+
if not isinstance(fbo, FramebufferInfo):
|
|
2320
|
+
raise ValueError("Invalid type for `fbo`, must be `FramebufferInfo`.")
|
|
2321
|
+
|
|
2322
|
+
GL.glBindFramebuffer(fbo.target, fbo.name)
|
|
2323
|
+
|
|
2324
|
+
|
|
2325
|
+
def unbindFBO(fbo, target=None):
|
|
2326
|
+
"""Unbind a Framebuffer Object (FBO).
|
|
2327
|
+
|
|
2328
|
+
While the last FBO does not have to be unbound explicitly, passing the
|
|
2329
|
+
descriptor of the FBO to this function will ensure the target is unbound.
|
|
2330
|
+
|
|
2331
|
+
Parameters
|
|
2332
|
+
----------
|
|
2333
|
+
fbo :obj:`FramebufferInfo` or `None`
|
|
2334
|
+
OpenGL Framebuffer Object name/ID or descriptor. If `None`, the
|
|
2335
|
+
frambuffer with target `GL_FRAMEBUFFER` is bound to `0`. Values are
|
|
2336
|
+
`GL_FRAMEBUFFER`, `GL_DRAW_FRAMEBUFFER` and `GL_READ_FRAMEBUFFER`.
|
|
2337
|
+
target :obj:`int`, :obj:`GLenum`, :obj:`str` or `None`
|
|
2338
|
+
Target to unbind the framebuffer from. If `None`, the target of the
|
|
2339
|
+
framebuffer descriptor is used.
|
|
2340
|
+
|
|
2341
|
+
Examples
|
|
2342
|
+
--------
|
|
2343
|
+
Unbind a framebuffer::
|
|
2344
|
+
|
|
2345
|
+
unbindFBO(fbo)
|
|
2346
|
+
|
|
2347
|
+
"""
|
|
2348
|
+
if target is None:
|
|
2349
|
+
target = fbo.target if isinstance(fbo, FramebufferInfo) else GL.GL_FRAMEBUFFER
|
|
2350
|
+
|
|
2351
|
+
if isinstance(target, str):
|
|
2352
|
+
target = getattr(GL, target)
|
|
2353
|
+
|
|
2354
|
+
if fbo is None:
|
|
2355
|
+
GL.glBindFramebuffer(target, 0)
|
|
2356
|
+
return
|
|
2357
|
+
|
|
2358
|
+
if not isinstance(fbo, FramebufferInfo):
|
|
2359
|
+
raise ValueError("Invalid type for `fbo`, must be `FramebufferInfo`.")
|
|
2360
|
+
|
|
2361
|
+
GL.glBindFramebuffer(fbo.target, 0)
|
|
2362
|
+
|
|
2363
|
+
|
|
1391
2364
|
# ------------------------------
|
|
1392
2365
|
# Renderbuffer Objects Functions
|
|
1393
2366
|
# ------------------------------
|
|
@@ -1410,6 +2383,52 @@ Renderbuffer = namedtuple(
|
|
|
1410
2383
|
)
|
|
1411
2384
|
|
|
1412
2385
|
|
|
2386
|
+
class RenderbufferInfo:
|
|
2387
|
+
"""Renderbuffer object descriptor.
|
|
2388
|
+
|
|
2389
|
+
This class only stores information about the Renderbuffer it refers to, it
|
|
2390
|
+
does not contain any actual array data associated with the Renderbuffer.
|
|
2391
|
+
Calling :func:`createRenderbuffer` returns instances of this class.
|
|
2392
|
+
|
|
2393
|
+
It is recommended to use `gltools` functions :func:`bindRenderbuffer`,
|
|
2394
|
+
:func:`unbindRenderbuffer` and :func:`deleteRenderbuffer` to manage
|
|
2395
|
+
Renderbuffers.
|
|
2396
|
+
|
|
2397
|
+
Parameters
|
|
2398
|
+
----------
|
|
2399
|
+
name : int
|
|
2400
|
+
Handle of the Renderbuffer.
|
|
2401
|
+
target : int
|
|
2402
|
+
Target of the Renderbuffer.
|
|
2403
|
+
width : int
|
|
2404
|
+
Width of the Renderbuffer.
|
|
2405
|
+
height : int
|
|
2406
|
+
Height of the Renderbuffer.
|
|
2407
|
+
internalFormat : int
|
|
2408
|
+
Internal format of the Renderbuffer.
|
|
2409
|
+
samples : int
|
|
2410
|
+
Number of samples for multi-sampling.
|
|
2411
|
+
multiSample : bool
|
|
2412
|
+
True if the Renderbuffer is multi-sampled.
|
|
2413
|
+
userData : dict, optional
|
|
2414
|
+
User-defined data associated with the Renderbuffer.
|
|
2415
|
+
|
|
2416
|
+
"""
|
|
2417
|
+
__slots__ = ['name', 'target', 'width', 'height', 'internalFormat',
|
|
2418
|
+
'samples', 'multiSample', 'userData']
|
|
2419
|
+
|
|
2420
|
+
def __init__(self, name, target, width, height, internalFormat, samples,
|
|
2421
|
+
multiSample, userData=None):
|
|
2422
|
+
self.name = name
|
|
2423
|
+
self.target = target
|
|
2424
|
+
self.width = width
|
|
2425
|
+
self.height = height
|
|
2426
|
+
self.internalFormat = internalFormat
|
|
2427
|
+
self.samples = samples
|
|
2428
|
+
self.multiSample = multiSample
|
|
2429
|
+
self.userData = userData
|
|
2430
|
+
|
|
2431
|
+
|
|
1413
2432
|
def createRenderbuffer(width, height, internalFormat=GL.GL_RGBA8, samples=1):
|
|
1414
2433
|
"""Create a new Renderbuffer Object with a specified internal format. A
|
|
1415
2434
|
multisample storage buffer is created if samples > 1.
|
|
@@ -1428,7 +2447,7 @@ def createRenderbuffer(width, height, internalFormat=GL.GL_RGBA8, samples=1):
|
|
|
1428
2447
|
Format for renderbuffer data (e.g. GL_RGBA8, GL_DEPTH24_STENCIL8).
|
|
1429
2448
|
samples : :obj:`int`
|
|
1430
2449
|
Number of samples for multi-sampling, should be >1 and power-of-two.
|
|
1431
|
-
|
|
2450
|
+
If samples == 1, a single sample buffer is created.
|
|
1432
2451
|
|
|
1433
2452
|
Returns
|
|
1434
2453
|
-------
|
|
@@ -1441,6 +2460,8 @@ def createRenderbuffer(width, height, internalFormat=GL.GL_RGBA8, samples=1):
|
|
|
1441
2460
|
be used to store arbitrary data associated with the buffer.
|
|
1442
2461
|
|
|
1443
2462
|
"""
|
|
2463
|
+
internalFormat = _getGLEnum(internalFormat)
|
|
2464
|
+
|
|
1444
2465
|
width = int(width)
|
|
1445
2466
|
height = int(height)
|
|
1446
2467
|
|
|
@@ -1476,14 +2497,14 @@ def createRenderbuffer(width, height, internalFormat=GL.GL_RGBA8, samples=1):
|
|
|
1476
2497
|
# done, unbind it
|
|
1477
2498
|
GL.glBindRenderbuffer(GL.GL_RENDERBUFFER, 0)
|
|
1478
2499
|
|
|
1479
|
-
return
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
2500
|
+
return RenderbufferInfo(rbId,
|
|
2501
|
+
GL.GL_RENDERBUFFER,
|
|
2502
|
+
width,
|
|
2503
|
+
height,
|
|
2504
|
+
internalFormat,
|
|
2505
|
+
samples,
|
|
2506
|
+
samples > 1,
|
|
2507
|
+
dict())
|
|
1487
2508
|
|
|
1488
2509
|
|
|
1489
2510
|
def deleteRenderbuffer(renderBuffer):
|
|
@@ -1491,7 +2512,10 @@ def deleteRenderbuffer(renderBuffer):
|
|
|
1491
2512
|
renderbuffer's ID.
|
|
1492
2513
|
|
|
1493
2514
|
"""
|
|
1494
|
-
GL.glDeleteRenderbuffers(1, renderBuffer.
|
|
2515
|
+
GL.glDeleteRenderbuffers(1, renderBuffer.name)
|
|
2516
|
+
|
|
2517
|
+
# invalidate the descriptor
|
|
2518
|
+
renderBuffer.name = 0
|
|
1495
2519
|
|
|
1496
2520
|
|
|
1497
2521
|
# -----------------
|
|
@@ -1502,7 +2526,7 @@ def deleteRenderbuffer(renderBuffer):
|
|
|
1502
2526
|
# use them with functions that require that type as input.
|
|
1503
2527
|
#
|
|
1504
2528
|
|
|
1505
|
-
class
|
|
2529
|
+
class TexImage2DInfo:
|
|
1506
2530
|
"""Descriptor for a 2D texture.
|
|
1507
2531
|
|
|
1508
2532
|
This class is used for bookkeeping 2D textures stored in video memory.
|
|
@@ -1656,8 +2680,8 @@ def createTexImage2D(width, height, target=GL.GL_TEXTURE_2D, level=0,
|
|
|
1656
2680
|
|
|
1657
2681
|
Returns
|
|
1658
2682
|
-------
|
|
1659
|
-
|
|
1660
|
-
A `
|
|
2683
|
+
TexImage2DInfo
|
|
2684
|
+
A `TexImage2DInfo` descriptor.
|
|
1661
2685
|
|
|
1662
2686
|
Notes
|
|
1663
2687
|
-----
|
|
@@ -1699,6 +2723,9 @@ def createTexImage2D(width, height, target=GL.GL_TEXTURE_2D, level=0,
|
|
|
1699
2723
|
GL.glBindTexture(GL.GL_TEXTURE_2D, textureDesc.id)
|
|
1700
2724
|
|
|
1701
2725
|
"""
|
|
2726
|
+
target, internalFormat, pixelFormat, dataType = _getGLEnum(
|
|
2727
|
+
target, internalFormat, pixelFormat, dataType)
|
|
2728
|
+
|
|
1702
2729
|
width = int(width)
|
|
1703
2730
|
height = int(height)
|
|
1704
2731
|
|
|
@@ -1715,11 +2742,12 @@ def createTexImage2D(width, height, target=GL.GL_TEXTURE_2D, level=0,
|
|
|
1715
2742
|
texId = GL.GLuint()
|
|
1716
2743
|
GL.glGenTextures(1, ctypes.byref(texId))
|
|
1717
2744
|
|
|
1718
|
-
GL.glBindTexture(target, texId)
|
|
2745
|
+
GL.glBindTexture(target, texId.value)
|
|
1719
2746
|
GL.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, int(unpackAlignment))
|
|
1720
2747
|
GL.glTexImage2D(target, level, internalFormat,
|
|
1721
2748
|
width, height, 0,
|
|
1722
2749
|
pixelFormat, dataType, data)
|
|
2750
|
+
GL.glGenerateMipmap(target)
|
|
1723
2751
|
|
|
1724
2752
|
# apply texture parameters
|
|
1725
2753
|
if texParams is not None:
|
|
@@ -1727,16 +2755,17 @@ def createTexImage2D(width, height, target=GL.GL_TEXTURE_2D, level=0,
|
|
|
1727
2755
|
GL.glTexParameteri(target, pname, param)
|
|
1728
2756
|
|
|
1729
2757
|
# new texture descriptor
|
|
1730
|
-
tex =
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
2758
|
+
tex = TexImage2DInfo(
|
|
2759
|
+
name=texId.value,
|
|
2760
|
+
target=target,
|
|
2761
|
+
width=width,
|
|
2762
|
+
height=height,
|
|
2763
|
+
internalFormat=internalFormat,
|
|
2764
|
+
level=level,
|
|
2765
|
+
pixelFormat=pixelFormat,
|
|
2766
|
+
dataType=dataType,
|
|
2767
|
+
unpackAlignment=unpackAlignment,
|
|
2768
|
+
texParams=texParams)
|
|
1740
2769
|
|
|
1741
2770
|
tex._texParamsNeedUpdate = False
|
|
1742
2771
|
|
|
@@ -1762,7 +2791,7 @@ def createTexImage2dFromFile(imgFile, transpose=True):
|
|
|
1762
2791
|
|
|
1763
2792
|
Returns
|
|
1764
2793
|
-------
|
|
1765
|
-
|
|
2794
|
+
TexImage2DInfo
|
|
1766
2795
|
Texture descriptor.
|
|
1767
2796
|
|
|
1768
2797
|
"""
|
|
@@ -1978,62 +3007,62 @@ def createCubeMap(width, height, target=GL.GL_TEXTURE_CUBE_MAP, level=0,
|
|
|
1978
3007
|
return tex
|
|
1979
3008
|
|
|
1980
3009
|
|
|
1981
|
-
def bindTexture(texture, unit=None, enable=True):
|
|
1982
|
-
|
|
3010
|
+
# def bindTexture(texture, unit=None, enable=True):
|
|
3011
|
+
# """Bind a texture.
|
|
1983
3012
|
|
|
1984
|
-
|
|
1985
|
-
|
|
3013
|
+
# Function binds `texture` to `unit` (if specified). If `unit` is `None`, the
|
|
3014
|
+
# texture will be bound but not assigned to a texture unit.
|
|
1986
3015
|
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
3016
|
+
# Parameters
|
|
3017
|
+
# ----------
|
|
3018
|
+
# texture : TexImage2DInfo
|
|
3019
|
+
# Texture descriptor to bind.
|
|
3020
|
+
# unit : int, optional
|
|
3021
|
+
# Texture unit to associated the texture with.
|
|
3022
|
+
# enable : bool
|
|
3023
|
+
# Enable textures upon binding.
|
|
1995
3024
|
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
3025
|
+
# """
|
|
3026
|
+
# if not texture._isBound:
|
|
3027
|
+
# if enable:
|
|
3028
|
+
# GL.glEnable(texture.target)
|
|
2000
3029
|
|
|
2001
|
-
|
|
2002
|
-
|
|
3030
|
+
# GL.glBindTexture(texture.target, texture.name)
|
|
3031
|
+
# texture._isBound = True
|
|
2003
3032
|
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
3033
|
+
# if unit is not None:
|
|
3034
|
+
# texture._unit = unit
|
|
3035
|
+
# GL.glActiveTexture(GL.GL_TEXTURE0 + unit)
|
|
2007
3036
|
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
3037
|
+
# # update texture parameters if they have been accessed (changed?)
|
|
3038
|
+
# if texture._texParamsNeedUpdate:
|
|
3039
|
+
# for pname, param in texture._texParams.items():
|
|
3040
|
+
# GL.glTexParameteri(texture.target, pname, param)
|
|
3041
|
+
# texture._texParamsNeedUpdate = False
|
|
2013
3042
|
|
|
2014
3043
|
|
|
2015
|
-
def unbindTexture(texture=None):
|
|
2016
|
-
|
|
3044
|
+
# def unbindTexture(texture=None):
|
|
3045
|
+
# """Unbind a texture.
|
|
2017
3046
|
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
3047
|
+
# Parameters
|
|
3048
|
+
# ----------
|
|
3049
|
+
# texture : TexImage2DInfo
|
|
3050
|
+
# Texture descriptor to unbind.
|
|
2022
3051
|
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
3052
|
+
# """
|
|
3053
|
+
# if texture._isBound:
|
|
3054
|
+
# # set the texture unit
|
|
3055
|
+
# if texture._unit is not None:
|
|
3056
|
+
# GL.glActiveTexture(GL.GL_TEXTURE0 + texture._unit)
|
|
3057
|
+
# texture._unit = None
|
|
2029
3058
|
|
|
2030
|
-
|
|
2031
|
-
|
|
3059
|
+
# GL.glBindTexture(texture.target, 0)
|
|
3060
|
+
# texture._isBound = False
|
|
2032
3061
|
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
3062
|
+
# GL.glDisable(texture.target)
|
|
3063
|
+
# else:
|
|
3064
|
+
# raise RuntimeError('Trying to unbind a texture that was not previously'
|
|
3065
|
+
# 'bound.')
|
|
2037
3066
|
|
|
2038
3067
|
|
|
2039
3068
|
# Descriptor for 2D mutlisampled texture
|
|
@@ -2049,6 +3078,72 @@ TexImage2DMultisample = namedtuple(
|
|
|
2049
3078
|
'userData'])
|
|
2050
3079
|
|
|
2051
3080
|
|
|
3081
|
+
class TexImage2DMultisampleInfo:
|
|
3082
|
+
"""Descriptor for a 2D multisampled texture.
|
|
3083
|
+
|
|
3084
|
+
This class is used for bookkeeping 2D multisampled textures stored in video
|
|
3085
|
+
memory. Information about the texture (eg. `width` and `height`) is
|
|
3086
|
+
available via class attributes. Attributes should never be modified
|
|
3087
|
+
directly.
|
|
3088
|
+
|
|
3089
|
+
"""
|
|
3090
|
+
__slots__ = ['width',
|
|
3091
|
+
'height',
|
|
3092
|
+
'target',
|
|
3093
|
+
'_name',
|
|
3094
|
+
'internalFormat',
|
|
3095
|
+
'samples',
|
|
3096
|
+
'multisample',
|
|
3097
|
+
'userData']
|
|
3098
|
+
|
|
3099
|
+
def __init__(self,
|
|
3100
|
+
name=0,
|
|
3101
|
+
target=GL.GL_TEXTURE_2D_MULTISAMPLE,
|
|
3102
|
+
width=64,
|
|
3103
|
+
height=64,
|
|
3104
|
+
internalFormat=GL.GL_RGBA8,
|
|
3105
|
+
samples=1,
|
|
3106
|
+
multisample=True,
|
|
3107
|
+
userData=None):
|
|
3108
|
+
"""
|
|
3109
|
+
Parameters
|
|
3110
|
+
----------
|
|
3111
|
+
name : `int` or `GLuint`
|
|
3112
|
+
OpenGL handle for texture. Is `0` if uninitialized.
|
|
3113
|
+
target : :obj:`int`
|
|
3114
|
+
The target texture should only be `GL_TEXTURE_2D_MULTISAMPLE`.
|
|
3115
|
+
width : :obj:`int`
|
|
3116
|
+
Texture width in pixels.
|
|
3117
|
+
height : :obj:`int`
|
|
3118
|
+
Texture height in pixels.
|
|
3119
|
+
internalFormat : :obj:`int`
|
|
3120
|
+
Internal format for texture data (e.g. GL_RGBA8, GL_R11F_G11F_B10F).
|
|
3121
|
+
samples : :obj:`int`
|
|
3122
|
+
Number of samples for multi-sampling, should be >1 and power-of-two.
|
|
3123
|
+
Work with one sample, but will raise a warning.
|
|
3124
|
+
multisample : :obj:`bool`
|
|
3125
|
+
True if the texture is multi-sampled.
|
|
3126
|
+
userData : :obj:`dict`
|
|
3127
|
+
User-defined data associated with the texture.
|
|
3128
|
+
|
|
3129
|
+
"""
|
|
3130
|
+
self.name = name
|
|
3131
|
+
self.width = width
|
|
3132
|
+
self.height = height
|
|
3133
|
+
self.target = target
|
|
3134
|
+
self.internalFormat = internalFormat
|
|
3135
|
+
self.samples = samples
|
|
3136
|
+
self.multisample = multisample
|
|
3137
|
+
|
|
3138
|
+
if userData is None:
|
|
3139
|
+
self.userData = {}
|
|
3140
|
+
elif isinstance(userData, dict):
|
|
3141
|
+
self.userData = userData
|
|
3142
|
+
else:
|
|
3143
|
+
raise TypeError('Invalid type for `userData`.')
|
|
3144
|
+
|
|
3145
|
+
|
|
3146
|
+
|
|
2052
3147
|
def createTexImage2DMultisample(width, height,
|
|
2053
3148
|
target=GL.GL_TEXTURE_2D_MULTISAMPLE, samples=1,
|
|
2054
3149
|
internalFormat=GL.GL_RGBA8, texParameters=()):
|
|
@@ -2075,8 +3170,8 @@ def createTexImage2DMultisample(width, height,
|
|
|
2075
3170
|
|
|
2076
3171
|
Returns
|
|
2077
3172
|
-------
|
|
2078
|
-
|
|
2079
|
-
A
|
|
3173
|
+
TexImage2DMultisampleInfo
|
|
3174
|
+
A TexImage2DMultisampleInfo descriptor.
|
|
2080
3175
|
|
|
2081
3176
|
"""
|
|
2082
3177
|
width = int(width)
|
|
@@ -2107,14 +3202,15 @@ def createTexImage2DMultisample(width, height,
|
|
|
2107
3202
|
|
|
2108
3203
|
GL.glBindTexture(target, 0)
|
|
2109
3204
|
|
|
2110
|
-
return
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
3205
|
+
return TexImage2DMultisampleInfo(
|
|
3206
|
+
colorTexId,
|
|
3207
|
+
target,
|
|
3208
|
+
width,
|
|
3209
|
+
height,
|
|
3210
|
+
internalFormat,
|
|
3211
|
+
samples,
|
|
3212
|
+
True,
|
|
3213
|
+
dict())
|
|
2118
3214
|
|
|
2119
3215
|
|
|
2120
3216
|
def deleteTexture(texture):
|
|
@@ -2300,6 +3396,11 @@ def createVAO(attribBuffers, indexBuffer=None, attribDivisors=None, legacy=False
|
|
|
2300
3396
|
activeAttribs = {}
|
|
2301
3397
|
bufferIndices = []
|
|
2302
3398
|
for i, buffer in attribBuffers.items():
|
|
3399
|
+
if isinstance(i, str):
|
|
3400
|
+
i = VERTEX_ATTRIBS.get(i, None)
|
|
3401
|
+
if i is None:
|
|
3402
|
+
raise ValueError('Invalid attribute name specified.')
|
|
3403
|
+
|
|
2303
3404
|
if isinstance(buffer, (list, tuple,)):
|
|
2304
3405
|
if len(buffer) == 1:
|
|
2305
3406
|
buffer = buffer[0] # size 1 tuple or list eg. (buffer,)
|
|
@@ -2370,6 +3471,13 @@ def createVAO(attribBuffers, indexBuffer=None, attribDivisors=None, legacy=False
|
|
|
2370
3471
|
legacy)
|
|
2371
3472
|
|
|
2372
3473
|
|
|
3474
|
+
# use the appropriate VAO binding function for the platform
|
|
3475
|
+
if _thisPlatform != 'Darwin':
|
|
3476
|
+
_glBindVertexArray = GL.glBindVertexArray
|
|
3477
|
+
else:
|
|
3478
|
+
_glBindVertexArray = GL.glBindVertexArrayAPPLE
|
|
3479
|
+
|
|
3480
|
+
|
|
2373
3481
|
def drawVAO(vao, mode=GL.GL_TRIANGLES, start=0, count=None, instanceCount=None,
|
|
2374
3482
|
flush=False):
|
|
2375
3483
|
"""Draw a vertex array object. Uses `glDrawArrays` or `glDrawElements` if
|
|
@@ -2381,7 +3489,9 @@ def drawVAO(vao, mode=GL.GL_TRIANGLES, start=0, count=None, instanceCount=None,
|
|
|
2381
3489
|
vao : VertexArrayObject
|
|
2382
3490
|
Vertex Array Object (VAO) to draw.
|
|
2383
3491
|
mode : int, optional
|
|
2384
|
-
Drawing mode to use (e.g. GL_TRIANGLES, GL_QUADS, GL_POINTS, etc.)
|
|
3492
|
+
Drawing mode to use (e.g. GL_TRIANGLES, GL_QUADS, GL_POINTS, etc.) for
|
|
3493
|
+
rasterization. Default is `GL_TRIANGLES`. Strings can be used for
|
|
3494
|
+
convenience (e.g. 'GL_TRIANGLES', 'GL_QUADS', 'GL_POINTS').
|
|
2385
3495
|
start : int, optional
|
|
2386
3496
|
Starting index for array elements. Default is `0` which is the beginning
|
|
2387
3497
|
of the array.
|
|
@@ -2402,11 +3512,13 @@ def drawVAO(vao, mode=GL.GL_TRIANGLES, start=0, count=None, instanceCount=None,
|
|
|
2402
3512
|
drawVAO(vaoDesc, GL.GL_TRIANGLES)
|
|
2403
3513
|
|
|
2404
3514
|
"""
|
|
3515
|
+
if isinstance(mode, str):
|
|
3516
|
+
mode = getattr(GL, mode, None)
|
|
3517
|
+
if mode is None:
|
|
3518
|
+
raise ValueError('Invalid drawing mode specified.')
|
|
3519
|
+
|
|
2405
3520
|
# draw the array
|
|
2406
|
-
|
|
2407
|
-
GL.glBindVertexArray(vao.name)
|
|
2408
|
-
else:
|
|
2409
|
-
GL.glBindVertexArrayAPPLE(vao.name)
|
|
3521
|
+
_glBindVertexArray(vao.name)
|
|
2410
3522
|
|
|
2411
3523
|
if count is None:
|
|
2412
3524
|
count = vao.count
|
|
@@ -2432,10 +3544,7 @@ def drawVAO(vao, mode=GL.GL_TRIANGLES, start=0, count=None, instanceCount=None,
|
|
|
2432
3544
|
GL.glFlush()
|
|
2433
3545
|
|
|
2434
3546
|
# reset
|
|
2435
|
-
|
|
2436
|
-
GL.glBindVertexArray(0)
|
|
2437
|
-
else:
|
|
2438
|
-
GL.glBindVertexArrayAPPLE(0)
|
|
3547
|
+
_glBindVertexArray(0)
|
|
2439
3548
|
|
|
2440
3549
|
|
|
2441
3550
|
def deleteVAO(vao):
|
|
@@ -2449,25 +3558,185 @@ def deleteVAO(vao):
|
|
|
2449
3558
|
reset.
|
|
2450
3559
|
|
|
2451
3560
|
"""
|
|
2452
|
-
if isinstance(vao, VertexArrayInfo):
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
3561
|
+
if not isinstance(vao, VertexArrayInfo):
|
|
3562
|
+
raise TypeError('Invalid type for `vao`, must be `VertexArrayInfo`.')
|
|
3563
|
+
|
|
3564
|
+
if vao.name:
|
|
3565
|
+
GL.glDeleteVertexArrays(1, GL.GLuint(vao.name))
|
|
3566
|
+
vao.name = 0
|
|
3567
|
+
vao.isLegacy = False
|
|
3568
|
+
vao.indexBuffer = None
|
|
3569
|
+
vao.activeAttribs = {}
|
|
3570
|
+
vao.count = 0
|
|
2461
3571
|
|
|
2462
|
-
# ---------------------------
|
|
2463
|
-
# Vertex Buffer Objects (VBO)
|
|
2464
|
-
#
|
|
2465
3572
|
|
|
3573
|
+
def drawClientArrays(attribBuffers, mode=GL.GL_TRIANGLES, indexBuffer=None):
|
|
3574
|
+
"""Draw vertex arrays using client-side arrays.
|
|
3575
|
+
|
|
3576
|
+
This is a convenience function for drawing vertex arrays by passing
|
|
3577
|
+
client-side (resident in CPU memory space) arrays to the driver directly.
|
|
3578
|
+
Performance may be suboptimal compared to using VBOs and VAOs, as data
|
|
3579
|
+
must be transferred to the GPU each time this function is called. This may
|
|
3580
|
+
also stall the rendering pipeline, preventing the GPU from processing
|
|
3581
|
+
commands in parallel.
|
|
2466
3582
|
|
|
2467
|
-
|
|
2468
|
-
|
|
3583
|
+
For best performance, use interleaved arrays for vertex attributes and an
|
|
3584
|
+
index buffer. Using an index buffer is optional, but it greatly reduces
|
|
3585
|
+
the amount of data that must be transferred to the GPU.
|
|
2469
3586
|
|
|
2470
|
-
|
|
3587
|
+
Parameters
|
|
3588
|
+
----------
|
|
3589
|
+
attribBuffers : dict
|
|
3590
|
+
Attributes and associated buffers to draw. Keys are vertex attribute
|
|
3591
|
+
pointer indices, values are buffer arrays. Values can be `tuples` where
|
|
3592
|
+
the first value is the buffer array, the second is the number of
|
|
3593
|
+
attribute components (`int`, either 2, 3 or 4), the third is the offset
|
|
3594
|
+
(`int`), and the last is whether to normalize the array (`bool`). Buffer
|
|
3595
|
+
arrays may be `numpy` arrays or lists. If lists, the arrays will be
|
|
3596
|
+
converted to `numpy` arrays with `float` (`GL_DOUBLE`) data type.
|
|
3597
|
+
mode : int
|
|
3598
|
+
Drawing mode to use (e.g. GL_TRIANGLES, GL_QUADS, GL_POINTS, etc.) for
|
|
3599
|
+
rasterization.
|
|
3600
|
+
indexBuffer : VertexBufferInfo
|
|
3601
|
+
Optional index buffer. If `None`, `glDrawArrays` is used, else
|
|
3602
|
+
`glDrawElements` is used. The index buffer must be a 2D array that is
|
|
3603
|
+
appropriately sized for the drawing mode. For example, if `mode` is
|
|
3604
|
+
`GL_TRIANGLES`, the index buffer must be a 2D array with 3 columns. The
|
|
3605
|
+
index array is always assumed to be of type `GL_UNSIGNED_INT`, it will
|
|
3606
|
+
be converted if necessary.
|
|
3607
|
+
|
|
3608
|
+
Examples
|
|
3609
|
+
--------
|
|
3610
|
+
Drawing vertex arrays using client-side arrays::
|
|
3611
|
+
|
|
3612
|
+
attribArrays = {
|
|
3613
|
+
'gl_Vertex': vertexPos, # vertex positions
|
|
3614
|
+
'gl_Color': vertexColors} # per vertex colors
|
|
3615
|
+
|
|
3616
|
+
drawClientArrays('triangles', attribArrays)
|
|
3617
|
+
|
|
3618
|
+
Drawing using index buffers::
|
|
3619
|
+
|
|
3620
|
+
vertex, normal, texCoord, faces = createAnnulus() # ring geometry
|
|
3621
|
+
|
|
3622
|
+
# bind and setup shader uniforms here...
|
|
3623
|
+
|
|
3624
|
+
attribs = {
|
|
3625
|
+
'gl_Vertex': vertex,
|
|
3626
|
+
'gl_Normal': normal,
|
|
3627
|
+
'gl_MultiTexCoord0': texCoord}
|
|
3628
|
+
|
|
3629
|
+
drawClientArrays('triangles', attribs, indexBuffer=faces)
|
|
3630
|
+
|
|
3631
|
+
Using interleaved arrays is recommended for greater performance, all
|
|
3632
|
+
attributes are stored in a single array which reduces the number of
|
|
3633
|
+
binding calls::
|
|
3634
|
+
|
|
3635
|
+
vertex, normal, texCoord, faces = createPlane() # square
|
|
3636
|
+
interleaved, sizes, offsets = interleaveArrays(
|
|
3637
|
+
[vertex, normal, texCoord])
|
|
3638
|
+
|
|
3639
|
+
# interleaved array layout: 000111222
|
|
3640
|
+
attribs = {}
|
|
3641
|
+
for i, attrib in enumerate('gl_Vertex', 'gl_Normal', 'gl_TexCoord'):
|
|
3642
|
+
attribs[attrib] = (interleaved, sizes[i], offsets[i])
|
|
3643
|
+
|
|
3644
|
+
drawClientArrays('triangles', attribs, indexBuffer=faces)
|
|
3645
|
+
|
|
3646
|
+
"""
|
|
3647
|
+
mode = _getGLEnum(mode)
|
|
3648
|
+
if mode is None:
|
|
3649
|
+
raise ValueError('Invalid drawing mode specified.')
|
|
3650
|
+
|
|
3651
|
+
GL.glEnable(GL.GL_VERTEX_ARRAY)
|
|
3652
|
+
GL.glBindBuffer(GL.GL_ARRAY_BUFFER, 0)
|
|
3653
|
+
GL.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, 0)
|
|
3654
|
+
GL.glBindVertexArray(0)
|
|
3655
|
+
|
|
3656
|
+
useIndexBuffer = indexBuffer is not None
|
|
3657
|
+
if useIndexBuffer:
|
|
3658
|
+
# always use unsigned int for index buffer
|
|
3659
|
+
indexBuffer = np.ascontiguousarray(indexBuffer, dtype=np.uint32)
|
|
3660
|
+
if indexBuffer.ndim != 2:
|
|
3661
|
+
raise ValueError('Index buffer must be 2D array.')
|
|
3662
|
+
|
|
3663
|
+
boundArrays = []
|
|
3664
|
+
for arrIdx, buffer in attribBuffers.items():
|
|
3665
|
+
if isinstance(arrIdx, str):
|
|
3666
|
+
arrIdx= VERTEX_ATTRIBS.get(arrIdx, None)
|
|
3667
|
+
if arrIdx is None:
|
|
3668
|
+
raise ValueError('Invalid attribute name specified.')
|
|
3669
|
+
|
|
3670
|
+
if isinstance(buffer, (list, tuple,)):
|
|
3671
|
+
normalize = False
|
|
3672
|
+
nVals = len(buffer)
|
|
3673
|
+
if nVals == 1:
|
|
3674
|
+
buffer = buffer[0] # size 1 tuple or list eg. (buffer,)
|
|
3675
|
+
size = buffer.shape[1]
|
|
3676
|
+
offset = 0
|
|
3677
|
+
elif nVals == 2:
|
|
3678
|
+
buffer, size = buffer
|
|
3679
|
+
offset = 0
|
|
3680
|
+
elif nVals == 3:
|
|
3681
|
+
buffer, size, offset = buffer
|
|
3682
|
+
elif nVals == 4:
|
|
3683
|
+
buffer, size, offset, normalize = buffer
|
|
3684
|
+
else:
|
|
3685
|
+
raise ValueError('Invalid attribute values.')
|
|
3686
|
+
else:
|
|
3687
|
+
size = buffer.shape[1]
|
|
3688
|
+
offset = 0
|
|
3689
|
+
normalize = False
|
|
3690
|
+
|
|
3691
|
+
# make sure the buffer is contiguous array
|
|
3692
|
+
if not isinstance(buffer, np.ndarray):
|
|
3693
|
+
buffer = np.ascontiguousarray(buffer, dtype=float)
|
|
3694
|
+
|
|
3695
|
+
if buffer.ndim != 2:
|
|
3696
|
+
raise ValueError(
|
|
3697
|
+
'Buffer {} must be 2D array.'.format(arrIdx))
|
|
3698
|
+
|
|
3699
|
+
numVertices = buffer.shape[0]
|
|
3700
|
+
|
|
3701
|
+
# enable and set attribute pointers
|
|
3702
|
+
GL.glEnableVertexAttribArray(arrIdx)
|
|
3703
|
+
|
|
3704
|
+
arrayTypes = ARRAY_TYPES.get(buffer.dtype.type, None)
|
|
3705
|
+
if arrayTypes is None:
|
|
3706
|
+
raise ValueError('Unable to determine data type from buffer.')
|
|
3707
|
+
|
|
3708
|
+
GL.glVertexAttribPointer(
|
|
3709
|
+
arrIdx,
|
|
3710
|
+
size,
|
|
3711
|
+
arrayTypes[0],
|
|
3712
|
+
GL.GL_TRUE if normalize else GL.GL_FALSE,
|
|
3713
|
+
offset,
|
|
3714
|
+
buffer.ctypes)
|
|
3715
|
+
|
|
3716
|
+
boundArrays.append(arrIdx)
|
|
3717
|
+
|
|
3718
|
+
# use the appropriate draw function
|
|
3719
|
+
if useIndexBuffer:
|
|
3720
|
+
GL.glDrawElements(
|
|
3721
|
+
mode, indexBuffer.size, GL.GL_UNSIGNED_INT, indexBuffer.ctypes)
|
|
3722
|
+
else:
|
|
3723
|
+
GL.glDrawArrays(mode, 0, numVertices)
|
|
3724
|
+
|
|
3725
|
+
for arrIdx in boundArrays: # unbind arrays
|
|
3726
|
+
GL.glDisableVertexAttribArray(arrIdx)
|
|
3727
|
+
|
|
3728
|
+
GL.glDisable(GL.GL_VERTEX_ARRAY)
|
|
3729
|
+
|
|
3730
|
+
|
|
3731
|
+
# ---------------------------
|
|
3732
|
+
# Vertex Buffer Objects (VBO)
|
|
3733
|
+
#
|
|
3734
|
+
|
|
3735
|
+
|
|
3736
|
+
class VertexBufferInfo:
|
|
3737
|
+
"""Vertex buffer object (VBO) descriptor.
|
|
3738
|
+
|
|
3739
|
+
This class only stores information about the VBO it refers to, it does not
|
|
2471
3740
|
contain any actual array data associated with the VBO. Calling
|
|
2472
3741
|
:func:`createVBO` returns instances of this class.
|
|
2473
3742
|
|
|
@@ -2573,7 +3842,7 @@ class VertexBufferInfo:
|
|
|
2573
3842
|
return False
|
|
2574
3843
|
|
|
2575
3844
|
if self.target == GL.GL_ARRAY_BUFFER:
|
|
2576
|
-
bindTarget = GL.
|
|
3845
|
+
bindTarget = GL.GL_VERTEX_ARRAY_BINDING
|
|
2577
3846
|
elif self.target == GL.GL_ELEMENT_ARRAY_BUFFER:
|
|
2578
3847
|
bindTarget = GL.GL_ELEMENT_ARRAY_BUFFER_BINDING
|
|
2579
3848
|
else:
|
|
@@ -2609,7 +3878,7 @@ class VertexBufferInfo:
|
|
|
2609
3878
|
|
|
2610
3879
|
def createVBO(data,
|
|
2611
3880
|
target=GL.GL_ARRAY_BUFFER,
|
|
2612
|
-
dataType=
|
|
3881
|
+
dataType=None,
|
|
2613
3882
|
usage=GL.GL_STATIC_DRAW):
|
|
2614
3883
|
"""Create an array buffer object (VBO).
|
|
2615
3884
|
|
|
@@ -2621,15 +3890,21 @@ def createVBO(data,
|
|
|
2621
3890
|
data : array_like
|
|
2622
3891
|
A 2D array of values to write to the array buffer. The data type of the
|
|
2623
3892
|
VBO is inferred by the type of the array. If the input is a Python
|
|
2624
|
-
`list` or `tuple` type, the data type of the array will be `
|
|
2625
|
-
target : :obj:`int`
|
|
3893
|
+
`list` or `tuple` type, the data type of the array will be `GL_DOUBLE`.
|
|
3894
|
+
target : :obj:`int` or :obj:`str`, optional
|
|
2626
3895
|
Target used when binding the buffer (e.g. `GL_VERTEX_ARRAY` or
|
|
2627
|
-
`GL_ELEMENT_ARRAY_BUFFER`). Default is `GL_VERTEX_ARRAY`.
|
|
2628
|
-
|
|
3896
|
+
`GL_ELEMENT_ARRAY_BUFFER`). Default is `GL_VERTEX_ARRAY`. Strings may
|
|
3897
|
+
also be used to specify the target, where the following are valid:
|
|
3898
|
+
'array' (for `GL_VERTEX_ARRAY`) or 'element_array' (for
|
|
3899
|
+
`GL_ELEMENT_ARRAY_BUFFER`).
|
|
3900
|
+
dataType : Glenum or None, optional
|
|
2629
3901
|
Data type of array. Input data will be recast to an appropriate type if
|
|
2630
|
-
necessary. Default is `
|
|
3902
|
+
necessary. Default is `None`. If `None`, the data type will be
|
|
3903
|
+
inferred from the input data.
|
|
2631
3904
|
usage : GLenum or int, optional
|
|
2632
|
-
Usage
|
|
3905
|
+
Usage hint for the array (i.e. `GL_STATIC_DRAW`). This will hint to the
|
|
3906
|
+
GL driver how the buffer will be used so it can optimize memory
|
|
3907
|
+
allocation and access. Default is `GL_STATIC_DRAW`.
|
|
2633
3908
|
|
|
2634
3909
|
Returns
|
|
2635
3910
|
-------
|
|
@@ -2682,10 +3957,33 @@ def createVBO(data,
|
|
|
2682
3957
|
glFlush()
|
|
2683
3958
|
|
|
2684
3959
|
"""
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
3960
|
+
target, dataType, usage = _getGLEnum(target, dataType, usage)
|
|
3961
|
+
|
|
3962
|
+
# try and infer the data type if not specified
|
|
3963
|
+
if dataType is None: # get data type from input
|
|
3964
|
+
if isinstance(data, (list, tuple)): # default for Python array types
|
|
3965
|
+
dataType = GL.GL_DOUBLE
|
|
3966
|
+
elif isinstance(data, np.ndarray): # numpy arrays
|
|
3967
|
+
dataType = data.dtype.type
|
|
3968
|
+
else:
|
|
3969
|
+
raise ValueError('Could not infer data type from input.')
|
|
3970
|
+
|
|
3971
|
+
# get the OpenGL data type and numpy type
|
|
3972
|
+
typeVals = ARRAY_TYPES.get(dataType, None)
|
|
3973
|
+
if typeVals is None:
|
|
3974
|
+
raise ValueError('Invalid data type specified.')
|
|
3975
|
+
|
|
3976
|
+
glEnum, glType, npType = typeVals
|
|
3977
|
+
|
|
3978
|
+
# get the usage hint if a string was passed
|
|
3979
|
+
if isinstance(usage, str):
|
|
3980
|
+
usage = GL_ENUMS.get(usage, None)
|
|
3981
|
+
if usage is None:
|
|
3982
|
+
raise ValueError('Invalid `usage` hint string.')
|
|
3983
|
+
|
|
3984
|
+
# create the input data array
|
|
3985
|
+
data = np.ascontiguousarray(data, dtype=npType)
|
|
3986
|
+
|
|
2689
3987
|
# get buffer size and pointer
|
|
2690
3988
|
bufferSize = data.size * ctypes.sizeof(glType)
|
|
2691
3989
|
if data.ndim > 1:
|
|
@@ -2708,7 +4006,7 @@ def createVBO(data,
|
|
|
2708
4006
|
bufferName,
|
|
2709
4007
|
target,
|
|
2710
4008
|
usage,
|
|
2711
|
-
|
|
4009
|
+
glEnum,
|
|
2712
4010
|
bufferSize,
|
|
2713
4011
|
bufferStride,
|
|
2714
4012
|
data.shape) # leave userData empty
|
|
@@ -2734,7 +4032,7 @@ def bindVBO(vbo):
|
|
|
2734
4032
|
if isinstance(vbo, VertexBufferInfo):
|
|
2735
4033
|
GL.glBindBuffer(vbo.target, vbo.name)
|
|
2736
4034
|
else:
|
|
2737
|
-
raise TypeError('Specified `vbo` is not
|
|
4035
|
+
raise TypeError('Specified `vbo` is not a `VertexBufferInfo`.')
|
|
2738
4036
|
|
|
2739
4037
|
|
|
2740
4038
|
def unbindVBO(vbo):
|
|
@@ -2749,7 +4047,7 @@ def unbindVBO(vbo):
|
|
|
2749
4047
|
if isinstance(vbo, VertexBufferInfo):
|
|
2750
4048
|
GL.glBindBuffer(vbo.target, 0)
|
|
2751
4049
|
else:
|
|
2752
|
-
raise TypeError('Specified `vbo` is not
|
|
4050
|
+
raise TypeError('Specified `vbo` is not a `VertexBufferInfo`.')
|
|
2753
4051
|
|
|
2754
4052
|
|
|
2755
4053
|
def mapBuffer(vbo, start=0, length=None, read=True, write=True, noSync=False):
|
|
@@ -2813,7 +4111,7 @@ def mapBuffer(vbo, start=0, length=None, read=True, write=True, noSync=False):
|
|
|
2813
4111
|
unmapBuffer(vbo)
|
|
2814
4112
|
|
|
2815
4113
|
"""
|
|
2816
|
-
|
|
4114
|
+
_, glType, npType = ARRAY_TYPES[vbo.dataType]
|
|
2817
4115
|
start *= ctypes.sizeof(glType)
|
|
2818
4116
|
|
|
2819
4117
|
if length is None:
|
|
@@ -2867,6 +4165,103 @@ def unmapBuffer(vbo):
|
|
|
2867
4165
|
return GL.glUnmapBuffer(vbo.target) == GL.GL_TRUE
|
|
2868
4166
|
|
|
2869
4167
|
|
|
4168
|
+
@contextmanager
|
|
4169
|
+
def mappedBuffer(vbo, start=0, length=None, read=True, write=True, noSync=False):
|
|
4170
|
+
"""Context manager for mapping and unmapping a buffer. This is a convenience
|
|
4171
|
+
function for using :func:`mapBuffer` and :func:`unmapBuffer` together.
|
|
4172
|
+
|
|
4173
|
+
Parameters
|
|
4174
|
+
----------
|
|
4175
|
+
vbo : VertexBufferInfo
|
|
4176
|
+
Vertex buffer to map to client memory.
|
|
4177
|
+
start : int
|
|
4178
|
+
Initial index of the sub-range of the buffer to modify.
|
|
4179
|
+
length : int or None
|
|
4180
|
+
Number of elements of the sub-array to map from `offset`. If `None`, all
|
|
4181
|
+
elements to from `offset` to the end of the array are mapped.
|
|
4182
|
+
read : bool, optional
|
|
4183
|
+
Allow data to be read from the buffer (sets `GL_MAP_READ_BIT`). This is
|
|
4184
|
+
ignored if `noSync` is `True`.
|
|
4185
|
+
write : bool, optional
|
|
4186
|
+
Allow data to be written to the buffer (sets `GL_MAP_WRITE_BIT`).
|
|
4187
|
+
noSync : bool, optional
|
|
4188
|
+
If `True`, GL will not wait until the buffer is free (i.e. not being
|
|
4189
|
+
processed by the GPU) to map it (sets `GL_MAP_UNSYNCHRONIZED_BIT`). The
|
|
4190
|
+
contents of the previous storage buffer are discarded and the driver
|
|
4191
|
+
returns a new one. This prevents the CPU from stalling until the buffer
|
|
4192
|
+
is available
|
|
4193
|
+
|
|
4194
|
+
Yields
|
|
4195
|
+
------
|
|
4196
|
+
ndarray
|
|
4197
|
+
View of the data. The type of the returned array is one which best
|
|
4198
|
+
matches the data type of the buffer.
|
|
4199
|
+
|
|
4200
|
+
Examples
|
|
4201
|
+
--------
|
|
4202
|
+
Using the context manager to map and unmap a buffer::
|
|
4203
|
+
|
|
4204
|
+
with mappedBuffer(vbo) as arr:
|
|
4205
|
+
arr[:, :] += 2.0
|
|
4206
|
+
|
|
4207
|
+
"""
|
|
4208
|
+
arr = mapBuffer(vbo, start, length, read, write, noSync)
|
|
4209
|
+
yield arr
|
|
4210
|
+
unmapBuffer(vbo)
|
|
4211
|
+
|
|
4212
|
+
|
|
4213
|
+
def updateVBO(vbo, data, noSync=False):
|
|
4214
|
+
"""Update the contents of a VBO with new data.
|
|
4215
|
+
|
|
4216
|
+
This is a convenience function for mapping a buffer, updating the data, and
|
|
4217
|
+
then unmapping it if the data shape matches the buffer shape.
|
|
4218
|
+
|
|
4219
|
+
Parameters
|
|
4220
|
+
----------
|
|
4221
|
+
vbo : VertexBufferInfo
|
|
4222
|
+
Vertex buffer to update.
|
|
4223
|
+
data : array_like
|
|
4224
|
+
New data to write to the buffer. The shape of the data must match the
|
|
4225
|
+
shape of the buffer.
|
|
4226
|
+
noSync : bool, optional
|
|
4227
|
+
If `True`, GL will not wait until the buffer is free (i.e. not being
|
|
4228
|
+
processed by the GPU) to map it (sets `GL_MAP_UNSYNCHRONIZED_BIT`). The
|
|
4229
|
+
contents of the previous storage buffer are discarded and the driver
|
|
4230
|
+
returns a new one. This prevents the CPU from stalling until the buffer
|
|
4231
|
+
is available.
|
|
4232
|
+
|
|
4233
|
+
Returns
|
|
4234
|
+
-------
|
|
4235
|
+
bool
|
|
4236
|
+
`True` if the buffer has been successfully modified. If `False`, the
|
|
4237
|
+
data was corrupted for some reason and needs to be resubmitted.
|
|
4238
|
+
|
|
4239
|
+
Examples
|
|
4240
|
+
--------
|
|
4241
|
+
Update a VBO with new data::
|
|
4242
|
+
|
|
4243
|
+
# new vertices
|
|
4244
|
+
verts = [[ 1.0, 1.0, 0.0], # v0
|
|
4245
|
+
[ 0.0, -1.0, 0.0], # v1
|
|
4246
|
+
[-1.0, 1.0, 0.0]] # v2
|
|
4247
|
+
|
|
4248
|
+
# update the VBO
|
|
4249
|
+
updateVBO(vboDesc, verts)
|
|
4250
|
+
|
|
4251
|
+
"""
|
|
4252
|
+
if not isinstance(data, np.ndarray): # allow lists, tuples, etc.
|
|
4253
|
+
data = np.ascontiguousarray(data)
|
|
4254
|
+
|
|
4255
|
+
if data.shape != vbo.shape:
|
|
4256
|
+
raise ValueError('Data shape does not match VBO shape, expected {} '
|
|
4257
|
+
'but got {}.'.format(vbo.shape, data.shape))
|
|
4258
|
+
|
|
4259
|
+
mappedArray = mapBuffer(vbo, noSync=noSync)
|
|
4260
|
+
mappedArray[:, :] = data[:, :] # transfer data to GPU buffer array
|
|
4261
|
+
|
|
4262
|
+
return unmapBuffer(vbo)
|
|
4263
|
+
|
|
4264
|
+
|
|
2870
4265
|
def deleteVBO(vbo):
|
|
2871
4266
|
"""Delete a vertex buffer object (VBO).
|
|
2872
4267
|
|
|
@@ -2876,9 +4271,12 @@ def deleteVBO(vbo):
|
|
|
2876
4271
|
Descriptor of VBO to delete.
|
|
2877
4272
|
|
|
2878
4273
|
"""
|
|
4274
|
+
if not isinstance(vbo, VertexBufferInfo):
|
|
4275
|
+
raise TypeError('Invalid type for `vbo`, must be `VertexBufferInfo`.')
|
|
4276
|
+
|
|
2879
4277
|
if GL.glIsBuffer(vbo.name):
|
|
2880
4278
|
GL.glDeleteBuffers(1, vbo.name)
|
|
2881
|
-
vbo.name = GL.GLuint(0)
|
|
4279
|
+
vbo.name = GL.GLuint(0) # reset the object to invalidate it
|
|
2882
4280
|
|
|
2883
4281
|
|
|
2884
4282
|
def setVertexAttribPointer(index,
|
|
@@ -2911,7 +4309,7 @@ def setVertexAttribPointer(index,
|
|
|
2911
4309
|
versions.
|
|
2912
4310
|
|
|
2913
4311
|
On nVidia graphics drivers (and maybe others), the following attribute
|
|
2914
|
-
|
|
4312
|
+
pointer indices are aliased with reserved GLSL names:
|
|
2915
4313
|
|
|
2916
4314
|
* gl_Vertex - 0
|
|
2917
4315
|
* gl_Normal - 2
|
|
@@ -3011,7 +4409,7 @@ def setVertexAttribPointer(index,
|
|
|
3011
4409
|
if vbo.target != GL.GL_ARRAY_BUFFER:
|
|
3012
4410
|
raise ValueError('VBO must have `target` type `GL_ARRAY_BUFFER`.')
|
|
3013
4411
|
|
|
3014
|
-
_, glType =
|
|
4412
|
+
_, glType, _ = ARRAY_TYPES[vbo.dataType]
|
|
3015
4413
|
|
|
3016
4414
|
if size is None:
|
|
3017
4415
|
size = vbo.shape[1]
|
|
@@ -3094,120 +4492,506 @@ def disableVertexAttribArray(index, legacy=False):
|
|
|
3094
4492
|
GL.glDisableClientState(index)
|
|
3095
4493
|
|
|
3096
4494
|
|
|
3097
|
-
#
|
|
3098
|
-
#
|
|
3099
|
-
# -------------------------
|
|
3100
|
-
#
|
|
3101
|
-
# Materials affect the appearance of rendered faces. These helper functions and
|
|
3102
|
-
# datatypes simplify the creation of materials for rendering stimuli.
|
|
4495
|
+
# ---------------------------
|
|
4496
|
+
# Draw settings
|
|
3103
4497
|
#
|
|
3104
4498
|
|
|
3105
|
-
Material = namedtuple('Material', ['face', 'params', 'textures', 'userData'])
|
|
3106
|
-
|
|
3107
4499
|
|
|
3108
|
-
def
|
|
3109
|
-
"""
|
|
4500
|
+
def activeTexture(unit):
|
|
4501
|
+
"""Set the active texture unit.
|
|
3110
4502
|
|
|
3111
4503
|
Parameters
|
|
3112
4504
|
----------
|
|
4505
|
+
unit : GLenum, int or str
|
|
4506
|
+
Texture unit to activate. Values can be `GL_TEXTURE0`, `GL_TEXTURE1`,
|
|
4507
|
+
`GL_TEXTURE2`, etc.
|
|
3113
4508
|
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
(
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
values are specified, an empty material will be created.
|
|
3121
|
-
textures : :obj:`list` of :obj:`tuple`, optional
|
|
3122
|
-
List of texture units and TexImage2D descriptors. These will be written
|
|
3123
|
-
to the 'textures' field of the returned descriptor. For example,
|
|
3124
|
-
[(GL.GL_TEXTURE0, texDesc0), (GL.GL_TEXTURE1, texDesc1)]. The number of
|
|
3125
|
-
texture units per-material is GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS.
|
|
3126
|
-
face : :obj:`int`, optional
|
|
3127
|
-
Faces to apply material to. Values can be GL_FRONT_AND_BACK, GL_FRONT
|
|
3128
|
-
and GL_BACK. The default is GL_FRONT_AND_BACK.
|
|
4509
|
+
"""
|
|
4510
|
+
if isinstance(unit, str):
|
|
4511
|
+
unit = _getGLEnum(unit)
|
|
4512
|
+
else:
|
|
4513
|
+
if unit < MAX_TEXTURE_UNITS:
|
|
4514
|
+
unit = GL.GL_TEXTURE0 + unit
|
|
3129
4515
|
|
|
3130
|
-
|
|
3131
|
-
-------
|
|
3132
|
-
Material :
|
|
3133
|
-
A descriptor with material properties.
|
|
4516
|
+
GL.glActiveTexture(unit)
|
|
3134
4517
|
|
|
3135
|
-
Examples
|
|
3136
|
-
--------
|
|
3137
|
-
Creating a new material with given properties::
|
|
3138
4518
|
|
|
3139
|
-
|
|
3140
|
-
|
|
4519
|
+
def bindTexture(texture, target=None, unit=None):
|
|
4520
|
+
"""Bind a texture to a target.
|
|
3141
4521
|
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
4522
|
+
Parameters
|
|
4523
|
+
----------
|
|
4524
|
+
texture : GLuint, int, None, TexImage2DInfo or TexImage2DMultisampleInfo
|
|
4525
|
+
Texture to bind. If `None`, the texture will be unbound.
|
|
4526
|
+
target : GLenum, int or str, optional
|
|
4527
|
+
Target to bind the texture to. Default is `None`. If `None`, and the
|
|
4528
|
+
texture is a `TexImage2DInfo` or `TexImage2DMultisampleInfo` object,
|
|
4529
|
+
the target will be inferred from the texture object. If specified, the
|
|
4530
|
+
value will override the target inferred from the texture. If `None` and
|
|
4531
|
+
the texture is an integer, the target will default to `GL_TEXTURE_2D`.
|
|
4532
|
+
unit : GLenum, int or None, optional
|
|
4533
|
+
Texture unit to bind the texture to, this will also set the active
|
|
4534
|
+
texture unit. Default is `None`. If `None`, the texture will be bound to
|
|
4535
|
+
the currently active texture unit.
|
|
3148
4536
|
|
|
3149
|
-
|
|
4537
|
+
"""
|
|
4538
|
+
if isinstance(target, str):
|
|
4539
|
+
target = getattr(GL, target, None)
|
|
4540
|
+
if target is None:
|
|
4541
|
+
raise ValueError('Invalid target string specified.')
|
|
4542
|
+
|
|
4543
|
+
if texture is None:
|
|
4544
|
+
texture = 0
|
|
4545
|
+
else:
|
|
4546
|
+
if isinstance(texture, (TexImage2DInfo, TexImage2DMultisampleInfo)):
|
|
4547
|
+
texture = texture.name
|
|
4548
|
+
if target is None:
|
|
4549
|
+
target = texture.target
|
|
4550
|
+
else:
|
|
4551
|
+
texture = int(texture)
|
|
4552
|
+
if target is None:
|
|
4553
|
+
target = GL.GL_TEXTURE_2D
|
|
3150
4554
|
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
4555
|
+
if unit is not None: # bind the texture to a specific unit
|
|
4556
|
+
activeTexture(unit)
|
|
4557
|
+
|
|
4558
|
+
GL.glBindTexture(target, texture)
|
|
3154
4559
|
|
|
3155
|
-
Create a red plastic material, but define reflectance and shine later::
|
|
3156
4560
|
|
|
3157
|
-
|
|
4561
|
+
def setPolygonMode(face, mode):
|
|
4562
|
+
"""Set the polygon rasterization mode for a face.
|
|
3158
4563
|
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
4564
|
+
Parameters
|
|
4565
|
+
----------
|
|
4566
|
+
face : GLenum or str
|
|
4567
|
+
Face to set the polygon mode for. Values can be `GL_FRONT`, `GL_BACK`,
|
|
4568
|
+
or `GL_FRONT_AND_BACK`. Strings may also be used to specify the face,
|
|
4569
|
+
where the following are valid: 'front' (for `GL_FRONT`), 'back' (for
|
|
4570
|
+
`GL_BACK`), and 'front_and_back' (for `GL_FRONT_AND_BACK`).
|
|
4571
|
+
mode : GLenum or str
|
|
4572
|
+
Polygon rasterization mode. Values can be `GL_POINT`, `GL_LINE`, or
|
|
4573
|
+
`GL_FILL`. Strings may also be used to specify the mode, where the
|
|
4574
|
+
following are valid: 'point' (for `GL_POINT`), 'line' (for `GL_LINE`),
|
|
4575
|
+
and 'fill' (for `GL_FILL`).
|
|
3164
4576
|
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
4577
|
+
"""
|
|
4578
|
+
if isinstance(face, str):
|
|
4579
|
+
face = getattr(GL, face, None)
|
|
4580
|
+
if face is None:
|
|
4581
|
+
raise ValueError(
|
|
4582
|
+
'Invalid face string specified, got {}.'.format(face))
|
|
4583
|
+
if isinstance(mode, str):
|
|
4584
|
+
mode = getattr(GL, mode, None)
|
|
4585
|
+
if mode is None:
|
|
4586
|
+
raise ValueError(
|
|
4587
|
+
'Invalid mode string specified, got {}.'.format(mode))
|
|
4588
|
+
|
|
4589
|
+
GL.glPolygonMode(face, mode)
|
|
4590
|
+
|
|
4591
|
+
|
|
4592
|
+
def setWireframeDraw(face=GL.GL_FRONT_AND_BACK):
|
|
4593
|
+
"""Set the rasterization mode to wireframe.
|
|
4594
|
+
|
|
4595
|
+
Successive draw operations will render wireframe polygons (i.e. outlines).
|
|
4596
|
+
|
|
4597
|
+
Parameters
|
|
4598
|
+
----------
|
|
4599
|
+
face : GLenum or int, optional
|
|
4600
|
+
Faces to apply wireframe to. Values can be `GL_FRONT_AND_BACK`,
|
|
4601
|
+
`GL_FRONT` and `GL_BACK`. The default is `GL_FRONT_AND_BACK`. Strings
|
|
4602
|
+
may also be used to specify the face, where the following are valid:
|
|
4603
|
+
'front' (for `GL_FRONT`), 'back' (for `GL_BACK`), and 'front_and_back'
|
|
4604
|
+
(for `GL_FRONT_AND_BACK`).
|
|
3169
4605
|
|
|
3170
4606
|
"""
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
face
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
GL.GL_EMISSION,
|
|
3179
|
-
GL.GL_SHININESS)},
|
|
3180
|
-
dict(),
|
|
3181
|
-
dict())
|
|
3182
|
-
if params:
|
|
3183
|
-
for mode, param in params:
|
|
3184
|
-
matDesc.params[mode] = \
|
|
3185
|
-
(GL.GLfloat * 4)(*param) \
|
|
3186
|
-
if mode != GL.GL_SHININESS else GL.GLfloat(param)
|
|
3187
|
-
if textures:
|
|
3188
|
-
maxTexUnits = getIntegerv(GL.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS)
|
|
3189
|
-
for unit, texDesc in textures:
|
|
3190
|
-
if unit <= GL.GL_TEXTURE0 + (maxTexUnits - 1):
|
|
3191
|
-
matDesc.textures[unit] = texDesc
|
|
3192
|
-
else:
|
|
3193
|
-
raise ValueError("Invalid texture unit enum.")
|
|
4607
|
+
if isinstance(face, str):
|
|
4608
|
+
face = getattr(GL, face, None)
|
|
4609
|
+
if face is None:
|
|
4610
|
+
raise ValueError(
|
|
4611
|
+
'Invalid face string specified, got {}.'.format(face))
|
|
4612
|
+
|
|
4613
|
+
setPolygonMode(face, GL.GL_LINE)
|
|
3194
4614
|
|
|
3195
|
-
return matDesc
|
|
3196
4615
|
|
|
4616
|
+
def setFillDraw(face=GL.GL_FRONT_AND_BACK):
|
|
4617
|
+
"""Set the rasterization mode to fill polygons.
|
|
3197
4618
|
|
|
3198
|
-
|
|
3199
|
-
|
|
4619
|
+
Successive draw operations will render filled polygons.
|
|
4620
|
+
|
|
4621
|
+
Parameters
|
|
4622
|
+
----------
|
|
4623
|
+
face : GLenum or int, optional
|
|
4624
|
+
Faces to apply fill to. Values can be `GL_FRONT_AND_BACK`, `GL_FRONT`
|
|
4625
|
+
and `GL_BACK`. The default is `GL_FRONT_AND_BACK`. Strings may also be
|
|
4626
|
+
used to specify the face, where the following are valid: 'front' (for
|
|
4627
|
+
`GL_FRONT`), 'back' (for `GL_BACK`), and 'front_and_back' (for
|
|
4628
|
+
`GL_FRONT_AND_BACK`).
|
|
3200
4629
|
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
4630
|
+
"""
|
|
4631
|
+
if isinstance(face, str):
|
|
4632
|
+
face = getattr(GL, face, None)
|
|
4633
|
+
if face is None:
|
|
4634
|
+
raise ValueError(
|
|
4635
|
+
'Invalid face string specified, got {}.'.format(face))
|
|
4636
|
+
|
|
4637
|
+
setPolygonMode(face, GL.GL_FILL)
|
|
4638
|
+
|
|
4639
|
+
|
|
4640
|
+
def setDepthTest(enable=True):
|
|
4641
|
+
"""Enable or disable depth testing.
|
|
4642
|
+
|
|
4643
|
+
Parameters
|
|
4644
|
+
----------
|
|
4645
|
+
enable : bool, optional
|
|
4646
|
+
Enable or disable depth testing. Default is `True`.
|
|
3206
4647
|
|
|
3207
4648
|
"""
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
4649
|
+
if enable:
|
|
4650
|
+
GL.glEnable(GL.GL_DEPTH_TEST)
|
|
4651
|
+
else:
|
|
4652
|
+
GL.glDisable(GL.GL_DEPTH_TEST)
|
|
4653
|
+
|
|
4654
|
+
|
|
4655
|
+
def setDepthFunc(func):
|
|
4656
|
+
"""Set the depth comparison function.
|
|
4657
|
+
|
|
4658
|
+
Parameters
|
|
4659
|
+
----------
|
|
4660
|
+
func : GLenum or str
|
|
4661
|
+
Depth comparison function. Values can be `GL_NEVER`, `GL_LESS`,
|
|
4662
|
+
`GL_EQUAL`, `GL_LEQUAL`, `GL_GREATER`, `GL_NOTEQUAL`, `GL_GEQUAL`, or
|
|
4663
|
+
`GL_ALWAYS`. Strings may also be used to specify the function, where
|
|
4664
|
+
the following are valid: 'never' (for `GL_NEVER`), 'less' (for `GL_LESS`),
|
|
4665
|
+
'equal' (for `GL_EQUAL`), 'lequal' (for `GL_LEQUAL`), 'greater' (for
|
|
4666
|
+
`GL_GREATER`), 'notequal' (for `GL_NOTEQUAL`), 'gequal' (for `GL_GEQUAL`),
|
|
4667
|
+
and 'always' (for `GL_ALWAYS`).
|
|
4668
|
+
|
|
4669
|
+
"""
|
|
4670
|
+
func = _getGLEnum(func)
|
|
4671
|
+
GL.glDepthFunc(func)
|
|
4672
|
+
|
|
4673
|
+
|
|
4674
|
+
def setDepthMask(enable=True):
|
|
4675
|
+
"""Enable or disable writing to the depth buffer.
|
|
4676
|
+
|
|
4677
|
+
Parameters
|
|
4678
|
+
----------
|
|
4679
|
+
enable : bool, optional
|
|
4680
|
+
Enable or disable writing to the depth buffer. Default is `True`.
|
|
4681
|
+
|
|
4682
|
+
"""
|
|
4683
|
+
if enable:
|
|
4684
|
+
GL.glDepthMask(GL.GL_TRUE)
|
|
4685
|
+
else:
|
|
4686
|
+
GL.glDepthMask(GL.GL_FALSE)
|
|
4687
|
+
|
|
4688
|
+
|
|
4689
|
+
def setDepthRange(near, far):
|
|
4690
|
+
"""Set the range of depth values.
|
|
4691
|
+
|
|
4692
|
+
Parameters
|
|
4693
|
+
----------
|
|
4694
|
+
near : float
|
|
4695
|
+
Near clipping plane.
|
|
4696
|
+
far : float
|
|
4697
|
+
Far clipping plane.
|
|
4698
|
+
|
|
4699
|
+
"""
|
|
4700
|
+
GL.glDepthRange(near, far)
|
|
4701
|
+
|
|
4702
|
+
|
|
4703
|
+
def setClearColor(color):
|
|
4704
|
+
"""Set the clear color for the color buffer.
|
|
4705
|
+
|
|
4706
|
+
Parameters
|
|
4707
|
+
----------
|
|
4708
|
+
color : array_like
|
|
4709
|
+
Color to clear the color buffer with. The color should be in RGBA
|
|
4710
|
+
format, where each component is in the range [0, 1].
|
|
4711
|
+
|
|
4712
|
+
"""
|
|
4713
|
+
GL.glClearColor(*color)
|
|
4714
|
+
|
|
4715
|
+
|
|
4716
|
+
def setClearDepth(depth):
|
|
4717
|
+
"""Set the clear value for the depth buffer.
|
|
4718
|
+
|
|
4719
|
+
Parameters
|
|
4720
|
+
----------
|
|
4721
|
+
depth : float
|
|
4722
|
+
Value to clear the depth buffer with.
|
|
4723
|
+
|
|
4724
|
+
"""
|
|
4725
|
+
GL.glClearDepth(depth)
|
|
4726
|
+
|
|
4727
|
+
|
|
4728
|
+
def enable(enum):
|
|
4729
|
+
"""Enable a GL capability.
|
|
4730
|
+
|
|
4731
|
+
Parameters
|
|
4732
|
+
----------
|
|
4733
|
+
enum : GLenum, int or str
|
|
4734
|
+
Capability to enable.
|
|
4735
|
+
|
|
4736
|
+
"""
|
|
4737
|
+
if isinstance(enum, str):
|
|
4738
|
+
enum = getattr(GL, enum, None)
|
|
4739
|
+
|
|
4740
|
+
if enum is None:
|
|
4741
|
+
raise ValueError('Invalid capability string specified.')
|
|
4742
|
+
|
|
4743
|
+
GL.glEnable(enum)
|
|
4744
|
+
|
|
4745
|
+
|
|
4746
|
+
def disable(enum):
|
|
4747
|
+
"""Disable a GL capability.
|
|
4748
|
+
|
|
4749
|
+
Parameters
|
|
4750
|
+
----------
|
|
4751
|
+
enum : GLenum, int or str
|
|
4752
|
+
Capability to disable.
|
|
4753
|
+
|
|
4754
|
+
"""
|
|
4755
|
+
if isinstance(enum, str):
|
|
4756
|
+
enum = getattr(GL, enum, None)
|
|
4757
|
+
|
|
4758
|
+
if enum is None:
|
|
4759
|
+
raise ValueError('Invalid capability string specified.')
|
|
4760
|
+
|
|
4761
|
+
GL.glDisable(enum)
|
|
4762
|
+
|
|
4763
|
+
|
|
4764
|
+
def setLineWidth(width):
|
|
4765
|
+
"""Set the width of rasterized lines.
|
|
4766
|
+
|
|
4767
|
+
Parameters
|
|
4768
|
+
----------
|
|
4769
|
+
width : float
|
|
4770
|
+
Width of rasterized lines.
|
|
4771
|
+
|
|
4772
|
+
"""
|
|
4773
|
+
GL.glLineWidth(width)
|
|
4774
|
+
|
|
4775
|
+
|
|
4776
|
+
def setLineSmooth(enable=True):
|
|
4777
|
+
"""Enable or disable line antialiasing.
|
|
4778
|
+
|
|
4779
|
+
Parameters
|
|
4780
|
+
----------
|
|
4781
|
+
enable : bool, optional
|
|
4782
|
+
Enable or disable line antialiasing. Default is `True`.
|
|
4783
|
+
|
|
4784
|
+
"""
|
|
4785
|
+
if enable:
|
|
4786
|
+
GL.glEnable(GL.GL_LINE_SMOOTH)
|
|
4787
|
+
else:
|
|
4788
|
+
GL.glDisable(GL.GL_LINE_SMOOTH)
|
|
4789
|
+
|
|
4790
|
+
|
|
4791
|
+
def setPointSize(size):
|
|
4792
|
+
"""Set the size of rasterized points.
|
|
4793
|
+
|
|
4794
|
+
Parameters
|
|
4795
|
+
----------
|
|
4796
|
+
size : float
|
|
4797
|
+
Size of rasterized points.
|
|
4798
|
+
|
|
4799
|
+
"""
|
|
4800
|
+
GL.glPointSize(size)
|
|
4801
|
+
|
|
4802
|
+
|
|
4803
|
+
def setPointSmooth(enable=True):
|
|
4804
|
+
"""Enable or disable point antialiasing.
|
|
4805
|
+
|
|
4806
|
+
Parameters
|
|
4807
|
+
----------
|
|
4808
|
+
enable : bool, optional
|
|
4809
|
+
Enable or disable point antialiasing. Default is `True`.
|
|
4810
|
+
|
|
4811
|
+
"""
|
|
4812
|
+
if enable:
|
|
4813
|
+
GL.glEnable(GL.GL_POINT_SMOOTH)
|
|
4814
|
+
else:
|
|
4815
|
+
GL.glDisable(GL.GL_POINT_SMOOTH)
|
|
4816
|
+
|
|
4817
|
+
|
|
4818
|
+
def setMultiSample(enable=True):
|
|
4819
|
+
"""Enable or disable multisample antialiasing.
|
|
4820
|
+
|
|
4821
|
+
Parameters
|
|
4822
|
+
----------
|
|
4823
|
+
enable : bool, optional
|
|
4824
|
+
Enable or disable multisample antialiasing. Default is `True`.
|
|
4825
|
+
|
|
4826
|
+
"""
|
|
4827
|
+
if enable:
|
|
4828
|
+
GL.glEnable(GL.GL_MULTISAMPLE)
|
|
4829
|
+
else:
|
|
4830
|
+
GL.glDisable(GL.GL_MULTISAMPLE)
|
|
4831
|
+
|
|
4832
|
+
|
|
4833
|
+
def setBlend(enable=True):
|
|
4834
|
+
"""Enable or disable blending.
|
|
4835
|
+
|
|
4836
|
+
Parameters
|
|
4837
|
+
----------
|
|
4838
|
+
enable : bool, optional
|
|
4839
|
+
Enable or disable blending. Default is `True`.
|
|
4840
|
+
|
|
4841
|
+
"""
|
|
4842
|
+
if enable:
|
|
4843
|
+
GL.glEnable(GL.GL_BLEND)
|
|
4844
|
+
else:
|
|
4845
|
+
GL.glDisable(GL.GL_BLEND)
|
|
4846
|
+
|
|
4847
|
+
|
|
4848
|
+
def setBlendFunc(srcFactor, dstFactor):
|
|
4849
|
+
"""Set the blending function for source and destination factors.
|
|
4850
|
+
|
|
4851
|
+
Parameters
|
|
4852
|
+
----------
|
|
4853
|
+
srcFactor : GLenum or str
|
|
4854
|
+
Source blending factor. Eg. `GL_SRC_ALPHA`.
|
|
4855
|
+
dstFactor : GLenum or str
|
|
4856
|
+
Destination blending factor. Eg. `GL_ONE_MINUS_SRC_ALPHA`.
|
|
4857
|
+
|
|
4858
|
+
Examples
|
|
4859
|
+
--------
|
|
4860
|
+
Set the blending function to use source alpha and one minus source alpha::
|
|
4861
|
+
|
|
4862
|
+
setBlendFunc('GL_SRC_ALPHA', 'GL_ONE_MINUS_SRC_ALPHA')
|
|
4863
|
+
|
|
4864
|
+
"""
|
|
4865
|
+
if isinstance(srcFactor, str):
|
|
4866
|
+
srcFactor = getattr(GL, srcFactor, None)
|
|
4867
|
+
if srcFactor is None:
|
|
4868
|
+
raise ValueError(
|
|
4869
|
+
'Invalid enum specified for source factor. Got {}.'.format(
|
|
4870
|
+
srcFactor))
|
|
4871
|
+
if isinstance(dstFactor, str):
|
|
4872
|
+
dstFactor = getattr(GL, dstFactor, None)
|
|
4873
|
+
if dstFactor is None:
|
|
4874
|
+
raise ValueError(
|
|
4875
|
+
'Invalid enum specified for destination factor. Got {}.'.format(
|
|
4876
|
+
dstFactor))
|
|
4877
|
+
|
|
4878
|
+
GL.glBlendFunc(srcFactor, dstFactor)
|
|
4879
|
+
|
|
4880
|
+
|
|
4881
|
+
# -------------------------
|
|
4882
|
+
# Material Helper Functions
|
|
4883
|
+
# -------------------------
|
|
4884
|
+
#
|
|
4885
|
+
# Materials affect the appearance of rendered faces. These helper functions and
|
|
4886
|
+
# datatypes simplify the creation of materials for rendering stimuli.
|
|
4887
|
+
#
|
|
4888
|
+
|
|
4889
|
+
Material = namedtuple('Material', ['face', 'params', 'textures', 'userData'])
|
|
4890
|
+
|
|
4891
|
+
|
|
4892
|
+
def createMaterial(params=(), textures=(), face=GL.GL_FRONT_AND_BACK):
|
|
4893
|
+
"""Create a new material.
|
|
4894
|
+
|
|
4895
|
+
Parameters
|
|
4896
|
+
----------
|
|
4897
|
+
|
|
4898
|
+
params : :obj:`list` of :obj:`tuple`, optional
|
|
4899
|
+
List of material modes and values. Each mode is assigned a value as
|
|
4900
|
+
(mode, color). Modes can be GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR,
|
|
4901
|
+
GL_EMISSION, GL_SHININESS or GL_AMBIENT_AND_DIFFUSE. Colors must be
|
|
4902
|
+
a tuple of 4 floats which specify reflectance values for each RGBA
|
|
4903
|
+
component. The value of GL_SHININESS should be a single float. If no
|
|
4904
|
+
values are specified, an empty material will be created.
|
|
4905
|
+
textures : :obj:`list` of :obj:`tuple`, optional
|
|
4906
|
+
List of texture units and TexImage2D descriptors. These will be written
|
|
4907
|
+
to the 'textures' field of the returned descriptor. For example,
|
|
4908
|
+
[(GL.GL_TEXTURE0, texDesc0), (GL.GL_TEXTURE1, texDesc1)]. The number of
|
|
4909
|
+
texture units per-material is GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS.
|
|
4910
|
+
face : :obj:`int`, optional
|
|
4911
|
+
Faces to apply material to. Values can be GL_FRONT_AND_BACK, GL_FRONT
|
|
4912
|
+
and GL_BACK. The default is GL_FRONT_AND_BACK.
|
|
4913
|
+
|
|
4914
|
+
Returns
|
|
4915
|
+
-------
|
|
4916
|
+
Material :
|
|
4917
|
+
A descriptor with material properties.
|
|
4918
|
+
|
|
4919
|
+
Examples
|
|
4920
|
+
--------
|
|
4921
|
+
Creating a new material with given properties::
|
|
4922
|
+
|
|
4923
|
+
# The values for the material below can be found at
|
|
4924
|
+
# http://devernay.free.fr/cours/opengl/materials.html
|
|
4925
|
+
|
|
4926
|
+
# create a gold material
|
|
4927
|
+
gold = createMaterial([
|
|
4928
|
+
(GL.GL_AMBIENT, (0.24725, 0.19950, 0.07450, 1.0)),
|
|
4929
|
+
(GL.GL_DIFFUSE, (0.75164, 0.60648, 0.22648, 1.0)),
|
|
4930
|
+
(GL.GL_SPECULAR, (0.628281, 0.555802, 0.366065, 1.0)),
|
|
4931
|
+
(GL.GL_SHININESS, 0.4 * 128.0)])
|
|
4932
|
+
|
|
4933
|
+
Use the material when drawing::
|
|
4934
|
+
|
|
4935
|
+
useMaterial(gold)
|
|
4936
|
+
drawVAO( ... ) # all meshes will be gold
|
|
4937
|
+
useMaterial(None) # turn off material when done
|
|
4938
|
+
|
|
4939
|
+
Create a red plastic material, but define reflectance and shine later::
|
|
4940
|
+
|
|
4941
|
+
red_plastic = createMaterial()
|
|
4942
|
+
|
|
4943
|
+
# you need to convert values to ctypes!
|
|
4944
|
+
red_plastic.values[GL_AMBIENT] = (GLfloat * 4)(0.0, 0.0, 0.0, 1.0)
|
|
4945
|
+
red_plastic.values[GL_DIFFUSE] = (GLfloat * 4)(0.5, 0.0, 0.0, 1.0)
|
|
4946
|
+
red_plastic.values[GL_SPECULAR] = (GLfloat * 4)(0.7, 0.6, 0.6, 1.0)
|
|
4947
|
+
red_plastic.values[GL_SHININESS] = 0.25 * 128.0
|
|
4948
|
+
|
|
4949
|
+
# set and draw
|
|
4950
|
+
useMaterial(red_plastic)
|
|
4951
|
+
drawVertexbuffers( ... ) # all meshes will be red plastic
|
|
4952
|
+
useMaterial(None)
|
|
4953
|
+
|
|
4954
|
+
"""
|
|
4955
|
+
# setup material mode/value slots
|
|
4956
|
+
matDesc = Material(
|
|
4957
|
+
face,
|
|
4958
|
+
{mode: None for mode in (
|
|
4959
|
+
GL.GL_AMBIENT,
|
|
4960
|
+
GL.GL_DIFFUSE,
|
|
4961
|
+
GL.GL_SPECULAR,
|
|
4962
|
+
GL.GL_EMISSION,
|
|
4963
|
+
GL.GL_SHININESS)},
|
|
4964
|
+
dict(),
|
|
4965
|
+
dict())
|
|
4966
|
+
if params:
|
|
4967
|
+
for mode, param in params:
|
|
4968
|
+
matDesc.params[mode] = \
|
|
4969
|
+
(GL.GLfloat * 4)(*param) \
|
|
4970
|
+
if mode != GL.GL_SHININESS else GL.GLfloat(param)
|
|
4971
|
+
if textures:
|
|
4972
|
+
maxTexUnits = getIntegerv(GL.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS)
|
|
4973
|
+
for unit, texDesc in textures:
|
|
4974
|
+
if unit <= GL.GL_TEXTURE0 + (maxTexUnits - 1):
|
|
4975
|
+
matDesc.textures[unit] = texDesc
|
|
4976
|
+
else:
|
|
4977
|
+
raise ValueError("Invalid texture unit enum.")
|
|
4978
|
+
|
|
4979
|
+
return matDesc
|
|
4980
|
+
|
|
4981
|
+
|
|
4982
|
+
class SimpleMaterial:
|
|
4983
|
+
"""Class representing a simple material.
|
|
4984
|
+
|
|
4985
|
+
This class stores material information to modify the appearance of drawn
|
|
4986
|
+
primitives with respect to lighting, such as color (diffuse, specular,
|
|
4987
|
+
ambient, and emission), shininess, and textures. Simple materials are
|
|
4988
|
+
intended to work with features supported by the fixed-function OpenGL
|
|
4989
|
+
pipeline.
|
|
4990
|
+
|
|
4991
|
+
"""
|
|
4992
|
+
def __init__(self,
|
|
4993
|
+
win=None,
|
|
4994
|
+
diffuseColor=(.5, .5, .5),
|
|
3211
4995
|
specularColor=(-1., -1., -1.),
|
|
3212
4996
|
ambientColor=(-1., -1., -1.),
|
|
3213
4997
|
emissionColor=(-1., -1., -1.),
|
|
@@ -3239,8 +5023,8 @@ class SimpleMaterial:
|
|
|
3239
5023
|
colorSpace : float
|
|
3240
5024
|
Color space for `diffuseColor`, `specularColor`, `ambientColor`, and
|
|
3241
5025
|
`emissionColor`.
|
|
3242
|
-
diffuseTexture :
|
|
3243
|
-
specularTexture :
|
|
5026
|
+
diffuseTexture : TexImage2DInfo
|
|
5027
|
+
specularTexture : TexImage2DInfo
|
|
3244
5028
|
opacity : float
|
|
3245
5029
|
Opacity of the material. Ranges from 0.0 to 1.0 where 1.0 is fully
|
|
3246
5030
|
opaque.
|
|
@@ -4533,38 +6317,256 @@ def createBox(size=(1., 1., 1.), flipFaces=False):
|
|
|
4533
6317
|
return vertices, texCoords, normals, faces
|
|
4534
6318
|
|
|
4535
6319
|
|
|
4536
|
-
def
|
|
4537
|
-
"""
|
|
6320
|
+
def createDisc(radius=1.0, edges=16):
|
|
6321
|
+
"""Create a disc (filled circle) mesh.
|
|
4538
6322
|
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
configuration of an object while rendering.
|
|
6323
|
+
Generates a flat disc mesh with the specified radius and number of `edges`.
|
|
6324
|
+
The origin of the disc is located at the center. Textures coordinates will
|
|
6325
|
+
be mapped to a square which bounds the circle. Normals are perpendicular to
|
|
6326
|
+
the face of the circle.
|
|
4544
6327
|
|
|
4545
6328
|
Parameters
|
|
4546
6329
|
----------
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
Position vector to transform mesh vertices. If Nx3, `vertices` will be
|
|
4553
|
-
transformed by corresponding rows of `pos`.
|
|
4554
|
-
ori : array_like, optional
|
|
4555
|
-
Orientation quaternion in form [x, y, z, w]. If Nx4, `vertices` and
|
|
4556
|
-
`normals` will be transformed by corresponding rows of `ori`.
|
|
6330
|
+
radius : float
|
|
6331
|
+
Radius of the disc in scene units.
|
|
6332
|
+
edges : int
|
|
6333
|
+
Number of segments to use to define the outer rim of the disc. Higher
|
|
6334
|
+
numbers will result in a smoother circle but will use more triangles.
|
|
4557
6335
|
|
|
4558
6336
|
Returns
|
|
4559
6337
|
-------
|
|
4560
6338
|
tuple
|
|
4561
|
-
|
|
6339
|
+
Vertex attribute arrays (position, texture coordinates, and normals) and
|
|
6340
|
+
triangle indices.
|
|
4562
6341
|
|
|
4563
6342
|
Examples
|
|
4564
6343
|
--------
|
|
4565
|
-
Create
|
|
6344
|
+
Create a vertex array object to draw a disc::
|
|
4566
6345
|
|
|
4567
|
-
vertices, textureCoords, normals, faces =
|
|
6346
|
+
vertices, textureCoords, normals, faces = gltools.createDisc(edges=128)
|
|
6347
|
+
vertexVBO = gltools.createVBO(vertices)
|
|
6348
|
+
texCoordVBO = gltools.createVBO(textureCoords)
|
|
6349
|
+
normalsVBO = gltools.createVBO(normals)
|
|
6350
|
+
indexBuffer = gltools.createVBO(
|
|
6351
|
+
faces.flatten(),
|
|
6352
|
+
target=GL.GL_ELEMENT_ARRAY_BUFFER,
|
|
6353
|
+
dataType=GL.GL_UNSIGNED_INT)
|
|
6354
|
+
|
|
6355
|
+
vao = gltools.createVAO(
|
|
6356
|
+
{gltools.gl_Vertex: vertexVBO,
|
|
6357
|
+
gltools.gl_MultiTexCoord0: texCoordVBO,
|
|
6358
|
+
gltools.gl_Normal: normalsVBO},
|
|
6359
|
+
indexBuffer=indexBuffer)
|
|
6360
|
+
|
|
6361
|
+
"""
|
|
6362
|
+
# get number of steps for vertices to get the number of edges we want
|
|
6363
|
+
nVerts = edges + 1
|
|
6364
|
+
steps = np.linspace(0, 2 * np.pi, num=nVerts, dtype=np.float32)
|
|
6365
|
+
|
|
6366
|
+
# offset since the first vertex is the centre
|
|
6367
|
+
vertices = np.zeros((nVerts + 1, 3), dtype=np.float32)
|
|
6368
|
+
vertices[1:, 0] = np.sin(steps)
|
|
6369
|
+
vertices[1:, 1] = np.cos(steps)
|
|
6370
|
+
|
|
6371
|
+
# compute the face indices
|
|
6372
|
+
faces = []
|
|
6373
|
+
for i in range(nVerts):
|
|
6374
|
+
faces.append([0, i + 1, i])
|
|
6375
|
+
|
|
6376
|
+
faces = np.ascontiguousarray(faces, dtype=np.uint32)
|
|
6377
|
+
|
|
6378
|
+
# compute the texture coordinates for each vertex
|
|
6379
|
+
normals = np.zeros_like(vertices, dtype=np.float32)
|
|
6380
|
+
normals[:, 2] = 1.
|
|
6381
|
+
|
|
6382
|
+
# compute texture coordinates
|
|
6383
|
+
texCoords = vertices.copy()
|
|
6384
|
+
texCoords[:, :] += 1.0
|
|
6385
|
+
texCoords[:, :] *= 0.5
|
|
6386
|
+
|
|
6387
|
+
# scale to specified radius
|
|
6388
|
+
vertices *= radius
|
|
6389
|
+
|
|
6390
|
+
return vertices, texCoords, normals, faces
|
|
6391
|
+
|
|
6392
|
+
|
|
6393
|
+
def createAnnulus(innerRadius=0.5, outerRadius=1.0, edges=16):
|
|
6394
|
+
"""Create an annulus (ring) mesh.
|
|
6395
|
+
|
|
6396
|
+
Generates a flat ring mesh with the specified inner/outer radii and number
|
|
6397
|
+
of `edges`. The origin of the ring is located at the center. Textures
|
|
6398
|
+
coordinates will be mapped to a square which bounds the ring. Normals are
|
|
6399
|
+
perpendicular to the plane of the ring.
|
|
6400
|
+
|
|
6401
|
+
Parameters
|
|
6402
|
+
----------
|
|
6403
|
+
innerRadius, outerRadius : float
|
|
6404
|
+
Radius of the inner and outer rims of the ring in scene units.
|
|
6405
|
+
edges : int
|
|
6406
|
+
Number of segments to use to define the band of the ring. The higher the
|
|
6407
|
+
value, the rounder the ring will look.
|
|
6408
|
+
|
|
6409
|
+
Returns
|
|
6410
|
+
-------
|
|
6411
|
+
tuple
|
|
6412
|
+
Vertex attribute arrays (position, texture coordinates, and normals) and
|
|
6413
|
+
triangle indices.
|
|
6414
|
+
|
|
6415
|
+
"""
|
|
6416
|
+
# error checks
|
|
6417
|
+
if innerRadius >= outerRadius:
|
|
6418
|
+
raise ValueError("Inner radius must be less than outer.")
|
|
6419
|
+
elif outerRadius <= 0.:
|
|
6420
|
+
raise ValueError("Outer radius must be >0.")
|
|
6421
|
+
elif innerRadius < 0.:
|
|
6422
|
+
raise ValueError("Inner radius must be positive.")
|
|
6423
|
+
elif edges <= 2:
|
|
6424
|
+
raise ValueError("Number of edges must be >2.")
|
|
6425
|
+
|
|
6426
|
+
# generate inner and outer vertices
|
|
6427
|
+
nVerts = edges + 1
|
|
6428
|
+
steps = np.linspace(0, 2 * np.pi, num=nVerts, dtype=np.float32)
|
|
6429
|
+
|
|
6430
|
+
innerVerts = np.zeros((nVerts, 3), dtype=np.float32)
|
|
6431
|
+
outerVerts = np.zeros((nVerts, 3), dtype=np.float32)
|
|
6432
|
+
innerVerts[:, 0] = outerVerts[:, 0] = np.sin(steps)
|
|
6433
|
+
innerVerts[:, 1] = outerVerts[:, 1] = np.cos(steps)
|
|
6434
|
+
|
|
6435
|
+
# Keep the ring size between -1 and 1 to simplify computing texture
|
|
6436
|
+
# coordinates. We'll scale the vertices to the correct dimensions
|
|
6437
|
+
# afterwards.
|
|
6438
|
+
frac = innerRadius / float(outerRadius)
|
|
6439
|
+
innerVerts[:, :2] *= frac
|
|
6440
|
+
|
|
6441
|
+
# combine inner and outer vertex rings
|
|
6442
|
+
vertPos = np.vstack((innerVerts, outerVerts))
|
|
6443
|
+
|
|
6444
|
+
# generate faces
|
|
6445
|
+
faces = []
|
|
6446
|
+
for i in range(nVerts):
|
|
6447
|
+
faces.append([i, edges + i + 1, edges + i])
|
|
6448
|
+
faces.append([i, i + 1, edges + i + 1])
|
|
6449
|
+
|
|
6450
|
+
vertPos = np.ascontiguousarray(vertPos, dtype=np.float32)
|
|
6451
|
+
normals = np.zeros_like(vertPos, dtype=np.float32)
|
|
6452
|
+
normals[:, 2] = 1.0
|
|
6453
|
+
faces = np.ascontiguousarray(faces, dtype=np.uint32)
|
|
6454
|
+
|
|
6455
|
+
# compute texture coordinates
|
|
6456
|
+
texCoords = vertPos.copy()
|
|
6457
|
+
texCoords[:, :] += 1.0
|
|
6458
|
+
texCoords[:, :] *= 0.5
|
|
6459
|
+
|
|
6460
|
+
# scale to specified outer radius
|
|
6461
|
+
vertPos[:, :2] *= outerRadius
|
|
6462
|
+
|
|
6463
|
+
return vertPos, texCoords, normals, faces
|
|
6464
|
+
|
|
6465
|
+
|
|
6466
|
+
def createCylinder(radius=1.0, height=1.0, edges=16, stacks=1):
|
|
6467
|
+
"""Create a cylinder mesh.
|
|
6468
|
+
|
|
6469
|
+
Generate a cylinder mesh with a given `height` and `radius`. The origin of
|
|
6470
|
+
the mesh will centered on it and offset to the base. Texture coordinates
|
|
6471
|
+
will be generated allowing a texture to wrap around it.
|
|
6472
|
+
|
|
6473
|
+
Parameters
|
|
6474
|
+
----------
|
|
6475
|
+
radius : float
|
|
6476
|
+
Radius of the cylinder in scene units.
|
|
6477
|
+
height : float
|
|
6478
|
+
Height in scene units.
|
|
6479
|
+
edges : int
|
|
6480
|
+
Number of edges, the greater the number, the smoother the cylinder will
|
|
6481
|
+
appear when drawn.
|
|
6482
|
+
stacks : int
|
|
6483
|
+
Number of subdivisions along the height of cylinder to make. Setting to
|
|
6484
|
+
1 will result in vertex data only being generated for the base and end
|
|
6485
|
+
of the cylinder.
|
|
6486
|
+
|
|
6487
|
+
Returns
|
|
6488
|
+
-------
|
|
6489
|
+
tuple
|
|
6490
|
+
Vertex attribute arrays (position, texture coordinates, and normals) and
|
|
6491
|
+
triangle indices.
|
|
6492
|
+
|
|
6493
|
+
"""
|
|
6494
|
+
# generate vertex positions
|
|
6495
|
+
nEdgeVerts = edges + 1
|
|
6496
|
+
rings = stacks + 1
|
|
6497
|
+
steps = np.linspace(0, 2 * np.pi, num=nEdgeVerts)
|
|
6498
|
+
vertPos = np.zeros((nEdgeVerts, 3))
|
|
6499
|
+
vertPos[:, 0] = np.sin(steps)
|
|
6500
|
+
vertPos[:, 1] = np.cos(steps)
|
|
6501
|
+
vertPos = np.tile(vertPos, (rings, 1))
|
|
6502
|
+
|
|
6503
|
+
# apply offset in height for each stack
|
|
6504
|
+
stackHeight = np.linspace(0, height, num=rings)
|
|
6505
|
+
vertPos[:, 2] = np.repeat(stackHeight, nEdgeVerts)
|
|
6506
|
+
|
|
6507
|
+
# generate texture coordinates to they wrap around the cylinder
|
|
6508
|
+
u = np.linspace(0.0, 1.0, nEdgeVerts)
|
|
6509
|
+
v = np.linspace(1.0, 0.0, rings)
|
|
6510
|
+
uu, vv = np.meshgrid(u, v)
|
|
6511
|
+
texCoords = np.vstack([uu.ravel(), vv.ravel()]).T
|
|
6512
|
+
|
|
6513
|
+
# generate vertex normals, since our vertices all on a unit circle, we can
|
|
6514
|
+
# do a trick here
|
|
6515
|
+
normals = vertPos.copy()
|
|
6516
|
+
normals[:, 2] = 0.0
|
|
6517
|
+
|
|
6518
|
+
# create face indices
|
|
6519
|
+
faces = []
|
|
6520
|
+
for i in range(0, stacks):
|
|
6521
|
+
stackOffset = nEdgeVerts * i
|
|
6522
|
+
for j in range(nEdgeVerts):
|
|
6523
|
+
j = stackOffset + j
|
|
6524
|
+
faces.append([j, edges + j, edges + j + 1])
|
|
6525
|
+
faces.append([j, edges + j + 1, j + 1])
|
|
6526
|
+
|
|
6527
|
+
vertPos, texCoords, normals = [
|
|
6528
|
+
np.ascontiguousarray(i, dtype=np.float32) for i in (
|
|
6529
|
+
vertPos, texCoords, normals)]
|
|
6530
|
+
faces = np.ascontiguousarray(faces, dtype=np.uint32)
|
|
6531
|
+
|
|
6532
|
+
# scale the cylinder's radius and height to what the user specified
|
|
6533
|
+
vertPos[:, :2] *= radius
|
|
6534
|
+
|
|
6535
|
+
return vertPos, texCoords, normals, faces
|
|
6536
|
+
|
|
6537
|
+
|
|
6538
|
+
def transformMeshPosOri(vertices, normals, pos=(0., 0., 0.), ori=(0., 0., 0., 1.)):
|
|
6539
|
+
"""Transform a mesh.
|
|
6540
|
+
|
|
6541
|
+
Transform mesh vertices and normals to a new position and orientation using
|
|
6542
|
+
a position coordinate and rotation quaternion. Values `vertices` and
|
|
6543
|
+
`normals` must be the same shape. This is intended to be used when editing
|
|
6544
|
+
raw vertex data prior to rendering. Do not use this to change the
|
|
6545
|
+
configuration of an object while rendering.
|
|
6546
|
+
|
|
6547
|
+
Parameters
|
|
6548
|
+
----------
|
|
6549
|
+
vertices : array_like
|
|
6550
|
+
Nx3 array of vertices.
|
|
6551
|
+
normals : array_like
|
|
6552
|
+
Nx3 array of normals.
|
|
6553
|
+
pos : array_like, optional
|
|
6554
|
+
Position vector to transform mesh vertices. If Nx3, `vertices` will be
|
|
6555
|
+
transformed by corresponding rows of `pos`.
|
|
6556
|
+
ori : array_like, optional
|
|
6557
|
+
Orientation quaternion in form [x, y, z, w]. If Nx4, `vertices` and
|
|
6558
|
+
`normals` will be transformed by corresponding rows of `ori`.
|
|
6559
|
+
|
|
6560
|
+
Returns
|
|
6561
|
+
-------
|
|
6562
|
+
tuple
|
|
6563
|
+
Transformed vertices and normals.
|
|
6564
|
+
|
|
6565
|
+
Examples
|
|
6566
|
+
--------
|
|
6567
|
+
Create and re-orient a plane to face upwards::
|
|
6568
|
+
|
|
6569
|
+
vertices, textureCoords, normals, faces = createPlane()
|
|
4568
6570
|
|
|
4569
6571
|
# rotation quaternion
|
|
4570
6572
|
qr = quatFromAxisAngle((1., 0., 0.), -90.0) # -90 degrees about +X axis
|
|
@@ -4581,31 +6583,39 @@ def transformMeshPosOri(vertices, normals, pos=(0., 0., 0.), ori=(0., 0., 0., 1.
|
|
|
4581
6583
|
normals = np.ascontiguousarray(normals)
|
|
4582
6584
|
|
|
4583
6585
|
if not np.allclose(pos, [0., 0., 0.]):
|
|
4584
|
-
vertices = mt.transform(pos, ori, vertices)
|
|
6586
|
+
vertices = mt.transform(pos, ori, vertices, dtype=np.float32)
|
|
4585
6587
|
|
|
4586
6588
|
if not np.allclose(ori, [0., 0., 0., 1.]):
|
|
4587
|
-
normals = mt.applyQuat(ori, normals)
|
|
6589
|
+
normals = mt.applyQuat(ori, normals, dtype=np.float32)
|
|
4588
6590
|
|
|
4589
6591
|
return vertices, normals
|
|
4590
6592
|
|
|
4591
6593
|
|
|
6594
|
+
# ------------------------------
|
|
6595
|
+
# Mesh editing and cleanup tools
|
|
6596
|
+
# ------------------------------
|
|
6597
|
+
#
|
|
6598
|
+
|
|
4592
6599
|
def calculateVertexNormals(vertices, faces, shading='smooth'):
|
|
4593
6600
|
"""Calculate vertex normals given vertices and triangle faces.
|
|
4594
6601
|
|
|
4595
6602
|
Finds all faces sharing a vertex index and sets its normal to either
|
|
4596
6603
|
the face normal if `shading='flat'` or the average normals of adjacent
|
|
4597
|
-
faces if `shading='smooth'`.
|
|
6604
|
+
faces if `shading='smooth'`. Note, this function does not convert between
|
|
6605
|
+
flat and smooth shading. Flat shading only works correctly if each
|
|
4598
6606
|
vertex belongs to exactly one face.
|
|
4599
6607
|
|
|
4600
6608
|
The direction of the normals are determined by the winding order of
|
|
4601
|
-
triangles, assumed counter clock-wise (OpenGL default). Most model
|
|
6609
|
+
triangles, assumed counter clock-wise (OpenGL default). Most 3D model
|
|
4602
6610
|
editing software exports using this convention. If not, winding orders
|
|
4603
6611
|
can be reversed by calling::
|
|
4604
6612
|
|
|
4605
|
-
faces =
|
|
6613
|
+
faces = numpy.fliplr(faces)
|
|
4606
6614
|
|
|
4607
|
-
In some case, creases may appear if vertices are at the
|
|
4608
|
-
but do not share the same index.
|
|
6615
|
+
In some case when using 'smooth', creases may appear if vertices are at the
|
|
6616
|
+
same location, but do not share the same index. This may be desired in some
|
|
6617
|
+
cases, however one may use the :func:`smoothCreases` function computing
|
|
6618
|
+
normals to smooth out creases.
|
|
4609
6619
|
|
|
4610
6620
|
Parameters
|
|
4611
6621
|
----------
|
|
@@ -4615,7 +6625,8 @@ def calculateVertexNormals(vertices, faces, shading='smooth'):
|
|
|
4615
6625
|
Nx3 vertex indices.
|
|
4616
6626
|
shading : str, optional
|
|
4617
6627
|
Shading mode. Options are 'smooth' and 'flat'. Flat only works with
|
|
4618
|
-
meshes where no vertex index is shared across faces
|
|
6628
|
+
meshes where no vertex index is shared across faces, if not, the
|
|
6629
|
+
returned normals will be invalid.
|
|
4619
6630
|
|
|
4620
6631
|
Returns
|
|
4621
6632
|
-------
|
|
@@ -4635,7 +6646,7 @@ def calculateVertexNormals(vertices, faces, shading='smooth'):
|
|
|
4635
6646
|
# compute surface normals for all faces
|
|
4636
6647
|
faceNormals = mt.surfaceNormal(vertices[faces])
|
|
4637
6648
|
|
|
4638
|
-
normals = []
|
|
6649
|
+
normals = [] # new list of normals to return
|
|
4639
6650
|
if shading == 'flat':
|
|
4640
6651
|
for vertexIdx in np.unique(faces):
|
|
4641
6652
|
match, _ = np.where(faces == vertexIdx)
|
|
@@ -4646,7 +6657,611 @@ def calculateVertexNormals(vertices, faces, shading='smooth'):
|
|
|
4646
6657
|
match, _ = np.where(faces == vertexIdx)
|
|
4647
6658
|
normals.append(mt.vertexNormal(faceNormals[match, :]))
|
|
4648
6659
|
|
|
4649
|
-
return np.ascontiguousarray(normals) + 0.0
|
|
6660
|
+
return np.ascontiguousarray(np.vstack(normals), np.float32) + 0.0
|
|
6661
|
+
|
|
6662
|
+
|
|
6663
|
+
def mergeVertices(vertices, faces, textureCoords=None, vertDist=0.0001,
|
|
6664
|
+
texDist=0.0001):
|
|
6665
|
+
"""Simplify a mesh by removing redundant vertices.
|
|
6666
|
+
|
|
6667
|
+
This function simplifies a mesh by merging overlapping (doubled) vertices,
|
|
6668
|
+
welding together adjacent faces and removing sharp creases that appear when
|
|
6669
|
+
rendering. This is useful in cases where a mesh's triangles do not share
|
|
6670
|
+
vertices and one wishes to have it appear smoothly shaded when rendered. One
|
|
6671
|
+
can also use this function reduce the detail of a mesh, however the quality
|
|
6672
|
+
of the results may vary.
|
|
6673
|
+
|
|
6674
|
+
The position of the new vertex after merging will be the average position
|
|
6675
|
+
of the adjacent vertices. Re-indexed faces and recalculated normals are also
|
|
6676
|
+
returned with the cleaned-up vertex data. If texture coordinates are
|
|
6677
|
+
supplied, adjacent vertices will not be removed if their is a distance in
|
|
6678
|
+
texel space is greater than `texDist`. This avoids discontinuities in the
|
|
6679
|
+
texture of the simplified mesh.
|
|
6680
|
+
|
|
6681
|
+
Parameters
|
|
6682
|
+
----------
|
|
6683
|
+
vertices : ndarray
|
|
6684
|
+
Nx3 array of vertex positions.
|
|
6685
|
+
faces : ndarray
|
|
6686
|
+
Nx3 integer array of face vertex indices.
|
|
6687
|
+
textureCoords : ndarray
|
|
6688
|
+
Nx2 array of texture coordinates.
|
|
6689
|
+
vertDist : float
|
|
6690
|
+
Maximum distance between two adjacent vertices to merge in scene units.
|
|
6691
|
+
texDist : float
|
|
6692
|
+
Maximum distance between texels to permit merging of vertices. If a
|
|
6693
|
+
vertex is within merging distance, it will be moved instead of merged.
|
|
6694
|
+
|
|
6695
|
+
Returns
|
|
6696
|
+
-------
|
|
6697
|
+
tuple
|
|
6698
|
+
Tuple containing newly computed vertices, normals and face indices. If
|
|
6699
|
+
`textureCoords` was specified, a new array of texture coordinates will
|
|
6700
|
+
be returned too at the second index.
|
|
6701
|
+
|
|
6702
|
+
Notes
|
|
6703
|
+
-----
|
|
6704
|
+
* This function only works on meshes consisting of triangle faces.
|
|
6705
|
+
|
|
6706
|
+
Examples
|
|
6707
|
+
--------
|
|
6708
|
+
Remove redundant vertices from a sphere::
|
|
6709
|
+
|
|
6710
|
+
vertices, textureCoords, normals, faces = gltools.createUVSphere()
|
|
6711
|
+
vertices, textureCoords, normals, faces = gltools.removeDoubles(
|
|
6712
|
+
vertices, faces, textureCoords)
|
|
6713
|
+
|
|
6714
|
+
Same but no texture coordinates are specified::
|
|
6715
|
+
|
|
6716
|
+
vertices, normals, faces = gltools.removeDoubles(vertices, faces)
|
|
6717
|
+
|
|
6718
|
+
"""
|
|
6719
|
+
# keep track of vertices that we merged
|
|
6720
|
+
vertsProcessed = np.zeros((vertices.shape[0],), dtype=np.bool)
|
|
6721
|
+
|
|
6722
|
+
faces = faces.flatten() # existing faces but flattened
|
|
6723
|
+
# new array of faces that will get updated
|
|
6724
|
+
newFaces = np.zeros_like(faces, dtype=np.uint32)
|
|
6725
|
+
|
|
6726
|
+
# loop over all vertices in the original mesh
|
|
6727
|
+
newVerts = []
|
|
6728
|
+
newTexCoords = []
|
|
6729
|
+
lastProcIdx = 0 # last index processed, used to reindex
|
|
6730
|
+
for i, vertex in enumerate(vertices):
|
|
6731
|
+
if vertsProcessed[i]: # don't do merge check if already processed
|
|
6732
|
+
continue
|
|
6733
|
+
|
|
6734
|
+
# get the distance to all other vertices in mesh
|
|
6735
|
+
vertDists = mt.distance(vertex, vertices)
|
|
6736
|
+
|
|
6737
|
+
# get vertices that fall with the threshold distance
|
|
6738
|
+
toProcess = np.where(vertDists <= vertDist)[0]
|
|
6739
|
+
|
|
6740
|
+
# if all adjacent vertices were processed, move on to the next
|
|
6741
|
+
if np.all(vertsProcessed[toProcess]):
|
|
6742
|
+
continue
|
|
6743
|
+
|
|
6744
|
+
# if we have close verts and they have not been processed, merge them
|
|
6745
|
+
if len(toProcess) > 1:
|
|
6746
|
+
# If we have texture coords, merge those whose texture coords are
|
|
6747
|
+
# close. Move the vertex to the new location for any that are not.
|
|
6748
|
+
if textureCoords is not None:
|
|
6749
|
+
# create a new vertex by averaging out positions
|
|
6750
|
+
texCoordDists = mt.distance(textureCoords[i, :],
|
|
6751
|
+
textureCoords[toProcess, :])
|
|
6752
|
+
|
|
6753
|
+
# get the vertices to merge or move
|
|
6754
|
+
toMerge = toProcess[texCoordDists <= texDist]
|
|
6755
|
+
toMove = toProcess[texCoordDists > texDist]
|
|
6756
|
+
|
|
6757
|
+
# compute mean positions
|
|
6758
|
+
newPos = np.mean(vertices[toMerge, :], axis=0)
|
|
6759
|
+
newTexCoord = np.mean(textureCoords[toMerge, :], axis=0)
|
|
6760
|
+
|
|
6761
|
+
newVerts.append(newPos)
|
|
6762
|
+
newTexCoords.append(newTexCoord)
|
|
6763
|
+
newFaces[np.in1d(faces, toMerge).nonzero()[0]] = lastProcIdx
|
|
6764
|
+
|
|
6765
|
+
# handle vertices that were moved
|
|
6766
|
+
for j, idx in enumerate(toMove):
|
|
6767
|
+
newVerts.append(newPos)
|
|
6768
|
+
newTexCoords.append(textureCoords[idx, :])
|
|
6769
|
+
newFaces[np.argwhere(faces == idx)] = lastProcIdx + j
|
|
6770
|
+
|
|
6771
|
+
lastProcIdx += len(toMove)
|
|
6772
|
+
|
|
6773
|
+
vertsProcessed[toProcess] = 1 # update verts we processed
|
|
6774
|
+
|
|
6775
|
+
else:
|
|
6776
|
+
newPos = np.mean(vertices[toProcess, :], axis=0)
|
|
6777
|
+
newVerts.append(newPos)
|
|
6778
|
+
newFaces[np.in1d(faces, toProcess).nonzero()[0]] = lastProcIdx
|
|
6779
|
+
vertsProcessed[toProcess] = 1 # update verts we processed
|
|
6780
|
+
|
|
6781
|
+
else:
|
|
6782
|
+
# single vertices need to be added too
|
|
6783
|
+
newVerts.append(vertex)
|
|
6784
|
+
if textureCoords is not None:
|
|
6785
|
+
newTexCoords.append(textureCoords[i, :])
|
|
6786
|
+
vertsProcessed[i] = 1 # update merged list
|
|
6787
|
+
newFaces[np.argwhere(faces == i)] = lastProcIdx
|
|
6788
|
+
|
|
6789
|
+
lastProcIdx += 1
|
|
6790
|
+
|
|
6791
|
+
# all vertices have been processed, exit loop early
|
|
6792
|
+
if np.all(vertsProcessed):
|
|
6793
|
+
break
|
|
6794
|
+
|
|
6795
|
+
# create new output arrays
|
|
6796
|
+
newVerts = np.ascontiguousarray(np.vstack(newVerts), dtype=np.float32)
|
|
6797
|
+
newFaces = np.ascontiguousarray(newFaces.reshape((-1, 3)), dtype=np.uint32)
|
|
6798
|
+
newNormals = calculateVertexNormals(newVerts, newFaces, 'smooth')
|
|
6799
|
+
|
|
6800
|
+
if textureCoords is not None:
|
|
6801
|
+
newTexCoords = np.ascontiguousarray(
|
|
6802
|
+
np.vstack(newTexCoords), dtype=np.float32)
|
|
6803
|
+
toReturn = (newVerts, newTexCoords, newNormals, newFaces)
|
|
6804
|
+
else:
|
|
6805
|
+
toReturn = (newVerts, newNormals, newFaces)
|
|
6806
|
+
|
|
6807
|
+
return toReturn
|
|
6808
|
+
|
|
6809
|
+
|
|
6810
|
+
def smoothCreases(vertices, normals, vertDist=0.0001):
|
|
6811
|
+
"""Remove creases caused by misaligned surface normals.
|
|
6812
|
+
|
|
6813
|
+
A problem arises where surface normals are not correctly interpolated across
|
|
6814
|
+
the edge where two faces meet, resulting in a visible 'crease' or sharp
|
|
6815
|
+
discontinuity in shading. This is usually caused by the normals of the
|
|
6816
|
+
overlapping vertices forming the edge being mis-aligned (not pointing in the
|
|
6817
|
+
same direction).
|
|
6818
|
+
|
|
6819
|
+
If you notice these crease artifacts are present in your mesh *after*
|
|
6820
|
+
computing surface normals, you can use this function to smooth them out.
|
|
6821
|
+
|
|
6822
|
+
Parameters
|
|
6823
|
+
----------
|
|
6824
|
+
vertices : ndarray
|
|
6825
|
+
Nx3 array of vertex coordinates.
|
|
6826
|
+
normals : ndarray
|
|
6827
|
+
Nx3 array of vertex normals.
|
|
6828
|
+
vertDist : float
|
|
6829
|
+
Maximum distance between vertices to average. Avoid using large numbers
|
|
6830
|
+
here, vertices to be smoothed should be overlapping. This distance
|
|
6831
|
+
should be as small as possible, just enough to account for numeric
|
|
6832
|
+
rounding errors between vertices intended which would otherwise be at
|
|
6833
|
+
the exact same location.
|
|
6834
|
+
|
|
6835
|
+
Returns
|
|
6836
|
+
-------
|
|
6837
|
+
ndarray
|
|
6838
|
+
Array of smoothed surface normals with the same shape as `normals`.
|
|
6839
|
+
|
|
6840
|
+
"""
|
|
6841
|
+
newNormals = normals.copy()
|
|
6842
|
+
|
|
6843
|
+
# keep track of vertices that we processed
|
|
6844
|
+
vertsProcessed = np.zeros((vertices.shape[0],), dtype=np.bool)
|
|
6845
|
+
|
|
6846
|
+
for i, vertex in enumerate(vertices):
|
|
6847
|
+
if vertsProcessed[i]: # don't do merge check if already processed
|
|
6848
|
+
continue
|
|
6849
|
+
|
|
6850
|
+
# get the distance to all other vertices in mesh
|
|
6851
|
+
dist = mt.distance(vertex, vertices)
|
|
6852
|
+
|
|
6853
|
+
# get vertices that fall with the threshold distance
|
|
6854
|
+
adjacentIdx = list(np.where(dist <= vertDist)[0])
|
|
6855
|
+
|
|
6856
|
+
if np.all(vertsProcessed[adjacentIdx]):
|
|
6857
|
+
continue
|
|
6858
|
+
|
|
6859
|
+
# now get their normals and average them
|
|
6860
|
+
if len(adjacentIdx) > 1:
|
|
6861
|
+
toAverage = vertices[adjacentIdx, :]
|
|
6862
|
+
newNormal = np.mean(toAverage, axis=0)
|
|
6863
|
+
|
|
6864
|
+
# normalize
|
|
6865
|
+
newNormal = mt.normalize(newNormal, out=newNormal)
|
|
6866
|
+
|
|
6867
|
+
# overwrite the normals we used to compute this one
|
|
6868
|
+
newNormals[adjacentIdx, :] = newNormal
|
|
6869
|
+
|
|
6870
|
+
# flag these normals as used
|
|
6871
|
+
vertsProcessed[adjacentIdx] = 1
|
|
6872
|
+
|
|
6873
|
+
return newNormals
|
|
6874
|
+
|
|
6875
|
+
|
|
6876
|
+
def flipFaces(normals, faces):
|
|
6877
|
+
"""Change the winding order of face indices.
|
|
6878
|
+
|
|
6879
|
+
OpenGL uses the winding order of face vertices to determine which side of
|
|
6880
|
+
the face is either the front and back. This function reverses the winding
|
|
6881
|
+
order of face indices and flips vertex normals so faces can be correctly
|
|
6882
|
+
shaded.
|
|
6883
|
+
|
|
6884
|
+
Parameters
|
|
6885
|
+
----------
|
|
6886
|
+
normals : ndarray
|
|
6887
|
+
Nx3 array of surface normals.
|
|
6888
|
+
faces : ndarray
|
|
6889
|
+
Nx3 array of face indices.
|
|
6890
|
+
|
|
6891
|
+
Returns
|
|
6892
|
+
-------
|
|
6893
|
+
ndarray
|
|
6894
|
+
Face indices with winding order reversed.
|
|
6895
|
+
|
|
6896
|
+
Examples
|
|
6897
|
+
--------
|
|
6898
|
+
Flip faces and normals of a box mesh so it can be viewed and lit from the
|
|
6899
|
+
inside::
|
|
6900
|
+
|
|
6901
|
+
vertices, texCoords, normals, faces = createBox((5, 5, 5))
|
|
6902
|
+
faces = flipFaces(faces)
|
|
6903
|
+
|
|
6904
|
+
"""
|
|
6905
|
+
normals = -normals # invert normal vectors
|
|
6906
|
+
faces = np.fliplr(faces)
|
|
6907
|
+
|
|
6908
|
+
return normals, faces
|
|
6909
|
+
|
|
6910
|
+
|
|
6911
|
+
def interleaveAttributes(attribArrays):
|
|
6912
|
+
"""Interleave vertex attributes into a single array.
|
|
6913
|
+
|
|
6914
|
+
Interleave vertex attributes into a single array for use with OpenGL's
|
|
6915
|
+
vertex array objects. This function is useful when creating a VBO from
|
|
6916
|
+
separate arrays of vertex positions, normals, and texture coordinates.
|
|
6917
|
+
|
|
6918
|
+
Parameters
|
|
6919
|
+
----------
|
|
6920
|
+
attribArrays : list of array_like
|
|
6921
|
+
List of arrays containing vertex attributes. Each array must have the
|
|
6922
|
+
same number of rows.
|
|
6923
|
+
|
|
6924
|
+
Returns
|
|
6925
|
+
-------
|
|
6926
|
+
tuple
|
|
6927
|
+
A tuple containing the interleaved vertex attribute array, a list of
|
|
6928
|
+
attribute sizes, and a list of attribute offsets.
|
|
6929
|
+
|
|
6930
|
+
Examples
|
|
6931
|
+
--------
|
|
6932
|
+
Interleave vertex attributes for use with a VAO::
|
|
6933
|
+
|
|
6934
|
+
vertices, textureCoords, normals, faces = createBox()
|
|
6935
|
+
interleaved, sizes, offsets = interleaveAttributes(
|
|
6936
|
+
[vertices, textureCoords, normals])
|
|
6937
|
+
|
|
6938
|
+
# create a VBO with interleaved attributes
|
|
6939
|
+
vboInterleaved = createVBO(interleaved)
|
|
6940
|
+
|
|
6941
|
+
# ... before rendering, set the attribute pointers
|
|
6942
|
+
GL.glBindBuffer(vboInterleaved.target, vboInterleaved.name)
|
|
6943
|
+
for i, attrib in enumerate([0, 8, 3]):
|
|
6944
|
+
gltools.setVertexAttribPointer(
|
|
6945
|
+
attrib, vboInterleaved, size=sizes[i], offset=offsets[i])
|
|
6946
|
+
|
|
6947
|
+
"""
|
|
6948
|
+
# get the number of rows in the first array
|
|
6949
|
+
nRows = attribArrays[0].shape[0]
|
|
6950
|
+
|
|
6951
|
+
# check if all arrays have the same number of rows
|
|
6952
|
+
if any([i.shape[0] != nRows for i in attribArrays]):
|
|
6953
|
+
raise ValueError("All arrays must have the same number of rows.")
|
|
6954
|
+
|
|
6955
|
+
# get a list of attribute widths
|
|
6956
|
+
sizes = [i.shape[1] for i in attribArrays]
|
|
6957
|
+
offsets = [0] + [sum(sizes[:i]) for i in range(1, len(sizes))]
|
|
6958
|
+
|
|
6959
|
+
# combine all arrays horizontally
|
|
6960
|
+
toReturn = np.hstack(attribArrays)
|
|
6961
|
+
|
|
6962
|
+
return np.ascontiguousarray(toReturn, dtype=np.float32), sizes, offsets
|
|
6963
|
+
|
|
6964
|
+
|
|
6965
|
+
def generateTexCoords(vertices):
|
|
6966
|
+
"""Generate texture coordinates for a mesh.
|
|
6967
|
+
|
|
6968
|
+
Generate texture coordinates for a mesh by normalizing the vertex positions
|
|
6969
|
+
to the bounding box of the mesh.
|
|
6970
|
+
|
|
6971
|
+
Parameters
|
|
6972
|
+
----------
|
|
6973
|
+
vertices : ndarray
|
|
6974
|
+
Nx2 or Nx3 array of vertex positions.
|
|
6975
|
+
|
|
6976
|
+
Returns
|
|
6977
|
+
-------
|
|
6978
|
+
ndarray
|
|
6979
|
+
Nx2 array of normalized texture coordinates.
|
|
6980
|
+
|
|
6981
|
+
Examples
|
|
6982
|
+
--------
|
|
6983
|
+
Generate texture coordinates for a box mesh::
|
|
6984
|
+
|
|
6985
|
+
vertices, textureCoords, normals, faces = createBox()
|
|
6986
|
+
texCoords = generateTexCoords(vertices)
|
|
6987
|
+
|
|
6988
|
+
"""
|
|
6989
|
+
# normalize the vertex positions to the bounding box
|
|
6990
|
+
texCoords = (vertices - np.min(vertices, axis=0)) / np.ptp(vertices, axis=0)
|
|
6991
|
+
|
|
6992
|
+
return np.ascontiguousarray(texCoords, dtype=np.float32)
|
|
6993
|
+
|
|
6994
|
+
|
|
6995
|
+
def tesselate(points, mode='triangle', config=None):
|
|
6996
|
+
"""Tesselate (or fill) a 2D polygon edge loop.
|
|
6997
|
+
|
|
6998
|
+
Tesselate a 2D polygon defined by a set of vertices. This creates a 'filled'
|
|
6999
|
+
polygon where the vertices are connected by a set of triangles. The function
|
|
7000
|
+
will return a tesselated mesh with vertex positions, surface normals, and
|
|
7001
|
+
face indices. Texture coordinates are generated by normalizing the vertex
|
|
7002
|
+
positions.
|
|
7003
|
+
|
|
7004
|
+
Parameters
|
|
7005
|
+
----------
|
|
7006
|
+
points : ndarray
|
|
7007
|
+
Nx2 array of vertex positions of the outer boundary.
|
|
7008
|
+
mode : str, optional
|
|
7009
|
+
Tesselation mode (algorithm) to use. Options are 'triangle', 'delaunay',
|
|
7010
|
+
'fan' or 'simple'. Default is 'triangle' (recommended) which is the most
|
|
7011
|
+
robust method that can handle concave polygons and holes. Use 'fan' for
|
|
7012
|
+
equilateral polygons.
|
|
7013
|
+
config : dict, optional
|
|
7014
|
+
Configuration options for the tesselation. This can be used to specify
|
|
7015
|
+
additional options for the tesselation algorithm.
|
|
7016
|
+
|
|
7017
|
+
Returns
|
|
7018
|
+
-------
|
|
7019
|
+
tuple
|
|
7020
|
+
A tuple containing the vertex positions, surface normals, texture
|
|
7021
|
+
coordinates, and face indices of the tesselated mesh.
|
|
7022
|
+
|
|
7023
|
+
Examples
|
|
7024
|
+
--------
|
|
7025
|
+
Tesselate a simple square::
|
|
7026
|
+
|
|
7027
|
+
vertices = np.array([[0, 0], [1, 0], [1, 1], [0, 1]])
|
|
7028
|
+
vertices, normals, texCoords, faces = tesselate(points)
|
|
7029
|
+
|
|
7030
|
+
Notes
|
|
7031
|
+
-----
|
|
7032
|
+
* The 'triangle' mode uses the Triangle library (via MeshPy) to tesselate
|
|
7033
|
+
the polygon, see: https://www.cs.cmu.edu/~quake/triangle.html
|
|
7034
|
+
* The 'delaunay' mode uses the `scipy.spatial.Delaunay` for tesselation
|
|
7035
|
+
(using the Qhull library). However, it cannot handle holes or concave
|
|
7036
|
+
polygons.
|
|
7037
|
+
* The 'fan' mode creates a fan tesselation where the first vertex is the
|
|
7038
|
+
center of the fan and the rest are the outer boundary. This is useful for
|
|
7039
|
+
creating simple, nonconcave shapes like circles.
|
|
7040
|
+
|
|
7041
|
+
"""
|
|
7042
|
+
config = config or dict() # ensure we have a config dictionary
|
|
7043
|
+
|
|
7044
|
+
nPoints = len(points)
|
|
7045
|
+
if nPoints < 3:
|
|
7046
|
+
raise ValueError(
|
|
7047
|
+
"At least 3 points are required to tesselate a polygon.")
|
|
7048
|
+
elif nPoints == 3:
|
|
7049
|
+
# if we only have 3 points, we can't tesselate the polygon
|
|
7050
|
+
vertices = np.array(points, dtype=np.float32)
|
|
7051
|
+
faces = np.array([[0, 1, 2]], dtype=np.uint32)
|
|
7052
|
+
normals = np.ascontiguousarray(
|
|
7053
|
+
np.tile([0., 0., -1.], (faces.shape[0], 1)), dtype=np.float32)
|
|
7054
|
+
texCoords = generateTexCoords(vertices)
|
|
7055
|
+
|
|
7056
|
+
return vertices, normals, texCoords, faces
|
|
7057
|
+
|
|
7058
|
+
# perform tesselation based on the mode
|
|
7059
|
+
if mode == 'triangle' or mode == 'meshpy':
|
|
7060
|
+
# trinagulation using meshpy (triangle)
|
|
7061
|
+
from meshpy.triangle import MeshInfo, build
|
|
7062
|
+
|
|
7063
|
+
# create a mesh info object
|
|
7064
|
+
meshInfo = MeshInfo()
|
|
7065
|
+
meshInfo.set_points(points)
|
|
7066
|
+
|
|
7067
|
+
# compute facets
|
|
7068
|
+
facetEnd = len(points) - 1
|
|
7069
|
+
facets = [(i, i + 1) for i in range(0, facetEnd)] + [(facetEnd, 0)]
|
|
7070
|
+
meshInfo.set_facets(facets)
|
|
7071
|
+
|
|
7072
|
+
# build the mesh
|
|
7073
|
+
mesh = build(meshInfo, **config)
|
|
7074
|
+
|
|
7075
|
+
# get the vertices and faces
|
|
7076
|
+
vertices = mesh.points
|
|
7077
|
+
faces = mesh.elements
|
|
7078
|
+
|
|
7079
|
+
elif mode == 'fan' or mode == 'equilateral':
|
|
7080
|
+
# This mode creates a fan tesselation where the first vertex is the
|
|
7081
|
+
# centroid of the polygon and the rest are the outer boundary. Works
|
|
7082
|
+
# well for equilateral polygons that have not concavities. This is
|
|
7083
|
+
# probably the fastest method to tesselate an equilateral polygon with
|
|
7084
|
+
# good results.
|
|
7085
|
+
|
|
7086
|
+
# find the centroid of the polygon
|
|
7087
|
+
centroid = np.mean(points, axis=0)
|
|
7088
|
+
|
|
7089
|
+
# add the centroid to the list of vertices
|
|
7090
|
+
vertices = np.vstack((centroid, points))
|
|
7091
|
+
|
|
7092
|
+
# generate faces
|
|
7093
|
+
faces = np.zeros((len(points), 3), dtype=np.uint32)
|
|
7094
|
+
faces[:, 0] = 0
|
|
7095
|
+
faces[:, 1] = np.arange(1, len(points) + 1)
|
|
7096
|
+
faces[:, 2] = np.roll(faces[:, 1], 1)
|
|
7097
|
+
|
|
7098
|
+
elif mode == 'delaunay' or mode == 'scipy':
|
|
7099
|
+
# Delaunay triangulation using scipy. This triangulates the polygon
|
|
7100
|
+
# using the Qhull library. This method is reasonably fast however it
|
|
7101
|
+
# outputs the convex hull of the polygon which may not be desired.
|
|
7102
|
+
|
|
7103
|
+
# do a delaunay triangulation
|
|
7104
|
+
from scipy.spatial import Delaunay
|
|
7105
|
+
|
|
7106
|
+
# create a delaunay triangulation
|
|
7107
|
+
result = Delaunay(points, **config)
|
|
7108
|
+
|
|
7109
|
+
# get the face indices
|
|
7110
|
+
vertices = result.points
|
|
7111
|
+
faces = result.simplices
|
|
7112
|
+
|
|
7113
|
+
elif mode == 'simple':
|
|
7114
|
+
# Simple tesselation mode where the vertices are connected in a simple
|
|
7115
|
+
# fan pattern from the first vertex in the provided array. This is
|
|
7116
|
+
# useful for simple shapes like circles or squares.
|
|
7117
|
+
vertices = np.vstack((points, points[0]))
|
|
7118
|
+
faces = np.array(
|
|
7119
|
+
[[0, i + 1, i + 2] for i in range(len(points) - 1)])
|
|
7120
|
+
|
|
7121
|
+
else:
|
|
7122
|
+
raise ValueError("Invalid mode '{}' specified.".format(mode))
|
|
7123
|
+
|
|
7124
|
+
# compute surface normals for all faces
|
|
7125
|
+
vertices = np.ascontiguousarray(vertices, dtype=np.float32)
|
|
7126
|
+
faces = np.ascontiguousarray(faces, dtype=np.uint32)
|
|
7127
|
+
|
|
7128
|
+
# generate normals for each vertex, facing outwards
|
|
7129
|
+
normals = np.ascontiguousarray(
|
|
7130
|
+
np.tile([0., 0., -1.], (faces.shape[0], 1)),
|
|
7131
|
+
dtype=np.float32)
|
|
7132
|
+
|
|
7133
|
+
# generate texture coordinates which are normalized vertex positions
|
|
7134
|
+
texCoords = generateTexCoords(vertices)
|
|
7135
|
+
|
|
7136
|
+
return vertices, normals, texCoords, faces
|
|
7137
|
+
|
|
7138
|
+
|
|
7139
|
+
def mergeMeshes(meshData):
|
|
7140
|
+
"""Merge multiple meshes into a single mesh.
|
|
7141
|
+
|
|
7142
|
+
Merge multiple meshes into a single mesh by combining their vertex positions,
|
|
7143
|
+
normals, texture coordinates, and face indices.
|
|
7144
|
+
|
|
7145
|
+
Parameters
|
|
7146
|
+
----------
|
|
7147
|
+
meshData : list of tuple
|
|
7148
|
+
List of tuples containing vertex positions, normals, texture coordinates,
|
|
7149
|
+
and face indices for each mesh to merge. Each tuple must contain the
|
|
7150
|
+
vertex positions, and may optionally contain the normals and texture
|
|
7151
|
+
coordinates. The face indices must be provided.
|
|
7152
|
+
|
|
7153
|
+
Returns
|
|
7154
|
+
-------
|
|
7155
|
+
tuple
|
|
7156
|
+
A tuple containing the merged vertex positions, normals, texture
|
|
7157
|
+
coordinates, and face indices. If `normals` or `texCoords` are not
|
|
7158
|
+
provided, they will be set to `None`.
|
|
7159
|
+
|
|
7160
|
+
Examples
|
|
7161
|
+
--------
|
|
7162
|
+
Merge two meshes into a single mesh::
|
|
7163
|
+
|
|
7164
|
+
vertices1, normals1, texCoords1, faces1 = createBox()
|
|
7165
|
+
vertices2, normals2, texCoords2, faces2 = createUVSphere()
|
|
7166
|
+
|
|
7167
|
+
# merge the two meshes
|
|
7168
|
+
vertices, normals, texCoords, faces = mergeMeshes([
|
|
7169
|
+
(vertices1, normals1, texCoords1, faces1),
|
|
7170
|
+
(vertices2, normals2, texCoords2, faces2)])
|
|
7171
|
+
|
|
7172
|
+
Create a tube using primatives::
|
|
7173
|
+
|
|
7174
|
+
tubeOuter = createCylinder(1.0, 1.0, 16, 1)
|
|
7175
|
+
tubeInner = createCylinder(0.5, 1.0, 16, 1)
|
|
7176
|
+
|
|
7177
|
+
# merge the tube parts
|
|
7178
|
+
vertices, normals, texCoords, faces = mergeMeshes(
|
|
7179
|
+
tubeOuter, tubeInner])
|
|
7180
|
+
|
|
7181
|
+
"""
|
|
7182
|
+
vertices = np.ascontiguousarray(
|
|
7183
|
+
np.vstack([i[0] for i in meshData]), dtype=np.float32)
|
|
7184
|
+
|
|
7185
|
+
newFaces = []
|
|
7186
|
+
offset = 0
|
|
7187
|
+
for i in meshData:
|
|
7188
|
+
newFaces.append(i[3] + offset)
|
|
7189
|
+
offset += np.max(i[3]) + 1
|
|
7190
|
+
|
|
7191
|
+
faces = np.ascontiguousarray(np.vstack(newFaces), dtype=np.uint32)
|
|
7192
|
+
|
|
7193
|
+
# check if normals and texture coordinates are provided
|
|
7194
|
+
if all([i[1] is not None for i in meshData]):
|
|
7195
|
+
normals = np.ascontiguousarray(
|
|
7196
|
+
np.vstack([i[1] for i in meshData]), dtype=np.float32)
|
|
7197
|
+
else:
|
|
7198
|
+
normals = None
|
|
7199
|
+
|
|
7200
|
+
if all([i[2] is not None for i in meshData]):
|
|
7201
|
+
texCoords = np.ascontiguousarray(
|
|
7202
|
+
np.vstack([i[2] for i in meshData]), dtype=np.float32)
|
|
7203
|
+
else:
|
|
7204
|
+
texCoords = None
|
|
7205
|
+
|
|
7206
|
+
return vertices, normals, texCoords, faces
|
|
7207
|
+
|
|
7208
|
+
|
|
7209
|
+
def nextPowerOfTwo(value):
|
|
7210
|
+
"""Compute the next power of two for a given value.
|
|
7211
|
+
|
|
7212
|
+
Parameters
|
|
7213
|
+
----------
|
|
7214
|
+
value : int
|
|
7215
|
+
The value to compute the next power of two for.
|
|
7216
|
+
|
|
7217
|
+
Returns
|
|
7218
|
+
-------
|
|
7219
|
+
int
|
|
7220
|
+
The next power of two for the given value.
|
|
7221
|
+
|
|
7222
|
+
"""
|
|
7223
|
+
return 2 ** int(np.ceil(np.log2(value)))
|
|
7224
|
+
|
|
7225
|
+
|
|
7226
|
+
def fitTextureToPowerOfTwo(width, height, square=False):
|
|
7227
|
+
"""Determine the horizontal and vertical dimensions of a image buffer that
|
|
7228
|
+
can contain the specified width and height, fitted to the next power of two.
|
|
7229
|
+
|
|
7230
|
+
Parameters
|
|
7231
|
+
----------
|
|
7232
|
+
width : int
|
|
7233
|
+
The width of the texture in pixels.
|
|
7234
|
+
height : int
|
|
7235
|
+
The height of the texture in pixels.
|
|
7236
|
+
square : bool, optional
|
|
7237
|
+
If True, the texture will be fitted to the next power of two in both
|
|
7238
|
+
dimensions. Default is False.
|
|
7239
|
+
|
|
7240
|
+
Returns
|
|
7241
|
+
-------
|
|
7242
|
+
tuple
|
|
7243
|
+
A tuple containing the width and height of the texture fitted to the
|
|
7244
|
+
next power of two.
|
|
7245
|
+
|
|
7246
|
+
Raises
|
|
7247
|
+
------
|
|
7248
|
+
ValueError
|
|
7249
|
+
If the texture dimensions exceed the maximum texture size
|
|
7250
|
+
supported by the OpenGL implementation.
|
|
7251
|
+
|
|
7252
|
+
"""
|
|
7253
|
+
maxTexSize = getOpenGLInfo().maxTextureSize
|
|
7254
|
+
|
|
7255
|
+
newWidth = nextPowerOfTwo(width)
|
|
7256
|
+
newHeight = nextPowerOfTwo(height)
|
|
7257
|
+
|
|
7258
|
+
if newWidth > maxTexSize or newHeight > maxTexSize:
|
|
7259
|
+
raise ValueError("Texture dimensions exceed maximum texture size.")
|
|
7260
|
+
|
|
7261
|
+
if square:
|
|
7262
|
+
newWidth = newHeight = max(newWidth, newHeight)
|
|
7263
|
+
|
|
7264
|
+
return newWidth, newHeight
|
|
4650
7265
|
|
|
4651
7266
|
|
|
4652
7267
|
# -----------------------------
|
|
@@ -4718,7 +7333,7 @@ def getModelViewMatrix():
|
|
|
4718
7333
|
|
|
4719
7334
|
"""
|
|
4720
7335
|
modelview = np.zeros((4, 4), dtype=np.float32)
|
|
4721
|
-
|
|
7336
|
+
|
|
4722
7337
|
GL.glGetFloatv(GL.GL_MODELVIEW_MATRIX, modelview.ctypes.data_as(
|
|
4723
7338
|
ctypes.POINTER(ctypes.c_float)))
|
|
4724
7339
|
|
|
@@ -4746,214 +7361,5 @@ def getProjectionMatrix():
|
|
|
4746
7361
|
return proj
|
|
4747
7362
|
|
|
4748
7363
|
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
'OpenGLInfo',
|
|
4752
|
-
['vendor',
|
|
4753
|
-
'renderer',
|
|
4754
|
-
'version',
|
|
4755
|
-
'majorVersion',
|
|
4756
|
-
'minorVersion',
|
|
4757
|
-
'doubleBuffer',
|
|
4758
|
-
'maxTextureSize',
|
|
4759
|
-
'stereo',
|
|
4760
|
-
'maxSamples',
|
|
4761
|
-
'extensions',
|
|
4762
|
-
'userData'])
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
def getOpenGLInfo():
|
|
4766
|
-
"""Get general information about the OpenGL implementation on this machine.
|
|
4767
|
-
This should provide a consistent means of doing so regardless of the OpenGL
|
|
4768
|
-
interface we are using.
|
|
4769
|
-
|
|
4770
|
-
Returns are dictionary with the following fields::
|
|
4771
|
-
|
|
4772
|
-
vendor, renderer, version, majorVersion, minorVersion, doubleBuffer,
|
|
4773
|
-
maxTextureSize, stereo, maxSamples, extensions
|
|
4774
|
-
|
|
4775
|
-
Supported extensions are returned as a list in the 'extensions' field. You
|
|
4776
|
-
can check if a platform supports an extension by checking the membership of
|
|
4777
|
-
the extension name in that list.
|
|
4778
|
-
|
|
4779
|
-
Returns
|
|
4780
|
-
-------
|
|
4781
|
-
OpenGLInfo
|
|
4782
|
-
|
|
4783
|
-
"""
|
|
4784
|
-
return OpenGLInfo(getString(GL.GL_VENDOR),
|
|
4785
|
-
getString(GL.GL_RENDERER),
|
|
4786
|
-
getString(GL.GL_VERSION),
|
|
4787
|
-
getIntegerv(GL.GL_MAJOR_VERSION),
|
|
4788
|
-
getIntegerv(GL.GL_MINOR_VERSION),
|
|
4789
|
-
getIntegerv(GL.GL_DOUBLEBUFFER),
|
|
4790
|
-
getIntegerv(GL.GL_MAX_TEXTURE_SIZE),
|
|
4791
|
-
getIntegerv(GL.GL_STEREO),
|
|
4792
|
-
getIntegerv(GL.GL_MAX_SAMPLES),
|
|
4793
|
-
[i for i in getString(GL.GL_EXTENSIONS).split(' ')],
|
|
4794
|
-
dict())
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
# ---------------------
|
|
4798
|
-
# OpenGL/VRML Materials
|
|
4799
|
-
# ---------------------
|
|
4800
|
-
#
|
|
4801
|
-
# A collection of pre-defined materials for stimuli. Keep in mind that these
|
|
4802
|
-
# materials only approximate real-world equivalents. Values were obtained from
|
|
4803
|
-
# http://devernay.free.fr/cours/opengl/materials.html (08/24/18). There are four
|
|
4804
|
-
# material libraries to use, where individual material descriptors are accessed
|
|
4805
|
-
# via property names.
|
|
4806
|
-
#
|
|
4807
|
-
# Usage:
|
|
4808
|
-
#
|
|
4809
|
-
# useMaterial(metalMaterials.gold)
|
|
4810
|
-
# drawVAO(myObject)
|
|
4811
|
-
# ...
|
|
4812
|
-
#
|
|
4813
|
-
mineralMaterials = namedtuple(
|
|
4814
|
-
'mineralMaterials',
|
|
4815
|
-
['emerald', 'jade', 'obsidian', 'pearl', 'ruby', 'turquoise'])(
|
|
4816
|
-
createMaterial(
|
|
4817
|
-
[(GL.GL_AMBIENT, (0.0215, 0.1745, 0.0215, 1.0)),
|
|
4818
|
-
(GL.GL_DIFFUSE, (0.07568, 0.61424, 0.07568, 1.0)),
|
|
4819
|
-
(GL.GL_SPECULAR, (0.633, 0.727811, 0.633, 1.0)),
|
|
4820
|
-
(GL.GL_SHININESS, 0.6 * 128.0)]),
|
|
4821
|
-
createMaterial(
|
|
4822
|
-
[(GL.GL_AMBIENT, (0.135, 0.2225, 0.1575, 1.0)),
|
|
4823
|
-
(GL.GL_DIFFUSE, (0.54, 0.89, 0.63, 1.0)),
|
|
4824
|
-
(GL.GL_SPECULAR, (0.316228, 0.316228, 0.316228, 1.0)),
|
|
4825
|
-
(GL.GL_SHININESS, 0.1 * 128.0)]),
|
|
4826
|
-
createMaterial(
|
|
4827
|
-
[(GL.GL_AMBIENT, (0.05375, 0.05, 0.06625, 1.0)),
|
|
4828
|
-
(GL.GL_DIFFUSE, (0.18275, 0.17, 0.22525, 1.0)),
|
|
4829
|
-
(GL.GL_SPECULAR, (0.332741, 0.328634, 0.346435, 1.0)),
|
|
4830
|
-
(GL.GL_SHININESS, 0.3 * 128.0)]),
|
|
4831
|
-
createMaterial(
|
|
4832
|
-
[(GL.GL_AMBIENT, (0.25, 0.20725, 0.20725, 1.0)),
|
|
4833
|
-
(GL.GL_DIFFUSE, (1, 0.829, 0.829, 1.0)),
|
|
4834
|
-
(GL.GL_SPECULAR, (0.296648, 0.296648, 0.296648, 1.0)),
|
|
4835
|
-
(GL.GL_SHININESS, 0.088 * 128.0)]),
|
|
4836
|
-
createMaterial(
|
|
4837
|
-
[(GL.GL_AMBIENT, (0.1745, 0.01175, 0.01175, 1.0)),
|
|
4838
|
-
(GL.GL_DIFFUSE, (0.61424, 0.04136, 0.04136, 1.0)),
|
|
4839
|
-
(GL.GL_SPECULAR, (0.727811, 0.626959, 0.626959, 1.0)),
|
|
4840
|
-
(GL.GL_SHININESS, 0.6 * 128.0)]),
|
|
4841
|
-
createMaterial(
|
|
4842
|
-
[(GL.GL_AMBIENT, (0.1, 0.18725, 0.1745, 1.0)),
|
|
4843
|
-
(GL.GL_DIFFUSE, (0.396, 0.74151, 0.69102, 1.0)),
|
|
4844
|
-
(GL.GL_SPECULAR, (0.297254, 0.30829, 0.306678, 1.0)),
|
|
4845
|
-
(GL.GL_SHININESS, 0.1 * 128.0)])
|
|
4846
|
-
)
|
|
4847
|
-
|
|
4848
|
-
metalMaterials = namedtuple(
|
|
4849
|
-
'metalMaterials',
|
|
4850
|
-
['brass', 'bronze', 'chrome', 'copper', 'gold', 'silver'])(
|
|
4851
|
-
createMaterial(
|
|
4852
|
-
[(GL.GL_AMBIENT, (0.329412, 0.223529, 0.027451, 1.0)),
|
|
4853
|
-
(GL.GL_DIFFUSE, (0.780392, 0.568627, 0.113725, 1.0)),
|
|
4854
|
-
(GL.GL_SPECULAR, (0.992157, 0.941176, 0.807843, 1.0)),
|
|
4855
|
-
(GL.GL_SHININESS, 0.21794872 * 128.0)]),
|
|
4856
|
-
createMaterial(
|
|
4857
|
-
[(GL.GL_AMBIENT, (0.2125, 0.1275, 0.054, 1.0)),
|
|
4858
|
-
(GL.GL_DIFFUSE, (0.714, 0.4284, 0.18144, 1.0)),
|
|
4859
|
-
(GL.GL_SPECULAR, (0.393548, 0.271906, 0.166721, 1.0)),
|
|
4860
|
-
(GL.GL_SHININESS, 0.2 * 128.0)]),
|
|
4861
|
-
createMaterial(
|
|
4862
|
-
[(GL.GL_AMBIENT, (0.25, 0.25, 0.25, 1.0)),
|
|
4863
|
-
(GL.GL_DIFFUSE, (0.4, 0.4, 0.4, 1.0)),
|
|
4864
|
-
(GL.GL_SPECULAR, (0.774597, 0.774597, 0.774597, 1.0)),
|
|
4865
|
-
(GL.GL_SHININESS, 0.6 * 128.0)]),
|
|
4866
|
-
createMaterial(
|
|
4867
|
-
[(GL.GL_AMBIENT, (0.19125, 0.0735, 0.0225, 1.0)),
|
|
4868
|
-
(GL.GL_DIFFUSE, (0.7038, 0.27048, 0.0828, 1.0)),
|
|
4869
|
-
(GL.GL_SPECULAR, (0.256777, 0.137622, 0.086014, 1.0)),
|
|
4870
|
-
(GL.GL_SHININESS, 0.1 * 128.0)]),
|
|
4871
|
-
createMaterial(
|
|
4872
|
-
[(GL.GL_AMBIENT, (0.24725, 0.1995, 0.0745, 1.0)),
|
|
4873
|
-
(GL.GL_DIFFUSE, (0.75164, 0.60648, 0.22648, 1.0)),
|
|
4874
|
-
(GL.GL_SPECULAR, (0.628281, 0.555802, 0.366065, 1.0)),
|
|
4875
|
-
(GL.GL_SHININESS, 0.4 * 128.0)]),
|
|
4876
|
-
createMaterial(
|
|
4877
|
-
[(GL.GL_AMBIENT, (0.19225, 0.19225, 0.19225, 1.0)),
|
|
4878
|
-
(GL.GL_DIFFUSE, (0.50754, 0.50754, 0.50754, 1.0)),
|
|
4879
|
-
(GL.GL_SPECULAR, (0.508273, 0.508273, 0.508273, 1.0)),
|
|
4880
|
-
(GL.GL_SHININESS, 0.4 * 128.0)])
|
|
4881
|
-
)
|
|
4882
|
-
|
|
4883
|
-
plasticMaterials = namedtuple(
|
|
4884
|
-
'plasticMaterials',
|
|
4885
|
-
['black', 'cyan', 'green', 'red', 'white', 'yellow'])(
|
|
4886
|
-
createMaterial(
|
|
4887
|
-
[(GL.GL_AMBIENT, (0, 0, 0, 1.0)),
|
|
4888
|
-
(GL.GL_DIFFUSE, (0.01, 0.01, 0.01, 1.0)),
|
|
4889
|
-
(GL.GL_SPECULAR, (0.5, 0.5, 0.5, 1.0)),
|
|
4890
|
-
(GL.GL_SHININESS, 0.25 * 128.0)]),
|
|
4891
|
-
createMaterial(
|
|
4892
|
-
[(GL.GL_AMBIENT, (0, 0.1, 0.06, 1.0)),
|
|
4893
|
-
(GL.GL_DIFFUSE, (0.06, 0, 0.50980392, 1.0)),
|
|
4894
|
-
(GL.GL_SPECULAR, (0.50196078, 0.50196078, 0.50196078, 1.0)),
|
|
4895
|
-
(GL.GL_SHININESS, 0.25 * 128.0)]),
|
|
4896
|
-
createMaterial(
|
|
4897
|
-
[(GL.GL_AMBIENT, (0, 0, 0, 1.0)),
|
|
4898
|
-
(GL.GL_DIFFUSE, (0.1, 0.35, 0.1, 1.0)),
|
|
4899
|
-
(GL.GL_SPECULAR, (0.45, 0.55, 0.45, 1.0)),
|
|
4900
|
-
(GL.GL_SHININESS, 0.25 * 128.0)]),
|
|
4901
|
-
createMaterial(
|
|
4902
|
-
[(GL.GL_AMBIENT, (0, 0, 0, 1.0)),
|
|
4903
|
-
(GL.GL_DIFFUSE, (0.5, 0, 0, 1.0)),
|
|
4904
|
-
(GL.GL_SPECULAR, (0.7, 0.6, 0.6, 1.0)),
|
|
4905
|
-
(GL.GL_SHININESS, 0.25 * 128.0)]),
|
|
4906
|
-
createMaterial(
|
|
4907
|
-
[(GL.GL_AMBIENT, (0, 0, 0, 1.0)),
|
|
4908
|
-
(GL.GL_DIFFUSE, (0.55, 0.55, 0.55, 1.0)),
|
|
4909
|
-
(GL.GL_SPECULAR, (0.7, 0.7, 0.7, 1.0)),
|
|
4910
|
-
(GL.GL_SHININESS, 0.25 * 128.0)]),
|
|
4911
|
-
createMaterial(
|
|
4912
|
-
[(GL.GL_AMBIENT, (0, 0, 0, 1.0)),
|
|
4913
|
-
(GL.GL_DIFFUSE, (0.5, 0.5, 0, 1.0)),
|
|
4914
|
-
(GL.GL_SPECULAR, (0.6, 0.6, 0.5, 1.0)),
|
|
4915
|
-
(GL.GL_SHININESS, 0.25 * 128.0)])
|
|
4916
|
-
)
|
|
4917
|
-
|
|
4918
|
-
rubberMaterials = namedtuple(
|
|
4919
|
-
'rubberMaterials',
|
|
4920
|
-
['black', 'cyan', 'green', 'red', 'white', 'yellow'])(
|
|
4921
|
-
createMaterial(
|
|
4922
|
-
[(GL.GL_AMBIENT, (0.02, 0.02, 0.02, 1.0)),
|
|
4923
|
-
(GL.GL_DIFFUSE, (0.01, 0.01, 0.01, 1.0)),
|
|
4924
|
-
(GL.GL_SPECULAR, (0.4, 0.4, 0.4, 1.0)),
|
|
4925
|
-
(GL.GL_SHININESS, 0.078125 * 128.0)]),
|
|
4926
|
-
createMaterial(
|
|
4927
|
-
[(GL.GL_AMBIENT, (0, 0.05, 0.05, 1.0)),
|
|
4928
|
-
(GL.GL_DIFFUSE, (0.4, 0.5, 0.5, 1.0)),
|
|
4929
|
-
(GL.GL_SPECULAR, (0.04, 0.7, 0.7, 1.0)),
|
|
4930
|
-
(GL.GL_SHININESS, 0.078125 * 128.0)]),
|
|
4931
|
-
createMaterial(
|
|
4932
|
-
[(GL.GL_AMBIENT, (0, 0.05, 0, 1.0)),
|
|
4933
|
-
(GL.GL_DIFFUSE, (0.4, 0.5, 0.4, 1.0)),
|
|
4934
|
-
(GL.GL_SPECULAR, (0.04, 0.7, 0.04, 1.0)),
|
|
4935
|
-
(GL.GL_SHININESS, 0.078125 * 128.0)]),
|
|
4936
|
-
createMaterial(
|
|
4937
|
-
[(GL.GL_AMBIENT, (0.05, 0, 0, 1.0)),
|
|
4938
|
-
(GL.GL_DIFFUSE, (0.5, 0.4, 0.4, 1.0)),
|
|
4939
|
-
(GL.GL_SPECULAR, (0.7, 0.04, 0.04, 1.0)),
|
|
4940
|
-
(GL.GL_SHININESS, 0.078125 * 128.0)]),
|
|
4941
|
-
createMaterial(
|
|
4942
|
-
[(GL.GL_AMBIENT, (0.05, 0.05, 0.05, 1.0)),
|
|
4943
|
-
(GL.GL_DIFFUSE, (0.5, 0.5, 0.5, 1.0)),
|
|
4944
|
-
(GL.GL_SPECULAR, (0.7, 0.7, 0.7, 1.0)),
|
|
4945
|
-
(GL.GL_SHININESS, 0.078125 * 128.0)]),
|
|
4946
|
-
createMaterial(
|
|
4947
|
-
[(GL.GL_AMBIENT, (0.05, 0.05, 0, 1.0)),
|
|
4948
|
-
(GL.GL_DIFFUSE, (0.5, 0.5, 0.4, 1.0)),
|
|
4949
|
-
(GL.GL_SPECULAR, (0.7, 0.7, 0.04, 1.0)),
|
|
4950
|
-
(GL.GL_SHININESS, 0.078125 * 128.0)])
|
|
4951
|
-
)
|
|
4952
|
-
|
|
4953
|
-
# default material according to the OpenGL spec.
|
|
4954
|
-
defaultMaterial = createMaterial(
|
|
4955
|
-
[(GL.GL_AMBIENT, (0.2, 0.2, 0.2, 1.0)),
|
|
4956
|
-
(GL.GL_DIFFUSE, (0.8, 0.8, 0.8, 1.0)),
|
|
4957
|
-
(GL.GL_SPECULAR, (0.0, 0.0, 0.0, 1.0)),
|
|
4958
|
-
(GL.GL_EMISSION, (0.0, 0.0, 0.0, 1.0)),
|
|
4959
|
-
(GL.GL_SHININESS, 0)])
|
|
7364
|
+
if __name__ == "__main__":
|
|
7365
|
+
pass
|