diff --git a/LightRays.xcodeproj/project.pbxproj b/LightRays.xcodeproj/project.pbxproj new file mode 100644 index 0000000..cfd0d6e --- /dev/null +++ b/LightRays.xcodeproj/project.pbxproj @@ -0,0 +1,385 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 022C25F6239ADF4400E2D35D /* Source.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 022C25F4239ADF4400E2D35D /* Source.cpp */; }; + 026842D323D727A70023E614 /* ObjetDiffusant.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 026842D123D727A70023E614 /* ObjetDiffusant.cpp */; }; + 02862264240ED9750000A6ED /* Scene.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02F319B423DA713B0059D3AF /* Scene.cpp */; }; + 02AEC71D23F2CB8900816C72 /* ObjetsOptiques.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02AEC71B23F2CB8900816C72 /* ObjetsOptiques.cpp */; }; + 02B2BEE923FF44B8009FDF7F /* SceneTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02B2BEE723FF44B8009FDF7F /* SceneTest.cpp */; }; + 02C0BBF923F99485006F99C7 /* Brouillard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02C0BBF723F99485006F99C7 /* Brouillard.cpp */; }; + 02D1B5942376E96400B7FC13 /* ObjetsCourbes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02D1B5922376E96400B7FC13 /* ObjetsCourbes.cpp */; }; + 02D1B5972376EC0E00B7FC13 /* Rayon.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02D1B5952376EC0E00B7FC13 /* Rayon.cpp */; }; + 02D1B59A2377242B00B7FC13 /* Util.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02D1B5982377242B00B7FC13 /* Util.cpp */; }; + 02D1B59D237744EC00B7FC13 /* ObjetMilieux.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02D1B59B237744EC00B7FC13 /* ObjetMilieux.cpp */; }; + 02D1B5A02379ED3B00B7FC13 /* Ecran.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02D1B59E2379ED3B00B7FC13 /* Ecran.cpp */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 020AFBD8240006F6009C70B1 /* main_brouillard.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main_brouillard.cpp; sourceTree = ""; }; + 022C25F4239ADF4400E2D35D /* Source.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Source.cpp; sourceTree = ""; }; + 022C25F5239ADF4400E2D35D /* Source.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Source.h; sourceTree = ""; }; + 0231D0D223FF5743009E26E3 /* main_milieux.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main_milieux.cpp; sourceTree = ""; }; + 0266D47823A9A64D00A89D08 /* sfml_c01.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = sfml_c01.hpp; path = ../../Libs/sfml_c01.hpp; sourceTree = ""; }; + 026842D123D727A70023E614 /* ObjetDiffusant.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ObjetDiffusant.cpp; sourceTree = ""; }; + 026842D223D727A70023E614 /* ObjetDiffusant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjetDiffusant.h; sourceTree = ""; }; + 0273457B240C0E6B00C35E09 /* Makefile */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; xcLanguageSpecificationIdentifier = ""; }; + 02AEC71B23F2CB8900816C72 /* ObjetsOptiques.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ObjetsOptiques.cpp; sourceTree = ""; }; + 02AEC71C23F2CB8900816C72 /* ObjetsOptiques.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjetsOptiques.h; sourceTree = ""; }; + 02AEC71F23F33ACF00816C72 /* ObjetsCourbes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjetsCourbes.h; sourceTree = ""; }; + 02AEC72123F3619600816C72 /* main_debug.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main_debug.cpp; sourceTree = ""; }; + 02B2BEE523FEAF75009FDF7F /* main_diffus_test.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main_diffus_test.cpp; sourceTree = ""; }; + 02B2BEE723FF44B8009FDF7F /* SceneTest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SceneTest.cpp; sourceTree = ""; }; + 02B2BEE823FF44B8009FDF7F /* SceneTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SceneTest.h; sourceTree = ""; }; + 02C0BBF723F99485006F99C7 /* Brouillard.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Brouillard.cpp; sourceTree = ""; }; + 02C0BBF823F99485006F99C7 /* Brouillard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Brouillard.h; sourceTree = ""; }; + 02D1B57A23762C8200B7FC13 /* LightRays.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LightRays.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 02D1B57E23762C8300B7FC13 /* LightRays-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "LightRays-Info.plist"; sourceTree = ""; }; + 02D1B58223762C8300B7FC13 /* main_store.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = main_store.cpp; sourceTree = ""; }; + 02D1B5922376E96400B7FC13 /* ObjetsCourbes.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ObjetsCourbes.cpp; sourceTree = ""; }; + 02D1B5932376E96400B7FC13 /* Objet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Objet.h; sourceTree = ""; }; + 02D1B5952376EC0E00B7FC13 /* Rayon.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Rayon.cpp; sourceTree = ""; }; + 02D1B5962376EC0E00B7FC13 /* Rayon.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Rayon.h; sourceTree = ""; }; + 02D1B5982377242B00B7FC13 /* Util.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Util.cpp; sourceTree = ""; }; + 02D1B5992377242B00B7FC13 /* Util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Util.h; sourceTree = ""; }; + 02D1B59B237744EC00B7FC13 /* ObjetMilieux.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ObjetMilieux.cpp; sourceTree = ""; }; + 02D1B59C237744EC00B7FC13 /* ObjetMilieux.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjetMilieux.h; sourceTree = ""; }; + 02D1B59E2379ED3B00B7FC13 /* Ecran.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Ecran.cpp; sourceTree = ""; }; + 02D1B59F2379ED3B00B7FC13 /* Ecran.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Ecran.h; sourceTree = ""; }; + 02F319B423DA713B0059D3AF /* Scene.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Scene.cpp; sourceTree = ""; }; + 02F319B523DA713B0059D3AF /* Scene.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Scene.h; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 02D1B57623762C8200B7FC13 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 02D1B57023762C8200B7FC13 = { + isa = PBXGroup; + children = ( + 02D1B57C23762C8200B7FC13 /* LightRays */, + 02D1B57B23762C8200B7FC13 /* Products */, + ); + sourceTree = ""; + }; + 02D1B57B23762C8200B7FC13 /* Products */ = { + isa = PBXGroup; + children = ( + 02D1B57A23762C8200B7FC13 /* LightRays.app */, + ); + name = Products; + sourceTree = ""; + }; + 02D1B57C23762C8200B7FC13 /* LightRays */ = { + isa = PBXGroup; + children = ( + 02D1B5992377242B00B7FC13 /* Util.h */, + 02D1B5982377242B00B7FC13 /* Util.cpp */, + 02D1B5962376EC0E00B7FC13 /* Rayon.h */, + 02D1B5952376EC0E00B7FC13 /* Rayon.cpp */, + 02D1B5932376E96400B7FC13 /* Objet.h */, + 02AEC71F23F33ACF00816C72 /* ObjetsCourbes.h */, + 02D1B5922376E96400B7FC13 /* ObjetsCourbes.cpp */, + 02D1B59C237744EC00B7FC13 /* ObjetMilieux.h */, + 02D1B59B237744EC00B7FC13 /* ObjetMilieux.cpp */, + 026842D223D727A70023E614 /* ObjetDiffusant.h */, + 026842D123D727A70023E614 /* ObjetDiffusant.cpp */, + 02AEC71C23F2CB8900816C72 /* ObjetsOptiques.h */, + 02AEC71B23F2CB8900816C72 /* ObjetsOptiques.cpp */, + 02C0BBF823F99485006F99C7 /* Brouillard.h */, + 02C0BBF723F99485006F99C7 /* Brouillard.cpp */, + 022C25F5239ADF4400E2D35D /* Source.h */, + 022C25F4239ADF4400E2D35D /* Source.cpp */, + 02D1B59F2379ED3B00B7FC13 /* Ecran.h */, + 02D1B59E2379ED3B00B7FC13 /* Ecran.cpp */, + 02F319B523DA713B0059D3AF /* Scene.h */, + 02F319B423DA713B0059D3AF /* Scene.cpp */, + 02B2BEE823FF44B8009FDF7F /* SceneTest.h */, + 02B2BEE723FF44B8009FDF7F /* SceneTest.cpp */, + 02B2BEE523FEAF75009FDF7F /* main_diffus_test.cpp */, + 02D1B58223762C8300B7FC13 /* main_store.cpp */, + 020AFBD8240006F6009C70B1 /* main_brouillard.cpp */, + 0231D0D223FF5743009E26E3 /* main_milieux.cpp */, + 02AEC72123F3619600816C72 /* main_debug.cpp */, + 0266D47823A9A64D00A89D08 /* sfml_c01.hpp */, + 0273457B240C0E6B00C35E09 /* Makefile */, + 02D1B57D23762C8200B7FC13 /* Supporting Files */, + ); + path = LightRays; + sourceTree = ""; + }; + 02D1B57D23762C8200B7FC13 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 02D1B57E23762C8300B7FC13 /* LightRays-Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 02D1B57923762C8200B7FC13 /* LightRays */ = { + isa = PBXNativeTarget; + buildConfigurationList = 02D1B58F23762C8300B7FC13 /* Build configuration list for PBXNativeTarget "LightRays" */; + buildPhases = ( + 02D1B57523762C8200B7FC13 /* Sources */, + 02D1B57623762C8200B7FC13 /* Frameworks */, + 02D1B57723762C8200B7FC13 /* Resources */, + 02D1B57823762C8200B7FC13 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = LightRays; + productName = LightRays; + productReference = 02D1B57A23762C8200B7FC13 /* LightRays.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 02D1B57123762C8200B7FC13 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0620; + ORGANIZATIONNAME = xif; + TargetAttributes = { + 02D1B57923762C8200B7FC13 = { + CreatedOnToolsVersion = 6.2; + }; + }; + }; + buildConfigurationList = 02D1B57423762C8200B7FC13 /* Build configuration list for PBXProject "LightRays" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 02D1B57023762C8200B7FC13; + productRefGroup = 02D1B57B23762C8200B7FC13 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 02D1B57923762C8200B7FC13 /* LightRays */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 02D1B57723762C8200B7FC13 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 02D1B57823762C8200B7FC13 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# This shell script simply copies required SFML dylibs/frameworks into the application bundle frameworks folder.\n# If you're using static libraries (which is not recommended) you should remove this script from your project.\n\n# SETTINGS\nCMAKE_INSTALL_FRAMEWORK_PREFIX=\"/Library/Frameworks\"\nCMAKE_INSTALL_LIB_PREFIX=\"/usr/local/lib\"\nFRAMEWORKS_FULL_PATH=\"$BUILT_PRODUCTS_DIR/$FRAMEWORKS_FOLDER_PATH/\"\n\n# Are we building a project that uses frameworks or dylibs?\ncase \"$SFML_BINARY_TYPE\" in\n DYLIBS)\n frameworks=\"false\"\n ;;\n *)\n frameworks=\"true\"\n ;;\nesac\n\n# Echoes to stderr\nerror () # $* message to display\n{\n echo $* 1>&2\n exit 2\n}\n\nassert () # $1 is a boolean, $2...N is an error message\n{\n if [ $# -lt 2 ]\n then\n error \"Internal error in assert: not enough args\"\n fi\n\n if [ $1 -ne 0 ]\n then\n shift\n error \"$*\"\n fi\n}\n\nforce_remove () # $@ is some paths\n{\n test $# -ge 1\n assert $? \"force_remove() requires at least one parameter\"\n rm -fr $@\n assert $? \"couldn't remove $@\"\n}\n\ncopy () # $1 is a source, $2 is a destination\n{\n test $# -eq 2\n assert $? \"copy() requires two parameters\"\n ditto \"$1\" \"$2\"\n assert $? \"couldn't copy $1 to $2\"\n}\n\nrequire () # $1 is a SFML module like 'system' or 'audio'\n{\n dest=\"$BUILT_PRODUCTS_DIR/$FRAMEWORKS_FOLDER_PATH/\"\n\n if [ -z \"$1\" ]\n then\n error \"require() requires one parameter!\"\n else\n # clean potentially old stuff\n force_remove \"$dest/libsfml-$1\"*\n force_remove \"$dest/sfml-$1.framework\"\n\n # copy SFML libraries\n if [ \"$frameworks\" = \"true\" ]\n then\n source=\"$CMAKE_INSTALL_FRAMEWORK_PREFIX/sfml-$1.framework\"\n target=\"sfml-$1.framework\"\n elif [ \"$SFML_LINK_DYLIBS_SUFFIX\" = \"-d\" ]\n then\n source=\"$CMAKE_INSTALL_LIB_PREFIX/libsfml-$1-d.dylib\"\n target=\"`readlink $source`\"\n else\n source=\"$CMAKE_INSTALL_LIB_PREFIX/libsfml-$1.dylib\"\n target=\"`readlink $source`\"\n fi\n\n copy \"$source\" \"$dest/$target\"\n\n # copy extra dependencies\n if [ \"$1\" = \"audio\" ]\n then\n # copy \"FLAC\" \"ogg\" \"vorbis\" \"vorbisenc\" \"vorbisfile\" \"OpenAL\" frameworks too\n for f in \"FLAC\" \"ogg\" \"vorbis\" \"vorbisenc\" \"vorbisfile\" \"OpenAL\"\n do\n copy \"$CMAKE_INSTALL_FRAMEWORK_PREFIX/$f.framework\" \"$dest/$f.framework\"\n done\n elif [ \"$1\" = \"graphics\" ]\n then\n copy \"$CMAKE_INSTALL_FRAMEWORK_PREFIX/freetype.framework\" \"$dest/freetype.framework\"\n fi\n fi\n}\n\nif [ -n \"$SFML_SYSTEM\" ]\nthen\n require \"system\"\nfi\n\nif [ -n \"$SFML_AUDIO\" ]\nthen\n require \"audio\"\nfi\n\nif [ -n \"$SFML_NETWORK\" ]\nthen\n require \"network\"\nfi\n\nif [ -n \"$SFML_WINDOW\" ]\nthen\n require \"window\"\nfi\n\nif [ -n \"$SFML_GRAPHICS\" ]\nthen\n require \"graphics\"\nfi\n\n "; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 02D1B57523762C8200B7FC13 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 02D1B5942376E96400B7FC13 /* ObjetsCourbes.cpp in Sources */, + 02862264240ED9750000A6ED /* Scene.cpp in Sources */, + 026842D323D727A70023E614 /* ObjetDiffusant.cpp in Sources */, + 02D1B59D237744EC00B7FC13 /* ObjetMilieux.cpp in Sources */, + 022C25F6239ADF4400E2D35D /* Source.cpp in Sources */, + 02D1B5A02379ED3B00B7FC13 /* Ecran.cpp in Sources */, + 02C0BBF923F99485006F99C7 /* Brouillard.cpp in Sources */, + 02D1B5972376EC0E00B7FC13 /* Rayon.cpp in Sources */, + 02B2BEE923FF44B8009FDF7F /* SceneTest.cpp in Sources */, + 02D1B59A2377242B00B7FC13 /* Util.cpp in Sources */, + 02AEC71D23F2CB8900816C72 /* ObjetsOptiques.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 02D1B58D23762C8300B7FC13 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(NATIVE_ARCH_ACTUAL)"; + CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; + CLANG_CXX_LIBRARY = "libc++"; + FRAMEWORK_SEARCH_PATHS = ( + /Library/Frameworks/, + "$(inherited)", + ); + GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + HEADER_SEARCH_PATHS = ( + /usr/local/include/, + "$(inherited)", + ../../Libs/, + ); + LIBRARY_SEARCH_PATHS = ( + /usr/local/lib/, + "$(inherited)", + ); + MACOSX_DEPLOYMENT_TARGET = 10.7; + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-DSFMLC01_WINDOW_UNIT=720", + "-Dvec2_t=vec_t", + "-Dpt2_t=point_t", + "-DFONT_PATH=\\\"/Users/xif/Desktop/Code/LightRays/LightRays/DejaVuSansMono.ttf\\\"", + "-DNOSTDOPTIONAL", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "$(SFML_SYSTEM)", + "$(SFML_WINDOW)", + "$(SFML_GRAPHICS)", + "$(SFML_AUDIO)", + "$(SFML_NETWORK)", + "-lfmt", + ); + SFML_AUDIO = ""; + SFML_BINARY_TYPE = FRAMEWORKS; + SFML_GRAPHICS = "$(SFML_LINK_PREFIX) sfml-graphics$(SFML_LINK_SUFFIX)"; + SFML_LINK_DYLIBS_PREFIX = "-l"; + SFML_LINK_DYLIBS_SUFFIX = ""; + SFML_LINK_FRAMEWORKS_PREFIX = "-framework"; + SFML_LINK_FRAMEWORKS_SUFFIX = ""; + SFML_LINK_PREFIX = "$(SFML_LINK_$(SFML_BINARY_TYPE)_PREFIX)"; + SFML_LINK_SUFFIX = "$(SFML_LINK_$(SFML_BINARY_TYPE)_SUFFIX)"; + SFML_NETWORK = ""; + SFML_SYSTEM = "$(SFML_LINK_PREFIX) sfml-system$(SFML_LINK_SUFFIX)"; + SFML_WINDOW = "$(SFML_LINK_PREFIX) sfml-window$(SFML_LINK_SUFFIX)"; + SUPPORTED_PLATFORMS = macosx; + }; + name = Debug; + }; + 02D1B58E23762C8300B7FC13 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(NATIVE_ARCH_ACTUAL)"; + CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; + CLANG_CXX_LIBRARY = "libc++"; + FRAMEWORK_SEARCH_PATHS = ( + /Library/Frameworks/, + "$(inherited)", + ); + GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + HEADER_SEARCH_PATHS = ( + /usr/local/include/, + "$(inherited)", + ../../Libs/, + ); + LIBRARY_SEARCH_PATHS = ( + /usr/local/lib/, + "$(inherited)", + ); + MACOSX_DEPLOYMENT_TARGET = 10.7; + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-DSFMLC01_WINDOW_UNIT=720", + "-Dvec2_t=vec_t", + "-Dpt2_t=point_t", + "-DFONT_PATH=\\\"/Users/xif/Desktop/Code/LightRays/LightRays/DejaVuSansMono.ttf\\\"", + "-DNOSTDOPTIONAL", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "$(SFML_SYSTEM)", + "$(SFML_WINDOW)", + "$(SFML_GRAPHICS)", + "$(SFML_AUDIO)", + "$(SFML_NETWORK)", + "-lfmt", + ); + SFML_AUDIO = ""; + SFML_BINARY_TYPE = FRAMEWORKS; + SFML_GRAPHICS = "$(SFML_LINK_PREFIX) sfml-graphics$(SFML_LINK_SUFFIX)"; + SFML_LINK_DYLIBS_PREFIX = "-l"; + SFML_LINK_DYLIBS_SUFFIX = ""; + SFML_LINK_FRAMEWORKS_PREFIX = "-framework"; + SFML_LINK_FRAMEWORKS_SUFFIX = ""; + SFML_LINK_PREFIX = "$(SFML_LINK_$(SFML_BINARY_TYPE)_PREFIX)"; + SFML_LINK_SUFFIX = "$(SFML_LINK_$(SFML_BINARY_TYPE)_SUFFIX)"; + SFML_NETWORK = ""; + SFML_SYSTEM = "$(SFML_LINK_PREFIX) sfml-system$(SFML_LINK_SUFFIX)"; + SFML_WINDOW = "$(SFML_LINK_PREFIX) sfml-window$(SFML_LINK_SUFFIX)"; + SUPPORTED_PLATFORMS = macosx; + }; + name = Release; + }; + 02D1B59023762C8300B7FC13 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + INFOPLIST_FILE = "LightRays/LightRays-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; + OTHER_CFLAGS = ""; + PRODUCT_NAME = "$(TARGET_NAME)"; + WARNING_CFLAGS = "-Wall"; + }; + name = Debug; + }; + 02D1B59123762C8300B7FC13 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + INFOPLIST_FILE = "LightRays/LightRays-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; + OTHER_CFLAGS = ""; + PRODUCT_NAME = "$(TARGET_NAME)"; + WARNING_CFLAGS = "-Wall"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 02D1B57423762C8200B7FC13 /* Build configuration list for PBXProject "LightRays" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 02D1B58D23762C8300B7FC13 /* Debug */, + 02D1B58E23762C8300B7FC13 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 02D1B58F23762C8300B7FC13 /* Build configuration list for PBXNativeTarget "LightRays" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 02D1B59023762C8300B7FC13 /* Debug */, + 02D1B59123762C8300B7FC13 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 02D1B57123762C8200B7FC13 /* Project object */; +} diff --git a/LightRays.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/LightRays.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..fb7e8af --- /dev/null +++ b/LightRays.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/LightRays/Brouillard.cpp b/LightRays/Brouillard.cpp new file mode 100644 index 0000000..c170f95 --- /dev/null +++ b/LightRays/Brouillard.cpp @@ -0,0 +1,151 @@ +#include "Brouillard.h" +#include "ObjetsCourbes.h" +#include + +Objet::extension_t Objet_Brouillard::objet_extension () const { + vec_t v = { lx*reso_x/2, ly*reso_y/2 }; + return { + .pos = o + v, + .rayon = std::max(v.x, v.y) + }; +} + +// Test d'interception avec le brouillard : parcours du rayon à travers les cellules +// et décision si oui ou non on diffuserait le rayon +// +Objet::intercept_t Objet_Brouillard::essai_intercept (const Rayon& ray) const { + + if (ray.spectre.intensite_tot() < intens_cutoff) + return { .dist2 = Inf, .intercept_struct = nullptr }; + + // on test si le rayon passe sur le rectange bornant le brouillard + std::vector isects; + auto test_isect = [&] (point_t a, point_t b) { + auto isect = ObjetLigne::intersection_segment_demidroite(a, b, ray.orig, ray.dir_angle); + if (isect.has_value()) + isects.push_back(isect.value()); + }; + point_t o2 = o + vec_t{lx*reso_x,0}, + o3 = o + vec_t{lx*reso_x,ly*reso_y}, + o4 = o + vec_t{0,ly*reso_y}; + test_isect(o , o2); + test_isect(o2, o3); + test_isect(o3, o4); + test_isect(o4, o ); +// assert(isects.size() <= 2); // le rayon ne peut avoir que 1 (si source interne) ou 2 intersections (ou zéro) avec les bords + + if (isects.size() != 0) { + + float s; + float s_fin = isects[0].t_dd; + if (isects.size() == 1) { // rayon émis à l'intérieur du brouillard + s = 0; + } else { // rayon provenant de l'expérieur du brouillard + s = isects[1].t_dd; + if (s > s_fin) + std::swap(s, s_fin); + } + float ds = std::min(reso_x,reso_y) / 2; + vec_t u_ray = isects[0].u_dd; + // le rayon parcourt le brouillard jusqu'à s_fin par incréments de ds + // avec une probabilité donnée par `densit_brouillard` d'être diffusé à chaque pas, pour la méthode 1 + // et avec une probabilité de `diffus_partielle_syst_proba` + intercept_brouillard_t p; + float proba_acc = 0; + + while (s < s_fin) { + p.p_diff = ray.orig + s * u_ray; + vec_t v = p.p_diff - this->o; + p.x_diff = floorf( v.x / reso_x ); + p.y_diff = floorf( v.y / reso_y ); + if (0 <= p.x_diff and p.x_diff < (int)this->lx and 0 <= p.y_diff and p.y_diff < (int)this->ly) { + + float proba = ds / diffus_partielle_syst_libreparcours; + if (proba > 1e-5) { + // méthode par diffusion systématique partielle + proba_acc += proba; + p.fraction_transmis = std::max(0, 1 - ds/proba * this->densit_brouillard(p.x_diff, p.y_diff)); + } else { + // méthode par diffusion probabiliste complète + proba = ds * this->densit_brouillard(p.x_diff, p.y_diff); + proba_acc += proba; + p.fraction_transmis = 0; + } + bool diffuse = parcours_deterministe ? (proba_acc >= 1) : (rand01() < proba); + if (diffuse) { + // rayon diffusé + s += /*rand01() */ ds; + p.p_diff = ray.orig + s * u_ray; + return { .dist2 = s*s, + .intercept_struct = std::make_shared(std::move(p)) }; + } + + } + s += ds; + } + + } + return { .dist2 = Inf, .intercept_struct = nullptr }; +}; + +// Ré-émission du rayon intercepté par le brouillard +// +std::vector Objet_Brouillard::re_emit (const Rayon& ray, std::shared_ptr interception) { + const intercept_brouillard_t& intercept = *(intercept_brouillard_t*)interception.get(); + std::vector rayons; + + // Nombre de rayons secondaires + size_t n_re_emit = std::max(1, lroundf( + n_re_emit_par_intens * ray.spectre.intensite_tot() * (1-intercept.fraction_transmis)) + ); + float fact_I_re_emit = 1./n_re_emit; + + // On laisse vivre le rayon incident seulement si il a une intensité > 0.01 + if (intercept.fraction_transmis > 0.01) { + Rayon ray_trsm = ray; + ray_trsm.spectre.for_each([&] (float lambda, pola_t, float& I) { + I *= intercept.fraction_transmis; + }); + ray_trsm.orig = intercept.p_diff; + rayons.push_back(std::move(ray_trsm)); + + fact_I_re_emit *= 1 - intercept.fraction_transmis; + } + + // Émission des rayons secondaires + for (size_t k = 0; k < n_re_emit; k++) { + + float ang_diff = 2*M_PI * rand01(); + Rayon ray_diff; + ray_diff.orig = intercept.p_diff; + ray_diff.dir_angle = ray.dir_angle + ang_diff; + + ray_diff.spectre = ray.spectre; + ray_diff.spectre.for_each([&] (float lambda, pola_t, float& I) { + I *= this->directivite_diffus(ang_diff, lambda) * fact_I_re_emit; + }); + + rayons.push_back(std::move(ray_diff)); + } + return rayons; +} + +#include "sfml_c01.hpp" + +void Objet_Brouillard::dessiner (sf::RenderWindow& window, bool emphasize) const { + for (size_t x = 0; x < this->lx; x++) { + for (size_t y = 0; y < this->ly; y++) { + sf::RectangleShape dens; + sf::c01::setRectShape(dens, o + vec_t{reso_x*x, reso_y*y}, {reso_x, reso_y}); + dens.setFillColor(sf::Color(255,255,255, std::min(255.f, this->densit_brouillard(x,y)) )); + window.draw(dens); + } + } +} + +std::optional Objet_Brouillard::point_interception (std::shared_ptr interception) const { + if (interception) { + return ((intercept_brouillard_t*)interception.get())->p_diff; + } else + return std::nullopt; +} diff --git a/LightRays/Brouillard.h b/LightRays/Brouillard.h new file mode 100644 index 0000000..83bbbb5 --- /dev/null +++ b/LightRays/Brouillard.h @@ -0,0 +1,75 @@ +/******************************************************************************* + * Brouillards. + *******************************************************************************/ + +#ifndef _LIGHTRAYS_BROUILLARD_H_ +#define _LIGHTRAYS_BROUILLARD_H_ + +#include "Objet.h" + +//------------------------------------------------------------------------------ +// Brouillard à "cellules/voxels" de densité arbitraire. Implémentation naïve, +// loin d'être performante. + +class Objet_Brouillard : virtual public Objet { +public: + point_t o; // bottom-left corner (origine) + float reso_x, reso_y; // résolutions (taille d'une cellule du brouillard) dans les directions x et y + uint lx, ly; // taille du brouillard en nombre de cellules + std::function< float(size_t x, size_t y) > densit_brouillard; // fonction densité(x,y) du brouillard + std::function< float(float theta, float lambda) > directivite_diffus; // distribution angulaire des rayons diffusés, avec theta l'angle du rayon diffusé par rapport au rayon incident + + // Il y a deux techniques de diffusion (non équivalentes en terme de résultat) : + // - soit on diffuse totalement (en `n_re_emit_par_intens` rayons-fils, avec la directivité `directivite_diffus`) + // le rayon avec une probabilité à chaque pas donnée par la densité du brouillard + // ("diffusion browienne") + // (adapté au temps réel car peu de rayons produits, mais résultat bruité) + // - soit on diffuse partiellement (fraction donnée par la densité du brouillard, et en + // `n_re_emit_par_intens` rayons-fils, avec la directivité `directivite_diffus`) le rayon, + // mais systématiquement avec un libre parcours moyen `diffus_partielle_syst_libreparcours` + // ("diffusion à la bert-lambert") (attention, la directivité effective est `directivite_diffus` + un dirac theta=0) + // (peu bruité, mais lent car "cascade" -> nombreux rayons produits) + // La deuxième méthode est utilisée lorsque `diffus_partielle_syst_libreparcours` est non infini + // Lorsque la longueur `diffus_partielle_syst_libreparcours` n'est pas trop grande face à la taille du brouillard, + // et qu'elle est supérieure à la résolution, les deux méthodes doivent donner la même intensité + // transmise en ligne droite ("rayon non dévié") en moyenne + float n_re_emit_par_intens; + float diffus_partielle_syst_libreparcours; + + // Parcours déterministe ou non du rayon passant à traver le brouillard. Typiquement, soit + // on fait une diffusion du rayon aléatoire avec une certaine probabilité, soit on diffuse + // régulièrement le rayon avec cette même "probabilité". Les deux méthodes convergent à + // grande résolution. + bool parcours_deterministe; + + // Les rayons d'intensité < `intens_cutoff` sont ignorés (amélioration de performance) + float intens_cutoff; + + // Par défaut, méthode "brownienne", directivté uniforme, parcours aléatoire, pas de cutoff + Objet_Brouillard (point_t o, float reso_x, float reso_y, uint lx, uint ly, decltype(densit_brouillard) densit_brouillard) : + o(o), reso_x(reso_x), reso_y(reso_y), lx(lx), ly(ly), densit_brouillard(densit_brouillard), + directivite_diffus([] (float, float) { return 1.f; }), + n_re_emit_par_intens(5), diffus_partielle_syst_libreparcours(Inf), parcours_deterministe(false), intens_cutoff(0) {} + + Objet_Brouillard& operator= (const Objet_Brouillard&) = default; + Objet_Brouillard (const Objet_Brouillard&) = default; + virtual ~Objet_Brouillard () {} + + // Routines d'interception et de ré-émission + struct intercept_brouillard_t { + point_t p_diff; + int x_diff, y_diff; + float fraction_transmis; + }; + virtual intercept_t essai_intercept (const Rayon&) const override; + virtual extension_t objet_extension () const override; + virtual std::vector re_emit (const Rayon&, std::shared_ptr) override; + virtual std::optional point_interception (std::shared_ptr intercept_struct) const override; + + // Dessin + virtual void dessiner (sf::RenderWindow& window, bool emphasize) const override; + // Pas de dessin d'interception + virtual void dessiner_interception (sf::RenderWindow& window, const Rayon& ray, std::shared_ptr intercept) const override {}; +}; + +#endif diff --git a/LightRays/DejaVuSansMono.ttf b/LightRays/DejaVuSansMono.ttf new file mode 100644 index 0000000..30fce76 Binary files /dev/null and b/LightRays/DejaVuSansMono.ttf differ diff --git a/LightRays/Ecran.cpp b/LightRays/Ecran.cpp new file mode 100644 index 0000000..7eb132e --- /dev/null +++ b/LightRays/Ecran.cpp @@ -0,0 +1,142 @@ +#include "Ecran.h" +#include +#include +#include "sfml_c01.hpp" + +///------------------------ EcranLigne_Multi ------------------------/// + +EcranLigne_Multi::EcranLigne_Multi (point_t a, point_t b, float bin_dens, float lumino) : + EcranLigne_Multi (a, b, (uint16_t)(bin_dens * !(b-a)), lumino) {} + +EcranLigne_Multi::EcranLigne_Multi (point_t a, point_t b, uint16_t bins_n, float lumino) : + Ecran_Base(lumino), + ObjetLigne(a, b), + bins_intensit( std::max(1, bins_n) ), + epaisseur_affich(std::nullopt) + { this->reset(); } + +void EcranLigne_Multi::reset () { + n_acc = 0; + for (Specte& intensit : bins_intensit) { + intensit.for_each([&] (float, pola_t, float& I) -> void { + I = 0; + }); + } +} + +// Accumulation des rayons dans les pixels +// +std::vector EcranLigne_Multi::re_emit (const Rayon& ray, std::shared_ptr interception) { + intercept_ligne_t& intercept = *(intercept_ligne_t*)interception.get(); + size_t N = bins_intensit.size(); + ssize_t k_bin = floorf(intercept.s_incid * N); + if (k_bin == -1) k_bin = 0; + if (k_bin == (ssize_t)N) k_bin = N-1; + Specte::for_each_manual([&] (size_t i, float lambda, pola_t pol) -> void { + bins_intensit[k_bin].comps[i] += ray.spectre.comps[i]; + }); + return {}; +} + +// Retrourne la matrice de pixels traitée +// +std::vector EcranLigne_Multi::matrice_pixels () const { + size_t N = this->bins_intensit.size(); + // dans chaque pixel, c'est une puissance qu'on accumule, proportionnelle + // à la taille d'un pixel ( |b-a|/N ) pour une source omnidirectionnelle + // -> il faut diviser par la taille d'un pixel pour avoir d'intensité + // lumineuse (dont la luminosité RGB affichée est proportionnelle) + float ItoL = this->luminosite / this->n_acc * (float)N / !(b-a); + std::vector mat (N); + for (size_t k = 0; k < N; k++) { + mat[k].s1 = k / (float)N; + mat[k].s2 = (k+1) / (float)N; + mat[k].s_mid = (mat[k].s1 + mat[k].s2) / 2; + mat[k].spectre = bins_intensit[k]; + mat[k].spectre.for_each([&] (float, pola_t, float& I) -> void { + I *= ItoL; + }); + std::tie(mat[k].r, mat[k].g, mat[k].b, mat[k].sat) = mat[k].spectre.rgb256_noir_intensite(false); + } + return mat; +} + +// Dessin de la matrice de pixels +// +void EcranLigne_Multi::dessiner (sf::RenderWindow& window, bool emphasize) const { + std::vector pix = + this->matrice_pixels(); + vec_t v = a - b; + auto p = [&] (float s) { return b + v * s; }; + if (epaisseur_affich.has_value()) { + vec_t perp = v.rotate_p90() / (!v) * (*epaisseur_affich); + for (size_t k = 0; k < pix.size(); k++) { + auto color = sf::Color(pix[k].r, pix[k].g, pix[k].b); + vec_t lng = v * (pix[k].s2 - pix[k].s1); + sf::ConvexShape rect = sf::c01::buildParallelogram(p(pix[k].s1), lng, perp, color); + window.draw(rect); + } + } else { + for (size_t k = 0; k < pix.size(); k++) { + if (pix[k].sat) { // saturation + auto c = sf::c01::buildCircleShapeCR(p(pix[k].s_mid), 0.003); + c.setFillColor(sf::Color::Yellow); + window.draw(c); + } + auto color = sf::Color(pix[k].r, pix[k].g, pix[k].b); + auto line = sf::c01::buildLine(p(pix[k].s1), p(pix[k].s2), color, color); + window.draw(line); + } + } +} + +///------------------------ EcranLigne_Mono ------------------------/// + +EcranLigne_Mono::EcranLigne_Mono (point_t a, point_t b, float lumino) : + Ecran_Base(lumino), + ObjetLigne(a, b), + intensit() + { this->reset(); } + +void EcranLigne_Mono::reset () { + n_acc = 0; + intensit.for_each([&] (float, pola_t, float& I) -> void { + I = 0; + }); +} + +// Accumulations des rayons sur l'écran +// +std::vector EcranLigne_Mono::re_emit (const Rayon& ray, std::shared_ptr) { + Specte::for_each_manual([&] (size_t i, float lambda, pola_t pol) -> void { + intensit.comps[i] += ray.spectre.comps[i]; + }); + return {}; +} + +// Pixel traité +// +EcranLigne_Mono::pixel_t EcranLigne_Mono::pixel () const { + float ItoL = this->luminosite / this->n_acc / !(b-a); + pixel_t pix; + Specte sp = intensit; + sp.for_each([&] (float, pola_t, float& I) -> void { + I *= ItoL; + }); + std::tie(pix.r, pix.g, pix.b, pix.sat) = sp.rgb256_noir_intensite(false); + return pix; +} + +// Dessin du pixel +// +void EcranLigne_Mono::dessiner (sf::RenderWindow& window, bool emphasize) const { + pixel_t pix = this->pixel(); + if (pix.sat) { + auto c = sf::c01::buildCircleShapeCR(milieu_2points(a,b), 0.003); + c.setFillColor(sf::Color::Yellow); + window.draw(c); + } + auto color = sf::Color(pix.r, pix.g, pix.b); + auto line = sf::c01::buildLine(a, b, color, color); + window.draw(line); +} diff --git a/LightRays/Ecran.h b/LightRays/Ecran.h new file mode 100644 index 0000000..0920525 --- /dev/null +++ b/LightRays/Ecran.h @@ -0,0 +1,83 @@ +/******************************************************************************* + * Écrans : objets absorbant les rayons et accumulant l'intensité pour + * l'afficher ou l'enregistrer ("CCD"). + *******************************************************************************/ + +#ifndef _LIGHTRAYS_ECRAN_H_ +#define _LIGHTRAYS_ECRAN_H_ + +#include "ObjetsCourbes.h" +#include +#include "Rayon.h" + +//------------------------------------------------------------------------------ +// Classe de base virtuelle des écrans. Ne fait rien. + +class Ecran_Base : virtual public Objet { +protected: + size_t n_acc; // nombre de frames accumulées +public: + float luminosite; // coefficient de conversion intensité réelle -> intensité affichée + + Ecran_Base (float lumino = 1.) : n_acc(0), luminosite(lumino) {} + virtual ~Ecran_Base () {} + + // appellé à la fin de chaque frame + virtual void commit () { n_acc++; } + // réinitialisation de l'écran + virtual void reset () = 0; +}; + +//------------------------------------------------------------------------------ +// Écran en forme de segment. Matrice (1D) de pixels, affichable ou enregistrable. + +class EcranLigne_Multi : virtual public Ecran_Base, virtual public ObjetLigne { +protected: + std::vector bins_intensit; // matrice de pixels; un spectre par pixel +public: + std::optional epaisseur_affich = std::nullopt; // épaisseur de l'écran affiché + + // Construction à partir des points A et B et du nombre de pixels par unité de longueur + EcranLigne_Multi (point_t pos_a, point_t pos_b, float pixel_density, float lumino = 1.); + // Construction à partir des points A et B et du nombre de pixels + EcranLigne_Multi (point_t pos_a, point_t pos_b, uint16_t pixels_n, float lumino = 1.); + + virtual ~EcranLigne_Multi () {} + + // Absorption et accumulation des rayons; pas de ré-émission + virtual std::vector re_emit (const Rayon& ray, std::shared_ptr intercept) override; + // Réinitialisation de l'écran + virtual void reset () override; + + // Récupération de la matrice de pixels RGB ou spectres : + struct pixel_t { + float s1, s_mid, s2; // abscice du début, du milieu et de la fin du pixel + uint8_t r, g, b; // valeurs RGB (les mêmes qu'affichées) + bool sat; // saturation du pixel + Specte spectre; // spectre accumulé sur le pixel + }; + std::vector matrice_pixels () const; + // Dessin de cette matrice de pixels + virtual void dessiner (sf::RenderWindow& window, bool emphasize) const override; +}; + +//------------------------------------------------------------------------------ +// Écran en forme de segment avec un unique pixel + +class EcranLigne_Mono : virtual public Ecran_Base, virtual public ObjetLigne { +protected: + Specte intensit; // spectre accumulé +public: + + EcranLigne_Mono (point_t pos_a, point_t pos_b, float lumino = 1.); + virtual ~EcranLigne_Mono () {} + + virtual std::vector re_emit (const Rayon& ray, std::shared_ptr intercept) override; + virtual void reset () override; + + struct pixel_t { uint8_t r, g, b; bool sat; }; + pixel_t pixel () const; + virtual void dessiner (sf::RenderWindow& window, bool emphasize) const override; +}; + +#endif diff --git a/LightRays/LightRays-Info.plist b/LightRays/LightRays-Info.plist new file mode 100644 index 0000000..086d403 --- /dev/null +++ b/LightRays/LightRays-Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + + CFBundleIdentifier + xif.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleSignature + ???? + NSHighResolutionCapable + + + diff --git a/LightRays/Makefile b/LightRays/Makefile new file mode 100644 index 0000000..9d6aa2e --- /dev/null +++ b/LightRays/Makefile @@ -0,0 +1,29 @@ +CPPFLAGS := -O3 -Wall -DSFMLC01_WINDOW_UNIT=720 -Dvec2_t=vec_t -Dpt2_t=point_t -DFONT_PATH=\"DejaVuSansMono.ttf\" +LDFLAGS := -lsfml-graphics -lsfml-window -lsfml-system + +all: brouillard diffus_test milieux store + +COMMON := Rayon.o Util.o Scene.o SceneTest.o Ecran.o ObjetsCourbes.o Source.o ObjetsOptiques.o + +brouillard: $(COMMON) Brouillard.o ObjetDiffusant.o main_brouillard.o + g++ -o lightrays-brouillard -lm $^ $(LDFLAGS) + ./lightrays-brouillard + +diffus_test: $(COMMON) ObjetDiffusant.o main_diffus_test.o + g++ -o lightrays-diffus_test -lm $^ $(LDFLAGS) + ./lightrays-diffus_test + +milieux: $(COMMON) ObjetMilieux.o main_milieux.o + g++ -o lightrays-milieux -lm $^ $(LDFLAGS) + ./lightrays-milieux + +store: $(COMMON) ObjetDiffusant.o main_store.o + g++ -o lightrays-store -lm $^ $(LDFLAGS) + ./lightrays-store + +%.o: %.cpp + g++ -o $@ -c $< -std=c++17 $(CPPFLAGS) + +clean: + rm *.o + rm lightrays-* diff --git a/LightRays/Objet.h b/LightRays/Objet.h new file mode 100644 index 0000000..c381f45 --- /dev/null +++ b/LightRays/Objet.h @@ -0,0 +1,58 @@ +/******************************************************************************* + * Objet optique générique, capable de se dessiner, et surtout de tester + * l'interception d'un rayon, et, le cas échéant, de ré-émettre le rayon. + *******************************************************************************/ + +#ifndef _LIGHTRAYS_OBJET_H_ +#define _LIGHTRAYS_OBJET_H_ + +#include +#include "Rayon.h" +#include "Util.h" +#include +#include +// Note : tous les std::optional> pourraient être remplacés par des T* (null ou pas), +// mais il faudrait gérer la désallocation à la main + +//------------------------------------------------------------------------------ +// Classe de base des objets optiques. Déclare l'interface commune utilisée lors +// de la propagation (interception puis ré-émission) des rayons, et l'affichage. +// Un objet est capable de tester l'interception d'un rayon avec `essai_intercept`, +// et, le cas échéant, de ré-émettre le rayon `re_emit` et (optionnellement) de +// donner le point d'interception avec `point_interception`. Il est aussi capable +// de se dessiner et de donner son extension spatiale approximative. + +class Objet { +public: + + Objet () {} + Objet& operator= (const Objet&) = default; + Objet (const Objet&) = default; + virtual ~Objet () {} + + // L'objet intercepte-t-il le rayon ? Si oui, donne la distance géométrique au carré + // `dist2` parcourue par le rayon depuis son point d'émission, et renvoie une structure + // interne à utiliser éventuellement pour la ré-émission du même rayon. + // Si non, `intercept_struct` est nul et `dist2 = Inf` + // Attention, la création de std::shared_ptr est délicate (https://stackoverflow.com/questions/25858939/is-there-a-way-to-cast-shared-ptrvoid-to-shared-ptrt/25858963 et voir ObjetCourbe::essai_intercept) + struct intercept_t { float dist2; std::shared_ptr intercept_struct; }; + virtual intercept_t essai_intercept (const Rayon& ray) const = 0; + // Point d'interception. Aucune garantie, non défini par défaut. + virtual std::optional point_interception (std::shared_ptr intercept_struct) const { return std::nullopt; } + + // Extension spatiale approximative pessimiste de l'objet à fin d'optimisation + // (ne pas avoir à appeller un coûteux `essai_intercept(ray)` lorsque qu'on est + // certain que le rayon ne sera pas intercepté par l'objet) + struct extension_t { point_t pos; float rayon; }; + virtual extension_t objet_extension () const = 0; + + // Ré-émission du rayon, devant utiliser la structure `.intercept_struct` renvoyée par `essai_intercept(ray)` + virtual std::vector re_emit (const Rayon& ray, std::shared_ptr intercept) = 0; + + // Rendu graphique de l'objet + virtual void dessiner (sf::RenderWindow& window, bool emphasize) const = 0; + // Rendu graphique de l'interception d'un rayon (voir re_emit) + virtual void dessiner_interception (sf::RenderWindow& window, const Rayon& ray, std::shared_ptr intercept) const = 0; +}; + +#endif diff --git a/LightRays/ObjetDiffusant.cpp b/LightRays/ObjetDiffusant.cpp new file mode 100644 index 0000000..7afdfbc --- /dev/null +++ b/LightRays/ObjetDiffusant.cpp @@ -0,0 +1,50 @@ +#include "ObjetDiffusant.h" +#include + +decltype(ObjetCourbe_Diffusant::BRDF) ObjetCourbe_Diffusant::BRDF_Lambert = [] (float theta_i, float theta_r) -> float { + return 1; +}; + +// Diffusion du rayon incident +// +std::vector ObjetCourbe_Diffusant::re_emit (const Rayon& ray, std::shared_ptr interception) { + intercept_courbe_t& intercept = *(intercept_courbe_t*)interception.get(); + std::vector rayons; + + // nombre de rayons ré-émis selon l'intensité du rayon incident + size_t n_re_emit = std::max(1, lroundf(n_re_emit_par_intens * ray.spectre.intensite_tot())); + + for (size_t k = 0; k < n_re_emit; k++) { + + float ang_refl = 0; + switch (diff_met) { + case diffus_methode_t::AleaUnif: + ang_refl = M_PI/2 * (1-2*rand01()); break; // angles aléatoires équirépartis + case diffus_methode_t::Equirep: + ang_refl = M_PI/2 * (1-2*(k+1)/(float)(n_re_emit+1)); break; // angles déterministes équirépartis + default: + throw std::runtime_error("TODO"); + } + + Rayon ray_refl; + ray_refl.orig = intercept.p_incid; + ray_refl.dir_angle = intercept.ang_normale + ang_refl; + + // pondération de l'intensité par la BRDF en fonction de l'angle incident et réfléchi + ray_refl.spectre = ray.spectre; + if (not BRDF_lambda) { + float ampl = albedo / n_re_emit * BRDF( intercept.ang_incid, ang_refl ); + ray_refl.spectre.for_each([&] (float, pola_t, float& I) { + I *= ampl; + }); + } else { + ray_refl.spectre.for_each([&] (float lambda, pola_t, float& I) { + I *= albedo / n_re_emit * BRDF_lambda( intercept.ang_incid, ang_refl, lambda ); + }); + } + + rayons.push_back(std::move(ray_refl)); + } + + return rayons; +} diff --git a/LightRays/ObjetDiffusant.h b/LightRays/ObjetDiffusant.h new file mode 100644 index 0000000..f3903af --- /dev/null +++ b/LightRays/ObjetDiffusant.h @@ -0,0 +1,53 @@ +/******************************************************************************* + * Surfaces diffusantes à distribution de réflectivité (BRDF) arbitraire. + *******************************************************************************/ + +#ifndef _LIGHTRAYS_DIFFUS_H_ +#define _LIGHTRAYS_DIFFUS_H_ + +#include "ObjetsCourbes.h" + +class ObjetCourbe_Diffusant : virtual public ObjetCourbe { +public: + + // Méthode de tirage de rayon (même résultats en moyenne, mais performances et fluctuations différentes) + enum diffus_methode_t { + Equirep, // Équirépartition des rayons sur tous les angles i_r avec modulation d'amplitude par la BRDF + AleaUnif, // Émission aléatoire uniforme avec modulation d'amplitude par la BRDF + MonteCarlo, // Tirage de rayons d'intensité constante avec une distribution respectant la BRDF (Metropolis) + } diff_met; + // Bidirectional reflectance distribution function, possiblement dépendante de la couleur + std::function BRDF; // utilisée si `BRDF_lambda` nulle + std::function BRDF_lambda; // utilisée si non nulle + // Albedo, entre 0 et 1 (simple facteur d'intensité) + float albedo; + // Nombre moyen de rayons ré-émis par rayon incident par unité d'intensité. Doit être grand si diffus_methode_t::Equirep utilisée. + float n_re_emit_par_intens; + + static decltype(BRDF) BRDF_Lambert; // Diffusion lambertienne (isotrope <=> loi en cos(θ) <=> BRDF = 1) + static decltype(BRDF) BRDF_Oren_Nayar (float sigma); // Diffusion de Oren Nayar + + ObjetCourbe_Diffusant (decltype(BRDF) BRDF) : diff_met(AleaUnif), BRDF(BRDF), BRDF_lambda(nullptr), albedo(1), n_re_emit_par_intens(1) {} + ObjetCourbe_Diffusant& operator= (const ObjetCourbe_Diffusant&) = default; + ObjetCourbe_Diffusant (const ObjetCourbe_Diffusant&) = default; + virtual ~ObjetCourbe_Diffusant () {}; + + // Diffusion du rayon incident + virtual std::vector re_emit (const Rayon& ray, std::shared_ptr intercept) override final; +}; + +class ObjetArc_Diffusant : virtual public ObjetCourbe_Diffusant, virtual public ObjetArc { +public: + // relai des arguments aux constructeurs de ObjetArc + template ObjetArc_Diffusant (decltype(BRDF) BRDF, Args&&... x) : ObjetCourbe_Diffusant(BRDF), ObjetArc(x...) {} + virtual ~ObjetArc_Diffusant () {} +}; + +class ObjetLigne_Diffusant : virtual public ObjetCourbe_Diffusant, virtual public ObjetLigne { +public: + // relai des arguments aux constructeurs de ObjetLigne + template ObjetLigne_Diffusant (decltype(BRDF) BRDF, Args&&... x) : ObjetCourbe_Diffusant(BRDF), ObjetLigne(x...) {} + virtual ~ObjetLigne_Diffusant () {}; +}; + +#endif diff --git a/LightRays/ObjetMilieux.cpp b/LightRays/ObjetMilieux.cpp new file mode 100644 index 0000000..c8d7736 --- /dev/null +++ b/LightRays/ObjetMilieux.cpp @@ -0,0 +1,94 @@ +#include "ObjetMilieux.h" +#include + +void ObjetComposite_LignesMilieu::re_positionne (point_t o) { + std::optional trsl = std::nullopt; + for (auto& obj : this->comp) { + ObjetLigne_Milieux& ligne = *dynamic_cast(&(*obj)); + if (not trsl.has_value()) + trsl = o - ligne.a; + ligne.a = ligne.a + *trsl; + ligne.b = ligne.b + *trsl; + } +} + +// Réflexion et réfraction sur un dioptre : lois de Snell-Descartes +// et coefficients de Fresnel. Voir lois.pdf pour plus de détails. +// Deux cas : indice de réfraction indep. de λ (peu cher) et dépendant +// de λ (cher car séparation en N_COULEURS différentes) +// +std::vector ObjetCourbe_Milieux::re_emit (const Rayon& ray, std::shared_ptr interception) { + intercept_courbe_t& intercept = *(intercept_courbe_t*)interception.get(); + std::vector rays_emis; + + Rayon ray_refl; + ray_refl.orig = intercept.p_incid; + ray_refl.dir_angle = intercept.ang_normale - intercept.ang_incid; // i_refl = i par rapport à la normale + ray_refl.spectre = ray.spectre; + + auto refracte = [&intercept,&rays_emis] (Rayon& ray_refl, float n_out, float n_in) { + + float gamma = intercept.sens_reg ? n_out/n_in : n_in/n_out; // n1/n2 + + float s = gamma * sinf(intercept.ang_incid); + + if (fabsf(s) <= 1) { // on a un rayon transmis (i <= i_critique) + + Rayon ray_trsm; + ray_trsm.orig = intercept.p_incid; + ray_trsm.dir_angle = (intercept.ang_normale + M_PI) + asinf(s); + + float b = sqrtf( 1 - s*s ); + float cosi = cosf(intercept.ang_incid); + float a_TE = gamma * cosi, a_TM = cosi / gamma; + float r_coeff[2]; + r_coeff [PolTE] = (a_TE - b) / (a_TE + b); + r_coeff [PolTM] = (a_TM - b) / (a_TM + b); + + Specte::for_each_manual([&] (uint8_t i, float lambda, pola_t pol) { + float R = r_coeff[pol] * r_coeff[pol]; + ray_trsm.spectre.comps[i] = (1-R) * ray_refl.spectre.comps[i]; + ray_refl.spectre.comps[i] = R * ray_refl.spectre.comps[i]; + }); + + rays_emis.push_back(ray_trsm); + } + // sinon, pas de rayon transmis (i > i_critique), et ray_refl est déjà prêt à être envoyé + + rays_emis.push_back(ray_refl); + }; + + // indice de réfraction fixe + if (not (bool)n_lambda) { + refracte (ray_refl, /*n_out*/1., /*n_in*/this->n_fixe); + } + // indice de réfraction dépendant de la longueur d'onde + // -> séparation de toutes les composantes en rayons monochromatiques, car les directions sont différentes + else { + for (color_id_t i = 0; i < N_COULEURS; i++) { + Rayon ray_refl_mono = ray_refl; + ray_refl_mono.spectre.for_each_cid([&] (color_id_t cid, pola_t pol, float& I) { + if (cid != i) + I = 0; + }); + float n_in = n_lambda(lambda_color[i]); // variable + refracte (ray_refl_mono, /*n_out*/1., n_in); + } + } + + return rays_emis; +} + +#include "sfml_c01.hpp" + +void ObjetLigne_Milieux::dessiner (sf::RenderWindow& window, bool emphasize) const { + ObjetLigne::dessiner(window, emphasize); + sf::ConvexShape rect_milieu(4); + vec_t v_perp = vecteur_u_perp() * 0.05; + rect_milieu.setPoint(0, sf::c01::toWin(a)); + rect_milieu.setPoint(1, sf::c01::toWin(b)); + rect_milieu.setPoint(2, sf::c01::toWin(b+v_perp)); + rect_milieu.setPoint(3, sf::c01::toWin(a+v_perp)); + rect_milieu.setFillColor(sf::Color(255,255,255,20)); + window.draw(rect_milieu); +} diff --git a/LightRays/ObjetMilieux.h b/LightRays/ObjetMilieux.h new file mode 100644 index 0000000..ab263f8 --- /dev/null +++ b/LightRays/ObjetMilieux.h @@ -0,0 +1,92 @@ +/******************************************************************************* + * Objets optiques définissant différents milieux : dioptres entre deux milieux + * d'indices différents : loi de Snell-Descartes et coefficients de Fresnel + *******************************************************************************/ + +// Notes : l'incide de réfraction `n` est réel, donc pas d'absorption, pas de comportement métalique. +// De plus, les objets sont supposés être des dioptres entre du vide (n=1) et un milieu (n≠1). +// Pour faire des interfaces entre milieux, ça nécessiterait pas mal de travail. +#warning To do + +#ifndef _LIGHTRAYS_OBJET_MILIEU_H_ +#define _LIGHTRAYS_OBJET_MILIEU_H_ + +#include "ObjetsCourbes.h" + +//------------------------------------------------------------------------------ +// Dioptres de type courbe (1D) entre le vide et un milieu d'indice n, fixe +// (`n_fixe`) ou dépendnant de λ (`n_lambda`). Implémentation des lois de +// Snell-Descartes et des coefficients de Fresnel en intensité. + +class ObjetCourbe_Milieux : virtual public ObjetCourbe { +public: + + std::function n_lambda; // n(λ) /!\ coûteux -> N_COULEURS rayons réfractés à lancer + float n_fixe; // n indépendant de λ, considéré si `indice_refr_lambda` est nul + + ObjetCourbe_Milieux (float incide_refr_fixe) : ObjetCourbe(), n_lambda(nullptr), n_fixe(incide_refr_fixe) {} + ObjetCourbe_Milieux (std::function indice_refr_lambda) : ObjetCourbe(), n_lambda(indice_refr_lambda) {} + + ObjetCourbe_Milieux& operator= (const ObjetCourbe_Milieux&) = default; + ObjetCourbe_Milieux (const ObjetCourbe_Milieux&) = default; + virtual ~ObjetCourbe_Milieux () {} + + // Ré-émission du rayons intercepté en un rayon réfléchi et un rayon réfracté + virtual std::vector re_emit (const Rayon& ray, std::shared_ptr intercept) override final; +}; + +//------------------------------------------------------------------------------ +// Dioptre linéaire : ObjetLigne + ObjetCourbe_Milieux + +class ObjetLigne_Milieux : virtual public ObjetCourbe_Milieux, virtual public ObjetLigne { +public: + // relai des arguments aux contructeurs de ObjetLigne et ObjetCourbe_Milieux + // (toutes les combinaisons sont possibles) + template ObjetLigne_Milieux (incide_refr_t incide_refr, Args&&... x) : + ObjetCourbe_Milieux(incide_refr), ObjetLigne(x...) {} + + virtual ~ObjetLigne_Milieux () {} + + // Dessin (intérieur coloré) + virtual void dessiner (sf::RenderWindow& window, bool emphasize) const override; +}; + +//------------------------------------------------------------------------------ +// Dioptre en forme d'arc de cercle : ObjetArc + ObjetCourbe_Milieux + +class ObjetArc_Milieux : virtual public ObjetCourbe_Milieux, virtual public ObjetArc { +public: + // relai des arguments aux contructeurs de ObjetArc et ObjetCourbe_Milieux + template ObjetArc_Milieux (incide_refr_t incide_refr, Args&&... x) : + ObjetCourbe_Milieux(incide_refr), ObjetArc(x...) {} + + virtual ~ObjetArc_Milieux () {} +}; + +//------------------------------------------------------------------------------ +// Utilitaire : Objet composite fermé, délimité par des lignes +// `ObjetLigne_Milieux`, à partir d'une liste de points `points`, +// et d'indice de réfraction intérieur `incide_refr`. + +class ObjetComposite_LignesMilieu : virtual public ObjetComposite { +private: + template + static std::vector< std::unique_ptr > construct_liste (std::vector points, incide_refr_t n) { + std::vector< std::unique_ptr > objs; + size_t N = points.size(); + for (size_t i = 0; i < N; i++) + objs.emplace_back(new ObjetLigne_Milieux(n, points[ i ], points[ (i+1)%N ])); + return objs; + } +public: + // construction à partir de la liste de points + template ObjetComposite_LignesMilieu (std::vector points, incide_refr_t incide_refr) : + ObjetComposite(construct_liste(points,incide_refr)) {} + + virtual ~ObjetComposite_LignesMilieu () {} + + // simple translation : positionnement du premier point de la chaine en `o` + void re_positionne (point_t o); +}; + +#endif diff --git a/LightRays/ObjetsCourbes.cpp b/LightRays/ObjetsCourbes.cpp new file mode 100644 index 0000000..e433640 --- /dev/null +++ b/LightRays/ObjetsCourbes.cpp @@ -0,0 +1,302 @@ +#include "ObjetsCourbes.h" +#include +#include + +///------------------------ ObjetCourbe ------------------------/// + +#define INTERCEPTION_DIST_MINIMALE 0.0001 + +// Relai de ObjetCourbe::essai_intercept_courbe +// +Objet::intercept_t ObjetCourbe::essai_intercept (const Rayon& ray) const { + auto intercept = this->essai_intercept_courbe(ray); + if (intercept.has_value()) { + float dist2 = (ray.orig - (*intercept)->p_incid).norm2(); + // on introduit une distance minimale qu'un rayon peut parcourit avant d'être intercepté + // par une courbe pour éviter qu'un objet ré-intercepte immédiatement le rayon qu'il vient + // d'émettre, ce qui arriverait souvent en simple précision + if (dist2 < INTERCEPTION_DIST_MINIMALE*INTERCEPTION_DIST_MINIMALE) + return { .dist2 = Inf, .intercept_struct = nullptr }; + else + return { .dist2 = dist2, + .intercept_struct = std::static_pointer_cast(*intercept) }; + } else + return { .dist2 = Inf, .intercept_struct = nullptr }; +} + +std::optional ObjetCourbe::point_interception (std::shared_ptr intercept_struct) const { + if (intercept_struct) { + return ((intercept_courbe_t*)intercept_struct.get())->p_incid; + } else + return std::nullopt; +} + +///------------------------ ObjetLigne ------------------------/// + +ObjetLigne::ObjetLigne (point_t a, float l, float ang) : + a( a ), + b( a + l * vec_t{ cosf(ang), sinf(ang) } ) {} + +vec_t ObjetLigne::vecteur_u_perp () const { + vec_t v_perp = (b - a).rotate_p90(); + return v_perp / !v_perp; +} + +// Routine d'intersection segment avec demi-droite +// +std::optional ObjetLigne::intersection_segment_demidroite (point_t a, point_t b, point_t dd_orig, float angle) { + // cas particulier d'alignement non pris en compte + vec_t u_dd = { .x = cosf(angle), + .y = sinf(angle) }; + vec_t v_seg = a - b; + float s, t; + mat22_sol(v_seg.x, -u_dd.x, + v_seg.y, -u_dd.y, + dd_orig.x - b.x, dd_orig.y - b.y, + s, t); + if ((0 <= s and s <= 1) and t >= 0) { + return intersection_segdd_t{ .v_seg = v_seg, .u_dd = u_dd, .s_seg = s, .t_dd = t }; + } else + return std::nullopt; +} + +// Test d'interception du rayon sur la ligne. +// +std::optional> ObjetLigne::essai_intercept_courbe (const Rayon& ray) const { + // `intersection_segment_demidroite` n'est pas directement intégrée ici car elle sert ailleurs + auto isect = ObjetLigne::intersection_segment_demidroite(a, b, ray.orig, ray.dir_angle); + if (not isect.has_value()) + return std::nullopt; + std::shared_ptr intercept = std::make_shared(); + // point d'incidence + intercept->p_incid = ray.orig + isect->t_dd * isect->u_dd; + intercept->s_incid = isect->s_seg; + // angle d'incidence + float seg_angle = atan2f(isect->v_seg.y, isect->v_seg.x); // pourrait être calculé une bonne fois pour toutes + float alpha = angle_mod2pi_11(ray.dir_angle); + float i = alpha - (seg_angle - M_PI/2); + if (fabsf(angle_mod2pi_11(i)) < M_PI/2) { + intercept->ang_normale = seg_angle + M_PI/2; + intercept->ang_incid = i; + intercept->sens_reg = true; + } else { + intercept->ang_incid = i - M_PI; + if (intercept->ang_incid < -M_PI/2) intercept->ang_incid += 2*M_PI; + intercept->ang_normale = seg_angle - M_PI/2; + intercept->sens_reg = false; + } + return std::shared_ptr(intercept); +} + +///------------------------ ObjetArc ------------------------/// + +// Construction de l'arc de cercle à partir de deux points et du rayon +// +ObjetArc::ObjetArc (point_t a, point_t b, float radius, bool inv_interieur) : c({0,0}), R(radius), ang(0,0), inv_int(inv_interieur) { + // translation + vec_t ab = b - a; + // vérification + float ab2 = ab.norm2(); + if (ab2 > 4*R*R) + throw std::domain_error("ObjetArc(A,B,R) : points A et B trop éloignés"); + // rotation + float theta0 = atan2f(ab.y, ab.x) - M_PI/2; + // calcul dans le repère 'prime' + vec_t c_ = { .x = -sqrtf( R*R - ab2/4 ), + .y = sqrtf(ab2)/2 }; + float theta_ = atanf( c_.y / c_.x ); + angle_interv_t ang_ ( theta_, -theta_ ); + // dé-rotation + ang = ang_ + theta0; + c_ = c_.rotate(theta0); + // dé-translation + c = a + c_; +} + +std::pair ObjetArc::objet_extremit () const { + vec_t u_a, u_b; + std::tie(u_a,u_b) = this->ang.vec_a_b(); + return { c + R * u_a, + c + R * u_b }; +} + +// Routine d'interception du rayon sur l'arc de cercle. +// +std::optional> ObjetArc::essai_intercept_courbe (const Rayon& ray) const { + /// intersection arc de cercle / demi-droite + // (notations de `lois.pdf`) + vec_t oc = c - ray.orig; + float b = !oc / R; // d/R + float base_ang = atan2f(oc.y, oc.x); + // angles relatifs à l'axe OC + float alpha = angle_mod2pi_11( ray.dir_angle - base_ang ); + angle_interv_t thetaAB = ang + -base_ang; + // intersection avec le cecle si en dessous de l'angle critique + float y = b * sinf(alpha); + if ((b > 1.00001 and fabsf(alpha) >= M_PI/2) or fabsf(y) > 1) + return std::nullopt; + // angles repérant les points d'intersection avec le cercle + float arcsiny = asinf(y); + float theta1 = alpha - arcsiny + M_PI, + theta2 = alpha + arcsiny; + /// si on est bien sur notre arc de cercle + std::shared_ptr intercept = std::make_shared(); + // θ1 n'est accessible que si la rayon vient de l'extérieur + if ( b > 1.00001 and thetaAB.inclus(theta1) ) { + intercept->sens_reg = !inv_int; // ext vers int du cerlce + intercept->theta_incid = intercept->ang_normale = theta1 + base_ang; + intercept->ang_incid = M_PI - theta1 + alpha; // c'est un angle relatif, pas besoin de base_ang + } + // test de θ1 pour b<1 ou si θ1 a échoué pour b>1 + else if ( thetaAB.inclus(theta2) ) { + intercept->sens_reg = inv_int; // int vers ext du cercle + intercept->theta_incid = theta2 + base_ang; + intercept->ang_normale = intercept->theta_incid + M_PI; // normale vers l'intérieur du cercle + intercept->ang_incid = alpha - theta2; + } + else + return std::nullopt; + // calcul du point d'incidence + intercept->p_incid = c + R * vec_t{ .x = cosf(intercept->theta_incid), + .y = sinf(intercept->theta_incid) }; + return std::shared_ptr(intercept); +} + +///------------------------ ObjetComposite ------------------------/// + +// Calcul de l'extension approximative d'un objet composite : +// • position = barycentre +// • extension = distance (position objet - barycentre) maximale, +// + extension (objet) maximale +// (non-optimal, mais se comporte bien dans la plupart des cas) +// +Objet::extension_t ObjetComposite::objet_extension () const { + std::vector pos (comp.size()); + vec_t bary = {0,0}; + float rayon_max = 0; + for (const auto& obj : comp) { + extension_t ext = obj->objet_extension(); + pos.push_back( (vec_t)ext.pos ); + bary += (vec_t)ext.pos; + rayon_max = std::max(rayon_max, ext.rayon); + } + bary /= comp.size(); + float pos_max = 0; + for (vec_t p : pos) + pos_max = std::max(pos_max, !(p - bary)); + return { .pos = {bary.x,bary.y}, .rayon = rayon_max + pos_max }; +} + +// Relai du test d'interception sur chaque sous-objet, et interception +// par l'objet le plus proche sur le chemin du rayon. +// +Objet::intercept_t ObjetComposite::essai_intercept (const Rayon& ray) const { + intercept_composite_t interception { .courbe_intercept = comp.end() }; + float dist2_min = Inf; + // Test d'interception du rayon contre toutes les courbes composantes; + // la première interception en terme de distance entre l'origine du rayon + // et le point d'incidence est choisie (recherche de minimum) + for (auto it = comp.begin(); it != comp.end(); it++) { + std::optional> intercept + = (*it)->essai_intercept_courbe(ray); + if (intercept.has_value()) { + float dist2 = (ray.orig - (*intercept)->p_incid).norm2(); + if (dist2 < INTERCEPTION_DIST_MINIMALE*INTERCEPTION_DIST_MINIMALE) + continue; + if (dist2 < dist2_min) { + interception.courbe_intercept = it; + interception.intercept_struct = *intercept; + dist2_min = dist2; + } + } + } + return { .dist2 = dist2_min, + .intercept_struct = (interception.courbe_intercept == comp.end()) ? + nullptr : + std::make_shared(interception) + }; +} + +std::optional ObjetComposite::point_interception (std::shared_ptr interception) const { + if (interception) { + const intercept_composite_t& intercept = *(intercept_composite_t*)interception.get(); + if (intercept.courbe_intercept != comp.end()) + return intercept.intercept_struct->p_incid; + } + return std::nullopt; +} + +// Simple ré-émission par la courbe qui a intercepté le rayon +// +std::vector ObjetComposite::re_emit (const Rayon& ray, std::shared_ptr interception) { + const intercept_composite_t& intercept = *(intercept_composite_t*)interception.get(); + return (*intercept.courbe_intercept)->re_emit(ray, intercept.intercept_struct); +} + +// Test de fermeture +// +void ObjetComposite::test_fermeture () const { + if (comp.size() == 0) + throw std::logic_error("ObjetComposite : vide"); + auto verif_egaux = [] (point_t p, point_t p_bis) { + if ((p - p_bis).norm2() > 1e-10) + throw std::logic_error("ObjetComposite : courbes non bout-à-bout"); + }; + point_t beg, cur; + std::tie(beg, cur) = this->comp[0]->objet_extremit(); + for (size_t i = 1; i < this->comp.size(); i++) { + point_t cur_bis, next; + std::tie(cur_bis, next) = this->comp[i]->objet_extremit(); + verif_egaux(cur, cur_bis); + cur = next; + } + verif_egaux(beg, cur); +} + +///------------------------ Affichages ------------------------/// + +#include "sfml_c01.hpp" + +void ObjetLigne::dessiner (sf::RenderWindow& window, bool emphasize) const { + auto line = sf::c01::buildLine(this->a, this->b, emphasize ? sf::Color::White : sf::Color(200,200,200)); + window.draw(line); +} + +void ObjetCourbe::dessiner_interception (sf::RenderWindow& window, const Rayon& ray, std::shared_ptr interception) const { + const intercept_courbe_t& intercept = *(intercept_courbe_t*)interception.get(); + // dessin du rayon source -> objet + auto c = ray.spectre.rgb256_noir_intensite(false, std::nullopt); + auto line = sf::c01::buildLine(ray.orig, + intercept.p_incid, + sf::Color(std::get<0>(c), std::get<1>(c), std::get<2>(c), 255)); + window.draw(line); + // dessin de la normale au point d'interception + vec_t v = 0.03 * vec_t{ .x = cosf(intercept.ang_normale), .y = sinf(intercept.ang_normale) }; + line = sf::c01::buildLine(intercept.p_incid+(-0.2)*v, + intercept.p_incid+(+0.8)*v, + intercept.sens_reg ? sf::Color(255,200,200) : sf::Color(200,200,255)); + window.draw(line); +} + +void ObjetArc::dessiner (sf::RenderWindow& window, bool emphasize) const { + size_t N = R * 100 * ang.longueur(); + if (N < 3) N = 3; + std::vector pts (N+1); + for (size_t k = 0; k <= N; k++) { + float theta = ang.beg() + ang.longueur() * (float) k / N; + pts[k] = c + R * vec_t{ .x = cosf(theta), + .y = sinf(theta) }; + } + auto arc = sf::c01::buildLineStrip(pts, sf::Color::White); + window.draw(arc); +} + +void ObjetComposite::dessiner (sf::RenderWindow& window, bool emphasize) const { + for (const auto& p : this->comp) + p->dessiner(window, emphasize); +} + +void ObjetComposite::dessiner_interception (sf::RenderWindow& window, const Rayon& ray, std::shared_ptr interception) const { + const intercept_composite_t& intercept = *(intercept_composite_t*)interception.get(); + (*intercept.courbe_intercept)->dessiner_interception(window, ray, intercept.intercept_struct); +} diff --git a/LightRays/ObjetsCourbes.h b/LightRays/ObjetsCourbes.h new file mode 100644 index 0000000..c7f2d2e --- /dev/null +++ b/LightRays/ObjetsCourbes.h @@ -0,0 +1,161 @@ +/******************************************************************************* + * Objets optiques génériques de type courbes : ces classes virtuelles définissent + * la géométrie des objets (lignes, arcs de cercle, composites…) pour mettre en + * commun les routines géométriques d'interception et de ré-émission de rayons lumineux. + *******************************************************************************/ + +#ifndef _LIGHTRAYS_OBJETS_COURBES_H_ +#define _LIGHTRAYS_OBJETS_COURBES_H_ + +#include "Objet.h" + +//------------------------------------------------------------------------------ +// Objet optique courbe. Déclare la structure d'interception commune donnant +// le point d'intersection, l'angle d'incidence (angle du rayon à la normale), +// l'angle absolu `ang_normale` du vecteur normal à la courbe (par rapport à +// l'horizontale), et le sens d'incidence du rayon `sens_reg` (l'objet étant +// orienté; `true` si même sens que sur les figures du document). + +class ObjetCourbe : virtual public Objet { +public: + struct intercept_courbe_t { + point_t p_incid; + float ang_incid; + float ang_normale; + bool sens_reg; + }; + + // Pour faciliter l'utilisation des `ObjetCourbe`, la méthode `essai_intercept_courbe` revoie + // directement un pointeur de `intercept_courbe_t`, au lieu de la structure opaque renvoyée par `essai_intercept`. + virtual std::optional> essai_intercept_courbe (const Rayon& ray) const = 0; + // `essai_intercept` est alors une simple redirection vers `essai_intercept_courbe` et calcul de distance + virtual Objet::intercept_t essai_intercept (const Rayon& ray) const override final; + // Le point d'interception est toujours défini pour les `ObjetCourbe` + virtual std::optional point_interception (std::shared_ptr intercept_struct) const override; + + // Extrémités de la courbe. Utilisé pour vérifier qu'un `ObjetComposite` est fermé. + virtual std::pair objet_extremit () const = 0; + + // Dessin de l'interception : affiche le rayon et la normale + virtual void dessiner_interception (sf::RenderWindow& window, const Rayon& ray, std::shared_ptr intercept) const override; +}; + +//------------------------------------------------------------------------------ +// Objet optique de forme linéaire : segment du point `a` au point `b`. Routine +// d'intersection rayon-segment implémentée ici. `essai_intercept_courbe` renvoie +// une stucture `intercept_t` donnant, en plus de `intercept_curve_t`, l'abscisse +// du point d'interception `s_incid` ∈ [0,1] sur le segment a-b + +class ObjetLigne : virtual public ObjetCourbe { +public: + point_t a, b; + + ObjetLigne (point_t pos_a, point_t pos_b) : a(pos_a), b(pos_b) {} // point a et b + ObjetLigne (point_t pos_a, float l_b, float ang_b); // point a, longueur, et angle (horizontale,a,b) + + ObjetLigne& operator= (const ObjetLigne&) = default; + ObjetLigne (const ObjetLigne&) = default; + virtual ~ObjetLigne () {} + + // Routine d'intersection segment [seg_a,seg_b] avec demi-droite définie par son origine `o_droite` et un angle `ang_droite`. + // Retrourne (si intersection) le vecteur segment et le vecteur unitaire rayon (pour opti) et l'abscisse `s_seg` sur le segment et `t_dd` de la demi-droite du point d'intersection + struct intersection_segdd_t { vec_t v_seg; vec_t u_dd; float s_seg; float t_dd; }; + static std::optional intersection_segment_demidroite (point_t seg_a, point_t seg_b, point_t o_droite, float ang_droite); + // Test d'interception du rayon sur la ligne. + // Si interception, renvoie un pointeur de intercept_ligne_t + struct intercept_ligne_t : public ObjetCourbe::intercept_courbe_t { + float s_incid; + }; + std::optional> essai_intercept_courbe (const Rayon& ray) const override final; + + // Extension et extrémités du segment + virtual extension_t objet_extension () const override { return { .pos = a + (b-a)/2, .rayon = !(b-a) }; } + virtual std::pair objet_extremit () const override { return {a, b}; } + + // Dessin + virtual void dessiner (sf::RenderWindow& window, bool emphasize) const override; + vec_t vecteur_u_perp () const; // vecteur unitaire perpendiculaire au segment +}; + +//------------------------------------------------------------------------------ +// Objet optique en forme d'arc de cercle, de centre `c`, de rayon `R`, entre les +// deux angles par rapport à l'horizontale définis par l'intervalle angulaire +// `ang` dans le sens trigonométrique. Routine d'intersection rayon-arc de cercle +// implémentée ici `courbe_intercept` renvoie une stucture `intercept_arc_t` +// donnant, en plus de `intercept_curve_t`, l'angle absolu (par rapport à +// l'horizontale) `theta_incid` du point d'incidence (dans l'intervalle `ang`). +// Si `inv_int`=true, l'intérieur du cercle est marqué comme étant le milieu extérieur. + +class ObjetArc : virtual public ObjetCourbe { +public: + point_t c; + float R; + angle_interv_t ang; + bool inv_int; + + // construction par le centre, le rayon et l'intervalle angulaire + ObjetArc (point_t centre, float radius, angle_interv_t ang_interval, bool inv_interieur) : c(centre), R(radius), ang(ang_interval), inv_int(inv_interieur) {} + // construction du petit arc de cercle de rayon R passant par les points `a` et `b` + ObjetArc (point_t a, point_t b, float radius, bool inv_interieur); + + ObjetArc& operator= (const ObjetArc&) = default; + ObjetArc (const ObjetArc&) = default; + virtual ~ObjetArc () {} + + // Routine d'interception du rayon sur l'arc de cercle. + // Si interception, renvoie un pointeur de intercept_courbe_t + struct intercept_arc_t : public ObjetCourbe::intercept_courbe_t { + float theta_incid; + }; + std::optional> essai_intercept_courbe (const Rayon& ray) const override final; + + // Extension et extrémités de l'arc + virtual extension_t objet_extension () const override { return { .pos = c, .rayon = R }; } + virtual std::pair objet_extremit () const override; + + // Dessin + virtual void dessiner (sf::RenderWindow& window, bool emphasize) const override; +}; + +//------------------------------------------------------------------------------ +// Objet optique fermé composé, délimité par des objets de type `ObjetCourbe` +// Le test de fermeture a seulement le statut d'une assertion à la construction, +// et n'est pas imposé comme un invariant (`comp` est public). +// Les méthodes d'interception, de ré-émission et de dessin sont, en gros, +// juste relayés aux sous-objets. Joue le rôle d'un conteneur. + +class ObjetComposite : virtual public Objet { +protected: + // Liste des courbes composant cet objet + const std::vector< std::unique_ptr > comp; + + void test_fermeture () const; + +public: + + // Construction à partir d'une liste de courbes + ObjetComposite (std::vector>&& comp) : Objet(), comp(std::move(comp)) { test_fermeture(); } + + ObjetComposite& operator= (const ObjetArc&) = delete; // pas de copie polymorphique des sous-objets + ObjetComposite (const ObjetArc&) = delete; + virtual ~ObjetComposite () {} + + // Interception : si il y a, le rayon est simplement intercepté par l'objet le plus proche sur son chemin + struct intercept_composite_t { + decltype(comp)::const_iterator courbe_intercept; // itérateur de la courbe interceptée; `comp.end()` si pas d'interception + std::shared_ptr intercept_struct; // structure d'interception de cette courbe + }; + virtual Objet::intercept_t essai_intercept (const Rayon& ray) const override; + virtual std::optional point_interception (std::shared_ptr intercept_struct) const override; + + virtual extension_t objet_extension () const override; + + // Simple ré-émission par le sous-objet qui a intercepté + virtual std::vector re_emit (const Rayon& ray, std::shared_ptr intercept) override; + + // Dessin + virtual void dessiner (sf::RenderWindow& window, bool emphasize) const override; + virtual void dessiner_interception (sf::RenderWindow& window, const Rayon& ray, std::shared_ptr intercept) const override; +}; + +#endif diff --git a/LightRays/ObjetsOptiques.cpp b/LightRays/ObjetsOptiques.cpp new file mode 100644 index 0000000..726ed03 --- /dev/null +++ b/LightRays/ObjetsOptiques.cpp @@ -0,0 +1,77 @@ +#include "ObjetsOptiques.h" +#include "sfml_c01.hpp" +#include + +Objet_MatriceTrsfUnidir::Objet_MatriceTrsfUnidir (point_t centre, float diam, float angv, mat_trsf_t m) : ObjetLigne({0,0}, {0,0}), mat_trsf(m) { + vec_t v = diam/2 * vec_t{ -sinf(angv), cosf(angv) }; + a = centre + v; + b = centre + -v; +} + +// Dessin d'un Objet_MatriceTrsfUnidir : on rajoute une ligne qui marque le côté sortant +// +void Objet_MatriceTrsfUnidir::dessiner (sf::RenderWindow& window, bool emphasize) const { + ObjetLigne::dessiner(window, emphasize); + point_t o = milieu_2points(a,b); + window.draw(sf::c01::buildLine(o, o + 0.05 * vecteur_u_perp(), sf::Color(100,100,100))); +} + +// Objet_MatriceTrsfUnidir : Application de la matrice ABCD sur le rayon intercepté, dans le sens direct uniquement +// +std::vector Objet_MatriceTrsfUnidir::re_emit (const Rayon& ray, std::shared_ptr interception) { + intercept_ligne_t& intercept = *(intercept_ligne_t*)interception.get(); + std::vector rays; + if (intercept.sens_reg) { + float diam = !(a-b); + float y = (1 - 2 * intercept.s_incid) * diam/2; // élévation incidente + float yp = tanf(intercept.ang_incid); // pente incidente + float y2 = mat_trsf.A * y + mat_trsf.B * yp; // élévation en sortie + float y2p = mat_trsf.C * y + mat_trsf.D * yp; // pente en sortie + Rayon raytrsf = ray; + raytrsf.orig = milieu_2points(a,b) + y2 * (b-a)/diam; + raytrsf.dir_angle = atanf(y2p); + rays.push_back(std::move(raytrsf)); + } + return rays; +} + +// Objet_Filtre : filtrage du rayon incident : simple multiplication composante par composante du spectre par le spectre de transmission +// +std::vector Objet_Filtre::re_emit (const Rayon& ray, std::shared_ptr interception) { + intercept_courbe_t& intercept = *(intercept_courbe_t*)interception.get(); + Rayon ray_filtre = ray; + ray_filtre.orig = intercept.p_incid; + Specte::for_each_manual([&] (size_t i, float lambda, pola_t pol) -> void { + ray_filtre.spectre.comps[i] *= transm.comps[i]; + }); + return { std::move(ray_filtre) }; +} + +// Miroir : simple réflexion par rapport à la normale au point incident +// +std::vector ObjetCourbe_Miroir::re_emit (const Rayon& ray, std::shared_ptr interception) { + intercept_courbe_t& intercept = *(intercept_courbe_t*)interception.get(); + std::vector ray_emis; + Rayon ray_refl; + ray_refl.orig = intercept.p_incid; + ray_refl.dir_angle = intercept.ang_normale - intercept.ang_incid; // i_refl = i par rapport à la normale + ray_refl.spectre = ray.spectre; + ray_emis.push_back(std::move(ray_refl)); + return ray_emis; +} + +// Bilan d'énergie : accumulation des flux entrants et sortants +// +std::vector Objet_BilanEnergie::re_emit (const Rayon& ray, std::shared_ptr interception) { + intercept_courbe_t& intercept = *(intercept_courbe_t*)interception.get(); + Rayon rayon = ray; + rayon.orig = intercept.p_incid; + if (intercept.sens_reg) { + n_ray_in += 1; + flux_in += rayon.spectre.intensite_tot(); + } else { + n_ray_out += 1; + flux_out += rayon.spectre.intensite_tot(); + } + return { std::move(rayon) }; +} diff --git a/LightRays/ObjetsOptiques.h b/LightRays/ObjetsOptiques.h new file mode 100644 index 0000000..540a435 --- /dev/null +++ b/LightRays/ObjetsOptiques.h @@ -0,0 +1,146 @@ +/******************************************************************************* + * Objets d'optique géométrique divers : miroirs, bloqueur, filtre, + * matrice ABCD, bilan d'énergie… + *******************************************************************************/ + +#ifndef _LIGHTRAYS_OPTIQUES_H_ +#define _LIGHTRAYS_OPTIQUES_H_ + +#include "ObjetsCourbes.h" + +//------------------------------------------------------------------------------ +// Objet "matrice ABCD" unidirectionnel : objet linéaire transmettant les rayons +// inteceptés du côté direct, après multiplication des paramètres [y,y'] +// (élévation par rapport au centre, pente par rapport à la normale) par la +// matrice de transert ABCD spécifiée. + +class Objet_MatriceTrsfUnidir : virtual public ObjetLigne { +public: + // matrice de transfert + struct mat_trsf_t { float A, B, C, D; } mat_trsf; + // matrice de transfert d'une lentille + static mat_trsf_t mat_trsf_lentille (float f) { return {1, 0, -1/f, 1}; } + // matrice de transfert d'une propagation libre + static mat_trsf_t mat_trsf_propag (float l) { return {1, l, 0, 1}; } + + // Constructeur : l'objet est défini par son centre optique, son diamètre, l'angle à la + // verticale (sens trigo), et par la matrice de transfert ABCD (y relatif au plan et + // au centre, et y' par rapport à la normale du plan). Sens de propagation direct vers +x + // (si |angle| < π/2). Les rayons arrivant dans le sens inverse ne sont pas traités. + Objet_MatriceTrsfUnidir (point_t centre, float diam, float ang_vertical, mat_trsf_t m); + + Objet_MatriceTrsfUnidir& operator= (const Objet_MatriceTrsfUnidir&) = default; + Objet_MatriceTrsfUnidir (const Objet_MatriceTrsfUnidir&) = default; + virtual ~Objet_MatriceTrsfUnidir () {} + + virtual std::vector re_emit (const Rayon& ray, std::shared_ptr intercept) override; + + virtual void dessiner (sf::RenderWindow& window, bool emphasize) const override final; +}; + +//------------------------------------------------------------------------------ +// Bloqueur : simple objet linéaire absorbant tous les rayons interceptés. + +class Objet_Bloqueur : virtual public ObjetLigne { +public: + Objet_Bloqueur (point_t pos_a, point_t pos_b) : ObjetLigne(pos_a,pos_b) {} + virtual ~Objet_Bloqueur () {} + + virtual std::vector re_emit (const Rayon& ray, std::shared_ptr intercept) override final { + return {}; // oublie simplement le rayon + } +}; + +//------------------------------------------------------------------------------ +// Filtre : objet linéaire transmettant les rayons interceptés, mais modifiant +// leur spectre, multipliant chaque composante du spectre par un taux de +// transmission en intensité. Peut servir de filtre de couleur, de polariseur… + +class Objet_Filtre : virtual public ObjetLigne { +public: + // Spectre de transmission du filtre, chaque composante de 0 à 1 + Specte transm; + + // Spectre de transmission (en intensité) complet spécifié. + // Pour chaque composante de `sp_transmission`, 0 -> composante du rayon absorbée, et 1 -> composante du rayon transmise totalement + template Objet_Filtre (Specte sp_transmission, Args&&... x) : ObjetLigne(x...), transm(sp_transmission) {} + + // Seule couleur transmise (quelque soit la polarisation) spécifiée + template Objet_Filtre (color_id_t couleur_transmise, Args&&... x) : ObjetLigne(x...), transm(Specte::monochromatique(1, couleur_transmise)) {} + + Objet_Filtre& operator= (const Objet_Filtre&) = default; + Objet_Filtre (const Objet_Filtre&) = default; + virtual ~Objet_Filtre () {} + + // Transmission du rayon filtré + virtual std::vector re_emit (const Rayon& ray, std::shared_ptr intercept) override; +}; + +//------------------------------------------------------------------------------ +// Miroir courbe générique : simple réflexion des rayons tel que l'angle +// d'incidence est égal à l'angle du rayon réfléchi à la normale. + +class ObjetCourbe_Miroir : virtual public ObjetCourbe { +public: + ObjetCourbe_Miroir () : ObjetCourbe() {} + virtual ~ObjetCourbe_Miroir () {} + + // Réflexion parfaite du rayon + virtual std::vector re_emit (const Rayon& ray, std::shared_ptr intercept) override final; +}; + +//------------------------------------------------------------------------------ +// Miroir plan : ObjetCourbe_Miroir + ObjetLigne + +class ObjetLigne_Miroir : virtual public ObjetCourbe_Miroir, virtual public ObjetLigne { +public: + // relai des arguments aux contructeurs de ObjetLigne + ObjetLigne_Miroir (point_t a, point_t b) : ObjetCourbe_Miroir(), ObjetLigne(a, b) {} + ObjetLigne_Miroir (point_t a, float l, float ang) : ObjetCourbe_Miroir(), ObjetLigne(a, l, ang) {} + virtual ~ObjetLigne_Miroir () {} +}; + +//------------------------------------------------------------------------------ +// Miroir en arc de cercle : ObjetCourbe_Miroir + ObjetArc + +class ObjetArc_Miroir : virtual public ObjetCourbe_Miroir, virtual public ObjetArc { +public: + // relai des arguments aux contructeurs de ObjetArc + ObjetArc_Miroir (point_t c, float R, angle_interv_t ang, bool inv_int) : ObjetCourbe_Miroir(), ObjetArc(c, R, ang, inv_int) {} + ObjetArc_Miroir (point_t a, point_t b, float R, bool inv_int) : ObjetCourbe_Miroir(), ObjetArc(a, b, R, inv_int) {} + virtual ~ObjetArc_Miroir () {} +}; + +//------------------------------------------------------------------------------ +// Objet "bilan d'énergie" : intercepte les rayons sur un cercle et somme les +// flux entrants et sortants. Utile pour vérifier la conservation de l'intensité +// par un objet. Les rayons interceptés sont ré-émis à l'identique. + +class Objet_BilanEnergie : virtual public ObjetArc { +private: + float flux_in, flux_out; + size_t n_ray_in, n_ray_out; + size_t n_acc; +public: + Objet_BilanEnergie (point_t centre, float radius) : + ObjetArc(centre, radius, angle_interv_t::cercle_entier, false), + flux_in(0), flux_out(0), n_ray_in(0), n_ray_out(0), n_acc(0) {} + + Objet_BilanEnergie& operator= (const Objet_MatriceTrsfUnidir&) = delete; + Objet_BilanEnergie (const Objet_MatriceTrsfUnidir&) = delete; + virtual ~Objet_BilanEnergie () {} + + // intercepte les rayons et accumule les flux entrants et sortants + virtual std::vector re_emit (const Rayon& ray, std::shared_ptr intercept) override final; + + // typiquement appelé à chaque frame, pour moyenner les valeurs sur plusieurs frames + void commit () { n_acc++; } + void reset () { flux_in = flux_out = 0; n_ray_in = n_ray_out = n_acc = 0; } + // statistiques de flux : intensité entrante, intensité sortante, nombre de rayons entrants et sortants + struct stats_par_frame_t { + float flux_in, flux_out, n_ray_in, n_ray_out; + }; + stats_par_frame_t bilan () { return { flux_in/n_acc, flux_out/n_acc, n_ray_in/(float)n_acc, n_ray_out/(float)n_acc }; } +}; + +#endif \ No newline at end of file diff --git a/LightRays/Rayon.cpp b/LightRays/Rayon.cpp new file mode 100644 index 0000000..8827d01 --- /dev/null +++ b/LightRays/Rayon.cpp @@ -0,0 +1,113 @@ +#include "Rayon.h" +#include "cmath" + +// Spectre dont les composantes sont ajustées pour avoir à peu près du blanc en affichage RBG +const Specte spectre_blanc = Specte::polychromatique({{0.8,0.8,1.7,2.2}}); + +Specte Specte::monochromatique (float A, color_id_t color_id, std::optional pola_sel) { + if (color_id >= N_COULEURS) + throw std::out_of_range("identifiant couleur invalide"); + Specte specte; + Specte::for_each_manual([&] (size_t i, float lambda, pola_t pol) { + if (pola_sel.has_value() and *pola_sel != pol) { + specte.comps[i] = 0; + } else { + if (color_id == i/2) + specte.comps[i] = A; + else + specte.comps[i] = 0; + } + }); + return specte; +} + +Specte Specte::polychromatique (std::array I, std::optional pola_sel) { + Specte specte; + Specte::for_each_manual([&] (size_t i, float lambda, pola_t pol) { + if (pola_sel.has_value() and *pola_sel != pol) { + specte.comps[i] = 0; + } else { + specte.comps[i] = I[i/2]; + } + }); + return specte; +} + +// Convert a given wavelength of light to an approximate RGB color value. +// The wavelength must be given in mm in the range from 380nm through 750nm. +// Based on code by Dan Bruton : http://www.physics.sfasu.edu/astro/color/spectra.html +std::tuple wavelenght_to_rgb (float lamda, float gamma) { + float wavelength = lamda * 1e6; + float R = 0., G = 0., B = 0., attenuation; + if (wavelength >= 380 and wavelength <= 440) { + attenuation = 0.3 + 0.7 * (wavelength - 380) / (440 - 380); + R = powf( (-(wavelength - 440) / (440 - 380)) * attenuation, gamma); + B = powf( 1.0 * attenuation, gamma); + } else if (wavelength >= 440 and wavelength <= 490) { + G = powf( (wavelength - 440) / (490 - 440), gamma);; + B = 1.0; + } else if (wavelength >= 490 and wavelength <= 510) { + G = 1.0; + B = powf( -(wavelength - 510) / (510 - 490), gamma); + } else if (wavelength >= 510 and wavelength <= 580) { + R = powf( (wavelength - 510) / (580 - 510), gamma); + G = 1.0; + } else if (wavelength >= 580 and wavelength <= 645) { + R = 1.0; + G = powf( -(wavelength - 645) / (645 - 580), gamma); + } else if (wavelength >= 645 and wavelength <= 750) { + attenuation = 0.3 + 0.7 * (750 - wavelength) / (750 - 645); + R = powf( 1.0 * attenuation, gamma); + } + return { R, G, B }; +} + +struct rgb_cache_t { + std::array< std::tuple, 2*N_COULEURS> comp_couleurs_rgb; + rgb_cache_t () { + Specte::for_each_manual([&] (uint8_t i_comp_array, float lambda, pola_t pol) { + comp_couleurs_rgb[i_comp_array] = wavelenght_to_rgb( lambda ); + }); + } +} rgb_cache; + +std::tuple Specte::rgb256_noir_intensite (bool chroma_only, std::optional pola_sel) const { + float I_r = 0, I_g = 0, I_b = 0; + Specte::for_each_manual([&] (uint8_t i_comp_array, float lambda, pola_t pol) { + if (pola_sel.has_value() and *pola_sel != pol) + return; + float rc, gc, bc; std::tie(rc,gc,bc) = /* wavelenght_to_rgb(lambda) */ rgb_cache.comp_couleurs_rgb[i_comp_array]; + I_r += rc * comps[i_comp_array]; + I_g += gc * comps[i_comp_array]; + I_b += bc * comps[i_comp_array]; + }); + I_r /= 2*N_COULEURS; + I_g /= 2*N_COULEURS; + I_b /= 2*N_COULEURS; + float max_I = std::max({I_r, I_g, I_b}); + bool sat = (max_I > 1.); + if (sat or chroma_only) { + I_r /= max_I; I_g /= max_I; I_b /= max_I; + } + return { 255*I_r, 255*I_g, 255*I_b, sat }; +} + +float Specte::intensite_tot (pola_t pola_sel) const { + float I_tot = 0; + Specte::for_each_manual([&] (uint8_t i_comp_array, float, pola_t pol) { + if (pola_sel != pol) + return; + I_tot += comps[i_comp_array]; + }); + I_tot /= 2*N_COULEURS; + return I_tot; +} + +float Specte::intensite_tot () const { + float I_tot = 0; + Specte::for_each_manual([&] (uint8_t i_comp_array, float, pola_t) { + I_tot += comps[i_comp_array]; + }); + I_tot /= 2*N_COULEURS; + return I_tot; +} \ No newline at end of file diff --git a/LightRays/Rayon.h b/LightRays/Rayon.h new file mode 100644 index 0000000..d59435f --- /dev/null +++ b/LightRays/Rayon.h @@ -0,0 +1,81 @@ +/******************************************************************************* + * Définission des rayons et des spectres, fonctions utilitaires sur les rayons. + *******************************************************************************/ + +#ifndef _LIGHTRAYS_RAYON_H_ +#define _LIGHTRAYS_RAYON_H_ + +#include +#include +#include +#include "Util.h" + +#define N_COULEURS 4 + +// Longueurs d'ondes des `N_COULEURS` composantes des rayons +constexpr float lambda_color [N_COULEURS] = { + 7e-4, 6e-4, 5e-4, 4e-4 +}; +// La position dans ce tableau définit un `color_id_t` +typedef uint8_t color_id_t; + +// Conversion longueur d'onde -> RGB pour affichage +std::tuple wavelenght_to_rgb (float lamda_mm, float gamma = 0.8); + +// Polarisation d'un rayonnement. +// Puisque l'on ne s'intéresse qu'à l'optique des réfractions et réflexions, seul +// compte le caractère TE ou TM. Et puisque l'on est en 2D, ce caractère est invariant +// dans une réflexion ou une réfration, où le rayon reste dans le plan 2D. +enum pola_t { PolTE = 0, PolTM = 1 }; + +// Spectre en intensité/puissance (suivant le contexte) d'un rayonnement, discrétisé en `N_COULEURS` +// (×2 pour la polarisation TE/TM) composantes de longueur d'onde définies `lambda_color`. +// La manipulation de `AmplComp::comps` doit se faire systématiquement avec for_each ou for_each_manual { ampl[i_comp_array] }. +// +struct Specte { + std::array comps; + + inline static void for_each_manual (std::function f) { + for (uint8_t i = 0; i < 2*N_COULEURS; i++) + f(i, lambda_color[i/2], i%2==0 ? pola_t::PolTE : pola_t::PolTM); + } + inline void for_each (std::function f) { // version mutable avec lambda + for_each_manual([&] (uint8_t i, float lambda, pola_t pol) { f(lambda, pol, this->comps[i]); }); + } + inline void for_each_cid (std::function f) { // version mutable avec id de couleur + for_each_manual([&] (uint8_t i, float, pola_t pol) { f(i/2, pol, this->comps[i]); }); + } + inline void for_each (std::function f) const { // version constante avec lambda + for_each_manual([&] (uint8_t i, float lambda, pola_t pol) { f(lambda, pol, this->comps[i]); }); + } + + static Specte monochromatique (float I, color_id_t id, std::optional pol = std::nullopt); + static Specte polychromatique (std::array I, std::optional pol = std::nullopt); + + // Sommation des intensités de chaque composante (intégration sur tout le spectre), + // et division par le nombre de composantes (2×`N_COULEURS`). + // Optionellement, sélectionne seulement les composantes d'une polarisation donnée. + float intensite_tot () const; + float intensite_tot (pola_t pola_sel) const; + + // Conversion du spectre en couleur RGB pour affichage sur fond noir, par rapport à une intensité + // de saturation de 1.0 _par composante_. Si `chroma_only` est vrai, ignore l'intensité lumineuse. + // Le booléen indique si il y a saturation. + // Optionellement, sélectionne seulement les composantes d'une polarisation donnée. + // [TODO] Pour un affichage sur fond quelconque, il faudrait convertir la luminance en transparence. + std::tuple rgb256_noir_intensite (bool chroma_only, std::optional pola_sel = std::nullopt) const; +}; + +// Spectre tel que l'affichage RGB est à peu près blanc +extern const Specte spectre_blanc; + +// Définition d'un rayon : son origine, son angle à l'horizontale +// et son spectre en intensité associé. +// +struct Rayon { + point_t orig; + float dir_angle; + Specte spectre; +}; + +#endif diff --git a/LightRays/Scene.cpp b/LightRays/Scene.cpp new file mode 100644 index 0000000..c01f8e4 --- /dev/null +++ b/LightRays/Scene.cpp @@ -0,0 +1,140 @@ +#include "Scene.h" +#include +#include "sfml_c01.hpp" + +// Test d'interception du rayon contre toutes les objets de la scène puis renvoi des +// rayons ré-émis; la première interception sur le trajet du rayon est choisie. +// Voir ObjetComposite::essai_intercept_composite pour un code similaire. +// +std::vector Scene::interception_re_emission (const Rayon& ray) { + + decltype(objets)::const_iterator objet_intercept = objets.end(); + std::shared_ptr intercept_struct; + float dist2_min = Inf; + + for (auto it = objets.begin(); it != objets.end(); it++) { + // Objet::extension_t ex = (*it)->objet_extension(); + // Todo. Pour que ça puisse apporter quelque chose, il faut que ça soit calculé à l'avance et faire une grille et un test très rapide + #warning To do + Objet::intercept_t intercept = (*it)->essai_intercept(ray); + if (intercept.dist2 < dist2_min) { + dist2_min = intercept.dist2; + objet_intercept = it; + intercept_struct = intercept.intercept_struct; + } + } + + if (objet_intercept == objets.end()) + return {}; + else { + if (propag_intercept_dessin_window != nullptr) + (*objet_intercept)->dessiner_interception(*propag_intercept_dessin_window, ray, intercept_struct); + if (propag_rayons_dessin_window != nullptr) { + auto p = (*objet_intercept)->point_interception(intercept_struct); + if (p.has_value()) { + float c = propag_rayons_dessin_gain * ray.spectre.intensite_tot(); + auto line = sf::c01::buildLine(ray.orig, *p, sf::Color(255, 255, 255, (uint8_t)std::min(255.f,c))); + propag_rayons_dessin_window->draw(line); + } + } + if (propag_intercept_cb) + propag_intercept_cb(**objet_intercept, ray, intercept_struct); + return (*objet_intercept)->re_emit(ray, intercept_struct); + } +} + +// Fonction récurrente : interception par les objets de la scène puis ré-émission +// avec la méthode `interception_re_emission`. +// +void Scene::propagation_recur (const Rayon& ray, uint16_t profondeur_recur) { + stats_sum_prof_recur += profondeur_recur; + if (profondeur_recur >= propag_profondeur_recur_max) { + stats_n_rayons_profmax++; + return; + } + stats_n_rayons++; + std::vector rays = this->interception_re_emission(ray); + profondeur_recur++; + for (const Rayon& ray : rays) { + if (ray.spectre.intensite_tot() < intens_cutoff) { + stats_n_rayons_discarded++; + continue; + } + if (propag_emit_cb) + propag_emit_cb(ray, profondeur_recur); + this->propagation_recur(ray, profondeur_recur); + } +} + +// Émet les rayons de toutes les sources de la scène et appelle `propagation_recur`. +// +void Scene::emission_propagation () { + for (auto& source : sources) { + std::vector rays = source->genere_rayons(); + stats_n_rayons_emis += rays.size(); + for (const Rayon& ray : rays) { + if (propag_emit_cb) + propag_emit_cb(ray, 0); + this->propagation_recur(ray, 0); + } + } +} + +///------- Méthodes utilitaires et Scene_ObjetsBougeables -------/// + +void Scene::ecrans_do (std::function f) { + for (auto obj : objets) { + Ecran_Base* ecran = dynamic_cast(obj.get()); + if (ecran != nullptr) + f(*ecran); + } +} + +bool Scene_ObjetsBougeables::objetsBougeables_event_SFML (const sf::Event& event) { + if (objet_bougeant == nullptr) { + if (event.type == sf::Event::KeyPressed and event.key.code == sf::Keyboard::LShift) + bouge_action_alt = true; + if (event.type == sf::Event::KeyReleased and event.key.code == sf::Keyboard::LShift) + bouge_action_alt = false; + if (event.type == sf::Event::MouseButtonReleased) + objet_bougeant = bougeable_nearest; + } else { + if (event.type == sf::Event::MouseButtonReleased) { + bouge_action_alt = false; + objet_bougeant = nullptr; + } + } + if (event.type == sf::Event::MouseMoved) { + mouse = sf::c01::fromWin(sf::Vector2f(event.mouseMove.x,event.mouseMove.y)); + if (objet_bougeant == nullptr) { + if (bouge_actions.empty()) + bougeable_nearest = nullptr; + else { + bougeable_nearest = &bouge_actions.front(); + float dist2_min = Inf; + for (bouge_action_t& obj : bouge_actions) { + float dist2 = !(obj.pos - mouse); + if (dist2 < dist2_min) { + dist2_min = dist2; + bougeable_nearest = &obj; + } + } + } + } else { + vec_t v = mouse - objet_bougeant->pos; + float dir_angle = atan2f(v.y, v.x); + point_t new_pos = objet_bougeant->action_bouge(mouse, dir_angle, bouge_action_alt); + objet_bougeant->pos = new_pos; + return true; + } + } + return false; +} + +void Scene_ObjetsBougeables::dessiner_pointeur_nearest_bougeable (sf::RenderWindow& win) { + if (objet_bougeant == nullptr and bougeable_nearest != nullptr) { + auto pointeur = sf::c01::buildCircleShapeCR(bougeable_nearest->pos, 0.005); + pointeur.setFillColor(sf::Color::Blue); + win.draw(pointeur); + } +} diff --git a/LightRays/Scene.h b/LightRays/Scene.h new file mode 100644 index 0000000..ab2faaf --- /dev/null +++ b/LightRays/Scene.h @@ -0,0 +1,129 @@ +/******************************************************************************* + * Scène : objets, routines de propagation-interception-réémission des rayons, + * et sources. Aucune loi d'optique n'est implémentée ici, il ne s'agit + * que d'un conteneur, d'une récursion, et de méthodes utilitaires. + *******************************************************************************/ + +#ifndef _LIGHTRAYS_SCENE_H_ +#define _LIGHTRAYS_SCENE_H_ + +#include +#include +#include "Objet.h" +#include "Source.h" +#include "Ecran.h" +#include +#include + +class Scene { +public: + + // Liste des objets de la scène + // (liste de pointeurs car les objets sont traités polymorphiquement) + std::vector< std::shared_ptr > objets; + + // Raccourci pour création d'objet de type `ObjT` : simple transfert d'argument de constructeur + template + std::shared_ptr creer_objet (Args&& ...args) { + auto obj = std::make_shared(std::forward(args)...); + this->objets.push_back(obj); + return obj; + } + + // Liste des sources de la scène + std::vector< std::shared_ptr > sources; + + + ///--------- Routines de propagation des rayons ---------/// + + // Fenêtre de dessin des interceptions et/ou rayons (voir Scene::interception_re_emission) + sf::RenderWindow* propag_intercept_dessin_window = nullptr; + sf::RenderWindow* propag_rayons_dessin_window = nullptr; + float propag_rayons_dessin_gain = 10.; + + // Callback appelé lors de l'interception d'un rayon par un objet. Utilisation typique : déboguage. + // `intercept_struct` est le Objet::intercept_t::intercept_struct renvoyé par Objet::essai_intercept + std::function< void (Objet&, const Rayon&, std::shared_ptr intercept_struct) > propag_intercept_cb = nullptr; + // Callback appellé pour chaque rayon émis ou ré-émis + std::function< void (const Rayon&, uint16_t prof_recur) > propag_emit_cb = nullptr; + + // Statistiques + uint64_t stats_n_rayons, stats_n_rayons_profmax, stats_n_rayons_discarded, stats_sum_prof_recur, stats_n_rayons_emis; + + // Test d'interception du rayon contre toutes les objets de la scène puis ré-émission; la première + // interception sur le trajet du rayon depuis son origine est choisie. Renvoie tous les rayons ré-émis. + // Si `propagation_params.window` ≠ null, dessine l'interception du rayon avec `objet.dessiner_interception`, + // et appelle `propag_intercept_cb` (par exemple pour un dessin du rayon de la source à l'objet) si ≠ null. + // Si `propag_rayons_dessin_window` ≠ null, dessine les rayons en blanc transparent (∝ intensité) grâce + // à `objet.point_interception`. Méthode surtout interne, appelé par `propagation_recur`. + std::vector interception_re_emission (const Rayon& ray); + + /// Propagation d'un rayon : récursion de l'interception/ré-émission + + // Intensité en dessous de laquelle un rayon est ignoré. Fort impact sur la performance + float intens_cutoff = 1e-2; + // Profondeur de récursion (= nombre de ré-émission d'un rayon initial) maximale + // Ne devrait jouer que pour des réflexions infinies sans perte (où l'intensité ne passe jamais en dessous de `intens_cutoff`) + uint16_t propag_profondeur_recur_max = 20; + + // Fonction récurrente : interception par les objets de la scène puis ré-émission; + // utilise `interception_re_emission`, `intens_cutoff` et `propag_profondeur_recur_max`. + // Appelle `propag_emit_cb` si ≠ null. Méthode surtout interne, appelé par `emission_propagation`. + void propagation_recur (const Rayon& ray, uint16_t profondeur_recur); + + // Fonction principale : émet les rayons de toutes les sources de la scène et appelle `propagation_recur`. + void emission_propagation (); + + ///--------- Affichage et interface utilisateur ---------/// + + // Dessin de tous les objets et sources de la scène. + void dessiner_scene (sf::RenderWindow& window) const { + for (auto& objet : objets) + objet->dessiner(window, false); + for (auto& source : sources) + source->dessiner(window); + } + + // Appelle f() sur tous les objets de type Ecran_Base + void ecrans_do (std::function f); + +}; + +///---------------------------------------------------------------------- +/// Scène avec objets "bougeables" : routines utiles pour (par exemple) +/// déplacer/tourner les objets de la scène avec la souris, suivant des +/// actions spécifés par l'utilisateur (`bouge_action_t`). + +class Scene_ObjetsBougeables : public Scene { +public: + + // Ancre à la postion `pos` déclanchant l'action `action_bouge` lorsque cliqué, + // avec la postion de la souris et son angle à l'horizontale. Si le clic est fait + // avec la touche Maj, `alt=true`, sinon `false`. + struct bouge_action_t { + point_t pos; + std::function< point_t (point_t mouse, float angle, bool alt) > action_bouge; + }; + std::vector bouge_actions; + void ajouter_bouge_action (point_t pos_initiale, decltype(bouge_action_t::action_bouge) action) { + bouge_actions.push_back({pos_initiale, action}); + } + // Position courante de la souris + point_t mouse = {0,0}; + + // Dessine un point bleu sur l'ancre la plus proche de la souris + void dessiner_pointeur_nearest_bougeable (sf::RenderWindow& win); + // À appeller lorsqu'un évènement souris/clavier/clic SFML est déclanché + // Lorsqu'un objet/ancre a été déplacée, renvoie `true` (e.g. pour reset d'écrans) + bool objetsBougeables_event_SFML (const sf::Event& event); + +private: + bool bouge_action_alt = false; + bouge_action_t* bougeable_nearest = nullptr; + bouge_action_t* objet_bougeant = nullptr; +public: + Scene_ObjetsBougeables () = default; + Scene_ObjetsBougeables (const Scene_ObjetsBougeables&) = delete; +}; + +#endif diff --git a/LightRays/SceneTest.cpp b/LightRays/SceneTest.cpp new file mode 100644 index 0000000..cbf658f --- /dev/null +++ b/LightRays/SceneTest.cpp @@ -0,0 +1,256 @@ +#include "SceneTest.h" +#include +#include +#include + +// Création de la scène, des fenêtres SFML, et optionnellement +// de l'écran, de la fenêtre, et de la lentille image +// +Scene_TestCommon::Scene_TestCommon (std::wstring nom, bool creer_ecran_image, bool creer_lentille_image) : + win_scene(nullptr), nom(nom), reset_ecrans(false), frame_i(0), gel(false), affiche_text(true), win_pixels(nullptr) { + intens_cutoff = 1e-3; + propag_profondeur_recur_max = 50; + + // Création des fenêtres SFML + sf::ContextSettings settings; + settings.antialiasingLevel = 8; + win_scene = new sf::RenderWindow(sf::VideoMode(1100,720), std::wstring(L"Scène test - ")+nom, sf::Style::Close, settings); + win_scene->setPosition({0,0}); + if (creer_ecran_image) { + win_pixels = new sf::RenderWindow(sf::VideoMode(100,400), L"CCD", sf::Style::Titlebar, settings); + win_pixels->setPosition({1100,0}); + } + win_scene->requestFocus(); + propag_rayons_dessin_window = win_scene; + + // Écran et lentille image + if (creer_ecran_image) { + if (creer_lentille_image) { + lentille = this->creer_objet( point_t{1.2,0.5}, 0.2, 0, Objet_MatriceTrsfUnidir::mat_trsf_t{1,0,0,1} ); + this->lentille_mise_au_point(0.2); + } + ecran_image = this->creer_objet( point_t{1.5,0.45}, point_t{1.5,0.55}, (uint16_t)200, 0.0001 ); + } + + // Texte d'aide + static_text = { + L"[S] (dés)affiche rayons couleur intercept", + L"[D] (dés)affiche tous rayons transparence lumino, [G]/[F] augmente/diminue lumino rayons", + L"[Up]/[Down] augmente/diminue gain écrans, [R] reset écrans", + L"[Space] gel scène, [H] cacher texte", + L"[(Maj) clic] bouge ancre plus proche" + }; + if (not font.loadFromFile(FONT_PATH)) + throw std::runtime_error("Échec de chargement de fonte"); +} + +// Mise au point de la lentille image +// +void Scene_TestCommon::lentille_mise_au_point (float x_obj) { + if (lentille) { + float f = -1 / ( 1/(1.5-1.2) + 1/(1.2-x_obj) ); + lentille->mat_trsf = Objet_MatriceTrsfUnidir::mat_trsf_t{1,0,-1/f,1}; + } +} + +// Diaphragme autour de la lentille pour garder le CCD à l'ombre +// +void Scene_TestCommon::creer_bloqueurs_autour_lentille (float taille) { + this->creer_objet(point_t{1.2f,0.6f}, point_t{1.2f,0.6f+taille}); + this->creer_objet(point_t{1.2f,0.4f-taille}, point_t{1.2f,0.4f}); +} + +// Destruction des fenêtres SFML +// +Scene_TestCommon::~Scene_TestCommon () { + delete win_scene; + if (win_pixels != nullptr) + delete win_pixels; +} + +// Boucle principale SFML : +// - traite les évènements +// - effectue la propagation des rayons +// - dessine la scène, gère et affiche les statistiques, gère les écrans +// +void Scene_TestCommon::boucle (std::function f_event, + std::function f_pre_propag, + std::function f_post_propag) { + + while (win_scene->isOpen()) { + + // Gestion des évènements clavier et souris + sf::Event event; + while (win_scene->pollEvent(event)) { + if (event.type == sf::Event::Closed) + win_scene->close(); + + // Souris + bool objet_moved = this->objetsBougeables_event_SFML(event); + if (objet_moved) + reset_ecrans = true; + + // Commandes clavier + + if (event.type == sf::Event::KeyPressed and event.key.code == sf::Keyboard::R) + reset_ecrans = true; + + if (event.type == sf::Event::KeyPressed and event.key.code == sf::Keyboard::S) { + if (propag_intercept_dessin_window == nullptr) + propag_intercept_dessin_window = win_scene; + else + propag_intercept_dessin_window = nullptr; + } + if (event.type == sf::Event::KeyPressed and event.key.code == sf::Keyboard::D) { + if (propag_rayons_dessin_window == nullptr) + propag_rayons_dessin_window = win_scene; + else + propag_rayons_dessin_window = nullptr; + } + if (event.type == sf::Event::KeyPressed and event.key.code == sf::Keyboard::G) + propag_rayons_dessin_gain *= 1.1; + if (event.type == sf::Event::KeyPressed and event.key.code == sf::Keyboard::F) + propag_rayons_dessin_gain *= 0.9; + + if (event.type == sf::Event::KeyPressed and event.key.code == sf::Keyboard::Up) + this->ecrans_do([] (Ecran_Base& e) { e.luminosite *= 1.1; }); + if (event.type == sf::Event::KeyPressed and event.key.code == sf::Keyboard::Down) + this->ecrans_do([] (Ecran_Base& e) { e.luminosite *= 0.9; }); + + if (event.type == sf::Event::KeyPressed and event.key.code == sf::Keyboard::Space) + gel = !gel; + if (event.type == sf::Event::KeyPressed and event.key.code == sf::Keyboard::H) + affiche_text = !affiche_text; + + if (f_event) + f_event(event); + } + + if (gel) { + usleep(1000); + continue; + } + + // Dessin des objets de la scène avant propagation + win_scene->clear(sf::Color::Black); + this->dessiner_scene(*win_scene); + + if (f_pre_propag) + f_pre_propag(); + + // Reset statistiques et écrans + stats_n_rayons_emis = 0; + stats_n_rayons = 0; + stats_sum_prof_recur = 0; + stats_n_rayons_profmax = 0; + stats_n_rayons_discarded = 0; + + if (reset_ecrans) { + this->ecrans_do([] (Ecran_Base& e) { e.reset(); }); + reset_ecrans = false; + frame_i = 0; + } + + // Émission et propagation des rayons + this->emission_propagation(); + this->ecrans_do([] (Ecran_Base& e) { e.commit(); }); + + if (f_post_propag) + f_post_propag(); + + // Affichage de la matrice de pixels dans la fenêtre image + if (win_pixels != nullptr) { + std::vector pix = ecran_image->matrice_pixels(); + sf::RectangleShape rect; + rect.setSize(sf::Vector2f( 100, 400./pix.size() )); + for (size_t k = 0; k < pix.size(); k++) { + auto color = sf::Color(pix[k].r, pix[k].g, pix[k].b); + rect.setPosition(sf::Vector2f( 0, k * 400./pix.size() )); + rect.setFillColor(color); + win_pixels->draw(rect); + } + win_pixels->display(); + while (win_pixels->pollEvent(event)) {} + } + + // Pointeurs des objets bougeables + this->dessiner_pointeur_nearest_bougeable(*win_scene); + + // Affichage de l'aide et des statistiques + if (affiche_text) { + std::wstringstream text; + for (auto& s : static_text) + text << s << std::endl; + text << std::endl; + text << frame_i << L" frame accumulées" << std::endl; + text << stats_n_rayons_emis << " rayons primaires, " << stats_n_rayons << " rayons tot, " << std::fixed << std::setprecision(1) << (stats_sum_prof_recur/(float)stats_n_rayons) << " prof recur moy, " << stats_n_rayons_discarded << L" rayons jetés, " << stats_n_rayons_profmax << " max prof" << std::endl; + text << std::setprecision(3) << "pointeur : (" << mouse.x << "," << mouse.y << ")"; + win_scene->draw( sf::c01::buildText(font, point_t{0.1f,0.015f*(8+static_text.size())}, {text.str()}, sf::Color::White) ); + + win_scene->draw( sf::c01::buildText(font, point_t{1,0.1}, {this->nom}, sf::Color(255,255,255,160), 22) ); + + if (lentille != nullptr) + win_scene->draw( sf::c01::buildText(font, this->lentille->b+vec2_t{-0.03,0}, {L"Lentille"}, sf::Color::White) ); + } + + // Affichage SFML + win_scene->display(); + frame_i++; + } +} + +// Création d'une source d'un unique rayon rouge, déplaçable et tournable. +// +std::shared_ptr Scene_TestCommon::creer_source_unique_rayon (point_t pos, float dir_angle, float ampl) { + auto source = std::make_shared(pos, dir_angle, (color_id_t)0, ampl); + this->ajouter_bouge_action(source->position, [source] (point_t mouse, float angle, bool alt) -> point_t { + if (alt) source->dir_angle = angle; + else source->position = mouse; + return source->position; + }); + this->sources.push_back(source); + return source; +} + +// Création d'une source ponctuelle blanche omnidirectionnelle sur un secteur angulaire [ang_base-ang_ext, ang_base+ang_ext] +// +std::shared_ptr Scene_TestCommon::creer_source_omni_secteur (point_t pos, float ang_ext, float ang_base, float dens_ray) { + auto source = std::make_shared(pos, spectre_blanc); + source->dens_ang = dens_ray; + source->dir_alea = true; + this->ajouter_bouge_action(source->position, [ang_ext,source] (point_t mouse, float angle, bool alt) -> point_t { + if (alt) source->secteur = angle_interv_t(-ang_ext,+ang_ext) + angle; + else source->position = mouse; + return source->position; + }); + source->secteur = angle_interv_t(-ang_ext,+ang_ext) + ang_base; + this->sources.push_back(source); + return source; +} + +// Création d'une source linéaire lambertienne bleue qui prend tout le côté gauche de la scène. +// +std::shared_ptr Scene_TestCommon::creer_ciel_bleu (float dens_lin) { + auto ciel = std::make_shared( + /*a*/point_t{0.05,0.05}, /*segment*/vec_t{0,0.90}, + Specte::polychromatique({{0,0,1.1,2}}) + ); + ciel->dens_lin = dens_lin; + this->sources.push_back(ciel); + this->ajouter_bouge_action(ciel->a, [ciel] (point_t mouse, float, bool) -> point_t { + ciel->a = mouse; + return mouse; + }); + return ciel; +} + +// Création d'écrans d'épaisseur `a` tout autour de la scène. +// +void Scene_TestCommon::creer_ecrans_autour (float lumino, float a) { + float b = (float)(win_scene->getSize().x)/SFMLC01_WINDOW_UNIT; + float bin_density = 50; + this->creer_objet( point_t{ a, a}, point_t{b-a, a}, bin_density, lumino )->epaisseur_affich = a; + this->creer_objet( point_t{b-a, a}, point_t{b-a, 1-a}, bin_density, lumino )->epaisseur_affich = a; + this->creer_objet( point_t{b-a, 1-a}, point_t{ a, 1-a}, bin_density, lumino )->epaisseur_affich = a; + this->creer_objet( point_t{ a, 1-a}, point_t{ a, a}, bin_density, lumino )->epaisseur_affich = a; +} \ No newline at end of file diff --git a/LightRays/SceneTest.h b/LightRays/SceneTest.h new file mode 100644 index 0000000..e6dbee9 --- /dev/null +++ b/LightRays/SceneTest.h @@ -0,0 +1,66 @@ +/******************************************************************************* + * Scène "test" : regroupe tout le code qu'on trouverait typiquement dans + * les main(), mais pas assez générique pour être dans l'objet `Scene`. + *******************************************************************************/ + +// Aucune encapsulation ici, juste des fonctions utilitaires +// Création des fenêtres SFML, création de quelques objets courants & sources, +// (optionellement) lentille + écran + fenêtre "CCD" pour la formation d'images +// écrans autour de la scène. +// Hérite de Scene_ObjetsBougeables. + +#ifndef _LIGHTRAYS_SCENE_TEST_H_ +#define _LIGHTRAYS_SCENE_TEST_H_ + +#include +#include +#include "Source.h" +#include "ObjetsOptiques.h" +#include "Ecran.h" +#include "Scene.h" +#include +#include "sfml_c01.hpp" +#include +#include + +class Scene_TestCommon : public Scene_ObjetsBougeables { +public: + sf::RenderWindow* win_scene; // fenêtre SFML principale, affichage de la scène + sf::Font font; // fonte SFML + std::wstring nom; // nom de la scène + std::vector static_text; // texte supplémentaire affiché avec l'aide + bool reset_ecrans; // drapeau : si `true`, réinitialise les écrans et repasse à false + uint64_t frame_i; // numéro de frame depuis le dernier reset + bool gel; // si `vrai`, gèle la scène et l'affichage + bool affiche_text; // si `vrai`, affiche l'aide et le texte supplémentaire + + sf::RenderWindow* win_pixels; // fenêtre SFML secondaire optionnelle pour affichage de l'image sur l'écran `ecran_image` + std::shared_ptr ecran_image; // écran de formation d'images (si `win_pixels!=null`) + std::shared_ptr lentille; // lentille convergente pour former les images, optionnelle (et si `win_pixels!=null`) + void lentille_mise_au_point (float x_obj); // mise au point de la lentille sur le plan x = `x_obj` + void creer_bloqueurs_autour_lentille (float taille); + + // Création de la scène, des fenêtres SFML, optionnellement de l'écran et de la fenêtre image + // (si `creer_ecran_image` vrai) et de la lentille (si `creer_lentille_image` vrai). + Scene_TestCommon (std::wstring nom, bool creer_ecran_image, bool creer_lentille_image); + ~Scene_TestCommon (); + + // Boucle principale SFML : + // - traite les évènements (clavier souris) et (optionnel) appelle f_event() à chaque évènement + // - effectue la propagation des rayons avec Scene::emission_propagation; et avant cela, + // appelle (optionnel) `f_pre_propag`; et après cela, appelle (optionnel) `f_post_propag` + // - dessine la scène + // - gère et affiche les statistiques, gère les écrans + void boucle (std::function f_event, + std::function f_pre_propag, + std::function f_post_propag); + + // Création rapide de sources, voir implémentation + std::shared_ptr creer_source_unique_rayon (point_t pos, float dir_angle, float ampl); + std::shared_ptr creer_source_omni_secteur (point_t pos, float ang_ext, float ang_base, float dens_ray); + std::shared_ptr creer_ciel_bleu (float dens_lin = 1000); + // Création d'écrans autour de la scène + void creer_ecrans_autour (float lumino = 0.0001, float epaiss = 0.01); +}; + +#endif \ No newline at end of file diff --git a/LightRays/Source.cpp b/LightRays/Source.cpp new file mode 100644 index 0000000..b90f058 --- /dev/null +++ b/LightRays/Source.cpp @@ -0,0 +1,130 @@ +#include "Source.h" +#include + +const decltype(Source_Omni::directivite) Source_Omni::directivite_unif = [] (float) -> float { return 1.f; }; + +///------------------------ Émission des sources ------------------------/// + +std::vector Source_UniqueRayon::genere_rayons () { + return { Rayon{ + .orig = position, + .dir_angle = dir_angle, + .spectre = spectre + } }; +}; + +std::vector Source_LinParallels::genere_rayons () { + std::vector rayons; + size_t n_rayons = lroundf(dens_lin * !vec); + float seg_dir_angle = atan2f(vec.y, vec.x) - M_PI/2; + for (size_t k = 0; k < n_rayons; k++) { + Rayon ray = { + .orig = a + vec * (float)k / n_rayons, + .dir_angle = seg_dir_angle + dir_angle_rel, + .spectre = spectre + }; + rayons.push_back(std::move(ray)); + } + return rayons; +} + +std::vector Source_PonctOmni::genere_rayons () { + std::vector rayons; + size_t n_rayons = lroundf(dens_ang); + for (size_t k = 0; k < n_rayons; k++) { + Rayon rayon = { + .orig = position, + .dir_angle = dir_alea ? + (float)(2*M_PI) * rand01() : + (float)(2*M_PI * k) / n_rayons, + .spectre = spectre + }; + if (secteur.has_value() and not secteur->inclus(rayon.dir_angle)) + continue; + rayon.spectre.for_each([&] (float, pola_t, float& I) { + I *= this->directivite( rayon.dir_angle ); + }); + rayons.push_back(std::move(rayon)); + } + return rayons; +} + +std::vector Source_SecteurDisqueLambertien::genere_rayons () { + std::vector rayons; + size_t n_rayons = lroundf(dens_ang * ang.longueur()/(2*M_PI)); + for (size_t k = 0; k < n_rayons; k++) { + Rayon ray = { .spectre = spectre }; + ray.dir_angle = ang.beg() + ang.longueur() * ( dir_alea ? rand01() : ((float)k / n_rayons) ); + ray.orig = position + R * vec_t{ .x = cosf(ray.dir_angle), .y = sinf(ray.dir_angle) }; + float incr_angle = M_PI/2 * (1-2*rand01()); + ray.spectre.for_each([&] (float, pola_t, float& I) { + I *= this->directivite( ray.dir_angle ) * cosf(incr_angle); + }); + ray.dir_angle += incr_angle; + rayons.push_back(std::move(ray)); + } + return rayons; +} + +std::vector Source_LinLambertien::genere_rayons () { + std::vector rayons; + size_t n_rayons = lroundf(dens_lin * !vec); + float dir_angle = atan2f(vec.y, vec.x) - M_PI/2; + for (size_t k = 0; k < n_rayons; k++) { + float incr_angle = M_PI/2 * (1-2*rand01()); + Rayon ray = { + .orig = a + vec * (float)k / n_rayons, + .dir_angle = dir_angle + incr_angle, + .spectre = spectre + }; + ray.spectre.for_each([&] (float, pola_t, float& I) { + I *= cosf(incr_angle); + }); + rayons.push_back(std::move(ray)); + } + return rayons; +}; + +///------------------------ Affichage ------------------------/// + +#include "sfml_c01.hpp" + +void Source_UniqueRayon::dessiner (sf::RenderWindow& win) const { + win.draw( sf::c01::buildLine(position, + position + 0.01 * vec_t{ .x = cosf(dir_angle), .y = sinf(dir_angle) }, + sf::Color::Yellow) ); +} + +void Source_PonctOmni::dessiner (sf::RenderWindow& win) const { + auto c = sf::c01::buildCircleShapeCR(position, 0.005); + c.setFillColor(sf::Color::Yellow); + win.draw(c); +} + +void Source_SecteurDisqueLambertien::dessiner (sf::RenderWindow& win) const { + size_t N = R * 100 * ang.longueur(); + if (N < 3) N = 3; + sf::ConvexShape secteur (N+2); + for (size_t k = 0; k <= N; k++) { + float theta = ang.beg() + ang.longueur() * (float) k / N; + secteur.setPoint(k, sf::c01::toWin( + position + R * vec_t{ .x = cosf(theta), + .y = sinf(theta) } + )); + } + secteur.setPoint(N+1, sf::c01::toWin(position)); + secteur.setFillColor(sf::Color::Yellow); + win.draw(secteur); +} + +void Source_LinLambertien::dessiner (sf::RenderWindow& win) const { + win.draw( + sf::c01::buildLine(a, a+vec, sf::Color::Yellow) + ); +} + +void Source_LinParallels::dessiner (sf::RenderWindow& win) const { + win.draw( + sf::c01::buildLine(a, a+vec, sf::Color::Yellow) + ); +} diff --git a/LightRays/Source.h b/LightRays/Source.h new file mode 100644 index 0000000..9331f58 --- /dev/null +++ b/LightRays/Source.h @@ -0,0 +1,168 @@ +/******************************************************************************* + * Classe de base des sources. Divers types de sources. + *******************************************************************************/ + +#ifndef _LIGHTRAYS_SOURCE_H_ +#define _LIGHTRAYS_SOURCE_H_ + +#include "Rayon.h" +#include +#include + +//------------------------------------------------------------------------------ +// Classe de base, abstraite, des sources. À priori, seulement définie par +// son spectre. + +class Source { +public: + Specte spectre; // spectre de la source en intensité + + // source de spectre arbitraire + Source (Specte spectre) : spectre(spectre) {} + // source monochromatique polarisée + Source (color_id_t couleur, float I, pola_t pol) : spectre( Specte::monochromatique(I, couleur, pol) ) {} + // source monochromatique non-polarisée + Source (color_id_t couleur, float I) : spectre( Specte::monochromatique(I, couleur) ) {}; + + Source& operator= (const Source&) = default; + Source (const Source&) = default; + virtual ~Source () {} + + // génération des rayons + virtual std::vector genere_rayons () = 0; + + // dessin de la source + virtual void dessiner (sf::RenderWindow& window) const = 0; +}; + +//------------------------------------------------------------------------------ +// Source émettant un unique rayon à partir du point `position` et à un angle +// `dir_angle` à l'horizontale. "Rayon laser". + +class Source_UniqueRayon : public Source { +public: + point_t position; + float dir_angle; + + // relai aux constructeurs de `Source` + template Source_UniqueRayon (point_t pos, float dir, Args&&... x) : + Source(x...), position(pos), dir_angle(dir) {} + + Source_UniqueRayon& operator= (const Source_UniqueRayon&) = default; + Source_UniqueRayon (const Source_UniqueRayon&) = default; + virtual ~Source_UniqueRayon () {} + + // création de l'unique rayon + virtual std::vector genere_rayons () override; + + void dessiner (sf::RenderWindow& window) const override; +}; + +//------------------------------------------------------------------------------ +// Source étendue émettant un plusieurs rayon distribués sur une ligne et à un +// angle fixe. Peut être vu comme une source ponctuelle à l'infini, ou un +// ensemble de `Source_UniqueRayon`. + +class Source_LinParallels : public Source { +public: + point_t a; // point A du segment + vec_t vec; // vecteur AB du segment + float dens_lin; // nombre de rayons par unité de longueur + float dir_angle_rel; // angle par rapport au vecteur AB + + template Source_LinParallels (point_t pos_seg_a, point_t pos_seg_b, float dir_angle, Args&&... x) : + Source(x...), a(pos_seg_a), vec(pos_seg_b-pos_seg_a), dens_lin(100), dir_angle_rel(dir_angle) {} + template Source_LinParallels (point_t pos_seg_a, vec_t pos_ab_vecteur, float dir_angle, Args&&... x) : + Source(x...), a(pos_seg_a), vec(pos_ab_vecteur), dens_lin(100), dir_angle_rel(dir_angle) {} + + Source_LinParallels& operator= (const Source_LinParallels&) = default; + Source_LinParallels (const Source_LinParallels&) = default; + virtual ~Source_LinParallels () {} + + virtual std::vector genere_rayons () override; + + void dessiner (sf::RenderWindow& window) const override; +}; + +//------------------------------------------------------------------------------ +// Classe de base virtuelle des sources omnidirectionnelle +// (ou restreinte à un secteur angulaire `secteur`) à directivité arbitraire. + +class Source_Omni : public Source { +public: + point_t position; // position de la source + float dens_ang; // nombre de rayons pour 2π + bool dir_alea; // émission dans des directions aléatoires ou équiréparties/déterministe + std::function< float(float theta) > directivite; // directivité (normalisée à 1) + const static decltype(directivite) directivite_unif; // directivité unirforme + std::optional secteur; // secteur angulaire d'émission + + template Source_Omni (point_t pos, Args&&... x) : + Source(x...), position(pos), dens_ang(100), dir_alea(true), directivite(directivite_unif) {} + + Source_Omni& operator= (const Source_Omni&) = default; + Source_Omni (const Source_Omni&) = default; + virtual ~Source_Omni () {} +}; + +//------------------------------------------------------------------------------ +// Source ponctuelle omnidirectionnelle (ou restreinte à un secteur angulaire) + +class Source_PonctOmni : public Source_Omni { +public: + virtual std::vector genere_rayons () override; + + void dessiner (sf::RenderWindow& window) const override; + + template Source_PonctOmni (point_t pos, Args&&... x) : Source_Omni(pos, x...) {} + + virtual ~Source_PonctOmni () {} +}; + +//------------------------------------------------------------------------------ +// Source étendue en secteur de disque ("projecteur") avec une émission +// lambertienne à chaque point de sa surface (loi en cosinus). Si R très petit, +// équivalent à Source_PonctOmni. + +class Source_SecteurDisqueLambertien : public Source_Omni { +public: + float R; + angle_interv_t ang; + + template Source_SecteurDisqueLambertien (float radius, angle_interv_t ang_interval, Args&&... x) : + Source_Omni(x...), R(radius), ang(ang_interval) {} + + Source_SecteurDisqueLambertien& operator= (const Source_SecteurDisqueLambertien&) = default; + Source_SecteurDisqueLambertien (const Source_SecteurDisqueLambertien&) = default; + virtual ~Source_SecteurDisqueLambertien () {} + + virtual std::vector genere_rayons () override; + + void dessiner (sf::RenderWindow& window) const override; +}; + +//------------------------------------------------------------------------------ +// Source étendue linéaire ("écran lumineux") avec une émission lambertienne +// à chaque point de sa surface (loi en cosinus), et d'un seul côté du segment. + +class Source_LinLambertien : public Source { +public: + point_t a; + vec_t vec; + float dens_lin; + + template Source_LinLambertien (point_t pos_seg_a, point_t pos_seg_b, Args&&... x) : + Source(x...), a(pos_seg_a), vec(pos_seg_b-pos_seg_a), dens_lin(100) {} + template Source_LinLambertien (point_t pos_seg_a, vec_t pos_ab_vecteur, Args&&... x) : + Source(x...), a(pos_seg_a), vec(pos_ab_vecteur), dens_lin(100) {} + + Source_LinLambertien& operator= (const Source_LinLambertien&) = default; + Source_LinLambertien (const Source_LinLambertien&) = default; + virtual ~Source_LinLambertien () {} + + virtual std::vector genere_rayons () override; + + void dessiner (sf::RenderWindow& window) const override; +}; + +#endif \ No newline at end of file diff --git a/LightRays/Util.cpp b/LightRays/Util.cpp new file mode 100644 index 0000000..cda448d --- /dev/null +++ b/LightRays/Util.cpp @@ -0,0 +1,97 @@ +#include "Util.h" +#include +#include +#include + +float vec_t::operator! () const { + return hypotf(x, y); +} + +vec_t vec_t::rotate (float theta) const { + float c = cosf(theta), s = sinf(theta); + return { .x = c * x - s * y, + .y = s * x + c * y }; +} + +point_t milieu_2points (point_t a, point_t b) { + return { .x = (a.x + b.x)/2, .y = (a.y + b.y)/2 }; +} + +std::vector translate_points (vec_t by, std::vector pts) { + for (point_t& p : pts) + p = p + by; + return pts; +} + +std::vector rotate_points (point_t around, float angle, std::vector pts) { + for (point_t& p : pts) + p = around + (p - around).rotate(angle); + return pts; +} + +std::vector homothetie_points (point_t around, float coeff, std::vector pts) { + for (point_t& p : pts) + p = around + coeff * (p - around); + return pts; +} + +// ⎡a b⎤ ⎡x⎤ ⎡e⎤ +// ⎣c d⎦ ⎣y⎦ = ⎣f⎦ +// +void mat22_sol (float a, float b, float c, float d, float e, float f, float& x, float& y) { + float det = a * d - b * c; + x = (e * d - b * f) / det; + y = (a * f - e * c) / det; +} + +const angle_interv_t angle_interv_t::cercle_entier (2*M_PI); + +angle_interv_t::angle_interv_t (float theta_beg, float b) : a(theta_beg), l(0) { + if (fabsf(b-a) > 2*M_PI+1e-6) + throw std::domain_error("angle interval > 2π"); + if (b < 0 and a >= 0) + b += 2*M_PI; + l = b - a; + a = angle_mod2pi_02(a); +// assert(0 <= a and a <= 2*M_PI and 0 <= l and l <= 2*M_PI); +} + +angle_interv_t::angle_interv_t (float lenght) : a(0), l(lenght) { + if (l < 0 or l > 2*M_PI+1e-6) + throw std::domain_error("invalid angle interval length"); +} + +angle_interv_t angle_interv_t::operator+ (float delta_theta) const { + angle_interv_t o = *this; + o.a = angle_mod2pi_02(a + delta_theta); + return o; +} + +float angle_mod2pi_11 (float theta) { + while (theta > M_PI) + theta -= 2*M_PI; + while (theta < -M_PI) + theta += 2*M_PI; + return theta; +} + +float angle_mod2pi_02 (float theta) { + return theta - 2*M_PI* floorf( theta/(2*M_PI) ); +} + +bool angle_interv_t::inclus (float theta) const { + theta = angle_mod2pi_02(theta); + if (a <= theta and theta <= a+l) + return true; + theta += 2*M_PI; + return (a <= theta and theta <= a+l); +} + +std::pair angle_interv_t::vec_a_b () const { + return { vec_t{ .x = cosf(a), .y = sinf(a) }, + vec_t{ .x = cosf(a+l), .y = sinf(a+l) } }; +} + +float rand01 () { + return rand()/(float)RAND_MAX; +} diff --git a/LightRays/Util.h b/LightRays/Util.h new file mode 100644 index 0000000..a5c32a7 --- /dev/null +++ b/LightRays/Util.h @@ -0,0 +1,98 @@ +#ifndef _LIGHTRAYS_UTIL_H_ +#define _LIGHTRAYS_UTIL_H_ + +#include +#include + +#ifdef NOSTDOPTIONAL + #include + namespace std { + using boost::optional; + const boost::none_t nullopt = boost::none; + } +#else + #include +#endif + +#define Inf std::numeric_limits::infinity() +#define NaN std::numeric_limits::signaling_NaN() + +/// Vecteur 2D +struct vec_t { + float x, y; + void operator/= (float k) { x /= k; y /= k; } + void operator*= (float k) { x *= k; y *= k; } + void operator+= (vec_t o) { x += o.x; y += o.y; } + void operator-= (vec_t o) { x -= o.x; y -= o.y; } + vec_t operator* (float k) const { return vec_t{ k*x, k*y }; } + vec_t operator/ (float k) const { return vec_t{ x/k, y/k }; } + vec_t operator+ (vec_t o) const { return vec_t{ x+o.x, y+o.y }; } + vec_t operator- () const { return vec_t{ -x, -y }; } + vec_t operator- (vec_t o) const { return vec_t{ x-o.x, y-o.y }; } + float operator| (vec_t o) const { return x*o.x + y*o.y ; } // produit scalaire + float norm2 () const { return x*x + y*y; } // norme au carré + float operator! () const; // norme + vec_t rotate (float theta) const; // rotation d'un angle `theta` + vec_t rotate_p90 () const { return vec_t { -y, +x }; } + vec_t rotate_m90 () const { return vec_t { +y, -x }; } +}; +inline vec_t operator* (double k, const vec_t& v) { return v*k; } + +/// Point 2D +struct point_t { + float x, y; + vec_t operator- (point_t o) const { return vec_t{ x-o.x, y-o.y }; } // vecteur entre deux points + explicit operator vec_t () const { return vec_t{ x, y }; } + point_t operator+ (vec_t v) const { return point_t{ x+v.x, y+v.y }; } // translation du point par un vecteur +}; + +// Milieu entre deux points +point_t milieu_2points (point_t a, point_t b); + +// Transation d'un ensemble de points +std::vector translate_points (vec_t by, std::vector); +// Rotation d'un ensemble de points autour d'un centre `around` d'un angle `angle` +std::vector rotate_points (point_t around, float angle, std::vector); +// Homothétie d'un ensemble de points autout d'un centre `around` par un facter `coeff` +std::vector homothetie_points (point_t around, float coeff, std::vector); + + +/// Intervalle d'angle dans ℝ/2πℝ +struct angle_interv_t { +private: + float a, l; // angle de début (0 ≤ a ≤ 2π) et longueur de l'intervalle (0 ≤ l ≤ 2π) +public: + angle_interv_t (const angle_interv_t&) = default; + static const angle_interv_t cercle_entier; + + // construction à partir de l'angle de début et de fin + // si (theta_beg > 0 > theta_end) numériquement, interprète l'intervalle comme [theta_beg,theta_end+2π] + // si |theta_end-theta_beg| > 2π, lances une exception (n'est pas censé arriver dans ce programme) + angle_interv_t (float theta_beg, float theta_end); + // construction d'un intervalle de longeur donnée, partant de θ=0 + angle_interv_t (float lenght); + + angle_interv_t operator+ (float delta_theta) const; // rotation de l'intervalle + float longueur () const { return l; } // longueur de l'intervalle, entre 0 et 2π + bool inclus (float theta) const; // test si un angle (dans ℝ/2πℝ) est inclus dans l'intervalle + float beg () const { return a; } // angle de début, entre 0 et 2π + float end () const { return a+l; } // angle de fin, entre 0 et 4π + std::pair vec_a_b () const; // vecteurs unité définissant les angles début et fin +}; + +// réduit un angle à [-π,+π] +float angle_mod2pi_11 (float); +// réduit un angle à [0,2π] +float angle_mod2pi_02 (float); + +/// Divers + +// Réslution du système linéaire +// ⎡a b⎤ ⎡x⎤ ⎡e⎤ +// ⎣c d⎦ ⎣y⎦ = ⎣f⎦ +void mat22_sol (float a, float b, float c, float d, float e, float f, float& x, float& y); + +// Nombre au hasard entre 0 et 1 +float rand01 (); + +#endif diff --git a/LightRays/main_brouillard.cpp b/LightRays/main_brouillard.cpp new file mode 100644 index 0000000..400b6f4 --- /dev/null +++ b/LightRays/main_brouillard.cpp @@ -0,0 +1,84 @@ +/******************************************************************************** + * Scène de test d'un brouillard diffusant, avec source "laser" et "en secteur", + * avec différentes méthodes de diffusion et densité du brouillard variable + ********************************************************************************/ + +#include "SceneTest.h" +#include "Brouillard.h" +#include "ObjetDiffusant.h" + +int main (int, char const**) { + + Scene_TestCommon scene (L"Scène test brouillard", /*écran image*/true, /*lentille image*/true); + + scene.creer_ecrans_autour(/*lumino*/0.0005); + scene.propag_rayons_dessin_gain = 30; + scene.ecrans_do([] (Ecran_Base& e) { e.luminosite *= 20; }); + + // Création des sources (et d'un fond bleu parce que pourquoi pas) + auto source_unique = scene.creer_source_unique_rayon(point_t{1,0.445}, /*dir*/0.99*M_PI, /*ampl*/400.); + auto source_omni = scene.creer_source_omni_secteur(point_t{0.7,0.60}, /*ang_ext*/0.1, /*ang_base*/1.05*M_PI, /*dens_ray*/10000); + auto ciel = scene.creer_ciel_bleu(/*dens lin*/300); + + // Ancre de mise au point de la lentille à la souris, sur le bord droit du brouillard par défaut + scene.ajouter_bouge_action(point_t{0.5,0.5}, [&] (point_t mouse, float angle, bool alt) -> point_t { + scene.lentille_mise_au_point(mouse.x); + return point_t{mouse.x,0.5}; + }); + scene.lentille_mise_au_point(0.5); + + // Création du brouillard lui même avec sa fonction densité(x,y) + float dens_brouillard = 100; + const float taille_brouill = 0.25; + const float resol_brouill = 0.01; + const size_t sz_brouill = taille_brouill/resol_brouill; + auto brouillard = scene.creer_objet( + /* bottom right */ /* résolutions */ /* nombre de voxels */ + point_t{0.3,0.4}, resol_brouill, resol_brouill, sz_brouill, sz_brouill, + /* fonction retournant la densité du brouillard en fonction de la position */ + [&] (uint ix, uint iy) -> float { + float x = (ix-sz_brouill/2.)/(sz_brouill/2.); + float y = (iy-sz_brouill/2.)/(sz_brouill/2.); + return dens_brouillard * std::max(0.f, 1 - expf(x*x + y*y - 1)); + }); + brouillard->n_re_emit_par_intens = 2; + brouillard->intens_cutoff = 0.2; + bool diffus_meth_tot = true; + + scene.static_text.insert(scene.static_text.begin(), { + L"[T/Y] brouillard moins/plus diffusant", + L"[M] change méthode diffusion (totale/partielle)", + L"[P] parcours brouillard déterministe ou non", + L"" + }); + + scene.boucle( + /*f_event*/ [&] (sf::Event event) { + if (event.type == sf::Event::KeyPressed and event.key.code == sf::Keyboard::T) { // diminution de la densité du brouillard + dens_brouillard *= 0.9; + scene.reset_ecrans = true; + } + if (event.type == sf::Event::KeyPressed and event.key.code == sf::Keyboard::Y) { // augmentation de la densité du brouillard + dens_brouillard *= 1.1; + scene.reset_ecrans = true; + } + if (event.type == sf::Event::KeyPressed and event.key.code == sf::Keyboard::M) { // changement de méthode de diffusion + if (diffus_meth_tot) { + diffus_meth_tot = false; + brouillard->diffus_partielle_syst_libreparcours = resol_brouill; + brouillard->n_re_emit_par_intens = 1; + } else { + diffus_meth_tot = true; + brouillard->diffus_partielle_syst_libreparcours = Inf; + brouillard->n_re_emit_par_intens = 3; + } + scene.reset_ecrans = true; + } + if (event.type == sf::Event::KeyPressed and event.key.code == sf::Keyboard::P) { // changement de parcours du brouillard par les rayons, déterministe ou non + brouillard->parcours_deterministe = !brouillard->parcours_deterministe; + scene.reset_ecrans = true; + } + }, /*f_pre_propag*/ nullptr, /*f_post_propag*/ nullptr); + + return 0; +} diff --git a/LightRays/main_debug.cpp b/LightRays/main_debug.cpp new file mode 100644 index 0000000..aa168f1 --- /dev/null +++ b/LightRays/main_debug.cpp @@ -0,0 +1,111 @@ +#include "SceneTest.h" +#include + +int main (int, char const**) { + + Scene_TestCommon scene (L"Debug", /*ecran_image*/true, /*lentille image*/true); + scene.creer_ecrans_autour(); + + auto source_omni_ponct = std::make_shared( + point_t{0.34306,0.48611}, Specte::polychromatique({{0.8,0.8,1.7,2.2}}) + ); + auto color = source_omni_ponct->spectre.rgb256_noir_intensite(false); + fmt::print("({}, {}, {})", std::get<0>(color),std::get<1>(color),std::get<2>(color)); + source_omni_ponct->dens_ang = 500; + source_omni_ponct->dir_alea = false; + scene.sources.push_back(source_omni_ponct); + scene.ajouter_bouge_action(source_omni_ponct->position, [&] (point_t mouse, float angle, bool alt) -> point_t { + source_omni_ponct->position = mouse; + return mouse; + }); + + auto source_sect = std::make_shared( + 0.03, angle_interv_t(-M_PI/10, +M_PI/10)+M_PI, point_t{0.1,0.5}, Specte::polychromatique({{0.8,0.8,1.7,2.2}}) + ); + source_sect->dens_ang = 30000; + source_sect->dir_alea = true; + scene.sources.push_back(source_sect); + scene.ajouter_bouge_action(source_sect->position, [&] (point_t mouse, float angle, bool alt) -> point_t { + if (alt) { + float l = source_sect->ang.longueur(); + source_sect->ang = angle_interv_t(-l/2, +l/2) + angle; + } + else source_sect->position = mouse; + return source_sect->position; + }); + +// scene.propag_rayons_dessin_window = nullptr; +// scene.propag_intercept_dessin_window = scene.win_scene; + scene.static_text.insert(scene.static_text.begin(), { + L"[X] debug rayon", + }); + + struct propag_debug_rayons_t { + point_t milieu_rayon; + Rayon ray; + ObjetArc::intercept_courbe_t intercept; + }; + std::vector propag_debug_rayons; + bool propag_debug = false; + + auto propag_intercept_debug_info_mouse = [&] (Objet& o, const Rayon& ray, std::shared_ptr intercept_struct) { + if (dynamic_cast(&o) != nullptr) { // si c'est un ObjetCourbe (sinon le point d'interception n'est pas défini) + ObjetArc::intercept_courbe_t& intercept = *std::static_pointer_cast(intercept_struct); + propag_debug_rayons.push_back(propag_debug_rayons_t{ + .milieu_rayon = milieu_2points(ray.orig, intercept.p_incid), + .ray = ray, + .intercept = intercept + }); + } +/* auto p = o.point_interception(intercept_struct); + if (p.has_value()) { + propag_debug_rayons.push_back(propag_debug_rayons_t{ + .milieu_rayon = milieu_2points(ray.orig, *p), + .ray = ray, + }); + }*/ + }; + + scene.boucle( + /*f_event*/ [&] (sf::Event event) { + if (event.type == sf::Event::KeyPressed and event.key.code == sf::Keyboard::X) { + if (propag_debug) { + propag_debug = false; + scene.propag_intercept_cb = nullptr; + } else { + propag_debug = true; + scene.propag_intercept_cb = propag_intercept_debug_info_mouse; + } + } + }, + /*f_pre_propag*/ [&] () { + }, + /*f_post_propag*/ [&] () { + if (propag_debug and not propag_debug_rayons.empty()) { + auto closest_rayon = propag_debug_rayons.begin(); + float closest_dist2 = Inf; + for (auto it = propag_debug_rayons.begin(); it != propag_debug_rayons.end(); it++) { + float dist2 = (scene.mouse - it->milieu_rayon).norm2(); + if (dist2 < closest_dist2) { + closest_dist2 = dist2; + closest_rayon = it; + } + } + const Rayon& ray = closest_rayon->ray; + auto point_milieu = sf::c01::buildCircleShapeCR(closest_rayon->milieu_rayon, 0.003); + point_milieu.setFillColor(sf::Color::White); + scene.win_scene->draw(point_milieu); + auto text = sf::c01::buildText(scene.font, closest_rayon->milieu_rayon, { + fmt::format(L"orig : ( {:.3f}, {:.3f} )", ray.orig.x, ray.orig.y), + fmt::format(L"angle : {:.2f}π", ray.dir_angle/M_PI), + fmt::format(L"incid : ( {:.3f}, {:.3f} )", closest_rayon->intercept.p_incid.x, closest_rayon->intercept.p_incid.y), + fmt::format(L"angle incid : {:.2f}π/2 {}", closest_rayon->intercept.ang_incid/(M_PI/2), closest_rayon->intercept.sens_reg ? L"reg" : L"inv"), + fmt::format(L"I n°1 TE : {:.3e}", ray.spectre.comps[2]), + fmt::format(L"I n°1 TM : {:.3e}", ray.spectre.comps[3]), + }, sf::Color::White); + scene.win_scene->draw(text); + } + }); + + return 0; +} diff --git a/LightRays/main_diffus_test.cpp b/LightRays/main_diffus_test.cpp new file mode 100644 index 0000000..d0809db --- /dev/null +++ b/LightRays/main_diffus_test.cpp @@ -0,0 +1,140 @@ +/******************************************************************************** + * Scène de test d'une surface diffusante avec une composante de rélfexion + * lambertienne et une composante spéculaire en cos(θ)^n. Bilan d'énergie + ********************************************************************************/ + +#include "SceneTest.h" +#include "ObjetDiffusant.h" +#include +#include + +int main (int, char const**) { + + Scene_TestCommon scene (L"Test objets diffusants", /*ecran image*/true, /*lentille image*/true); + scene.creer_ecrans_autour(/*lumino*/ 0.0005, /*épaisseur*/ 0.025); + + float source_ext_ang = 0.1; + auto source_omni = scene.creer_source_omni_secteur(point_t{0.3,0.66}, source_ext_ang, /*ang_base*/-0.22*M_PI, /*dens_ray*/60000); + + // création du plan diffusant + auto panel_diffus = std::make_shared(ObjetCourbe_Diffusant::BRDF_Lambert, point_t{0.45,0.51}, point_t{0.55,0.49}); + point_t panel_m = milieu_2points(panel_diffus->a,panel_diffus->b); + scene.objets.push_back(panel_diffus); + // mise au point au milieu du plan + scene.lentille_mise_au_point(panel_m.x); + // déplacement/rotation du plan à la souris + scene.ajouter_bouge_action(panel_m, [&] (point_t mouse, float angle, bool alt) -> point_t { + vec_t v = panel_diffus->b - panel_diffus->a; + if (alt) { + v = !v * vec2_t{ cosf(angle), sinf(angle) }; + } else { + panel_m = mouse; + } + panel_diffus->a = panel_m + v/2; + panel_diffus->b = panel_m + -v/2; + return panel_m; + }); + // composante de réflexion en cos^n, où on se rapporche d'une réflexion très directive lorsque n augmente (spéculaire) + int diffus_n = 3; + // coeff de normalisation tel que l'intégrale de `cos^n / c` fasse 1 + // on vérifie qu'on a bien conservation de l'énergie avec `objet_bilan` ci-dessous + auto diffus_c_calc = [] (int n) -> float { + float c = 2; + while (n >= 3) { + c *= (n-1.f) / n; + n -= 2; + } + return c; + }; + float diffus_c = diffus_c_calc(diffus_n); + float refl_spec = 1; + // distribution angulaire de réflectance et ses deux composantes + panel_diffus->BRDF_lambda = [&] (float theta_i, float theta_r, float lambda) -> float { + // spectre de réflexion diffuse centrée autour de 450nm + float f_col = (lambda-4.5e-4)/1e-4; f_col = expf(-f_col*f_col); + return f_col * (1-refl_spec) + refl_spec * M_PI * powf( std::max(0.f, cosf(theta_i+theta_r)), diffus_n) / diffus_c; + // réflexion lambertienne réflexion spéculaire (réflectivité de lobe cos^n) + }; + // À améliorer : Il n'y a pas conservation de l'énergie lorsque le rayon incident n'est pas normal, + // car il y a troncature de l'angle du rayon réfléchi sur [-pi/2,+pi/2] ! + + // Bilan d'énergie pour vérifier si `diffus_c_calc` est correct. + auto objet_bilan = scene.creer_objet(point_t{0.5,0.5}, 0.2); + + // Diaphragme + scene.creer_objet(point_t{1.3,0.53}, point_t{1.3,0.6}); + scene.creer_objet(point_t{1.3,0.47}, point_t{1.3,0.4}); + + scene.static_text.insert(scene.static_text.begin(), { + L"Surface diffusante avec une composante de rélfexion lambertienne (uniforme) et une composante spéculaire en cos(θ)^n", + L"[W/X] augmente/diminue la directivité de la partie spéculaire de la réflexion", + L"[C/V] diminue/augmente la partie spéculaire de la réflexion, vs lambertienne", + L"[E/Z] élargit/rétrécit la source", + L"[K] mode déterministe", + L"" + }); + + scene.boucle( + /*f_event*/ [&] (sf::Event event) { + if (event.type == sf::Event::KeyPressed and event.key.code == sf::Keyboard::W) { // réflexion spéculaire plus piquée + diffus_n = 2*diffus_n+1; + diffus_c = diffus_c_calc(diffus_n); + scene.reset_ecrans = true; + } + if (event.type == sf::Event::KeyPressed and event.key.code == sf::Keyboard::X) { // réflexion spéculaire moins piquée + if (diffus_n > 1) + diffus_n = (diffus_n-1)/2; + diffus_c = diffus_c_calc(diffus_n); + scene.reset_ecrans = true; + } + if (event.type == sf::Event::KeyPressed and event.key.code == sf::Keyboard::V) { // plus de réflexion spéculaire + refl_spec = std::min(1, refl_spec+0.02); + scene.reset_ecrans = true; + } + if (event.type == sf::Event::KeyPressed and event.key.code == sf::Keyboard::C) { // moins de réflexion spéculaire + refl_spec = std::max(0, refl_spec-0.02); + scene.reset_ecrans = true; + } + if (event.type == sf::Event::KeyPressed and event.key.code == sf::Keyboard::E) { // étend la source + source_ext_ang *= 1.1; + scene.reset_ecrans = true; + } + if (event.type == sf::Event::KeyPressed and event.key.code == sf::Keyboard::Z) { // rétrécit la source + source_ext_ang *= 0.9; + scene.reset_ecrans = true; + } + if (event.type == sf::Event::KeyPressed and event.key.code == sf::Keyboard::K) { // mode déterministe (équirépartition des rayons émis) + panel_diffus->diff_met = ObjetLigne_Diffusant::Equirep; + panel_diffus->n_re_emit_par_intens = 300; + source_omni->dens_ang = 600; + source_omni->dir_alea = false; + scene.ecrans_do([] (Ecran_Base& e) { e.luminosite *= 80; }); + scene.propag_rayons_dessin_gain *= 80; + scene.reset_ecrans = true; + } + }, + /*f_pre_propag*/ [&] () { + // réinitialisation de `objet_bilan` + if (scene.reset_ecrans) + objet_bilan->reset(); + // on fait toujours pointer la souce vers le centre du plan diffusant + vec_t v = panel_m - source_omni->position; + source_omni->secteur = angle_interv_t(-source_ext_ang,+source_ext_ang) + atan2f(v.y,v.x); + }, + /*f_post_propag*/ [&] () { + // affichage du bilan d'énergie + objet_bilan->commit(); + auto stat = objet_bilan->bilan(); + float diff = stat.flux_in - stat.flux_out; + std::wstringstream s; + s << L"bilan énergie :" << std::endl; + s << std::setprecision(1) << std::fixed; + s << L"flux in " << stat.flux_in << ", flux out " << stat.flux_out << std::endl; + s << std::setprecision(1); + s << L"loss " << diff << ", loss rel " << 100*diff/stat.flux_in << std::endl; + auto text = sf::c01::buildText(scene.font, (objet_bilan->c + objet_bilan->R*vec_t{+0.8,+0.9}), {s.str()}, sf::Color::White); + scene.win_scene->draw(text); + }); + + return 0; +} diff --git a/LightRays/main_milieux.cpp b/LightRays/main_milieux.cpp new file mode 100644 index 0000000..3598a26 --- /dev/null +++ b/LightRays/main_milieux.cpp @@ -0,0 +1,98 @@ +/******************************************************************************** + * Scène de test des objets réfléchissants & réfractants (ObjetMilieux) : + * réflexion interne totale, séparation des couleurs (indice variable), lentille + * réelle "épaisse", miroirs, etc… + ********************************************************************************/ + +#include "SceneTest.h" +#include "ObjetMilieux.h" + +int main (int, char const**) { + + Scene_TestCommon scene (L"Réfraction et milieux", /*ecran image*/true, /*lentille image*/false); + scene.creer_ecrans_autour(/*lumino*/0.001, /*épaisseur*/0.02); + + /// --------- Sources --------- + + auto source_unique = scene.creer_source_unique_rayon(point_t{0.1,0.3}, /*dir*/0, /*ampl*/400.); + source_unique->spectre = Specte::polychromatique({{8,8,17,22}}); + + auto source_omni = scene.creer_source_omni_secteur(point_t{0.34722,0.50972}, /*ang_ext*/0.1, /*ang_base*/0, 10000); + + /// --------- Lame --------- + + std::vector pts = { {0,0}, {0.2,0}, {0.2,0.03}, {0,0.03} }; + pts = rotate_points({0.2,0.1}, 0.4*M_PI, pts); + pts = translate_points({0.2,0.35}, pts); + auto lame = std::make_shared(pts, 2.4); + scene.objets.push_back(lame); + scene.ajouter_bouge_action(pts[0], [&] (point_t mouse, float angle, bool alt) -> point_t { + lame->re_positionne(mouse); + return mouse; + }); + + /// --------- Dioptre à indice variable --------- + + std::function indice_refr_lambda = [] (float lambda) { + return 1 + 0.1 * lambda / lambda_color[N_COULEURS/2]; + }; + auto dioptre = std::make_shared(indice_refr_lambda, point_t{0.8,0.25}, point_t{0.85,0.35}); + scene.objets.push_back(dioptre); + scene.ajouter_bouge_action(dioptre->a, [&] (point_t mouse, float angle, bool alt) -> point_t { + if (!alt) { dioptre->b = dioptre->b + (mouse - dioptre->a); dioptre->a = mouse; } + else dioptre->b = mouse; + return dioptre->a; + }); + + /// --------- Lentille réelle convergente --------- + + // Les deux dioptres en arc de cercle formant la lentille convergente + scene.creer_objet( 1.7, point_t{1.2,0.4}, point_t{1.2,0.6}, 0.3, false ); + scene.creer_objet( 1.7, point_t{1.2,0.6}, point_t{1.2,0.4}, 0.3, false ); + // Diaphragme + scene.creer_objet(point_t{1.3,0.52}, point_t{1.3,0.6}); + scene.creer_objet(point_t{1.3,0.48}, point_t{1.3,0.4}); + + /// --------- Miroir --------- + + auto miroir = std::make_shared(point_t{0.2,0.45}, point_t{0.2,0.55}); + scene.objets.push_back(miroir); + scene.ajouter_bouge_action(miroir->a, [&] (point_t mouse, float angle, bool alt) -> point_t { + if (!alt) { miroir->b = miroir->b + (mouse - miroir->a); miroir->a = mouse; } + else miroir->b = mouse; + return miroir->a; + }); + + /// --------- Filtre --------- + + auto filtre_vert = scene.creer_objet((color_id_t)2, point_t{0.1,0.8}, point_t{0.2,0.8}); + scene.ajouter_bouge_action(filtre_vert->a, [&] (point_t mouse, float angle, bool alt) -> point_t { + if (!alt) { filtre_vert->b = filtre_vert->b + (mouse - filtre_vert->a); filtre_vert->a = mouse; } + else filtre_vert->b = mouse; + return filtre_vert->a; + }); + + /// --------- Prisme --------- + + const std::vector pts_triangle = { {1,0}, {cosf(2*M_PI/3),sinf(2*M_PI/3)}, {cosf(-2*M_PI/3),sinf(-2*M_PI/3)} }; + point_t prisme_position = {0.5, 0.8}; + float prisme_angle = 0; + float prisme_taille = 0.1; + auto prisme_scene_it = scene.objets.insert(scene.objets.end(), nullptr); + auto prisme_move_action = [&] (point_t mouse, float angle, bool alt) -> point_t { + if (alt) prisme_angle = angle; + else prisme_position = mouse; + auto pts_tri = homothetie_points({0,0}, prisme_taille, pts_triangle); + pts_tri = rotate_points({0,0}, prisme_angle, pts_tri); + pts_tri = translate_points((vec_t)prisme_position, pts_tri); + *prisme_scene_it = std::make_shared(pts_tri, indice_refr_lambda); + return prisme_position; + }; + prisme_move_action(prisme_position, 0, false); + scene.ajouter_bouge_action(prisme_position, prisme_move_action); + + + scene.boucle(nullptr, nullptr, nullptr); + + return 0; +} diff --git a/LightRays/main_store.cpp b/LightRays/main_store.cpp new file mode 100644 index 0000000..bf08b23 --- /dev/null +++ b/LightRays/main_store.cpp @@ -0,0 +1,50 @@ +/******************************************************************************** + * Scène de test d'un store constitué d'arcs de cercles diffusants + ********************************************************************************/ + +#include "SceneTest.h" +#include "ObjetDiffusant.h" + +int main (int, char const**) { + + Scene_TestCommon scene (L"Soleil et store", /*ecran_image*/false, /*lentille image*/false); + + scene.creer_ecrans_autour(/*lumino*/0.001, /*épaisseur*/0.02); + + auto ciel = scene.creer_ciel_bleu(); + // Soleil modélisé par des rayons parallèles : source de type `Source_LinParallels` + auto soleil = std::make_shared(point_t{0.1,0.65}, vec_t{0,0.3}, /*dir_angle*/-0.15*M_PI, spectre_blanc); + soleil->dens_lin = 6000; + scene.sources.push_back(soleil); + + // Store : ensemble d'arcs de cercles diffusants régulièrement espacés + std::vector> store; + for (float y = 0.4; y < 0.95; y += 0.03) { + auto arc = scene.creer_objet( + ObjetCourbe_Diffusant::BRDF_Lambert, + /*centre*/point_t{0.3,y}, /*rayon*/0.04, angle_interv_t(M_PI/4,M_PI/2), /*inv_int*/false + ); + arc->n_re_emit_par_intens = 1; + store.push_back(arc); + } + // Rotation des éléments du store avec la souris + scene.ajouter_bouge_action(point_t{0.32,0.5}, [&] (point_t mouse, float angle, bool alt) -> point_t { + for (auto arc : store) + arc->ang = angle_interv_t(M_PI/4,M_PI/2) + (angle - M_PI/2); + return point_t{0.32,0.5}; + }); + + // Un sol diffusant lambertien + auto sol = scene.creer_objet(ObjetCourbe_Diffusant::BRDF_Lambert, point_t{0.3,0.3}, point_t{2,0.3}); + sol->n_re_emit_par_intens = 1; + + scene.static_text.insert(scene.static_text.begin(), { + L"Store constitué d'arcs de cercles diffusants, dont l'angle est ajoutable en cliquant sur son ancre.", + L"Le but est de minimiser la lumière éblouissante du soleil tout en maximisant la lumière diffusée (e.g. sur le plafond)", + L"" + }); + + scene.boucle(nullptr, nullptr, nullptr); + + return 0; +} diff --git a/LightRays/sfml_c01.hpp b/LightRays/sfml_c01.hpp new file mode 120000 index 0000000..3d77df8 --- /dev/null +++ b/LightRays/sfml_c01.hpp @@ -0,0 +1 @@ +/Users/xif/Desktop/Code/Libs/sfml_c01.hpp \ No newline at end of file diff --git a/lois.pdf b/lois.pdf new file mode 100644 index 0000000..3422576 Binary files /dev/null and b/lois.pdf differ diff --git a/lois.tm b/lois.tm new file mode 100644 index 0000000..5333f97 --- /dev/null +++ b/lois.tm @@ -0,0 +1,427 @@ + + +> + +<\body> + > + + <\subsection> + vs. + + + L'intensit d'une source omnidirectionnelle diminue en + >, et non en >> + > ce n'est pas une source -isotrope, mais + plutt une source focalise dans la direction (comme un + phare-fente) ou un barre lumineuse infinie dans la direction . + + Conjecture : les rsultats de la simulation sont les mmes que en + avec des objets infinis suivant . + + >>> + + <\compact> + De faon gnrale, on ne ramne jamais systmatiquement les angles \S + gomtriques \T >|[>> + ou ,+\|[>>, car cela casse les potentielles + relations d'ordres entre les angles (e.g. + \\\+0.1*\> n'est + quivalent \\\0.1*\>, + qui n'est jamais vrai). Exceptions : + + <\itemize-dot> + angles d'incidences : |2>,+|2>|]>> + systmatiquement + + angle du rayon, ramen >|[>> + chaque mission, pour viter d'accumumuler des tours + + + + + + <\padded-center> + |pdf>|0.55par|||> + + + Segment d'extrmits et : *B> + pour >. + + Demi-droite porte par >>> d'origine + : >>> pour + |[>>. + + Intersection : + + <\equation*> + \s>,t>:A*s>+>|)>*B=O+t>*>>\A|\>*s>+t>*>>*=O|\> + + + <\equation*> + \\s>\,t>\|[>:-x>|>>>|-y>|>>>>>>*>>>|>>>>>>=-x>>|-y>>>>> + + + En ignorant le cas o le segment et la demi-droite sont parallles, il + suffit de rsoudre ce systme linaire. Si > + ou si 0>, alors il n'y a pas intersection. Sinon, le point + d'intersection est >*>>>, + et l'angle d'incidence est -\>, o + =\A|\>> et + =\-|2>>. L'angle de la normale + est >=\+\>. + + >> + + <\wide-tabular> + || + Aprs translation de >> et rotation d'un angle + >, on suppose que est l'origine et que + est la verticale de (=0>). + Le centre de l'arc de cercle voulu est l'intersection (de + droite ici) des ceux cercles centrs en et de rayons + . On voit immdiatement que + =A\B/2>. Le centre est + finalement dfini par la solution ngative de + +y=R>, + c'est--dire + + <\equation*> + x=--y>=--B/2|)>>B\2*R>> + + + Pour obtenir > et + >, on a simplement + + <\equation*> + tan\|)>=|-x>=1/B|)>-1> + + |<\cell> + |pdf>||||> + >>> + + + On obtient finalement =\+\> + et aprs rotation d'un angle > puis une + translation de >>. + + + + <\padded-center> + |pdf>|0.8par|||> + + + On suppose que \\>. La + distance C> est non signe ici, et les angles ne sont + pas absolus comme avec le segment, mais relatifs l'axe + C|\>> (on peut regarder la figure dans + n'importe quel sens). L'arc est de rayon de courbure + A=C\B>. Clairement, si + |\|>\|2>> et R>, + il n'y a pas intersection. + + Pour le premier point d'intersection avec le disque ( ici), + + <\equation*> + >>tan + \|\>>+>>=|)>|\>>>+\|)>|\>>>\b\sin|)>=sin-\|)>b= + + + La demi-droite intersecte l'arc B|\>> si + > est dfini, donc si<\footnote> + On a alors >-\>|)>|\|>=1>, + d'o >=\>\|2>>, + d'o\ + + <\equation*> + cos \>=cos>\|2>|)>=\sin>|)>=\ + + + On peut retrouver cet angle >>> en + crivant la condition de tengentialit rayon/cercle au point : + + <\equation*> + >>>|R*sin*\>>>>>>=O+t*>>>>>=>|>>>>+t*>>>|cos + \>>>>>> + + + Pour , on a >=-t*cos + \>> donc >/cos \>>. Alors, pour + , on a + + <\equation*> + R*cos \>=-d+|-R*>|cos \>>|>*sin*\>\R*cos + \>=-d*cos \>-R*sin + \>\cos + \>=-*+sin|)>=- + + + Et puisque |)>=>> + est indpendant du signe de , on peut tout aussi bien prendre + >=+R/d>. + + + <\wide-tabular> + | + <\compact> + <\equation*> + b*sin|)>|\|>\1\|\|>\\>=arcsin + + + lorsque R>. Pour R> ( + l'intrieur du cercle), il y a toujours une solution, videmment. + + + Pour le deuxime point d'intersection avec le disque, on a + + <\equation*> + >>tan + \|\>>+>>=|)>|\>++>>-\|)>|\>>> + + + ce qui revient au mme : + sin|)>=sin-\|)>>. + + Cette quation possde en effet, dans + \>|[>>, + nosdeux solutions (courbes + |)>> bi-values). + + Au final, les solution sont + + <\equation*> + =-arcsinsin|)>|)>+\+\>>|=+arcsinsin|)>|)>+\>>>>> + + + Pour R>, la solution correcte est + > si \\\\>, + et sinon, ventuellement > si + \\\\>. + Pour R>, la seule solution correcte est + > (et si on veut rester dans + >|[>>, on ajoute + >> > + pour \0>). + + Les angles d'incidence respectifs sont + |<\cell> + |pdf>|0.4par|||> + >>> + + + <\equation*> + i=-\|)>+\|2>=\+|)>+|2>-\|)>\i=\-\ + + + \n>> + + Rflexion : + + <\equation*> + avec un angle>i>=i\\>=\>-i + + + Rfraction (transmission) : + + <\equation*> + n*sin=n*sin>|)>|\n>>>>|i>=arcsin|)>> + lorsque \n>>>>>>>\\|n> + + + <\equation*> + \\>=\>+i>=>+\|)>+arcsin|\\sin|> + + + Coefficients de rflexion en puissance (c'est juste + |\|>>) : + + <\equation*> + R>=*cos + i-n*cos i>|n*cos i+n*cos + i>>|\|>R>=*cos + i-n*cos i>|n*cos i+n*cos + i>>|\|> + + + dvelopps<\footnote> + <\equation*> + *i>=arcsin||n>*sin|>\cos>|)>=*sin + i>>|i>>>>>|*sin + i-1>>|i>>>>>>>>x=|n>*sin + + : + + <\equation*> + R,>=|)>>|i>>>>>|int.tot.)>>>>>>>>|i>>>>>>>>|>=\*cos + i>>|>=\*cos + i>>>>>b=*sin + i> + + + Rflexion nulle () si : + + <\itemize-dot> + >=0\cos + i+sin i=1=\\n=n> + + >=0\cos + i+\*sin i=\\n=ni=i>=arctan||n>|>> + + + R>=0>|>|+\*sin=\=\*cos+\*sin>>||>||)>*cos=\*|)>*sin>>||>|=1tan=\>>>> + > + + + Coefficients de transmission en puissance (ce n'est <\footnote> + Coefficients de rflexion en amplitude : + + <\equation*> + \>=*cos + i-n*cos i>|n*cos i+n*cos + i>>\>=*cos + i-n*cos i>|n*cos i+n*cos + i>> + + + dvelopps : + + <\equation*> + \,>=>|i>>>>>|*b|a+\*b>||||>>>>>>>>>>>>|i>>>>>>>>|>=\*cos + i>>|>=\*cos + i>>>>>b=*sin + i|\|>> + + + Coefficients de transmission en amplitude : + + <\equation*> + \>=*cos + i|n*cos i+n*cos i>>\>=*cos + i|n*cos i+n*cos i>> + + + dvelopps : + + <\equation*> + \,>=>|i>>>>>|*b>>>|i>>>>>>>>|>=2*\*cos + i>>|>=2*cos + i>>>>>\T=*cos + i>|n*cos i>*|\|> + + juste |\|>>) : + + <\equation*> + T=1-R + + + par conservation de l'nergie. Ici, l'histoire d'angle de vue de + l'interface ne rentre pas en compte, puisqu'on a seulement des rayons + > l'intgration spatiale est dj faite, et on manipule + des , pas des intensits. Cela est cohrent avec le fait + d'oublier la formule de l'clairement =I*cos \>. + + + + + + Proprit d'une \S \T + >|)>> : + + <\itemize-dot> + rciprocit : >|)>=f>,i|)>>(crucial + pour avoir l'quivalence > + -invar. : les rayons \S perdus \T hors du plan sont compenss par + tous rayons \S hors plan \T (venant des points 0> des + sources) rflchis dans le plan) + + positivit : >|)>\0> + + conservation de l'nergie : i,|2>>|2>>\i> + f>|)>=2\>>> (les absorptions sont rgls + avec la variable d'albdo ddie) + + + \; + + +<\initial> + <\collection> + + + + +<\references> + <\collection> + > + > + > + > + > + > + > + > + > + > + > + > + > + + + +<\auxiliary> + <\collection> + <\associate|toc> + |1.|2d> + vs. |3d> + |.>>>>|> + > + + |2.Angles modulo + |||2*\>>> + |.>>>>|> + > + + |3.Intersection segment / + demi-droite |.>>>>|> + > + + |4.Intersection arc de cercle / + demi-droite |.>>>>|> + > + + |5.Construction d'un arc de + cercle partir de ||>> + |.>>>>|> + > + + |6.Rflexion et rfraction sur + une interface ||n\n>> + |.>>>>|> + > + + |7.Objets diffusants : BRDF + |.>>>>|> + > + + + \ No newline at end of file diff --git a/rapport.pdf b/rapport.pdf new file mode 100644 index 0000000..b4028ac Binary files /dev/null and b/rapport.pdf differ diff --git a/rapport.tm b/rapport.tm new file mode 100644 index 0000000..0045daa --- /dev/null +++ b/rapport.tm @@ -0,0 +1,366 @@ + + +> + +<\body> + <\hide-preamble> + \; + + > + + >>>> + + + : Moteur d'optique gomtrique + >|>>> + + sur Mint ou Debian).> + + + + Le but de ce projet est de simuler les lois de l'optique gomtrique sur + une scne , et ainsi de construire un moteur d'illumination de + scnes . Le principe est simple : + + <\itemize-arrow> + Les envoient des rayons lumineux, par exemple dans + toutes les directions pour une source isotrope, ou dans une seule + direction et de faon tendue pour une source de type "soleil". + + Les rayons sont intercepts par les de la scne. + Suivant l'objet, le rayon peut tre rflchi, rfract, diffus, filtr, + absorb L'objet r-met alors zro, un ou plusieurs rayons. + + Des accumulent l'intensit des rayons qu'ils + interceptent sur une matrice de pixels. + + Le processus d'interception - r-mission est rpt jusqu' ce que + le rayon soit totalement absorb. + + + On peut alors se servir des pixels des crans comme image, comme un + de camra ( !) si on a plac une lentille devant, ou + comme la luminosit qu'aurait un mur cet endroit (par exemple pour + illuminer une scne d'un jeu de plateforme). Plutt qu'un long discours, + mieux vaut regarder les exemples page 2. + + On peut voir a comme une mthode de Monte-Carlo. C'est trs inefficace + pour construire des images (on appelle a alors le ) car + une toute petite fraction des rayons mis arrivent au "" (comme + pour une camra relle)<\footnote> + Pour faire du rendu d'image raliste performant, les rayons sont lancs + depuis l'cran et sont propags dans le sens inverse (on appelle a alors + le ). C'est beaucoup moins simple implmenter pour les + processus irrversibles (diffusion). + . C'est par contre adapt pour faire une illumination + physiquement raliste (on appelle a alors l'illumination par + ). + + <\compact> + Les diffrents objets implments ici sont : + + <\itemize-dot> + Des lignes ou des arcs de cercles, dont les calculs + d'interception sont dtaills dans . + + Des dioptres (lois de Snell-Descartes et coefficients de Fresnel, + dtailles dans ), des miroirs, des objets + diffusants ( + ou plus sophistiqu), des crans, des filtres + + Ou bien des objets non localiss, comme du brouillard. + + + + Pour implmenter les couleurs, on choisit de rester proche des lois + physiques en attribuant un discret chaque rayon (ici 4 + longueurs d'onde). De plus, il faut prendre en compte la polarisation + / , qui se comportent diffremment pour la + rflexion/rfraction<\footnote> + Le plan d'incidence des rayons est toujours le mme : le plan o vivent + les objets . Ainsi, les polarisations et + (relatives au plan d'incidence) sont toujours les mmes, on + peut donc sparer la lumire de faon globale en composantes et + , et les traiter sparment. + . Ainsi, chaque rayon possde . De plus, on + oublie tout phnomne de nature ondulatoire. On travaille alors directement + en , plutt qu'en amplitude, et on les additionne + simplement. Cela simplifie largement l'implmentation. + + + + Les fichiers / implmentent des + structures et fonctions utilitaires classiques : un vecteur + et un point avec les + surcharges d'oprateurs usuelles, les rotations/translations/homothties, + une structure qui implmente un intervalle + angulaire dans > modulo > Le fichier + contient des fonctions utilitaires pour l'affichage + avec SFML, notamment pour convertir les coordonnes + \> en coordonnes + fentre. + + + + La structure est implmente dans + /. Un rayon est dfini par son point + d'origine, sa direction (angle par rapport l'horizontale), et son spectre + de couleur/polarisation en intensit. La structure + contient les 8 composantes ainsi que des routines de manipulation des + composantes. + + + + Pour dcrire le contenu de la scne, on utilise un arbre de classes (voir + diagramme page 4), pens pour tre facilement extensible, viter toute + duplication de code, et utiliser le polymorphisme au maximum. + + On distingue les sources, qui ne font qu'mettre des rayons, de tous les + autres objets de la scne. Tous les types de sources drivent de la classe + virtuelle , et implmentent la mthode + qui renvoie un + Rayon\>. On pourra regarder la classe + (), qui + implmente une source tendue lambertienne en forme de secteur. + + Tous les autres objets de la scne drivent de la classe virtuelle de base + , qui dclare l'interface commune utilise lors de la + propagation (interception puis r-mission) des rayons, et pour + l'affichage. Un objet est capable de avec la mthode , et, le cas + chant, de avec (o est une structure opaque renvoye par + , conservant les informations de l'interception) + : + + <\big-figure||pdf>|0.65par|||>> + Scnario typique (celui de ) + d'utilisation des objets. + + + Cela semble compliqu, mais l'interception et la r-mission sont spars + pour des raisons de performance : les calculs de r-mission peuvent tre + lourds (e.g.lois de Fresnel) et ne sont pas ncessaires chaque + interception (par exemple si le rayon a t intercept plus en amont par un + autre objet). renvoie aussi la distance entre le + point d'mission du rayon et l'interception, qui sert dterminer quel est + l'objet le plus proche sur la trajectoire du rayon (voir plus loin pour + l'utilisation de ces mthodes). + + D'un ct, on dfinit alors des classes (virtuelles) qui s'occupent + uniquement de la et de l'interception des rayons. + dcrit une courbe/interface bien dfinie, et la + structure opaque renvoye par contient le point + d'incidence, l'angle d'incidence dans le repre de la scne, l'angle la + normale de la courbe, et le ct sur lequel le rayon est intercept. + L'interception est enfin implmente pour deux gomtries particulires : + dcrit un segment, et dcrit un + arc de cercle. On pourra regarder le header et + les mthodes + () et + (). + + D'un autre ct, on implmente la et la + des rayons dans des classes virtuelles indpendantes + de la gomtrie : pour la + rflexion/rfraction sur un dioptre, pour + une surface diffusante, pour les + miroirs Ces classes implmentent, elles, la mthode + . On pourra regarder + (), + qui implmente les fameuses lois + + <\equation*> + \>=\>,n*sin>|)>=n*sin>|)>,>=*cos + \>-n*cos + \>|n*cos + \>+n*cos + \>>|\|>,R>=*cos + \>-n*cos + \>|n*cos + \>+n*cos + \>>|\|>> + + + Enfin, comme illustr sur le diagramme, on combine ces classes pour en + faire des objets compltements dfinis : par exemple + , qui est un dioptre plan, hrite des classes + et . + + Les crans (dfinis dans ) accumulent les rayons + incidents et calculent une intensit moyenne pour afficher ou enregistrer + le rsultat. On utilise surtout , qui est une + matrice () de pixels et qui hrite de . + + + + La logique de la propagation des rayons est implmente dans la classe + (/), qui stocke les + sources et les objets de faon polymorphique + (std::shared_ptr\Objet\\>). + Pour illuminer / former une image, on appelle la mthode + qui lance les rayons partir des + sources. Pour chaque rayon, la mthode + est appele. Cette mthode effectue les tests d'interception et la + r-mission de faon rcursive. Schmatiquement, on a + + <\indent> + <\wide-tabular> + |||||| + <\compact> + <\verbatim> + <\small> + void Scene::propagation_recur (rayon) { + + \ \ \ \ rayons_re_emis = interception_re_emission(rayon); + + \ \ \ \ for (ray : rayons_re_emis)\ + + \ \ \ \ \ \ \ \ propagation_recur(ray); + + } + + std::vector\Rayon\ Scene::interception_re_emission + (ray) { + + \ \ \ \ essai_intercept(ray)> + sur tous les objets de la scne et slectionne le premier> + + \ \ \ \ \ \ + + \ \ \ \ objet_le_plus_proche-\re_emit(ray, + intercept_struct); + + } + + + + >>> + + + + (cf.figure page 3). Les rayons sont ainsi propags sur la scne + jusqu' ce qu'ils soient absorbs par un cran ou qu'ils soient d'intensit + ngligeable (). Une limite est fixe pour + viter les rcursion infinies (par exemple avec une rflexion interne + totale dans une lame). + + Enfin, la mthode est appele pour afficher + tous les objets de la scne dans la fentre. Tant que rien ne bouge dans la + scne, on rpte ce processus (une ) autant que possible pour + avoir un rsultat de moins en moins bruit sur les crans (merci au + thorme central limite). + + + + La classe tend et regroupe tout le + code indpendant du reste et qu'on trouverait typiquement dans les + , mais pas assez gnrique pour tre dans la classe + : cration des fentres , de quelques objets + courants, d'une lentille et d'un cran pour former une image, + + Le code est fourni avec 4 scnes dmonstratives utilisant + : (qu'on pourra regarder), + , , + , et qu'on peut faire tourner avec + , , , + et respectivement. + + <\compact> + Quelques remarques finales : + + <\itemize-dot> + Tous les attributs de classes qui ne sont pas associs des + contraintes/invariants et qui sont susceptibles d'tre utiles + l'utilisateur de la classe sont publiques. L'encapsulation, ce n'est + pas embter l'utilisateur avec des getters, c'est protger + l'utilisateur contre des btises. + + L'extensibilit et la clart a t prfre la performance + quand le choix se posait. + + Lorsque les constructeurs par copie et d'affectation ne sont pas + dclars, c'est qu'on laisse le compilateur gnrer celui par dfaut, + ou, lorsque marqu dans une classe parent, aucun. + + + + <\wide-tabular> + ||| + + + On reproduit tout fait de nombreux effets d'optique gomtrique que + l'on connait bien : lentilles paisses pas tout fait stigmatiques et + avec des aberrations chromatiques, fibres optiques + + On obtient une illumination raliste (toutefois gourmande en + ) d'une scne . La formation d'images (par + exemple, droite, un surface bleue diffusante avec reflet de la source + ponctuelle l'clairant) sont correctes mais un peu ennuyeuses. Cela + donne envie d'tendre le programme une scne (ce qui ne + serait pas difficile) pour obtenir de vraies images . + |<\cell> + |png>||120pt||> + >>> + + + +<\initial> + <\collection> + + + + +<\references> + <\collection> + > + > + > + > + > + > + > + > + > + > + > + + + +<\auxiliary> + <\collection> + <\associate|figure> + |1>|> + Scnario typique (celui de |language||Scene::interception_re_emission>) + d'utilisation des objets. + |> + + <\associate|toc> + |1.Objectifs et mthodes + |.>>>>|> + > + + |2.Structure du code + |.>>>>|> + > + + |2.1.Objets + |.>>>>|> + > + + |2.2.Scne + |.>>>>|> + > + + |2.3.Divers + |.>>>>|> + > + + |3.Conclusion + |.>>>>|> + > + + + \ No newline at end of file diff --git a/screens/milieux2.png b/screens/milieux2.png new file mode 100644 index 0000000..6a020a5 Binary files /dev/null and b/screens/milieux2.png differ