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