Repository: Infinideastudio/NEWorld Branch: 0.5.0 Commit: 84e4762d014b Files: 159 Total size: 876.3 KB Directory structure: gitextract_jhc26ik1/ ├── .gitattributes ├── .gitignore ├── .gitmodules ├── Assets/ │ ├── GUI/ │ │ ├── InGame.xaml │ │ ├── InventorySlot.xaml │ │ ├── MainMenu.xaml │ │ └── Theme/ │ │ ├── Brushes.xaml │ │ ├── Colors.xaml │ │ ├── Fonts.xaml │ │ ├── NEWorld.xaml │ │ └── Styles.xaml │ ├── Shaders/ │ │ ├── Depth.fsh │ │ ├── Depth.vsh │ │ ├── Main.fsh │ │ ├── Main.vsh │ │ ├── Particle.fsh │ │ ├── Particle.vsh │ │ ├── Shadow.fsh │ │ ├── Shadow.vsh │ │ ├── Simple.fsh │ │ └── Simple.vsh │ └── Translations/ │ ├── Keys.json │ ├── Langs.txt │ ├── en_US.lang │ ├── zh_CN.lang │ └── zh_TW.lang ├── CMakeLists.txt ├── External/ │ ├── bvh/ │ │ ├── binned_sah_builder.hpp │ │ ├── bottom_up_algorithm.hpp │ │ ├── bounding_box.hpp │ │ ├── bvh.hpp │ │ ├── heuristic_primitive_splitter.hpp │ │ ├── hierarchy_refitter.hpp │ │ ├── leaf_collapser.hpp │ │ ├── linear_bvh_builder.hpp │ │ ├── locally_ordered_clustering_builder.hpp │ │ ├── morton.hpp │ │ ├── morton_code_based_builder.hpp │ │ ├── node_intersectors.hpp │ │ ├── node_layout_optimizer.hpp │ │ ├── parallel_reinsertion_optimizer.hpp │ │ ├── platform.hpp │ │ ├── prefix_sum.hpp │ │ ├── primitive_intersectors.hpp │ │ ├── radix_sort.hpp │ │ ├── ray.hpp │ │ ├── sah_based_algorithm.hpp │ │ ├── single_ray_traverser.hpp │ │ ├── spatial_split_bvh_builder.hpp │ │ ├── sphere.hpp │ │ ├── sweep_sah_builder.hpp │ │ ├── top_down_builder.hpp │ │ ├── triangle.hpp │ │ ├── utilities.hpp │ │ └── vector.hpp │ ├── stb/ │ │ └── stb_rect_pack.h │ └── tsl/ │ ├── bhopscotch_map.h │ ├── bhopscotch_set.h │ ├── hopscotch_growth_policy.h │ ├── hopscotch_hash.h │ ├── hopscotch_map.h │ └── hopscotch_set.h ├── NEWorld.Base/ │ ├── Common/ │ │ ├── Console.cpp │ │ ├── Console.h │ │ ├── Logger.cpp │ │ └── Logger.h │ ├── Math/ │ │ ├── Vector2.h │ │ ├── Vector3.h │ │ └── Vector4.h │ └── System/ │ ├── FileSystem.h │ ├── MessageBus.cpp │ └── MessageBus.h ├── NEWorld.Game/ │ ├── Audio/ │ │ ├── Audio.cpp │ │ └── Audio.h │ ├── AudioSystem.cpp │ ├── AudioSystem.h │ ├── Command.h │ ├── ControlContext.h │ ├── Definitions.cpp │ ├── Definitions.h │ ├── Dispatch.cpp │ ├── Dispatch.h │ ├── Frustum.cpp │ ├── Frustum.h │ ├── FunctionsKit.cpp │ ├── FunctionsKit.h │ ├── GUI/ │ │ ├── GUI.cpp │ │ ├── GUI.h │ │ ├── InventorySlot.cpp │ │ ├── InventorySlot.h │ │ ├── Menus/ │ │ │ ├── MainMenu.cpp │ │ │ └── Menus.h │ │ ├── Noesis.cpp │ │ └── Noesis.h │ ├── GameSettings.cpp │ ├── GameSettings.h │ ├── GameView.cpp │ ├── GameView.h │ ├── Globalization.cpp │ ├── Globalization.h │ ├── Items.cpp │ ├── Items.h │ ├── Menus.h │ ├── NEWorld.cpp │ ├── Particles.cpp │ ├── Particles.h │ ├── Renderer/ │ │ ├── BufferBuilder.h │ │ ├── GL/ │ │ │ ├── Objects.cpp │ │ │ ├── Objects.h │ │ │ ├── Pipeline.cpp │ │ │ └── Pipeline.h │ │ ├── Renderer.cpp │ │ ├── Renderer.h │ │ └── World/ │ │ ├── ChunkRenderer.cpp │ │ ├── ChunkRenderer.h │ │ ├── ShadowMaps.cpp │ │ ├── ShadowMaps.h │ │ ├── WorldRenderer.cpp │ │ └── WorldRenderer.h │ ├── Setup.cpp │ ├── Setup.h │ ├── Textures.cpp │ ├── Textures.h │ ├── Tick.cpp │ ├── Tick.h │ ├── Typedefs.h │ ├── Universe/ │ │ ├── CommandHandler.h │ │ ├── Entity/ │ │ │ ├── Entity.cpp │ │ │ ├── Entity.h │ │ │ ├── PlayerEntity.cpp │ │ │ ├── PlayerEntity.h │ │ │ ├── bvh.cpp │ │ │ └── bvh.h │ │ ├── Game.h │ │ └── World/ │ │ ├── BlockRegistry.cpp │ │ ├── BlockRegistry.h │ │ ├── Blocks.cpp │ │ ├── Blocks.h │ │ ├── Chunk.cpp │ │ ├── Chunk.h │ │ ├── ChunkPtrArray.cpp │ │ ├── ChunkPtrArray.h │ │ ├── Data/ │ │ │ ├── BitStorage.h │ │ │ ├── ChunkStorage.cpp │ │ │ └── ChunkStorage.h │ │ ├── OrderedArray.h │ │ ├── TerrainGen/ │ │ │ ├── Carve.cpp │ │ │ ├── Generate.h │ │ │ ├── Heights.h │ │ │ └── Noise.h │ │ ├── World.cpp │ │ └── World.h │ ├── resource.h │ ├── resource.rc │ └── stdinclude.h ├── NoesisGUI/ │ ├── CMake/ │ │ └── FindNoesis.cmake │ ├── CMakeLists.txt │ └── bin2h.py └── README.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ # Auto detect text files and perform LF normalization * text=auto # Custom for Visual Studio *.cs diff=csharp # Standard to msysgit *.doc diff=astextplain *.DOC diff=astextplain *.docx diff=astextplain *.DOCX diff=astextplain *.dot diff=astextplain *.DOT diff=astextplain *.pdf diff=astextplain *.PDF diff=astextplain *.rtf diff=astextplain *.RTF diff=astextplain Assets/Fonts/**/* filter=lfs diff=lfs merge=lfs -text Assets/Textures/**/* filter=lfs diff=lfs merge=lfs -text ================================================ FILE: .gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Pp]rofile/ [Rr]elease/ x64/ x86/ bld/ [Bb]in/ [Oo]bj/ # Visual Studio 2015 cache/options directory .vs/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding addin-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore #**/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Windows Azure Build Output csx/ *.build.csdef # Windows Store app package directory AppPackages/ # Others *.[Cc]ache ClientBin/ [Ss]tyle[Cc]op.* ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # NEWorld files [Ww]orlds/ [Ss]creen[Ss]hots/ [Cc]onfigs/ CMakeSettings.json .idea/ Mods/ cmake-build-* # External SDK /External/Noesis/ /NoesisGUI/SDK/ /out/ ================================================ FILE: .gitmodules ================================================ [submodule "Kaleidoscope"] path = Kaleidoscope url = https://github.com/NEWorldProject/klsxx ================================================ FILE: Assets/GUI/InGame.xaml ================================================ ================================================ FILE: Assets/GUI/MainMenu.xaml ================================================ ================================================ FILE: Assets/GUI/Theme/Brushes.xaml ================================================  #FF2AA60C #FF5AB541 #FF43B324 #FF218C08 #FF187306 ================================================ FILE: Assets/GUI/Theme/Colors.xaml ================================================  #20282f #41484e #5d6469 #7b8085 #93979b #abaeb2 #bbbec1 #cbced0 #dcdee0 #e5e6e8 #edeff0 #f6f7f8 ================================================ FILE: Assets/GUI/Theme/Fonts.xaml ================================================  #Consolas 17 15 12 ================================================ FILE: Assets/GUI/Theme/NEWorld.xaml ================================================  ================================================ FILE: Assets/GUI/Theme/Styles.xaml ================================================  17 1 0 1 2 2 -3 -8,-3 1.75 1.75,1.75,0,0 0,0,1.75,1.75 1.75,0,0,1.75 0,1.75,1.75,0 1.25 1.25,1.25,0,0 0,0,1.25,1.25 1.25,0,0,1.25 0,1.25,1.25,0 0.75 0.75,0.75,0,0 0,0,0.75,0.75 0.75,0,0,0.75 0,0.75,0.75,0 0 3 M0,7.5L5.5,12.5 14,2.75 11.75,1 5.5,9 1.5,5.5z M4,0L0,4 4,8 M0,0 L4,4 0,8 M0,4L4,0 8,4 M0,0 L4,4 8,0 M0,0L6,6 0,12 M0,0L6,6 0,12 M2,1 C2,1.5522847 1.5522847,2 1,2 C0.44771525,2 0,1.5522847 0,1 C0,0.44771525 0.44771525,0 1,0 C1.5522847,0 2,0.44771525 2,1 z M6,1 C6,1.5522847 5.5522847,2 5,2 C4.4477153,2 4,1.5522847 4,1 C4,0.44771525 4.4477153,0 5,0 C5.5522847,0 6,0.44771525 6,1 z M2,5 C2,5.5522847 1.5522847,6 1,6 C0.44771525,6 0,5.5522847 0,5 C0,4.4477153 0.44771525,4 1,4 C1.5522847,4 2,4.4477153 2,5 z M6,5 C6,5.5522847 5.5522847,6 5,6 C4.4477153,6 4,5.5522847 4,5 C4,4.4477153 4.4477153,4 5,4 C5.5522847,4 6,4.4477153 6,5 z M2,9 C2,9.5522847 1.5522847,10 1,10 C0.44771525,10 0,9.5522847 0,9 C0,8.4477153 0.44771525,8 1,8 C1.5522847,8 2,8.4477153 2,9 z M6,9 C6,9.5522847 5.5522847,10 5,10 C4.4477153,10 4,9.5522847 4,9 C4,8.4477153 4.4477153,8 5,8 C5.5522847,8 6,8.4477153 6,9 z M2,1 C2,1.5522847 1.5522847,2 1,2 C0.44771525,2 0,1.5522847 0,1 C0,0.44771525 0.44771525,0 1,0 C1.5522847,0 2,0.44771525 2,1 z M8,1 C8,1.5522847 7.5522847,2 7,2 C6.4477153,2 6,1.5522847 6,1 C6,0.44771525 6.4477153,0 7,0 C7.5522847,0 8,0.44771525 8,1 z M14,1 C14,1.5522847 13.552285,2 13,2 C12.447715,2 12,1.5522847 12,1 C12,0.44771525 12.447715,0 13,0 C13.552285,0 14,0.44771525 14,1 z ================================================ FILE: Assets/Shaders/Depth.fsh ================================================ #version 450 layout(location = 0) out vec4 color; void main() { float shade = (gl_FragCoord.z / gl_FragCoord.w + 1.0) * 0.5; color = vec4(shade, shade, shade, 1.0); } ================================================ FILE: Assets/Shaders/Depth.vsh ================================================ #version 450 compatibility layout(location = 4) in vec3 vCrood; void main() { gl_Position = gl_ModelViewProjectionMatrix * vec4(vCrood, 1.0); } ================================================ FILE: Assets/Shaders/Main.fsh ================================================ #version 450 compatibility #define SMOOTH_SHADOW const mat4 normalize = mat4( 0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.5, 0.5, 0.499, 1.0); const float delta = 0.05; layout(location=0) uniform sampler2D Tex; layout(location=1) uniform sampler2D DepthTex; layout(location=2) uniform mat4 Depth_proj; layout(location=3) uniform mat4 Depth_modl; layout(location=4) uniform mat4 TransMat; layout(location=5) uniform vec4 SkyColor; layout(location=6) uniform float renderdist; in vOut { vec2 fTexCrood; vec3 fShade; vec3 fCrood; flat int facing; }; out vec4 fragment; void main() { vec4 vertex = vec4(fCrood, 1.0); mat4 transf = normalize * Depth_proj * Depth_modl * TransMat; vec4 ShadowCoords = transf * vertex; #ifdef SMOOTH_SHADOW vec4 ShadowCoords01; vec4 ShadowCoords21; vec4 ShadowCoords10; vec4 ShadowCoords12; float dist = length(gl_ModelViewMatrix * vertex); //Shadow smoothing (Super-sample) if (dist < 16.0) { if (facing == 0 || facing == 1) { ShadowCoords01 = transf * vec4(vertex.x - delta, vertex.yzw); ShadowCoords21 = transf * vec4(vertex.x + delta, vertex.yzw); ShadowCoords10 = transf * vec4(vertex.x, vertex.y - delta, vertex.zw); ShadowCoords12 = transf * vec4(vertex.x, vertex.y + delta, vertex.zw); } else if (facing == 2 || facing == 3) { ShadowCoords01 = transf * vec4(vertex.x, vertex.y - delta, vertex.zw); ShadowCoords21 = transf * vec4(vertex.x, vertex.y + delta, vertex.zw); ShadowCoords10 = transf * vec4(vertex.xy, vertex.z - delta, vertex.w); ShadowCoords12 = transf * vec4(vertex.xy, vertex.z + delta, vertex.w); } else if (facing == 4 || facing == 5) { ShadowCoords01 = transf * vec4(vertex.x - delta, vertex.yzw); ShadowCoords21 = transf * vec4(vertex.x + delta, vertex.yzw); ShadowCoords10 = transf * vec4(vertex.xy, vertex.z - delta, vertex.w); ShadowCoords12 = transf * vec4(vertex.xy, vertex.z + delta, vertex.w); } } #endif //Shadow calculation float shadow = 0.0; if (facing == 1 || facing == 2 || facing == 5) shadow = 0.5; else if (ShadowCoords.x >= 0.0 && ShadowCoords.x <= 1.0 && ShadowCoords.y >= 0.0 && ShadowCoords.y <= 1.0 && ShadowCoords.z <= 1.0) { if (ShadowCoords.z < texture2D(DepthTex, ShadowCoords.xy).z) shadow += 1.2; else shadow += 0.5; #ifdef SMOOTH_SHADOW if (dist < 16.0) { if (ShadowCoords01.z < texture2D(DepthTex, ShadowCoords01.xy).z) shadow += 1.2; else shadow += 0.5; if (ShadowCoords21.z < texture2D(DepthTex, ShadowCoords21.xy).z) shadow += 1.2; else shadow += 0.5; if (ShadowCoords10.z < texture2D(DepthTex, ShadowCoords10.xy).z) shadow += 1.2; else shadow += 0.5; if (ShadowCoords12.z < texture2D(DepthTex, ShadowCoords12.xy).z) shadow += 1.2; else shadow += 0.5; shadow *= 0.2; } #endif } else shadow = 1.2; //Texture color vec4 texel = texture2D(Tex, fTexCrood); vec4 color = vec4(texel.rgb * shadow * fShade, texel.a); //Fog calculation & Final color //if (color.a < 0.99) color = vec4(color.rgb, mix(1.0, 0.3, clamp((renderdist * 0.5 - dist) / 64.0, 0.0, 1.0))); fragment = mix(SkyColor, color, clamp((renderdist - dist) / 32.0, 0.0, 1.0)); } ================================================ FILE: Assets/Shaders/Main.vsh ================================================ #version 450 compatibility layout(location = 1) in float VertexAttrib; layout(location = 2) in vec2 vTexCrood; layout(location = 3) in vec3 vShade; layout(location = 4) in vec3 vCrood; out vOut { vec2 fTexCrood; vec3 fShade; vec3 fCrood; flat int facing; }; void main() { facing = int(VertexAttrib + 0.5); fTexCrood = vTexCrood; fShade = vShade; fCrood = vCrood; gl_Position = gl_ModelViewProjectionMatrix * vec4(vCrood, 1.0); } ================================================ FILE: Assets/Shaders/Particle.fsh ================================================ #version 450 layout(location=0) uniform sampler2D Tex; in vOut { vec2 fTexCrood; flat vec2 fShade; }; out vec4 fragment; void main() { fragment = texture(Tex, fTexCrood) * vec4(vec3(fShade.x), fShade.y); } ================================================ FILE: Assets/Shaders/Particle.vsh ================================================ #version 450 compatibility layout(location = 1) in vec2 vTexCrood; layout(location = 2) in vec2 vShade; layout(location = 3) in vec3 vCrood; out vOut { vec2 fTexCrood; flat vec2 fShade; }; void main() { fTexCrood = vTexCrood; fShade = vShade; gl_Position = gl_ModelViewProjectionMatrix * vec4(vCrood, 1.0); } ================================================ FILE: Assets/Shaders/Shadow.fsh ================================================ #version 450 layout(location = 0) out vec4 color; void main() { color = vec4(1.0, 1.0, 1.0, 1.0); } ================================================ FILE: Assets/Shaders/Shadow.vsh ================================================ #version 450 compatibility layout(location = 4) in vec3 vCrood; void main() { gl_Position = gl_ModelViewProjectionMatrix * vec4(vCrood, 1.0); } ================================================ FILE: Assets/Shaders/Simple.fsh ================================================ #version 450 layout(location=0) uniform sampler2D Tex; in vOut { vec2 fTexCrood; vec3 fShade; }; out vec4 fragment; void main() { fragment = texture(Tex, fTexCrood) * vec4(fShade, 1.0); } ================================================ FILE: Assets/Shaders/Simple.vsh ================================================ #version 450 compatibility layout(location = 2) in vec2 vTexCrood; layout(location = 3) in vec3 vShade; layout(location = 4) in vec3 vCrood; out vOut { vec2 fTexCrood; vec3 fShade; }; void main() { fTexCrood = vTexCrood; fShade = vShade; gl_Position = gl_ModelViewProjectionMatrix * vec4(vCrood, 1.0); } ================================================ FILE: Assets/Translations/Keys.json ================================================ [ "NEWorld.yes", "NEWorld.no", "NEWorld.enabled", "NEWorld.disabled", "NEWorld.Blocks.Air", "NEWorld.Blocks.Rock", "NEWorld.Blocks.Grass", "NEWorld.Blocks.Dirt", "NEWorld.Blocks.Stone", "NEWorld.Blocks.Plank", "NEWorld.Blocks.Wood", "NEWorld.Blocks.Bedrock", "NEWorld.Blocks.Leaf", "NEWorld.Blocks.Glass", "NEWorld.Blocks.Water", "NEWorld.Blocks.Lava", "NEWorld.Blocks.GlowStone", "NEWorld.Blocks.Sand", "NEWorld.Blocks.Cement", "NEWorld.Blocks.Ice", "NEWorld.Blocks.Coal Block", "NEWorld.Blocks.Iron Block", "NEWorld.Blocks.TNT", "NEWorld.Blocks.Null Block", "NEWorld.main.start", "NEWorld.main.options", "NEWorld.main.exit", "NEWorld.options.caption", "NEWorld.options.fov", "NEWorld.options.sensitivity", "NEWorld.options.distance", "NEWorld.options.rendermenu", "NEWorld.options.guimenu", "NEWorld.options.languagemenu", "NEWorld.options.soundmenu", "NEWorld.options.back", "NEWorld.options.save", "NEWorld.render.caption", "NEWorld.render.smooth", "NEWorld.render.grasstex", "NEWorld.render.merge", "NEWorld.render.multisample", "NEWorld.render.shaders", "NEWorld.render.vsync", "NEWorld.render.back", "NEWorld.shaders.caption", "NEWorld.shaders.enable", "NEWorld.shaders.shadowres", "NEWorld.shaders.distance", "NEWorld.shaders.back", "NEWorld.gui.caption", "NEWorld.gui.unicode", "NEWorld.gui.blur", "NEWorld.gui.stretch", "NEWorld.gui.back", "NEWorld.worlds.caption", "NEWorld.worlds.Enter", "NEWorld.worlds.delete", "NEWorld.worlds.new", "NEWorld.worlds.back", "NEWorld.pause.caption", "NEWorld.pause.continue", "NEWorld.pause.back", "NEWorld.create.caption", "NEWorld.create.inputname", "NEWorld.create.ok", "NEWorld.create.back", "NEWorld.language.caption", "NEWorld.language.back", "NEWorld.Sound.caption", "NEWorld.Sound.MusicGain", "NEWorld.Sound.SoundGain" ] ================================================ FILE: Assets/Translations/Langs.txt ================================================ 3 en_US zh_CN zh_TW ================================================ FILE: Assets/Translations/en_US.lang ================================================ English English Yes No Enabled Disabled Air Rock Grass Dirt Stone Plank Wood Bedrock Leaves Glass Water Lava Glowstone Sand Cement Ice Coal Block Iron Block TNT Null Block Start game >> Options Exit =================< Options >================== Field of view: Mouse sensitivity: Render distance: >> Render options >> GUI options >> Select language >> Sound options << Back to main menu Save options =============< Render options >=============== Smooth lighting: Connect grass texture: Merge face rendering: MSAA: >> Advanced rendering Vertical sync: << Back to options menu ============< Advanced rendering >============ Enabled: ShadowMap resolution: Max shadow distance: << Back to render options ===============< GUI options >================ Force Unicode font: Background blur: PPI stretch (EXPERIMENTAL) << Back to options menu ==============< Select world >================ Enter this world Delete this world >> Create a new world << Back to main menu ================< Game menu >================= Resume game << Back to main menu ==============< Create a world >============== World name OK << Back to world menu ================< Languages >================= << Back to options menu ==================< Sound >=================== MusicGain SoundGain ================================================ FILE: Assets/Translations/zh_CN.lang ================================================ Chinese_Simplified 中文(简体) 是 否 开启 关闭 空气 岩石 草 泥土 石头 木板 原木 基岩 树叶 玻璃块 水 岩浆 萤石 沙子 水泥 冰 煤块 铁块 TNT Null Block 开始游戏 >> 选项 ... 退出 ==================< 选 项 >=================== 视野角度: 鼠标灵敏度: 渲染距离: >> 渲染选项 ... >> 图形界面选项 ... >> Select language... >> 音效选项 ... << 返回主菜单 保存设置 ===============< 渲 染 选 项 >================ 平滑光照: 草方块材质连接: 合并面渲染: 多重采样抗锯齿: >> 光影选项 ... 垂直同步: << 返回选项菜单 ===============< 光 影 选 项 >================ 启用光影: ShadowMap大小: 最大距离: << 返回渲染选项 ===============< 图形界面选项 >=============== 全部使用Unicode字体: 背景模糊: PPI stretch (实验性) << 返回选项菜单 ===============< 选 择 世 界 >================ 进入选定的世界 删除选定的世界 >> 创建新的世界 ... << 返回主菜单 ===============< 游 戏 菜 单 >================ 继续游戏 << 返回主菜单 ===============< 新 建 世 界 >================ 输入世界名称 确定 << 返回世界菜单 ===============< 语 言 设 置 >================ << 返回选项菜单 ===============< 音 效 设 置 >================ 背景音乐音量 音效音量 ================================================ FILE: Assets/Translations/zh_TW.lang ================================================ Traditional_Chinese 中文(繁體) 是 否 開啟 關閉 空氣 石頭 草方塊 泥土 碎石 木材 原木 基岩 樹葉 玻璃 水 岩漿 螢光石 沙 水泥 冰 煤炭磚 鐵磚 TNT Null方塊 開始遊戲 >>選項… 退出 ===================< 選 項 >=================== 視野角度: 滑鼠靈敏度: 視野距離: >> 進階顯示設定 ... >> 用戶圖形界面設定 ... >> Select language... >> 音效選項 ... << 返回主菜單 保存設定 ===============< 顯 示 設 定 >================= 柔和化照明效果: 連接草方塊材質: 合併面渲染: 多重採樣抗鋸齒: >> 進階渲染選項 ... 垂直同步: << 返回選項菜單 ===============< 進階渲染選項 >================ 啟用進階渲染: ShadowMap大小: 最大距離: << 返回顯示設定 =============< 用戶圖形界面設定 >============== 強制使用Unicode字符: 背景模糊: PPI stretch (實驗性) << 返回選項菜單 ================< 選 擇 世 界 >================ 進入選定的世界 移除選定的世界 >> 創建新的世界 ... << 返回主菜單 ================< 遊戲已暫停 >================= 返回遊戲 << 保存並回到主菜單 ===============< 創建新的世界 >================ 世界名稱 確認 << 返回選擇世界菜單 ================< 選 擇 語 言 >================ << 返回選項菜單 ================< 音 效 設 置 >================ 背景音樂音量 音效音量 ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) include(Kaleidoscope/Build/Config.cmake) project(NEWorld) kls_configure() kls_define_modules(NoesisGUI) kls_define_modules(Kaleidoscope) kls_add_library_module(NEWorld.Base NW::Base) kls_public_source_directory(NEWorld.Base NEWorld.Base) target_link_libraries(NEWorld.Base PUBLIC klsxx::essential) kls_add_executable_module(NEWorld) kls_public_source_directory(NEWorld NEWorld.Game) target_compile_definitions(NEWorld PRIVATE NEWORLD_GAME) target_link_libraries(NEWorld PRIVATE klsxx::essential klsxx::thread klsxx::coroutine) # copy assets add_custom_command( TARGET NEWorld POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/Assets $/Assets ) # opengl kls_vcpkg_package(glew) kls_vcpkg_package(glfw3) find_package(GLEW REQUIRED) find_package(glfw3 CONFIG REQUIRED) find_package(OpenGL REQUIRED) target_link_libraries(NEWorld PRIVATE OpenGL::GL GLEW::GLEW glfw) # nlohmann json kls_vcpkg_package(nlohmann-json) find_package(nlohmann_json CONFIG REQUIRED) target_link_libraries(NEWorld PRIVATE nlohmann_json::nlohmann_json) # All others # we do not use the tsl on vcpkg as currently there are some problems on the public repo kls_vcpkg_package(leveldb) kls_vcpkg_package(openal-soft) find_package(Threads REQUIRED) find_package(OpenAL REQUIRED) find_package(leveldb CONFIG REQUIRED) target_include_directories(NEWorld PRIVATE ${OPENAL_INCLUDE_DIR} External) target_link_libraries(NEWorld PRIVATE leveldb::leveldb NoesisGUI NEWorld.Base) ================================================ FILE: External/bvh/binned_sah_builder.hpp ================================================ #ifndef BVH_BINNED_SAH_BUILDER_HPP #define BVH_BINNED_SAH_BUILDER_HPP #include #include "bvh/bvh.hpp" #include "bvh/bounding_box.hpp" #include "bvh/top_down_builder.hpp" #include "bvh/sah_based_algorithm.hpp" namespace bvh { template class BinnedSahBuildTask; /// This is a top-down, classic binned SAH BVH builder. It works by approximating /// the SAH with bins of fixed size at every step of the recursion. /// See "On fast Construction of SAH-based Bounding Volume Hierarchies", /// by I. Wald. template class BinnedSahBuilder : public TopDownBuilder, public SahBasedAlgorithm { using Scalar = typename Bvh::ScalarType; using BuildTask = BinnedSahBuildTask; using TopDownBuilder::run_task; friend BuildTask; Bvh& bvh; public: using TopDownBuilder::max_depth; using TopDownBuilder::max_leaf_size; using SahBasedAlgorithm::traversal_cost; BinnedSahBuilder(Bvh& bvh) : bvh(bvh) {} void build( const BoundingBox& global_bbox, const BoundingBox* bboxes, const Vector3* centers, size_t primitive_count) { assert(primitive_count > 0); // Allocate buffers bvh.nodes = std::make_unique(2 * primitive_count + 1); bvh.primitive_indices = std::make_unique(primitive_count); bvh.node_count = 1; bvh.nodes[0].bounding_box_proxy() = global_bbox; #pragma omp parallel { #pragma omp for for (size_t i = 0; i < primitive_count; ++i) bvh.primitive_indices[i] = i; #pragma omp single { BuildTask first_task(*this, bboxes, centers); run_task(first_task, 0, 0, primitive_count, 0); } } } }; template class BinnedSahBuildTask : public TopDownBuildTask { using Scalar = typename Bvh::ScalarType; using Builder = BinnedSahBuilder; using TopDownBuildTask::WorkItem; struct Bin { BoundingBox bbox; size_t primitive_count; Scalar right_cost; }; static constexpr size_t bin_count = BinCount; std::array bins_per_axis[3]; Builder& builder; const BoundingBox* bboxes; const Vector3* centers; std::pair find_split(int axis) { auto& bins = bins_per_axis[axis]; // Right sweep to compute partial SAH auto current_bbox = BoundingBox::empty(); size_t current_count = 0; for (size_t i = bin_count - 1; i > 0; --i) { current_bbox.extend(bins[i].bbox); current_count += bins[i].primitive_count; bins[i].right_cost = current_bbox.half_area() * current_count; } // Left sweep to compute full cost and find minimum current_bbox = BoundingBox::empty(); current_count = 0; auto best_split = std::pair(std::numeric_limits::max(), bin_count); for (size_t i = 0; i < bin_count - 1; ++i) { current_bbox.extend(bins[i].bbox); current_count += bins[i].primitive_count; auto cost = current_bbox.half_area() * current_count + bins[i + 1].right_cost; if (cost < best_split.first) best_split = std::make_pair(cost, i + 1); } return best_split; } public: using WorkItemType = WorkItem; BinnedSahBuildTask(Builder& builder, const BoundingBox* bboxes, const Vector3* centers) : builder(builder), bboxes(bboxes), centers(centers) {} std::optional> build(const WorkItem& item) { auto& bvh = builder.bvh; auto& node = bvh.nodes[item.node_index]; auto make_leaf = [] (typename Bvh::Node& node, size_t begin, size_t end) { node.first_child_or_primitive = begin; node.primitive_count = end - begin; }; if (item.work_size() <= 1 || item.depth >= builder.max_depth) { make_leaf(node, item.begin, item.end); return std::nullopt; } auto primitive_indices = bvh.primitive_indices.get(); std::pair best_splits[3]; auto bbox = node.bounding_box_proxy().to_bounding_box(); auto center_to_bin = bbox.diagonal().inverse() * Scalar(bin_count); auto bin_offset = -bbox.min * center_to_bin; auto compute_bin_index = [=] (const Vector3& center, int axis) { auto bin_index = fast_multiply_add(center[axis], center_to_bin[axis], bin_offset[axis]); return std::min(bin_count - 1, size_t(std::max(Scalar(0), bin_index))); }; // Setup bins for (int axis = 0; axis < 3; ++axis) { for (auto& bin : bins_per_axis[axis]) { bin.bbox = BoundingBox::empty(); bin.primitive_count = 0; } } // Fill bins with primitives for (size_t i = item.begin; i < item.end; ++i) { auto primitive_index = bvh.primitive_indices[i]; for (int axis = 0; axis < 3; ++axis) { Bin& bin = bins_per_axis[axis][compute_bin_index(centers[primitive_index], axis)]; bin.primitive_count++; bin.bbox.extend(bboxes[primitive_index]); } } for (int axis = 0; axis < 3; ++axis) best_splits[axis] = find_split(axis); int best_axis = 0; if (best_splits[0].first > best_splits[1].first) best_axis = 1; if (best_splits[best_axis].first > best_splits[2].first) best_axis = 2; auto split_index = best_splits[best_axis].second; // Make sure the cost of splitting does not exceed the cost of not splitting auto max_split_cost = node.bounding_box_proxy().half_area() * (item.work_size() - builder.traversal_cost); if (best_splits[best_axis].second == bin_count || best_splits[best_axis].first >= max_split_cost) { if (item.work_size() > builder.max_leaf_size) { // Fallback strategy: approximate median split on largest axis best_axis = node.bounding_box_proxy().to_bounding_box().largest_axis(); for (size_t i = 0, count = 0; i < bin_count - 1; ++i) { count += bins_per_axis[best_axis][i].primitive_count; // Split when we reach 0.4 times the number of primitives in the node if (count >= (item.work_size() * 2 / 5 + 1)) { split_index = i + 1; break; } } } else { make_leaf(node, item.begin, item.end); return std::nullopt; } } // Split primitives according to split position size_t begin_right = std::partition(primitive_indices + item.begin, primitive_indices + item.end, [&] (size_t i) { return compute_bin_index(centers[i], best_axis) < split_index; }) - primitive_indices; // Check that the split does not leave one side empty if (begin_right > item.begin && begin_right < item.end) { // Allocate two nodes size_t first_child; #pragma omp atomic capture { first_child = bvh.node_count; bvh.node_count += 2; } auto& left = bvh.nodes[first_child + 0]; auto& right = bvh.nodes[first_child + 1]; node.first_child_or_primitive = first_child; node.primitive_count = 0; // Compute the bounding boxes of each node auto& bins = bins_per_axis[best_axis]; auto left_bbox = BoundingBox::empty(); auto right_bbox = BoundingBox::empty(); for (size_t i = 0; i < best_splits[best_axis].second; ++i) left_bbox.extend(bins[i].bbox); for (size_t i = split_index; i < bin_count; ++i) right_bbox.extend(bins[i].bbox); left.bounding_box_proxy() = left_bbox; right.bounding_box_proxy() = right_bbox; // Return new work items WorkItem first_item (first_child + 0, item.begin, begin_right, item.depth + 1); WorkItem second_item(first_child + 1, begin_right, item.end, item.depth + 1); return std::make_optional(std::make_pair(first_item, second_item)); } make_leaf(node, item.begin, item.end); return std::nullopt; } }; } // namespace bvh #endif ================================================ FILE: External/bvh/bottom_up_algorithm.hpp ================================================ #ifndef BVH_BOTTOM_UP_ALGORITHM_HPP #define BVH_BOTTOM_UP_ALGORITHM_HPP #include #include #include "bvh/bvh.hpp" #include "bvh/platform.hpp" namespace bvh { /// Base class for bottom-up BVH traversal algorithms. The implementation is inspired /// from T. Karras' bottom-up refitting algorithm, explained in the article /// "Maximizing Parallelism in the Construction of BVHs, Octrees, and k-d Trees". template class BottomUpAlgorithm { protected: std::unique_ptr parents; std::unique_ptr flags; Bvh& bvh; BottomUpAlgorithm(Bvh& bvh) : bvh(bvh) { bvh::assert_not_in_parallel(); parents = std::make_unique(bvh.node_count); flags = std::make_unique(bvh.node_count); parents[0] = 0; // Compute parent indices #pragma omp parallel for for (size_t i = 0; i < bvh.node_count; i++) { auto& node = bvh.nodes[i]; if (node.is_leaf()) continue; auto first_child = node.first_child_or_primitive; assert(first_child < bvh.node_count); parents[first_child + 0] = i; parents[first_child + 1] = i; } } ~BottomUpAlgorithm() {} template void traverse_in_parallel( const ProcessLeaf& process_leaf, const ProcessInnerNode& process_inner_node) { bvh::assert_in_parallel(); #pragma omp single nowait { // Special case if the BVH is just a leaf if (bvh.node_count == 1) process_leaf(0); } #pragma omp for for (size_t i = 1; i < bvh.node_count; ++i) { // Only process leaves if (!bvh.nodes[i].is_leaf()) continue; process_leaf(i); // Process inner nodes on the path from that leaf up to the root size_t j = i; do { j = parents[j]; // Make sure that the children of this inner node have been processed int previous_flag; #pragma omp atomic capture { previous_flag = flags[j]; flags[j]++; } if (previous_flag != 1) break; flags[j] = 0; process_inner_node(j); } while (j != 0); } } }; } // namespace bvh #endif ================================================ FILE: External/bvh/bounding_box.hpp ================================================ #ifndef BVH_BOUNDING_BOX_HPP #define BVH_BOUNDING_BOX_HPP #include "bvh/vector.hpp" namespace bvh { /// A bounding box, represented with two extreme points. template struct BoundingBox { Vector3 min, max; BoundingBox() = default; bvh_always_inline BoundingBox(const Vector3& v) : min(v), max(v) {} bvh_always_inline BoundingBox(const Vector3& min, const Vector3& max) : min(min), max(max) {} bvh_always_inline BoundingBox& shrink(const BoundingBox& bbox) { min = bvh::max(min, bbox.min); max = bvh::min(max, bbox.max); return *this; } bvh_always_inline BoundingBox& extend(const BoundingBox& bbox) { min = bvh::min(min, bbox.min); max = bvh::max(max, bbox.max); return *this; } bvh_always_inline BoundingBox& extend(const Vector3& v) { min = bvh::min(min, v); max = bvh::max(max, v); return *this; } bvh_always_inline Vector3 diagonal() const { return max - min; } bvh_always_inline Vector3 center() const { return (max + min) * Scalar(0.5); } bvh_always_inline Scalar half_area() const { auto d = diagonal(); return (d[0] + d[1]) * d[2] + d[0] * d[1]; } bvh_always_inline Scalar volume() const { auto d = diagonal(); return d[0] * d[1] * d[2]; } bvh_always_inline size_t largest_axis() const { auto d = diagonal(); size_t axis = 0; if (d[0] < d[1]) axis = 1; if (d[axis] < d[2]) axis = 2; return axis; } bvh_always_inline Scalar largest_extent() const { return diagonal()[largest_axis()]; } bvh_always_inline bool is_contained_in(const BoundingBox& other) const { return max[0] <= other.max[0] && min[0] >= other.min[0] && max[1] <= other.max[1] && min[1] >= other.min[1] && max[2] <= other.max[2] && min[2] >= other.min[2]; } bvh_always_inline static BoundingBox full() { return BoundingBox( Vector3(-std::numeric_limits::max()), Vector3(std::numeric_limits::max())); } bvh_always_inline static BoundingBox empty() { return BoundingBox( Vector3(std::numeric_limits::max()), Vector3(-std::numeric_limits::max())); } }; } // namespace bvh #endif ================================================ FILE: External/bvh/bvh.hpp ================================================ #ifndef BVH_BVH_HPP #define BVH_BVH_HPP #include #include #include #include "bvh/bounding_box.hpp" #include "bvh/utilities.hpp" namespace bvh { /// This structure represents a BVH with a list of nodes and primitives indices. /// The memory layout is such that the children of a node are always grouped together. /// This means that each node only needs one index to point to its children, as the other /// child can be obtained by adding one to the index of the first child. The root of the /// hierarchy is located at index 0 in the array of nodes. template struct Bvh { using IndexType = typename SizedIntegerType::Unsigned; using ScalarType = Scalar; // The size of this structure should be 32 bytes in // single precision and 64 bytes in double precision. struct Node { Scalar bounds[6]; IndexType primitive_count; IndexType first_child_or_primitive; bool is_leaf() const { return primitive_count != 0; } /// Accessor to simplify the manipulation of the bounding box of a node. /// This type is convertible to a `BoundingBox`. struct BoundingBoxProxy { Node& node; BoundingBoxProxy(Node& node) : node(node) {} BoundingBoxProxy& operator = (const BoundingBox& bbox) { node.bounds[0] = bbox.min[0]; node.bounds[1] = bbox.max[0]; node.bounds[2] = bbox.min[1]; node.bounds[3] = bbox.max[1]; node.bounds[4] = bbox.min[2]; node.bounds[5] = bbox.max[2]; return *this; } operator BoundingBox () const { return BoundingBox( Vector3(node.bounds[0], node.bounds[2], node.bounds[4]), Vector3(node.bounds[1], node.bounds[3], node.bounds[5])); } BoundingBox to_bounding_box() const { return static_cast>(*this); } Scalar half_area() const { return to_bounding_box().half_area(); } BoundingBoxProxy& extend(const BoundingBox& bbox) { return *this = to_bounding_box().extend(bbox); } BoundingBoxProxy& extend(const Vector3& vector) { return *this = to_bounding_box().extend(vector); } }; BoundingBoxProxy bounding_box_proxy() { return BoundingBoxProxy(*this); } const BoundingBoxProxy bounding_box_proxy() const { return BoundingBoxProxy(*const_cast(this)); } }; /// Given a node index, returns the index of its sibling. static size_t sibling(size_t index) { assert(index != 0); return index % 2 == 1 ? index + 1 : index - 1; } /// Returns true if the given node is the left sibling of another. static bool is_left_sibling(size_t index) { assert(index != 0); return index % 2 == 1; } std::unique_ptr nodes; std::unique_ptr primitive_indices; size_t node_count = 0; }; } // namespace bvh #endif ================================================ FILE: External/bvh/heuristic_primitive_splitter.hpp ================================================ #ifndef BVH_HEURISTIC_PRIMITIVE_SPLITTER_HPP #define BVH_HEURISTIC_PRIMITIVE_SPLITTER_HPP #include #include #include #include "bvh/bvh.hpp" #include "bvh/bounding_box.hpp" #include "bvh/prefix_sum.hpp" namespace bvh { /// Heuristic-based primitive splitter, inspired by the algorithm described in: /// "Fast Parallel Construction of High-Quality Bounding Volume Hierarchies", /// by T. Karras and T. Aila. template class HeuristicPrimitiveSplitter { using Scalar = typename Primitive::ScalarType; std::unique_ptr original_indices; PrefixSum prefix_sum; /// Returns the splitting priority of a primitive. static Scalar compute_priority(const Primitive& primitive, const BoundingBox& bbox) { // This is inspired from the priority function in the original paper, // except that the expression 2^i has been replaced by the largest // extent of the bounding box, which is similar in nature. return std::cbrt(bbox.largest_extent() * (Scalar(2) * bbox.half_area() - primitive.area())); } public: /// Performs triangle splitting on the given array of triangles. /// It returns the number of triangles after splitting. std::tuple[]>, std::unique_ptr[]>> split( const BoundingBox& global_bbox, const Primitive* primitives, size_t primitive_count, Scalar split_factor = Scalar(0.5)) { auto split_indices = std::make_unique(primitive_count); std::unique_ptr[]> bboxes; std::unique_ptr[]> centers; Scalar total_priority = 0; size_t reference_count = 0; #pragma omp parallel { #pragma omp for reduction(+: total_priority) for (size_t i = 0; i < primitive_count; ++i) total_priority += compute_priority(primitives[i], primitives[i].bounding_box()); #pragma omp for for (size_t i = 0; i < primitive_count; ++i) { auto priority = compute_priority(primitives[i], primitives[i].bounding_box()); split_indices[i] = 1 + priority * (Scalar(primitive_count) * split_factor / total_priority); } prefix_sum.sum_in_parallel(split_indices.get(), split_indices.get(), primitive_count); #pragma omp single { reference_count = split_indices[primitive_count - 1]; bboxes = std::make_unique[]>(reference_count); centers = std::make_unique[]>(reference_count); original_indices = std::make_unique(reference_count); } std::stack, size_t>> stack; #pragma omp for for (size_t i = 0; i < primitive_count; ++i) { size_t split_begin = i > 0 ? split_indices[i - 1] : 0; size_t split_count = split_indices[i] - split_begin; // Use the primitive's center instead of the bounding box // center if the primitive is not split. if (split_count == 1) { bboxes[split_begin] = primitives[i].bounding_box(); centers[split_begin] = primitives[i].center(); original_indices[split_begin] = i; continue; } // Split this primitive size_t j = split_begin; stack.emplace(primitives[i].bounding_box(), split_count); while (!stack.empty()) { auto [bbox, count] = stack.top(); stack.pop(); if (count == 1) { bboxes[j] = bbox; centers[j] = bbox.center(); original_indices[j] = i; j++; continue; } auto axis = bbox.largest_axis(); // Find the split depth (i.e. a power of 2 grid size) auto depth = std::min(Scalar(-1), std::floor(std::log2(bbox.largest_extent() / global_bbox.diagonal()[axis]))); auto cell_size = std::exp2(depth) * global_bbox.diagonal()[axis]; if (cell_size >= bbox.largest_extent()) cell_size *= Scalar(0.5); // Compute the split position auto mid_pos = (bbox.min[axis] + bbox.max[axis]) * Scalar(0.5); auto split_pos = global_bbox.min[axis] + std::round((mid_pos - global_bbox.min[axis]) / cell_size) * cell_size; if (split_pos < bbox.min[axis] || split_pos > bbox.max[axis]) { // Should only happen very rarely because of floating-point errors split_pos = mid_pos; } // Split the primitive and process fragments auto [left_bbox, right_bbox] = primitives[i].split(axis, split_pos); left_bbox.shrink(bbox); right_bbox.shrink(bbox); auto left_extent = left_bbox.largest_extent(); auto right_extent = right_bbox.largest_extent(); size_t left_count = count * left_extent / (right_extent + left_extent); left_count = std::max(size_t(1), std::min(count - 1, left_count)); stack.emplace(left_bbox, left_count); stack.emplace(right_bbox, count - left_count); } } } return std::make_tuple(reference_count, std::move(bboxes), std::move(centers)); } /// Remaps BVH primitive indices and removes duplicate triangle references in the BVH leaves. void repair_bvh_leaves(Bvh& bvh) { #pragma omp parallel for for (size_t i = 0; i < bvh.node_count; ++i) { auto& node = bvh.nodes[i]; if (node.is_leaf()) { auto begin = bvh.primitive_indices.get() + node.first_child_or_primitive; auto end = begin + node.primitive_count; std::transform(begin, end, begin, [&] (size_t i) { return original_indices[i]; }); std::sort(begin, end); node.primitive_count = std::unique(begin, end) - begin; } } } }; } // namespace bvh #endif ================================================ FILE: External/bvh/hierarchy_refitter.hpp ================================================ #ifndef BVH_HIERARCHY_REFITTER_HPP #define BVH_HIERARCHY_REFITTER_HPP #include "bvh/bvh.hpp" #include "bvh/bottom_up_algorithm.hpp" #include "bvh/platform.hpp" namespace bvh { template class HierarchyRefitter : public BottomUpAlgorithm { protected: using BottomUpAlgorithm::bvh; using BottomUpAlgorithm::traverse_in_parallel; using BottomUpAlgorithm::parents; template void refit_in_parallel(const UpdateLeaf& update_leaf) { bvh::assert_in_parallel(); // Refit every node of the tree in parallel traverse_in_parallel( [&] (size_t i) { update_leaf(bvh.nodes[i]); }, [&] (size_t i) { auto& node = bvh.nodes[i]; auto first_child = node.first_child_or_primitive; node.bounding_box_proxy() = bvh.nodes[first_child + 0] .bounding_box_proxy() .to_bounding_box() .extend(bvh.nodes[first_child + 1].bounding_box_proxy()); }); } public: HierarchyRefitter(Bvh& bvh) : BottomUpAlgorithm(bvh) {} template void refit(const UpdateLeaf& update_leaf) { #pragma omp parallel { refit_in_parallel(update_leaf); } } }; } // namespace bvh #endif ================================================ FILE: External/bvh/leaf_collapser.hpp ================================================ #ifndef BVH_LEAF_COLLAPSER_HPP #define BVH_LEAF_COLLAPSER_HPP #include #include "bvh/bvh.hpp" #include "bvh/sah_based_algorithm.hpp" #include "bvh/bottom_up_algorithm.hpp" #include "bvh/prefix_sum.hpp" #include "bvh/platform.hpp" namespace bvh { /// Collapses leaves of the BVH according to the SAH. This optimization /// is only helpful for bottom-up builders, as top-down builders already /// have a termination criterion that prevents leaf creation when the SAH /// cost does not improve. template class LeafCollapser : public SahBasedAlgorithm, public BottomUpAlgorithm { using Scalar = typename Bvh::ScalarType; PrefixSum prefix_sum; using BottomUpAlgorithm::traverse_in_parallel; using BottomUpAlgorithm::parents; using BottomUpAlgorithm::bvh; public: using SahBasedAlgorithm::traversal_cost; LeafCollapser(Bvh& bvh) : BottomUpAlgorithm(bvh) {} void collapse() { if (bvh_unlikely(bvh.nodes[0].is_leaf())) return; std::unique_ptr primitive_indices_copy; std::unique_ptr nodes_copy; auto node_counts = std::make_unique(bvh.node_count); auto primitive_counts = std::make_unique(bvh.node_count); size_t node_count = 0; #pragma omp parallel { #pragma omp for for (size_t i = 0; i < bvh.node_count; ++i) node_counts[i] = 1; // Bottom-up traversal to collapse leaves traverse_in_parallel( [&] (size_t i) { primitive_counts[i] = bvh.nodes[i].primitive_count; }, [&] (size_t i) { const auto& node = bvh.nodes[i]; assert(!node.is_leaf()); auto first_child = node.first_child_or_primitive; auto left_primitive_count = primitive_counts[first_child + 0]; auto right_primitive_count = primitive_counts[first_child + 1]; auto total_primitive_count = left_primitive_count + right_primitive_count; // Compute the cost of collapsing this node when both children are leaves if (left_primitive_count > 0 && right_primitive_count > 0) { const auto& left_child = bvh.nodes[first_child + 0]; const auto& right_child = bvh.nodes[first_child + 1]; auto collapse_cost = node.bounding_box_proxy().to_bounding_box().half_area() * (Scalar(total_primitive_count) - traversal_cost); auto base_cost = left_child .bounding_box_proxy().to_bounding_box().half_area() * left_primitive_count + right_child.bounding_box_proxy().to_bounding_box().half_area() * right_primitive_count; if (collapse_cost <= base_cost) { primitive_counts[i] = total_primitive_count; primitive_counts[first_child + 0] = 0; primitive_counts[first_child + 1] = 0; node_counts[first_child + 0] = 0; node_counts[first_child + 1] = 0; } } }); prefix_sum.sum_in_parallel(node_counts.get(), node_counts.get(), bvh.node_count); prefix_sum.sum_in_parallel(primitive_counts.get(), primitive_counts.get(), bvh.node_count); #pragma omp single { node_count = node_counts[bvh.node_count - 1]; if (primitive_counts[0] > 0) { // This means the root node has become a leaf. // We avoid copying the data and just swap the old primitive array with the new one. bvh.nodes[0].first_child_or_primitive = 0; bvh.nodes[0].primitive_count = primitive_counts[0]; std::swap(bvh.primitive_indices, primitive_indices_copy); std::swap(bvh.nodes, nodes_copy); bvh.node_count = 0; } else { nodes_copy = std::make_unique(node_count); primitive_indices_copy = std::make_unique(primitive_counts[bvh.node_count - 1]); nodes_copy[0] = bvh.nodes[0]; nodes_copy[0].first_child_or_primitive = node_counts[nodes_copy[0].first_child_or_primitive - 1]; } } #pragma omp for for (size_t i = 1; i < bvh.node_count; i++) { size_t node_index = node_counts[i - 1]; if (node_index == node_counts[i]) continue; nodes_copy[node_index] = bvh.nodes[i]; size_t first_primitive = primitive_counts[i - 1]; if (first_primitive != primitive_counts[i]) { nodes_copy[node_index].primitive_count = primitive_counts[i] - first_primitive; nodes_copy[node_index].first_child_or_primitive = first_primitive; // Top-down traversal to store the primitives contained in this subtree. size_t j = i; while (true) { const auto& node = bvh.nodes[j]; if (node.primitive_count != 0) { std::copy( bvh.primitive_indices.get() + node.first_child_or_primitive, bvh.primitive_indices.get() + node.first_child_or_primitive + node.primitive_count, primitive_indices_copy.get() + first_primitive); first_primitive += node.primitive_count; while (!bvh.is_left_sibling(j) && j != i) j = parents[j]; if (j == i) break; j = bvh.sibling(j); } else j = node.first_child_or_primitive; } assert(first_primitive == primitive_counts[i]); } else { auto& first_child = nodes_copy[node_index].first_child_or_primitive; first_child = node_counts[first_child - 1]; } } } std::swap(bvh.nodes, nodes_copy); std::swap(bvh.primitive_indices, primitive_indices_copy); bvh.node_count = node_count; } }; } // namespace bvh #endif ================================================ FILE: External/bvh/linear_bvh_builder.hpp ================================================ #ifndef BVH_LINEAR_BVH_BUILDER_HPP #define BVH_LINEAR_BVH_BUILDER_HPP #include #include #include #include "bvh/morton_code_based_builder.hpp" #include "bvh/prefix_sum.hpp" namespace bvh { /// Bottom-up BVH builder that uses Morton codes to create the hierarchy. /// This implementation is vaguely inspired from the original LBVH publication: /// "Fast BVH Construction on GPUs", by C. Lauterbach et al. template class LinearBvhBuilder : public MortonCodeBasedBuilder { using Scalar = typename Bvh::ScalarType; using ParentBuilder = MortonCodeBasedBuilder; using ParentBuilder::sort_primitives_by_morton_code; using Level = typename SizedIntegerType::Unsigned; using Node = typename Bvh::Node; Bvh& bvh; PrefixSum prefix_sum; std::pair merge( const Node* bvh_restrict input_nodes, Node* bvh_restrict output_nodes, const Level* bvh_restrict input_levels, Level* bvh_restrict output_levels, size_t* bvh_restrict merged_index, size_t* bvh_restrict needs_merge, size_t begin, size_t end, size_t previous_end) { size_t next_begin = 0; size_t next_end = 0; merged_index[end - 1] = 0; needs_merge [end - 1] = 0; #pragma omp parallel if (end - begin > loop_parallel_threshold) { // Determine, for each node, if it should be merged with the one on the right. #pragma omp for for (size_t i = begin; i < end - 1; ++i) needs_merge[i] = input_levels[i] >= input_levels[i + 1] && (i == begin || input_levels[i] >= input_levels[i - 1]); // Resolve conflicts between nodes that want to be merged with different neighbors. #pragma omp for for (size_t i = begin; i < end - 1; i += 2) { if (needs_merge[i] && needs_merge[i + 1]) needs_merge[i] = 0; } #pragma omp for for (size_t i = begin + 1; i < end - 1; i += 2) { if (needs_merge[i] && needs_merge[i + 1]) needs_merge[i] = 0; } // Perform a prefix sum to compute the insertion indices prefix_sum.sum_in_parallel(needs_merge + begin, merged_index + begin, end - begin); size_t merged_count = merged_index[end - 1]; size_t unmerged_count = end - begin - merged_count; size_t children_count = merged_count * 2; size_t children_begin = end - children_count; size_t unmerged_begin = end - (children_count + unmerged_count); #pragma omp single nowait { next_begin = unmerged_begin; next_end = children_begin; } // Perform one step of node merging #pragma omp for nowait for (size_t i = begin; i < end; ++i) { if (needs_merge[i]) { size_t unmerged_index = unmerged_begin + i + 1 - begin - merged_index[i]; auto& unmerged_node = output_nodes[unmerged_index]; auto first_child = children_begin + (merged_index[i] - 1) * 2; unmerged_node.bounding_box_proxy() = input_nodes[i] .bounding_box_proxy() .to_bounding_box() .extend(input_nodes[i + 1].bounding_box_proxy()); unmerged_node.primitive_count = 0; unmerged_node.first_child_or_primitive = first_child; output_nodes[first_child + 0] = input_nodes[i + 0]; output_nodes[first_child + 1] = input_nodes[i + 1]; output_levels[unmerged_index] = input_levels[i + 1]; } else if (i == begin || !needs_merge[i - 1]) { size_t unmerged_index = unmerged_begin + i - begin - merged_index[i]; output_nodes [unmerged_index] = input_nodes[i]; output_levels[unmerged_index] = input_levels[i]; } } // Copy the nodes of the previous level into the current array of nodes. #pragma omp for nowait for (size_t i = end; i < previous_end; ++i) output_nodes[i] = input_nodes[i]; } return std::make_pair(next_begin, next_end); } public: using ParentBuilder::loop_parallel_threshold; LinearBvhBuilder(Bvh& bvh) : bvh(bvh) {} void build( const BoundingBox& global_bbox, const BoundingBox* bboxes, const Vector3* centers, size_t primitive_count) { assert(primitive_count > 0); std::unique_ptr primitive_indices; std::unique_ptr morton_codes; std::tie(primitive_indices, morton_codes) = sort_primitives_by_morton_code(global_bbox, centers, primitive_count); auto node_count = 2 * primitive_count - 1; auto nodes = std::make_unique(node_count); auto nodes_copy = std::make_unique(node_count); auto auxiliary_data = std::make_unique(node_count * 2); auto level_data = std::make_unique(node_count * 2); size_t begin = node_count - primitive_count; size_t end = node_count; size_t previous_end = end; auto input_levels = level_data.get(); auto output_levels = level_data.get() + node_count; #pragma omp parallel if (primitive_count > loop_parallel_threshold) { // Create the leaves #pragma omp for nowait for (size_t i = 0; i < primitive_count; ++i) { auto& node = nodes[begin + i]; node.bounding_box_proxy() = bboxes[primitive_indices[i]]; node.primitive_count = 1; node.first_child_or_primitive = i; } // Compute the level of the tree where the current node is joined with the next. #pragma omp for nowait for (size_t i = 0; i < primitive_count - 1; ++i) input_levels[begin + i] = count_leading_zeros(morton_codes[i] ^ morton_codes[i + 1]); } while (end - begin > 1) { auto [next_begin, next_end] = merge( nodes.get(), nodes_copy.get(), input_levels, output_levels, auxiliary_data.get(), auxiliary_data.get() + node_count, begin, end, previous_end); std::swap(nodes, nodes_copy); std::swap(input_levels, output_levels); previous_end = end; begin = next_begin; end = next_end; } std::swap(bvh.nodes, nodes); std::swap(bvh.primitive_indices, primitive_indices); bvh.node_count = node_count; } }; } // namespace bvh #endif ================================================ FILE: External/bvh/locally_ordered_clustering_builder.hpp ================================================ #ifndef BVH_LOCALLY_ORDERED_CLUSTERING_BUILDER_HPP #define BVH_LOCALLY_ORDERED_CLUSTERING_BUILDER_HPP #include #include "bvh/morton_code_based_builder.hpp" #include "bvh/prefix_sum.hpp" #include "bvh/platform.hpp" namespace bvh { /// Bottom-up BVH builder based on agglomerative clustering. The algorithm starts /// by sorting primitives by their Morton code, and then clusters them iteratively /// to form the BVH nodes. Clusters are built starting from each primitive, by /// agglomerating nearby clusters that minimize a distance metric. The distance /// metric is in this case the area of the union of the bounding boxes of the two /// clusters of interest. /// See "Parallel Locally-Ordered Clustering for Bounding Volume Hierarchy Construction", /// by D. Meister and J. Bittner. template class LocallyOrderedClusteringBuilder : public MortonCodeBasedBuilder { using Scalar = typename Bvh::ScalarType; using Node = typename Bvh::Node; using ParentBuilder = MortonCodeBasedBuilder; using ParentBuilder::sort_primitives_by_morton_code; Bvh& bvh; PrefixSum prefix_sum; std::pair search_range(size_t i, size_t begin, size_t end) const { return std::make_pair( i > begin + search_radius ? i - search_radius : begin, std::min(i + search_radius + 1, end)); } std::pair cluster( const Node* bvh_restrict input, Node* bvh_restrict output, size_t* bvh_restrict neighbors, size_t* bvh_restrict merged_index, size_t begin, size_t end, size_t previous_end) { size_t next_begin = 0; size_t next_end = 0; #pragma omp parallel if (end - begin > loop_parallel_threshold) { auto thread_count = bvh::get_thread_count(); auto thread_id = bvh::get_thread_id(); auto chunk_size = (end - begin) / thread_count; auto chunk_begin = begin + thread_id * chunk_size; auto chunk_end = thread_id != thread_count - 1 ? chunk_begin + chunk_size : end; auto distances = std::make_unique((search_radius + 1) * search_radius); auto distance_matrix = std::make_unique(search_radius + 1); for (size_t i = 0; i <= search_radius; ++i) distance_matrix[i] = &distances[i * search_radius]; // Initialize the distance matrix, which caches the distances between // neighboring nodes in the array. A brute force approach that recomputes the // distances for every neighbor can be implemented without a distance matrix, // but would be slower for larger search radii. for (size_t i = search_range(chunk_begin, begin, end).first; i < chunk_begin; ++i) { auto search_end = search_range(i, begin, end).second; for (size_t j = i + 1; j < search_end; ++j) { distance_matrix[chunk_begin - i][j - i - 1] = input[i] .bounding_box_proxy() .to_bounding_box() .extend(input[j].bounding_box_proxy()) .half_area(); } } // Nearest neighbor search for (size_t i = chunk_begin; i < chunk_end; i++) { auto [search_begin, search_end] = search_range(i, begin, end); Scalar best_distance = std::numeric_limits::max(); size_t best_neighbor = -1; // Backward search (using the previously-computed distances stored in the distance matrix) for (size_t j = search_begin; j < i; ++j) { auto distance = distance_matrix[i - j][i - j - 1]; if (distance < best_distance) { best_distance = distance; best_neighbor = j; } } // Forward search (caching computed distances in the distance matrix) for (size_t j = i + 1; j < search_end; ++j) { auto distance = input[i] .bounding_box_proxy() .to_bounding_box() .extend(input[j].bounding_box_proxy()) .half_area(); distance_matrix[0][j - i - 1] = distance; if (distance < best_distance) { best_distance = distance; best_neighbor = j; } } assert(best_neighbor != size_t(-1)); neighbors[i] = best_neighbor; // Rotate the distance matrix columns auto last = distance_matrix[search_radius]; std::move_backward( distance_matrix.get(), distance_matrix.get() + search_radius, distance_matrix.get() + search_radius + 1); distance_matrix[0] = last; } #pragma omp barrier // Mark nodes that are the closest as merged, but keep // the one with lowest index to act as the parent #pragma omp for for (size_t i = begin; i < end; ++i) { auto j = neighbors[i]; bool is_mergeable = neighbors[j] == i; merged_index[i] = i < j && is_mergeable ? 1 : 0; } // Perform a prefix sum to compute the insertion indices prefix_sum.sum_in_parallel(merged_index + begin, merged_index + begin, end - begin); size_t merged_count = merged_index[end - 1]; size_t unmerged_count = end - begin - merged_count; size_t children_count = merged_count * 2; size_t children_begin = end - children_count; size_t unmerged_begin = end - (children_count + unmerged_count); #pragma omp single nowait { next_begin = unmerged_begin; next_end = children_begin; } // Finally, merge nodes that are marked for merging and create // their parents using the indices computed previously. #pragma omp for nowait for (size_t i = begin; i < end; ++i) { auto j = neighbors[i]; if (neighbors[j] == i) { if (i < j) { auto& unmerged_node = output[unmerged_begin + j - begin - merged_index[j]]; auto first_child = children_begin + (merged_index[i] - 1) * 2; unmerged_node.bounding_box_proxy() = input[j] .bounding_box_proxy() .to_bounding_box() .extend(input[i].bounding_box_proxy()); unmerged_node.primitive_count = 0; unmerged_node.first_child_or_primitive = first_child; output[first_child + 0] = input[i]; output[first_child + 1] = input[j]; } } else { output[unmerged_begin + i - begin - merged_index[i]] = input[i]; } } // Copy the nodes of the previous level into the current array of nodes. #pragma omp for nowait for (size_t i = end; i < previous_end; ++i) output[i] = input[i]; } return std::make_pair(next_begin, next_end); } public: using ParentBuilder::loop_parallel_threshold; /// Parameter of the algorithm. The larger the search radius, /// the longer the search for neighboring nodes lasts. size_t search_radius = 14; LocallyOrderedClusteringBuilder(Bvh& bvh) : bvh(bvh) {} void build( const BoundingBox& global_bbox, const BoundingBox* bboxes, const Vector3* centers, size_t primitive_count) { assert(primitive_count > 0); auto primitive_indices = sort_primitives_by_morton_code(global_bbox, centers, primitive_count).first; auto node_count = 2 * primitive_count - 1; auto nodes = std::make_unique(node_count); auto nodes_copy = std::make_unique(node_count); auto auxiliary_data = std::make_unique(node_count * 3); size_t begin = node_count - primitive_count; size_t end = node_count; size_t previous_end = end; // Create the leaves #pragma omp parallel for for (size_t i = 0; i < primitive_count; ++i) { auto& node = nodes[begin + i]; node.bounding_box_proxy() = bboxes[primitive_indices[i]]; node.primitive_count = 1; node.first_child_or_primitive = i; } while (end - begin > 1) { auto [next_begin, next_end] = cluster( nodes.get(), nodes_copy.get(), auxiliary_data.get(), auxiliary_data.get() + node_count, begin, end, previous_end); std::swap(nodes_copy, nodes); previous_end = end; begin = next_begin; end = next_end; } std::swap(bvh.nodes, nodes); std::swap(bvh.primitive_indices, primitive_indices); bvh.node_count = node_count; } }; } // namespace bvh #endif ================================================ FILE: External/bvh/morton.hpp ================================================ #ifndef BVH_MORTON_HPP #define BVH_MORTON_HPP #include #include #include #include "bvh/utilities.hpp" namespace bvh { /// Split an unsigned integer such that its bits are spaced by 2 zeros. /// For instance, morton_split(0b00110010) = 0b000000001001000000001000. template Morton morton_split(Morton x) { constexpr size_t bit_count = sizeof(Morton) * CHAR_BIT; constexpr size_t log_bits = round_up_log2(bit_count); auto mask = Morton(-1) >> (bit_count / 2); x &= mask; for (size_t i = log_bits - 1, n = 1 << i; i > 0; --i, n >>= 1) { mask = (mask | (mask << n)) & ~(mask << (n / 2)); x = (x | (x << n)) & mask; } return x; } /// Morton-encode three unsigned integers into one. template Morton morton_encode(Morton x, Morton y, Morton z) { return morton_split(x) | (morton_split(y) << 1) | (morton_split(z) << 2); } template class MortonEncoder { Vector3 world_to_grid; Vector3 grid_offset; size_t grid_dim; public: static constexpr size_t max_grid_dim = 1 << (sizeof(Morton) * CHAR_BIT / 3); MortonEncoder(const BoundingBox& bbox, size_t grid_dim = max_grid_dim) : grid_dim(grid_dim) { assert(grid_dim <= max_grid_dim); world_to_grid = Scalar(grid_dim) * bbox.diagonal().inverse(); grid_offset = -bbox.min * world_to_grid; } /// Morton-encode a 3D point into one unsigned integer. Morton encode(const Vector3& point) const { auto grid_position = point * world_to_grid + grid_offset; Morton x = std::min(Morton(grid_dim - 1), Morton(std::max(grid_position[0], Scalar(0)))); Morton y = std::min(Morton(grid_dim - 1), Morton(std::max(grid_position[1], Scalar(0)))); Morton z = std::min(Morton(grid_dim - 1), Morton(std::max(grid_position[2], Scalar(0)))); return morton_encode(x, y, z); } }; } // namespace bvh #endif ================================================ FILE: External/bvh/morton_code_based_builder.hpp ================================================ #ifndef BVH_MORTON_CODE_BASED_BUILDER_HPP #define BVH_MORTON_CODE_BASED_BUILDER_HPP #include #include #include #include #include "bvh/bounding_box.hpp" #include "bvh/vector.hpp" #include "bvh/morton.hpp" #include "bvh/radix_sort.hpp" namespace bvh { template class MortonCodeBasedBuilder { using Scalar = typename Bvh::ScalarType; /// Number of bits processed by every iteration of the radix sort. static constexpr size_t bits_per_iteration = 10; public: static_assert(std::is_unsigned_v); using MortonType = Morton; /// Maximum number of bits available per dimension. static constexpr size_t max_bit_count = (sizeof(Morton) * CHAR_BIT) / 3; /// Number of bits to use per dimension. size_t bit_count = max_bit_count; /// Threshold (number of nodes) under which the loops execute serially. size_t loop_parallel_threshold = 256; protected: using SortedPairs = std::pair, std::unique_ptr>; RadixSort radix_sort; ~MortonCodeBasedBuilder() {} SortedPairs sort_primitives_by_morton_code( const BoundingBox& global_bbox, const Vector3* centers, size_t primitive_count) { assert(bit_count <= max_bit_count); auto morton_codes = std::make_unique(primitive_count); auto morton_codes_copy = std::make_unique(primitive_count); auto primitive_indices = std::make_unique(primitive_count); auto primitive_indices_copy = std::make_unique(primitive_count); Morton* sorted_morton_codes = morton_codes.get(); size_t* sorted_primitive_indices = primitive_indices.get(); Morton* unsorted_morton_codes = morton_codes_copy.get(); size_t* unsorted_primitive_indices = primitive_indices_copy.get(); MortonEncoder encoder(global_bbox, size_t(1) << bit_count); #pragma omp parallel if (primitive_count > loop_parallel_threshold) { #pragma omp for for (size_t i = 0; i < primitive_count; ++i) { morton_codes[i] = encoder.encode(centers[i]); primitive_indices[i] = i; } // Sort primitives by morton code radix_sort.sort_in_parallel( sorted_morton_codes, unsorted_morton_codes, sorted_primitive_indices, unsorted_primitive_indices, primitive_count, bit_count * 3); } if (sorted_morton_codes != morton_codes.get()) { std::swap(morton_codes, morton_codes_copy); std::swap(primitive_indices, primitive_indices_copy); } assert(std::is_sorted(morton_codes.get(), morton_codes.get() + primitive_count)); return std::make_pair(std::move(primitive_indices), std::move(morton_codes)); } }; } // namespace bvh #endif ================================================ FILE: External/bvh/node_intersectors.hpp ================================================ #ifndef BVH_NODE_INTERSECTORS_HPP #define BVH_NODE_INTERSECTORS_HPP #include #include "bvh/vector.hpp" #include "bvh/ray.hpp" #include "bvh/platform.hpp" #include "bvh/utilities.hpp" namespace bvh { /// Base class for ray-node intersection algorithms. Does ray octant classification. template struct NodeIntersector { using Scalar = typename Bvh::ScalarType; std::array octant; NodeIntersector(const Ray& ray) : octant { std::signbit(ray.direction[0]), std::signbit(ray.direction[1]), std::signbit(ray.direction[2]) } {} template bvh_always_inline Scalar intersect_axis(int axis, Scalar p, const Ray& ray) const { return static_cast(this)->template intersect_axis(axis, p, ray); } bvh_always_inline std::pair intersect(const typename Bvh::Node& node, const Ray& ray) const { Vector3 entry, exit; entry[0] = intersect_axis(0, node.bounds[0 * 2 + octant[0]], ray); entry[1] = intersect_axis(1, node.bounds[1 * 2 + octant[1]], ray); entry[2] = intersect_axis(2, node.bounds[2 * 2 + octant[2]], ray); exit [0] = intersect_axis(0, node.bounds[0 * 2 + 1 - octant[0]], ray); exit [1] = intersect_axis(1, node.bounds[1 * 2 + 1 - octant[1]], ray); exit [2] = intersect_axis(2, node.bounds[2 * 2 + 1 - octant[2]], ray); // Note: This order for the min/max operations is guaranteed not to produce NaNs return std::make_pair( robust_max(entry[0], robust_max(entry[1], robust_max(entry[2], ray.tmin))), robust_min(exit [0], robust_min(exit [1], robust_min(exit [2], ray.tmax)))); } protected: ~NodeIntersector() {} }; /// Fully robust ray-node intersection algorithm (see "Robust BVH Ray Traversal", by T. Ize). template struct RobustNodeIntersector : public NodeIntersector> { using Scalar = typename Bvh::ScalarType; // Padded inverse direction to avoid false-negatives in the ray-node test. Vector3 padded_inverse_direction; Vector3 inverse_direction; RobustNodeIntersector(const Ray& ray) : NodeIntersector>(ray) { inverse_direction = ray.direction.inverse(); padded_inverse_direction = Vector3( add_ulp_magnitude(inverse_direction[0], 2), add_ulp_magnitude(inverse_direction[1], 2), add_ulp_magnitude(inverse_direction[2], 2)); } template bvh_always_inline Scalar intersect_axis(int axis, Scalar p, const Ray& ray) const { return (p - ray.origin[axis]) * (IsMin ? inverse_direction[axis] : padded_inverse_direction[axis]); } using NodeIntersector>::intersect; }; /// Semi-robust, fast ray-node intersection algorithm. template struct FastNodeIntersector : public NodeIntersector> { using Scalar = typename Bvh::ScalarType; Vector3 scaled_origin; Vector3 inverse_direction; FastNodeIntersector(const Ray& ray) : NodeIntersector>(ray) { inverse_direction = ray.direction.safe_inverse(); scaled_origin = -ray.origin * inverse_direction; } template bvh_always_inline Scalar intersect_axis(int axis, Scalar p, const Ray&) const { return fast_multiply_add(p, inverse_direction[axis], scaled_origin[axis]); } using NodeIntersector>::intersect; }; } // namespace bvh #endif ================================================ FILE: External/bvh/node_layout_optimizer.hpp ================================================ #ifndef BVH_NODE_LAYOUT_OPTIMIZER_HPP #define BVH_NODE_LAYOUT_OPTIMIZER_HPP #include #include "bvh/bvh.hpp" #include "bvh/utilities.hpp" #include "bvh/radix_sort.hpp" namespace bvh { /// Optimizes the layout of BVH nodes so that the nodes with /// the highest area are closer to the beginning of the array /// of nodes. This does not change the topology of the BVH; /// only the memory layout of the nodes is affected. template class NodeLayoutOptimizer { using Scalar = typename Bvh::ScalarType; using Key = typename SizedIntegerType::Unsigned; RadixSort<8> radix_sort; Bvh& bvh; public: NodeLayoutOptimizer(Bvh& bvh) : bvh(bvh) {} void optimize() { size_t pair_count = (bvh.node_count - 1) / 2; auto keys = std::make_unique(pair_count * 2); auto indices = std::make_unique(pair_count * 2); auto nodes_copy = std::make_unique(bvh.node_count); nodes_copy[0] = bvh.nodes[0]; auto sorted_indices = indices.get(); auto unsorted_indices = indices.get() + pair_count; auto sorted_keys = keys.get(); auto unsorted_keys = keys.get() + pair_count; #pragma omp parallel { // Compute the surface area of each pair of nodes #pragma omp for for (size_t i = 1; i < bvh.node_count; i += 2) { auto area = bvh.nodes[i + 0] .bounding_box_proxy() .to_bounding_box() .extend(bvh.nodes[i + 1].bounding_box_proxy()) .half_area(); size_t j = (i - 1) / 2; keys[j] = as(area); indices[j] = j; } // Sort pairs of nodes by area. This can be done with a // standard radix sort that interprets the floating point // data as integers, because the area is positive, and // positive floating point numbers can be compared like // integers (mandated by IEEE-754). radix_sort.sort_in_parallel( sorted_keys, unsorted_keys, sorted_indices, unsorted_indices, pair_count, sizeof(Scalar) * CHAR_BIT); // Copy the nodes of the old layout into the new one #pragma omp for for (size_t i = 0; i < pair_count; ++i) { auto j = sorted_indices[pair_count - i - 1]; auto k = 1 + j * 2; auto l = 1 + i * 2; nodes_copy[l + 0] = bvh.nodes[k + 0]; nodes_copy[l + 1] = bvh.nodes[k + 1]; unsorted_indices[j] = l; } // Remap children indices to the new layout #pragma omp for for (size_t i = 0; i < bvh.node_count; ++i) { if (nodes_copy[i].is_leaf()) continue; nodes_copy[i].first_child_or_primitive = unsorted_indices[(nodes_copy[i].first_child_or_primitive - 1) / 2]; } } std::swap(nodes_copy, bvh.nodes); } }; } // namespace bvh #endif ================================================ FILE: External/bvh/parallel_reinsertion_optimizer.hpp ================================================ #ifndef BVH_PARALLEL_REINSERTION_OPTIMIZER_HPP #define BVH_PARALLEL_REINSERTION_OPTIMIZER_HPP #include #include "bvh/bvh.hpp" #include "bvh/sah_based_algorithm.hpp" #include "bvh/hierarchy_refitter.hpp" namespace bvh { /// Optimization that tries to re-insert BVH nodes in such a way that the /// SAH cost of the tree decreases after the re-insertion. Inspired from the /// article "Parallel Reinsertion for Bounding Volume Hierarchy Optimization", /// by D. Meister and J. Bittner. template class ParallelReinsertionOptimizer : public SahBasedAlgorithm, protected HierarchyRefitter { using Scalar = typename Bvh::ScalarType; using Insertion = std::pair; using SahBasedAlgorithm::compute_cost; using HierarchyRefitter::bvh; using HierarchyRefitter::parents; using HierarchyRefitter::refit_in_parallel; public: ParallelReinsertionOptimizer(Bvh& bvh) : HierarchyRefitter(bvh) {} private: std::array get_conflicts(size_t in, size_t out) { // Return an array of re-insertion conflicts for the given nodes auto parent_in = parents[in]; return std::array { in, bvh.sibling(in), parent_in, parent_in == 0 ? in : parents[parent_in], out, out == 0 ? out : parents[out], }; } void reinsert(size_t in, size_t out) { auto sibling_in = bvh.sibling(in); auto parent_in = parents[in]; auto sibling_node = bvh.nodes[sibling_in]; auto out_node = bvh.nodes[out]; // Re-insert it into the destination bvh.nodes[out].bounding_box_proxy().extend(bvh.nodes[in].bounding_box_proxy()); bvh.nodes[out].first_child_or_primitive = std::min(in, sibling_in); bvh.nodes[out].primitive_count = 0; bvh.nodes[sibling_in] = out_node; bvh.nodes[parent_in] = sibling_node; // Update parent-child indices if (!out_node.is_leaf()) { parents[out_node.first_child_or_primitive + 0] = sibling_in; parents[out_node.first_child_or_primitive + 1] = sibling_in; } if (!sibling_node.is_leaf()) { parents[sibling_node.first_child_or_primitive + 0] = parent_in; parents[sibling_node.first_child_or_primitive + 1] = parent_in; } parents[sibling_in] = out; parents[in] = out; } Insertion search(size_t in) { bool down = true; size_t pivot = parents[in]; size_t out = bvh.sibling(in); size_t out_best = out; auto bbox_in = bvh.nodes[in].bounding_box_proxy(); auto bbox_parent = bvh.nodes[pivot].bounding_box_proxy(); auto bbox_pivot = BoundingBox::empty(); Scalar d = 0; Scalar d_best = 0; const Scalar d_bound = bbox_parent.half_area() - bbox_in.half_area(); // Perform a search to find a re-insertion position for the given node while (true) { auto bbox_out = bvh.nodes[out].bounding_box_proxy().to_bounding_box(); auto bbox_merged = BoundingBox(bbox_in).extend(bbox_out); if (down) { auto d_direct = bbox_parent.half_area() - bbox_merged.half_area(); if (d_best < d_direct + d) { d_best = d_direct + d; out_best = out; } d = d + bbox_out.half_area() - bbox_merged.half_area(); if (bvh.nodes[out].is_leaf() || d_bound + d <= d_best) down = false; else out = bvh.nodes[out].first_child_or_primitive; } else { d = d - bbox_out.half_area() + bbox_merged.half_area(); if (pivot == parents[out]) { bbox_pivot.extend(bbox_out); out = pivot; bbox_out = bvh.nodes[out].bounding_box_proxy(); if (out != parents[in]) { bbox_merged = BoundingBox(bbox_in).extend(bbox_pivot); auto d_direct = bbox_parent.half_area() - bbox_merged.half_area(); if (d_best < d_direct + d) { d_best = d_direct + d; out_best = out; } d = d + bbox_out.half_area() - bbox_pivot.half_area(); } if (out == 0) break; out = bvh.sibling(pivot); pivot = parents[out]; down = true; } else { if (bvh.is_left_sibling(out)) { down = true; out = bvh.sibling(out); } else { out = parents[out]; } } } } if (in == out_best || bvh.sibling(in) == out_best || parents[in] == out_best) return Insertion { 0, 0 }; return Insertion { out_best, d_best }; } public: void optimize(size_t u = 9, Scalar threshold = 0.1) { auto locks = std::make_unique[]>(bvh.node_count); auto outs = std::make_unique(bvh.node_count); auto old_cost = compute_cost(bvh); for (size_t iteration = 0; ; ++iteration) { size_t first_node = iteration % u + 1; #pragma omp parallel { // Clear the locks #pragma omp for nowait for (size_t i = 0; i < bvh.node_count; i++) locks[i] = 0; // Search for insertion candidates #pragma omp for for (size_t i = first_node; i < bvh.node_count; i += u) outs[i] = search(i); // Resolve topological conflicts with locking #pragma omp for for (size_t i = first_node; i < bvh.node_count; i += u) { if (outs[i].second <= 0) continue; // Encode locks into 64 bits using the highest 32 bits for the cost and the // lowest 32 bits for the index of the node requesting the re-insertion. // This takes advantage of the fact that IEEE-754 floats can be compared // with regular integer comparisons. auto lock = (uint64_t(as(float(outs[i].second))) << 32) | (uint64_t(i) & UINT64_C(0xFFFFFFFF)); for (auto c : get_conflicts(i, outs[i].first)) atomic_max(locks[c], lock); } // Check the locks to disable conflicting re-insertions #pragma omp for for (size_t i = first_node; i < bvh.node_count; i += u) { if (outs[i].second <= 0) continue; auto conflicts = get_conflicts(i, outs[i].first); // Make sure that this node owns all the locks for each and every conflicting node bool is_conflict_free = std::all_of(conflicts.begin(), conflicts.end(), [&] (size_t j) { return (locks[j] & UINT64_C(0xFFFFFFFF)) == i; }); if (!is_conflict_free) outs[i] = Insertion { 0, 0 }; } // Perform the reinsertions #pragma omp for for (size_t i = first_node; i < bvh.node_count; i += u) { if (outs[i].second > 0) reinsert(i, outs[i].first); } // Update the bounding boxes of each node in the tree refit_in_parallel([] (typename Bvh::Node&) {}); } // Compare the old SAH cost to the new one and decrease the number // of nodes that are ignored during the optimization if the change // in cost is below the threshold. auto new_cost = compute_cost(bvh); if (std::abs(new_cost - old_cost) <= threshold || iteration >= u) { if (u <= 1) break; u = u - 1; iteration = 0; } old_cost = new_cost; } } }; } // namespace bvh #endif ================================================ FILE: External/bvh/platform.hpp ================================================ #ifndef BVH_PLATFORM_HPP #define BVH_PLATFORM_HPP #include #include #ifdef _OPENMP #include #endif #if defined(__GNUC__) || defined(__clang__) #define bvh_restrict __restrict #define bvh_always_inline [[gnu::always_inline]] #elif defined(_MSC_VER) #define bvh_restrict __restrict #define bvh_always_inline [[msvc::forceinline]] #else #define bvh_restrict #define bvh_always_inline #endif namespace bvh { #ifdef _OPENMP inline size_t get_thread_count() { return omp_get_num_threads(); } inline size_t get_thread_id() { return omp_get_thread_num(); } inline void assert_not_in_parallel() { assert(omp_get_level() == 0); } inline void assert_in_parallel() { assert(omp_get_level() > 0); } #else inline constexpr size_t get_thread_count() { return 1; } inline constexpr size_t get_thread_id() { return 0; } inline void assert_not_in_parallel() {} inline void assert_in_parallel() {} #endif } // namespace bvh #endif ================================================ FILE: External/bvh/prefix_sum.hpp ================================================ #ifndef BVH_PREFIX_SUM_HPP #define BVH_PREFIX_SUM_HPP #include #include #include #include "bvh/utilities.hpp" namespace bvh { /// Parallel prefix sum. The parallel algorithm used in this implementation /// needs twice the work as the naive serial version, and is thus enabled /// only if the number of cores if greater or equal than 3. template class PrefixSum { public: /// Performs the prefix sum. Must be called from a parallel region. template > void sum_in_parallel(const T* input, T* output, size_t count, F f = F()) { bvh::assert_in_parallel(); size_t thread_count = bvh::get_thread_count(); size_t thread_id = bvh::get_thread_id(); // This algorithm is not effective when there are fewer than 2 threads. if (thread_count <= 2) { #pragma omp single { std::partial_sum(input, input + count, output, f); } return; } // Allocate temporary storage #pragma omp single { if (per_thread_data_size < thread_count + 1) { per_thread_sums = std::make_unique(thread_count + 1); per_thread_data_size = thread_count + 1; per_thread_sums[0] = 0; } } T sum = T(0); // Compute partial sums #pragma omp for nowait schedule(static) for (size_t i = 0; i < count; ++i) { sum = f(sum, input[i]); output[i] = sum; } per_thread_sums[thread_id + 1] = sum; #pragma omp barrier // Fix the sums auto offset = std::accumulate(per_thread_sums.get(), per_thread_sums.get() + thread_id + 1, 0, f); #pragma omp for schedule(static) for (size_t i = 0; i < count; ++i) output[i] = f(output[i], offset); } private: std::unique_ptr per_thread_sums; size_t per_thread_data_size = 0; }; } // namespace bvh #endif ================================================ FILE: External/bvh/primitive_intersectors.hpp ================================================ #ifndef BVH_PRIMITIVE_INTERSECTORS_HPP #define BVH_PRIMITIVE_INTERSECTORS_HPP #include #include "bvh/ray.hpp" namespace bvh { /// Base class for primitive intersectors. template struct PrimitiveIntersector { PrimitiveIntersector(const Bvh& bvh, const Primitive* primitives) : bvh(bvh), primitives(primitives) {} std::pair primitive_at(size_t index) const { index = Permuted ? index : bvh.primitive_indices[index]; return std::pair { primitives[index], index }; } const Bvh& bvh; const Primitive* primitives = nullptr; static constexpr bool any_hit = AnyHit; protected: ~PrimitiveIntersector() {} }; /// An intersector that looks for the closest intersection. template struct ClosestPrimitiveIntersector : public PrimitiveIntersector { using Scalar = typename Primitive::ScalarType; using Intersection = typename Primitive::IntersectionType; struct Result { size_t primitive_index; Intersection intersection; Scalar distance() const { return intersection.distance(); } }; ClosestPrimitiveIntersector(const Bvh& bvh, const Primitive* primitives) : PrimitiveIntersector(bvh, primitives) {} std::optional intersect(size_t index, const Ray& ray) const { auto [p, i] = this->primitive_at(index); if (auto hit = p.intersect(ray)) return std::make_optional(Result { i, *hit }); return std::nullopt; } }; /// An intersector that exits after the first hit and only stores the distance to the primitive. template struct AnyPrimitiveIntersector : public PrimitiveIntersector { using Scalar = typename Primitive::ScalarType; struct Result { Scalar t; Scalar distance() const { return t; } }; AnyPrimitiveIntersector(const Bvh& bvh, const Primitive* primitives) : PrimitiveIntersector(bvh, primitives) {} std::optional intersect(size_t index, const Ray& ray) const { auto [p, i] = this->primitive_at(index); if (auto hit = p.intersect(ray)) return std::make_optional(Result { hit->distance() }); return std::nullopt; } }; } // namespace bvh #endif ================================================ FILE: External/bvh/radix_sort.hpp ================================================ #ifndef BVH_RADIX_SORT_HPP #define BVH_RADIX_SORT_HPP #include #include #include #include "bvh/platform.hpp" #include "bvh/utilities.hpp" namespace bvh { /// Parallel implementation of the radix sort algorithm. template class RadixSort { public: static constexpr size_t bits_per_iteration = BitsPerIteration; /// Performs the sort. Must be called from a parallel region. template void sort_in_parallel( Key* bvh_restrict& keys, Key* bvh_restrict& keys_copy, Value* bvh_restrict& values, Value* bvh_restrict& values_copy, size_t count, size_t bit_count) { bvh::assert_in_parallel(); static constexpr size_t bucket_count = 1 << bits_per_iteration; static constexpr Key mask = (Key(1) << bits_per_iteration) - 1; size_t thread_count = bvh::get_thread_count(); size_t thread_id = bvh::get_thread_id(); // Allocate temporary storage #pragma omp single { size_t data_size = (thread_count + 1) * bucket_count; if (per_thread_data_size < data_size) { per_thread_buckets = std::make_unique(data_size); per_thread_data_size = data_size; } } for (size_t bit = 0; bit < bit_count; bit += BitsPerIteration) { auto buckets = &per_thread_buckets[thread_id * bucket_count]; std::fill(buckets, buckets + bucket_count, 0); #pragma omp for schedule(static) for (size_t i = 0; i < count; ++i) buckets[(keys[i] >> bit) & mask]++; #pragma omp for for (size_t i = 0; i < bucket_count; i++) { // Do a prefix sum of the elements in one bucket over all threads size_t sum = 0; for (size_t j = 0; j < thread_count; ++j) { size_t old_sum = sum; sum += per_thread_buckets[j * bucket_count + i]; per_thread_buckets[j * bucket_count + i] = old_sum; } per_thread_buckets[thread_count * bucket_count + i] = sum; } for (size_t i = 0, sum = 0; i < bucket_count; ++i) { size_t old_sum = sum; sum += per_thread_buckets[thread_count * bucket_count + i]; buckets[i] += old_sum; } #pragma omp for schedule(static) for (size_t i = 0; i < count; ++i) { size_t j = buckets[(keys[i] >> bit) & mask]++; keys_copy[j] = keys[i]; values_copy[j] = values[i]; } #pragma omp single { std::swap(keys_copy, keys); std::swap(values_copy, values); } } } /// Creates a radix sort key from a floating point value. template ::value, int> = 0> static typename SizedIntegerType::Unsigned make_key(T x) { using U = typename SizedIntegerType::Unsigned; auto mask = U(1) << (sizeof(T) * CHAR_BIT - 1); auto y = as(x); return (y & mask ? (-y) ^ mask : y) ^ mask; } private: std::unique_ptr per_thread_buckets; size_t per_thread_data_size = 0; }; } // namespace bvh #endif ================================================ FILE: External/bvh/ray.hpp ================================================ #ifndef BVH_RAY_HPP #define BVH_RAY_HPP #include "bvh/vector.hpp" namespace bvh { /// A ray, defined by an origin and a direction, with minimum and maximum distances along the direction from the origin. template struct Ray { Vector3 origin; Vector3 direction; Scalar tmin; Scalar tmax; Ray() = default; Ray(const Vector3& origin, const Vector3& direction, Scalar tmin = Scalar(0), Scalar tmax = std::numeric_limits::max()) : origin(origin), direction(direction), tmin(tmin), tmax(tmax) {} }; } // namespace bvh #endif ================================================ FILE: External/bvh/sah_based_algorithm.hpp ================================================ #ifndef BVH_SAH_BASED_ALGORITHM_HPP #define BVH_SAH_BASED_ALGORITHM_HPP #include "bvh/bvh.hpp" namespace bvh { template class SahBasedAlgorithm { using Scalar = typename Bvh::ScalarType; public: /// Cost of intersecting a ray with a node of the data structure. /// This cost is relative to the cost of intersecting a primitive, /// which is assumed to be equal to 1. Scalar traversal_cost = 1; protected: ~SahBasedAlgorithm() {} Scalar compute_cost(const Bvh& bvh) const { // Compute the SAH cost for the entire BVH Scalar cost(0); #pragma omp parallel for reduction(+: cost) for (size_t i = 0; i < bvh.node_count; ++i) { if (bvh.nodes[i].is_leaf()) cost += bvh.nodes[i].bounding_box_proxy().half_area() * bvh.nodes[i].primitive_count; else cost += traversal_cost * bvh.nodes[i].bounding_box_proxy().half_area(); } return cost / bvh.nodes[0].bounding_box_proxy().half_area(); } }; } // namespace bvh #endif ================================================ FILE: External/bvh/single_ray_traverser.hpp ================================================ #ifndef BVH_SINGLE_RAY_TRAVERSAL_HPP #define BVH_SINGLE_RAY_TRAVERSAL_HPP #include #include "bvh/bvh.hpp" #include "bvh/ray.hpp" #include "bvh/node_intersectors.hpp" #include "bvh/utilities.hpp" namespace bvh { /// Single ray traversal algorithm, using the provided ray-node intersector. template > class SingleRayTraverser { public: static constexpr size_t stack_size = StackSize; private: using Scalar = typename Bvh::ScalarType; struct Stack { using Element = typename Bvh::IndexType; Element elements[stack_size]; size_t size = 0; void push(const Element& t) { assert(size < stack_size); elements[size++] = t; } Element pop() { assert(!empty()); return elements[--size]; } bool empty() const { return size == 0; } }; template bvh_always_inline std::optional& intersect_leaf( const typename Bvh::Node& node, Ray& ray, std::optional& best_hit, PrimitiveIntersector& primitive_intersector, Statistics& statistics) const { assert(node.is_leaf()); size_t begin = node.first_child_or_primitive; size_t end = begin + node.primitive_count; statistics.intersections += end - begin; for (size_t i = begin; i < end; ++i) { if (auto hit = primitive_intersector.intersect(i, ray)) { best_hit = hit; if (primitive_intersector.any_hit) return best_hit; ray.tmax = hit->distance(); } } return best_hit; } template bvh_always_inline std::optional intersect(Ray ray, PrimitiveIntersector& primitive_intersector, Statistics& statistics) const { auto best_hit = std::optional(std::nullopt); // If the root is a leaf, intersect it and return if (bvh_unlikely(bvh.nodes[0].is_leaf())) return intersect_leaf(bvh.nodes[0], ray, best_hit, primitive_intersector, statistics); NodeIntersector node_intersector(ray); // This traversal loop is eager, because it immediately processes leaves instead of pushing them on the stack. // This is generally beneficial for performance because intersections will likely be found which will // allow to cull more subtrees with the ray-box test of the traversal loop. Stack stack; auto* left_child = &bvh.nodes[bvh.nodes[0].first_child_or_primitive]; while (true) { statistics.traversal_steps++; auto* right_child = left_child + 1; auto distance_left = node_intersector.intersect(*left_child, ray); auto distance_right = node_intersector.intersect(*right_child, ray); if (distance_left.first <= distance_left.second) { if (bvh_unlikely(left_child->is_leaf())) { if (intersect_leaf(*left_child, ray, best_hit, primitive_intersector, statistics) && primitive_intersector.any_hit) break; left_child = nullptr; } } else left_child = nullptr; if (distance_right.first <= distance_right.second) { if (bvh_unlikely(right_child->is_leaf())) { if (intersect_leaf(*right_child, ray, best_hit, primitive_intersector, statistics) && primitive_intersector.any_hit) break; right_child = nullptr; } } else right_child = nullptr; if (left_child) { if (right_child) { if (distance_left.first > distance_right.first) std::swap(left_child, right_child); stack.push(right_child->first_child_or_primitive); } left_child = &bvh.nodes[left_child->first_child_or_primitive]; } else if (right_child) { left_child = &bvh.nodes[right_child->first_child_or_primitive]; } else { if (stack.empty()) break; left_child = &bvh.nodes[stack.pop()]; } } return best_hit; } const Bvh& bvh; public: /// Statistics collected during traversal. struct Statistics { size_t traversal_steps = 0; size_t intersections = 0; }; SingleRayTraverser(const Bvh& bvh) : bvh(bvh) {} /// Intersects the BVH with the given ray and intersector. template bvh_always_inline std::optional traverse(const Ray& ray, PrimitiveIntersector& intersector) const { struct { struct Empty { Empty& operator ++ (int) { return *this; } Empty& operator ++ () { return *this; } Empty& operator += (size_t) { return *this; } } traversal_steps, intersections; } statistics; return intersect(ray, intersector, statistics); } /// Intersects the BVH with the given ray and intersector. /// Record statistics on the number of traversal and intersection steps. template bvh_always_inline std::optional traverse(const Ray& ray, PrimitiveIntersector& primitive_intersector, Statistics& statistics) const { return intersect(ray, primitive_intersector, statistics); } }; } // namespace bvh #endif ================================================ FILE: External/bvh/spatial_split_bvh_builder.hpp ================================================ #ifndef BVH_SPATIAL_SPLIT_BVH_BUILDER_HPP #define BVH_SPATIAL_SPLIT_BVH_BUILDER_HPP #include #include #include #include "bvh/bvh.hpp" #include "bvh/bounding_box.hpp" #include "bvh/top_down_builder.hpp" #include "bvh/sah_based_algorithm.hpp" namespace bvh { template class SpatialSplitBvhBuildTask; /// This is a top-down, spatial split BVH builder based on: /// "Spatial Splits in Bounding Volume Hierarchies", by M. Stich et al. /// Even though the object splitting strategy is a full-sweep SAH evaluation, /// this builder is not as efficient as bvh::SweepSahBuilder when spatial splits /// are disabled, because it needs to sort primitive references at every step. template class SpatialSplitBvhBuilder : public TopDownBuilder, public SahBasedAlgorithm { using Scalar = typename Bvh::ScalarType; using BuildTask = SpatialSplitBvhBuildTask; using Reference = typename BuildTask::ReferenceType; using TopDownBuilder::run_task; friend BuildTask; Bvh& bvh; public: using TopDownBuilder::max_depth; using TopDownBuilder::max_leaf_size; using SahBasedAlgorithm::traversal_cost; /// Number of spatial binning passes that are run in order to /// find a spatial split. This brings additional accuracy without /// increasing the number of bins. size_t binning_pass_count = 2; SpatialSplitBvhBuilder(Bvh& bvh) : bvh(bvh) {} size_t build( const BoundingBox& global_bbox, const Primitive* primitives, const BoundingBox* bboxes, const Vector3* centers, size_t primitive_count, Scalar alpha = Scalar(1e-5), Scalar split_factor = Scalar(0.3)) { assert(primitive_count > 0); size_t max_reference_count = primitive_count + primitive_count * split_factor; size_t reference_count = 0; bvh.nodes = std::make_unique(2 * max_reference_count + 1); bvh.primitive_indices = std::make_unique(max_reference_count); auto accumulated_bboxes = std::make_unique[]>(max_reference_count); auto reference_data = std::make_unique(max_reference_count * 3); std::array references = { reference_data.get(), reference_data.get() + max_reference_count, reference_data.get() + 2 * max_reference_count }; // Compute the spatial split threshold, as specified in the original publication auto spatial_threshold = alpha * Scalar(2) * global_bbox.half_area(); bvh.node_count = 1; bvh.nodes[0].bounding_box_proxy() = global_bbox; #pragma omp parallel { #pragma omp for for (size_t i = 0; i < primitive_count; ++i) { for (int j = 0; j < 3; ++j) { references[j][i].bbox = bboxes[i]; references[j][i].center = centers[i]; references[j][i].primitive_index = i; } } #pragma omp single { BuildTask first_task( *this, primitives, accumulated_bboxes.get(), references, reference_count, primitive_count, spatial_threshold); run_task(first_task, 0, 0, primitive_count, max_reference_count, 0, false); } } return reference_count; } }; template class SpatialSplitBvhBuildTask : public TopDownBuildTask { using Scalar = typename Bvh::ScalarType; using Builder = SpatialSplitBvhBuilder; struct WorkItem : public TopDownBuildTask::WorkItem { size_t split_end; bool is_sorted; WorkItem() = default; WorkItem( size_t node_index, size_t begin, size_t end, size_t split_end, size_t depth, bool is_sorted = false) : TopDownBuildTask::WorkItem(node_index, begin, end, depth) , split_end(split_end) , is_sorted(is_sorted) {} }; struct Reference { BoundingBox bbox; Vector3 center; size_t primitive_index; }; struct Bin { BoundingBox bbox; BoundingBox accumulated_bbox; size_t entry; size_t exit; }; struct ObjectSplit { Scalar cost; size_t index; int axis; BoundingBox left_bbox; BoundingBox right_bbox; ObjectSplit( Scalar cost = std::numeric_limits::max(), size_t index = 1, int axis = 0, const BoundingBox& left_bbox = BoundingBox::empty(), const BoundingBox& right_bbox = BoundingBox::empty()) : cost(cost), index(index), axis(axis), left_bbox(left_bbox), right_bbox(right_bbox) {} }; struct SpatialSplit { Scalar cost; Scalar position; int axis; SpatialSplit( Scalar cost = std::numeric_limits::max(), Scalar position = 0, int axis = 0) : cost(cost), position(position), axis(axis) {} }; Builder& builder; const Primitive* primitives; BoundingBox* accumulated_bboxes; std::vector reference_marks; std::array references; size_t& reference_count; size_t primitive_count; Scalar spatial_threshold; static constexpr size_t bin_count = BinCount; std::array bins; ObjectSplit find_object_split(size_t begin, size_t end, bool is_sorted) const { if (!is_sorted) { // Sort references by the projection of their centers on this axis #pragma omp taskloop if (end - begin > builder.task_spawn_threshold) grainsize(1) default(shared) for (int axis = 0; axis < 3; ++axis) { std::sort(references[axis] + begin, references[axis] + end, [&] (const Reference& a, const Reference& b) { return a.center[axis] < b.center[axis]; }); } } ObjectSplit best_split; for (int axis = 0; axis < 3; ++axis) { // Sweep from the right to the left to accumulate bounding boxes auto bbox = BoundingBox::empty(); for (size_t i = end - 1; i > begin; --i) { bbox.extend(references[axis][i].bbox); accumulated_bboxes[i] = bbox; } // Sweep from the left to the right to compute the SAH cost bbox = BoundingBox::empty(); for (size_t i = begin; i < end - 1; ++i) { bbox.extend(references[axis][i].bbox); auto cost = bbox.half_area() * (i + 1 - begin) + accumulated_bboxes[i + 1].half_area() * (end - (i + 1)); if (cost < best_split.cost) best_split = ObjectSplit(cost, i + 1, axis, bbox, accumulated_bboxes[i + 1]); } } return best_split; } std::pair allocate_children( Bvh& bvh, const WorkItem& item, size_t right_begin, size_t right_end, const BoundingBox& left_bbox, const BoundingBox& right_bbox, bool is_sorted) { auto& parent = bvh.nodes[item.node_index]; // Allocate two nodes for the children size_t first_child; #pragma omp atomic capture { first_child = bvh.node_count; bvh.node_count += 2; } auto& left = bvh.nodes[first_child + 0]; auto& right = bvh.nodes[first_child + 1]; parent.first_child_or_primitive = first_child; parent.primitive_count = 0; left.bounding_box_proxy() = left_bbox; right.bounding_box_proxy() = right_bbox; // Allocate split space for the two children based on their SAH cost. // This assumes that reference ranges look like this: // - [item.begin...right_begin[ is the range of references on the left, // - [right_begin...right_end[ is the range of references on the right, // - [right_end...item.split_end[ is the free split space assert(item.begin < right_begin && right_begin < right_end && right_end <= item.split_end); size_t remaining_split_count = item.split_end - right_end; auto left_cost = left_bbox.half_area() * (right_begin - item.begin); auto right_cost = right_bbox.half_area() * (right_end - right_begin); auto left_split_ratio = left_cost + right_cost > 0 ? left_cost / (left_cost + right_cost) : Scalar(0.5); size_t left_split_count = remaining_split_count * left_split_ratio; assert(left_split_count <= remaining_split_count); // Move references of the right child to leave some split space for the left one if (left_split_count > 0) { std::move_backward(references[0] + right_begin, references[0] + right_end, references[0] + right_end + left_split_count); std::move_backward(references[1] + right_begin, references[1] + right_end, references[1] + right_end + left_split_count); std::move_backward(references[2] + right_begin, references[2] + right_end, references[2] + right_end + left_split_count); } size_t left_end = right_begin; right_begin += left_split_count; right_end += left_split_count; assert(right_begin < item.split_end); assert(right_end <= item.split_end); return std::make_pair( WorkItem(first_child + 0, item.begin, left_end, right_begin, item.depth + 1, is_sorted), WorkItem(first_child + 1, right_begin, right_end, item.split_end, item.depth + 1, is_sorted)); } std::pair apply_object_split(Bvh& bvh, const ObjectSplit& split, const WorkItem& item) { int other_axis[2] = { (split.axis + 1) % 3, (split.axis + 2) % 3 }; reference_marks.resize(primitive_count); for (size_t i = item.begin; i < split.index; ++i) reference_marks[references[split.axis][i].primitive_index] = true; for (size_t i = split.index; i < item.end; ++i) reference_marks[references[split.axis][i].primitive_index] = false; auto partition_predicate = [&] (const Reference& reference) { return reference_marks[reference.primitive_index]; }; #pragma omp taskgroup { #pragma omp task if (item.work_size() > builder.task_spawn_threshold) default(shared) { std::stable_partition(references[other_axis[0]] + item.begin, references[other_axis[0]] + item.end, partition_predicate); } #pragma omp task if (item.work_size() > builder.task_spawn_threshold) default(shared) { std::stable_partition(references[other_axis[1]] + item.begin, references[other_axis[1]] + item.end, partition_predicate); } } return allocate_children(bvh, item, split.index, item.end, split.left_bbox, split.right_bbox, true); } std::optional> run_binning_pass(SpatialSplit& split, int axis, size_t begin, size_t end, Scalar min, Scalar max) { for (size_t i = 0; i < bin_count; ++i) { bins[i].bbox = BoundingBox::empty(); bins[i].entry = 0; bins[i].exit = 0; } // Split primitives and add the bounding box of the fragments to the bins auto bin_size = (max - min) / bin_count; auto inv_size = Scalar(1) / bin_size; for (size_t i = begin; i < end; ++i) { auto& reference = references[0][i]; auto first_bin = std::min(bin_count - 1, size_t(std::max(Scalar(0), inv_size * (reference.bbox.min[axis] - min)))); auto last_bin = std::min(bin_count - 1, size_t(std::max(Scalar(0), inv_size * (reference.bbox.max[axis] - min)))); auto current_bbox = reference.bbox; for (size_t j = first_bin; j < last_bin; ++j) { auto [left_bbox, right_bbox] = primitives[reference.primitive_index].split(axis, min + (j + 1) * bin_size); bins[j].bbox.extend(left_bbox.shrink(current_bbox)); current_bbox.shrink(right_bbox); } bins[last_bin].bbox.extend(current_bbox); bins[first_bin].entry++; bins[last_bin].exit++; } // Accumulate bounding boxes auto current_bbox = BoundingBox::empty(); for (size_t i = bin_count; i > 0; --i) bins[i - 1].accumulated_bbox = current_bbox.extend(bins[i - 1].bbox); // Sweep and compute SAH cost size_t left_count = 0, right_count = end - begin; current_bbox = BoundingBox::empty(); bool found = false; for (size_t i = 0; i < bin_count - 1; ++i) { left_count += bins[i].entry; right_count -= bins[i].exit; current_bbox.extend(bins[i].bbox); auto cost = left_count * current_bbox.half_area() + right_count * bins[i + 1].accumulated_bbox.half_area(); if (cost < split.cost) { split.cost = cost; split.axis = axis; split.position = min + (i + 1) * bin_size; found = true; } } return found ? std::make_optional(std::make_pair(split.position - bin_size, split.position + bin_size)) : std::nullopt; } SpatialSplit find_spatial_split(const BoundingBox& node_bbox, size_t begin, size_t end, size_t binning_pass_count) { SpatialSplit split; for (int axis = 0; axis < 3; ++axis) { auto min = node_bbox.min[axis]; auto max = node_bbox.max[axis]; // Run several binning passes to get the best possible split for (size_t pass = 0; pass < binning_pass_count; ++pass) { auto next_bounds = run_binning_pass(split, axis, begin, end, min, max); if (next_bounds) std::tie(min, max) = *next_bounds; else break; } } return split; } std::pair apply_spatial_split(Bvh& bvh, const SpatialSplit& split, const WorkItem& item) { size_t left_end = item.begin; size_t right_begin = item.end; size_t right_end = item.end; auto left_bbox = BoundingBox::empty(); auto right_bbox = BoundingBox::empty(); // Choosing the references that are sorted on the split axis // is more efficient than the others, since fewers swaps are // necessary for primitives that are completely contained on // one side of the partition. auto references_to_split = references[split.axis]; // Partition references such that: // - [item.begin...left_end[ is on the left, // - [left_end...right_begin[ is in between, // - [right_begin...item.end[ is on the right for (size_t i = item.begin; i < right_begin;) { auto& bbox = references_to_split[i].bbox; if (bbox.max[split.axis] <= split.position) { left_bbox.extend(bbox); std::swap(references_to_split[i++], references_to_split[left_end++]); } else if (bbox.min[split.axis] >= split.position) { right_bbox.extend(bbox); std::swap(references_to_split[i], references_to_split[--right_begin]); } else { i++; } } size_t left_count = left_end - item.begin; size_t right_count = right_end - right_begin; if ((left_count == 0 || right_count == 0) && left_end == right_begin) { // Sometimes, because of numerical imprecision, // the algorithm will report that a spatial split is // possible, but all references end up on the same side // when applying it. To counteract this, we simply put // half of the primitives of the left side in the right side // and continue as usual. if (left_count > 0) { left_end -= left_count / 2; right_begin = left_end; } else { left_end += right_count / 2; right_begin = left_end; } // Recompute the left and right bounding boxes left_bbox = BoundingBox::empty(); right_bbox = BoundingBox::empty(); for (size_t i = item.begin; i < left_end; ++i) left_bbox.extend(references_to_split[i].bbox); for (size_t i = left_end; i < item.end; ++i) right_bbox.extend(references_to_split[i].bbox); } // Handle straddling references while (left_end < right_begin) { auto reference = references_to_split[left_end]; auto [left_primitive_bbox, right_primitive_bbox] = primitives[reference.primitive_index].split(split.axis, split.position); left_primitive_bbox .shrink(reference.bbox); right_primitive_bbox.shrink(reference.bbox); // Make sure there is enough space to split that reference if (item.split_end - right_end > 0) { left_bbox .extend(left_primitive_bbox); right_bbox.extend(right_primitive_bbox); references_to_split[right_end++] = Reference { right_primitive_bbox, right_primitive_bbox.center(), reference.primitive_index }; references_to_split[left_end++] = Reference { left_primitive_bbox, left_primitive_bbox.center(), reference.primitive_index }; left_count++; right_count++; } else if (left_count < right_count) { left_bbox.extend(reference.bbox); left_end++; left_count++; } else { right_bbox.extend(reference.bbox); std::swap(references_to_split[--right_begin], references_to_split[left_end]); right_count++; } } std::copy( references_to_split + item.begin, references_to_split + right_end, references[(split.axis + 1) % 3] + item.begin); std::copy( references_to_split + item.begin, references_to_split + right_end, references[(split.axis + 2) % 3] + item.begin); assert(left_end == right_begin); assert(right_end <= item.split_end); return allocate_children(bvh, item, right_begin, right_end, left_bbox, right_bbox, false); } public: using ReferenceType = Reference; using WorkItemType = WorkItem; SpatialSplitBvhBuildTask( Builder& builder, const Primitive* primitives, BoundingBox* accumulated_bboxes, const std::array& references, size_t& reference_count, size_t primitive_count, Scalar spatial_threshold) : builder(builder) , primitives(primitives) , accumulated_bboxes(accumulated_bboxes) , references { references[0], references[1], references[2] } , reference_count(reference_count) , primitive_count(primitive_count) , spatial_threshold(spatial_threshold) {} SpatialSplitBvhBuildTask(const SpatialSplitBvhBuildTask& other) : builder(other.builder) , primitives(other.primitives) , accumulated_bboxes(other.accumulated_bboxes) , references(other.references) , reference_count(other.reference_count) , primitive_count(other.primitive_count) , spatial_threshold(other.spatial_threshold) { // Note: no need to copy reference marks as it is // the task's private data and should not be shared } std::optional> build(const WorkItem& item) { auto& bvh = builder.bvh; auto& node = bvh.nodes[item.node_index]; auto make_leaf = [&] (typename Bvh::Node& node, size_t begin, size_t end) { size_t primitive_count = end - begin; // Reserve space for the primitives size_t first_primitive; #pragma omp atomic capture { first_primitive = reference_count; reference_count += primitive_count; } // Copy the primitives indices from the references to the BVH for (size_t i = 0; i < primitive_count; ++i) bvh.primitive_indices[first_primitive + i] = references[0][begin + i].primitive_index; node.first_child_or_primitive = first_primitive; node.primitive_count = primitive_count; }; if (item.work_size() <= 1 || item.depth >= builder.max_depth) { make_leaf(node, item.begin, item.end); return std::nullopt; } ObjectSplit best_object_split = find_object_split(item.begin, item.end, item.is_sorted); // Find a spatial split when the size SpatialSplit best_spatial_split; auto overlap = BoundingBox(best_object_split.left_bbox).shrink(best_object_split.right_bbox).half_area(); if (overlap > spatial_threshold && item.split_end - item.end > 0) { auto binning_pass_count = static_cast&>(builder).binning_pass_count; best_spatial_split = find_spatial_split(node.bounding_box_proxy(), item.begin, item.end, binning_pass_count); } auto best_cost = std::min(best_spatial_split.cost, best_object_split.cost); bool use_spatial_split = best_cost < best_object_split.cost; // Make sure the cost of splitting does not exceed the cost of not splitting auto max_split_cost = node.bounding_box_proxy().half_area() * (item.work_size() - builder.traversal_cost); if (best_cost >= max_split_cost) { if (item.work_size() > builder.max_leaf_size) { // Fallback strategy: median split on the largest axis use_spatial_split = false; best_object_split.index = (item.begin + item.end) / 2; best_object_split.axis = node.bounding_box_proxy().to_bounding_box().largest_axis(); best_object_split.left_bbox = BoundingBox::empty(); best_object_split.right_bbox = BoundingBox::empty(); for (size_t i = item.begin; i < best_object_split.index; ++i) best_object_split.left_bbox.extend(references[best_object_split.axis][i].bbox); for (size_t i = best_object_split.index; i < item.end; ++i) best_object_split.right_bbox.extend(references[best_object_split.axis][i].bbox); } else { make_leaf(node, item.begin, item.end); return std::nullopt; } } // Apply the (object/spatial) split return use_spatial_split ? std::make_optional(apply_spatial_split(bvh, best_spatial_split, item)) : std::make_optional(apply_object_split(bvh, best_object_split, item)); } }; } // namespace bvh #endif ================================================ FILE: External/bvh/sphere.hpp ================================================ #ifndef BVH_SPHERE_HPP #define BVH_SPHERE_HPP #include #include "bvh/vector.hpp" #include "bvh/bounding_box.hpp" #include "bvh/ray.hpp" namespace bvh { /// Sphere primitive defined by a center and a radius. template struct Sphere { struct Intersection { Scalar t; Scalar distance() const { return t; } }; using ScalarType = Scalar; using IntersectionType = Intersection; Vector3 origin; Scalar radius; Sphere() = default; Sphere(const Vector3& origin, Scalar radius) : origin(origin), radius(radius) {} Vector3 center() const { return origin; } BoundingBox bounding_box() const { return BoundingBox(origin - Vector3(radius), origin + Vector3(radius)); } template std::optional intersect(const Ray& ray) const { auto oc = ray.origin - origin; auto a = AssumeNormalized ? Scalar(1) : dot(ray.direction, ray.direction); auto b = 2 * dot(ray.direction, oc); auto c = dot(oc, oc) - radius * radius; auto delta = b * b - 4 * a * c; if (delta >= 0) { auto inv = -Scalar(0.5) / a; auto sqrt_delta = std::sqrt(delta); auto t0 = (b + sqrt_delta) * inv; if (t0 >= ray.tmin && t0 <= ray.tmax) return std::make_optional(Intersection { t0 }); auto t1 = (b - sqrt_delta) * inv; if (t1 >= ray.tmin && t1 <= ray.tmax) return std::make_optional(Intersection { t1 }); } return std::nullopt; } }; } // namespace bvh #endif ================================================ FILE: External/bvh/sweep_sah_builder.hpp ================================================ #ifndef BVH_SWEEP_SAH_BUILDER_HPP #define BVH_SWEEP_SAH_BUILDER_HPP #include #include #include "bvh/bvh.hpp" #include "bvh/bounding_box.hpp" #include "bvh/top_down_builder.hpp" #include "bvh/sah_based_algorithm.hpp" #include "bvh/radix_sort.hpp" namespace bvh { template class SweepSahBuildTask; /// This is a top-down, full-sweep SAH-based BVH builder. Primitives are only /// sorted once, and a stable partitioning algorithm is used when splitting, /// so as to keep the relative order of primitives within each partition intact. template class SweepSahBuilder : public TopDownBuilder, public SahBasedAlgorithm { using Scalar = typename Bvh::ScalarType; using BuildTask = SweepSahBuildTask; using Key = typename SizedIntegerType::Unsigned; using Mark = typename BuildTask::MarkType; using TopDownBuilder::run_task; friend BuildTask; RadixSort<10> radix_sort; Bvh& bvh; public: using TopDownBuilder::max_depth; using TopDownBuilder::max_leaf_size; using SahBasedAlgorithm::traversal_cost; SweepSahBuilder(Bvh& bvh) : bvh(bvh) {} void build( const BoundingBox& global_bbox, const BoundingBox* bboxes, const Vector3* centers, size_t primitive_count) { assert(primitive_count > 0); // Allocate buffers bvh.nodes = std::make_unique(2 * primitive_count + 1); bvh.primitive_indices = std::make_unique(primitive_count); auto reference_data = std::make_unique(primitive_count * 3); auto cost_data = std::make_unique(primitive_count * 3); auto key_data = std::make_unique(primitive_count * 2); auto mark_data = std::make_unique(primitive_count); std::array costs = { cost_data.get(), cost_data.get() + primitive_count, cost_data.get() + 2 * primitive_count }; std::array sorted_references; size_t* unsorted_references = bvh.primitive_indices.get(); Key* sorted_keys = key_data.get(); Key* unsorted_keys = key_data.get() + primitive_count; bvh.node_count = 1; bvh.nodes[0].bounding_box_proxy() = global_bbox; #pragma omp parallel { // Sort the primitives on each axis once for (int axis = 0; axis < 3; ++axis) { #pragma omp single { sorted_references[axis] = unsorted_references; unsorted_references = reference_data.get() + axis * primitive_count; // Make sure that one array is the final array of references used by the BVH if (axis != 0 && sorted_references[axis] == bvh.primitive_indices.get()) std::swap(sorted_references[axis], unsorted_references); assert(axis < 2 || sorted_references[0] == bvh.primitive_indices.get() || sorted_references[1] == bvh.primitive_indices.get()); } #pragma omp for for (size_t i = 0; i < primitive_count; ++i) { sorted_keys[i] = radix_sort.make_key(centers[i][axis]); sorted_references[axis][i] = i; } radix_sort.sort_in_parallel( sorted_keys, unsorted_keys, sorted_references[axis], unsorted_references, primitive_count, sizeof(Scalar) * CHAR_BIT); } #pragma omp single { BuildTask first_task(*this, bboxes, centers, sorted_references, costs, mark_data.get()); run_task(first_task, 0, 0, primitive_count, 0); } } } }; template class SweepSahBuildTask : public TopDownBuildTask { using Scalar = typename Bvh::ScalarType; using Builder = SweepSahBuilder; using Mark = uint_fast8_t; using TopDownBuildTask::WorkItem; Builder& builder; const BoundingBox* bboxes; const Vector3* centers; std::array references; std::array costs; Mark* marks; std::pair find_split(int axis, size_t begin, size_t end) { auto bbox = BoundingBox::empty(); for (size_t i = end - 1; i > begin; --i) { bbox.extend(bboxes[references[axis][i]]); costs[axis][i] = bbox.half_area() * (end - i); } bbox = BoundingBox::empty(); auto best_split = std::pair(std::numeric_limits::max(), end); for (size_t i = begin; i < end - 1; ++i) { bbox.extend(bboxes[references[axis][i]]); auto cost = bbox.half_area() * (i + 1 - begin) + costs[axis][i + 1]; if (cost < best_split.first) best_split = std::make_pair(cost, i + 1); } return best_split; } public: using MarkType = Mark; using WorkItemType = WorkItem; SweepSahBuildTask( Builder& builder, const BoundingBox* bboxes, const Vector3* centers, const std::array& references, const std::array& costs, Mark* marks) : builder(builder) , bboxes(bboxes) , centers(centers) , references { references[0], references[1], references[2] } , costs { costs[0], costs[1], costs[2] } , marks(marks) {} std::optional> build(const WorkItem& item) { auto& bvh = builder.bvh; auto& node = bvh.nodes[item.node_index]; auto make_leaf = [] (typename Bvh::Node& node, size_t begin, size_t end) { node.first_child_or_primitive = begin; node.primitive_count = end - begin; }; if (item.work_size() <= 1 || item.depth >= builder.max_depth) { make_leaf(node, item.begin, item.end); return std::nullopt; } std::pair best_splits[3]; [[maybe_unused]] bool should_spawn_tasks = item.work_size() > builder.task_spawn_threshold; // Sweep primitives to find the best cost #pragma omp taskloop if (should_spawn_tasks) grainsize(1) default(shared) for (int axis = 0; axis < 3; ++axis) best_splits[axis] = find_split(axis, item.begin, item.end); int best_axis = 0; if (best_splits[0].first > best_splits[1].first) best_axis = 1; if (best_splits[best_axis].first > best_splits[2].first) best_axis = 2; auto split_index = best_splits[best_axis].second; // Make sure the cost of splitting does not exceed the cost of not splitting auto max_split_cost = node.bounding_box_proxy().half_area() * (item.work_size() - builder.traversal_cost); if (best_splits[best_axis].first >= max_split_cost) { if (item.work_size() > builder.max_leaf_size) { // Fallback strategy: median split on largest axis best_axis = node.bounding_box_proxy().to_bounding_box().largest_axis(); split_index = (item.begin + item.end) / 2; } else { make_leaf(node, item.begin, item.end); return std::nullopt; } } int other_axis[2] = { (best_axis + 1) % 3, (best_axis + 2) % 3 }; for (size_t i = item.begin; i < split_index; ++i) marks[references[best_axis][i]] = 1; for (size_t i = split_index; i < item.end; ++i) marks[references[best_axis][i]] = 0; auto partition_predicate = [&] (size_t i) { return marks[i] != 0; }; auto left_bbox = BoundingBox::empty(); auto right_bbox = BoundingBox::empty(); // Partition reference arrays and compute bounding boxes #pragma omp taskgroup { #pragma omp task if (should_spawn_tasks) default(shared) { std::stable_partition(references[other_axis[0]] + item.begin, references[other_axis[0]] + item.end, partition_predicate); } #pragma omp task if (should_spawn_tasks) default(shared) { std::stable_partition(references[other_axis[1]] + item.begin, references[other_axis[1]] + item.end, partition_predicate); } #pragma omp task if (should_spawn_tasks) default(shared) { for (size_t i = item.begin; i < split_index; ++i) left_bbox.extend(bboxes[references[best_axis][i]]); } #pragma omp task if (should_spawn_tasks) default(shared) { for (size_t i = split_index; i < item.end; ++i) right_bbox.extend(bboxes[references[best_axis][i]]); } } // Allocate space for children size_t first_child; #pragma omp atomic capture { first_child = bvh.node_count; bvh.node_count += 2; } auto& left = bvh.nodes[first_child + 0]; auto& right = bvh.nodes[first_child + 1]; node.first_child_or_primitive = first_child; node.primitive_count = 0; left.bounding_box_proxy() = left_bbox; right.bounding_box_proxy() = right_bbox; WorkItem first_item (first_child + 0, item.begin, split_index, item.depth + 1); WorkItem second_item(first_child + 1, split_index, item.end, item.depth + 1); return std::make_optional(std::make_pair(first_item, second_item)); } }; } // namespace bvh #endif ================================================ FILE: External/bvh/top_down_builder.hpp ================================================ #ifndef BVH_TOP_DOWN_BUILDER_HPP #define BVH_TOP_DOWN_BUILDER_HPP #include #include namespace bvh { /// Base class for top-down build tasks. class TopDownBuildTask { protected: struct WorkItem { size_t node_index; size_t begin; size_t end; size_t depth; WorkItem() = default; WorkItem(size_t node_index, size_t begin, size_t end, size_t depth) : node_index(node_index), begin(begin), end(end), depth(depth) {} size_t work_size() const { return end - begin; } }; }; /// Base class for top-down BVH builders. class TopDownBuilder { public: /// Threshold (number of primitives) under which the builder /// doesn't spawn any more OpenMP tasks. size_t task_spawn_threshold = 1024; /// Maximum depth of the generated tree. This can be used to make /// sure the required traversal stack size is under a given constant. size_t max_depth = 64; /// Largest permissible leaf size. The builder will attempt to split /// using a median split on the largest axis as a default strategy /// to avoid creating leaves that are larger than this threshold. size_t max_leaf_size = 16; protected: ~TopDownBuilder() {} template void run_task(BuildTask& task, Args&&... args) { using WorkItem = typename BuildTask::WorkItemType; std::stack stack; stack.emplace(std::forward(args)...); while (!stack.empty()) { auto work_item = stack.top(); assert(work_item.depth <= max_depth); stack.pop(); auto more_work = task.build(work_item); if (more_work) { if (more_work->first.work_size() > more_work->second.work_size()) std::swap(more_work->first, more_work->second); stack.push(more_work->second); auto first_item = more_work->first; if (first_item.work_size() > task_spawn_threshold) { BuildTask new_task(task); #pragma omp task firstprivate(new_task, first_item) { run_task(new_task, first_item); } } else { stack.push(first_item); } } } } }; } // namespace bvh #endif ================================================ FILE: External/bvh/triangle.hpp ================================================ #ifndef BVH_TRIANGLE_HPP #define BVH_TRIANGLE_HPP #include #include #include "bvh/utilities.hpp" #include "bvh/vector.hpp" #include "bvh/bounding_box.hpp" #include "bvh/ray.hpp" namespace bvh { /// Triangle primitive, defined by three points, and using the Moeller-Trumbore test. /// By default, the normal is left-handed, which minimizes the number of operations in /// the intersection routine. template struct Triangle { struct Intersection { Scalar t, u, v; Scalar distance() const { return t; } }; using ScalarType = Scalar; using IntersectionType = Intersection; Vector3 p0, e1, e2, n; Triangle() = default; Triangle(const Vector3& p0, const Vector3& p1, const Vector3& p2) : p0(p0), e1(p0 - p1), e2(p2 - p0) { n = LeftHandedNormal ? cross(e1, e2) : cross(e2, e1); } Vector3 p1() const { return p0 - e1; } Vector3 p2() const { return p0 + e2; } BoundingBox bounding_box() const { BoundingBox bbox(p0); bbox.extend(p1()); bbox.extend(p2()); return bbox; } Vector3 center() const { return (p0 + p1() + p2()) * (Scalar(1.0) / Scalar(3.0)); } std::pair, Vector3> edge(size_t i) const { assert(i < 3); Vector3 p[] = { p0, p1(), p2() }; return std::make_pair(p[i], p[(i + 1) % 3]); } Scalar area() const { return length(n) * Scalar(0.5); } std::pair, BoundingBox> split(size_t axis, Scalar position) const { Vector3 p[] = { p0, p1(), p2() }; auto left = BoundingBox::empty(); auto right = BoundingBox::empty(); auto split_edge = [=] (const Vector3& a, const Vector3& b) { auto t = (position - a[axis]) / (b[axis] - a[axis]); return a + t * (b - a); }; auto q0 = p[0][axis] <= position; auto q1 = p[1][axis] <= position; auto q2 = p[2][axis] <= position; if (q0) left.extend(p[0]); else right.extend(p[0]); if (q1) left.extend(p[1]); else right.extend(p[1]); if (q2) left.extend(p[2]); else right.extend(p[2]); if (q0 ^ q1) { auto m = split_edge(p[0], p[1]); left.extend(m); right.extend(m); } if (q1 ^ q2) { auto m = split_edge(p[1], p[2]); left.extend(m); right.extend(m); } if (q2 ^ q0) { auto m = split_edge(p[2], p[0]); left.extend(m); right.extend(m); } return std::make_pair(left, right); } std::optional intersect(const Ray& ray) const { auto negate_when_right_handed = [] (Scalar x) { return LeftHandedNormal ? x : -x; }; auto c = p0 - ray.origin; auto r = cross(ray.direction, c); auto inv_det = negate_when_right_handed(1.0) / dot(n, ray.direction); auto u = dot(r, e2) * inv_det; auto v = dot(r, e1) * inv_det; auto w = Scalar(1.0) - u - v; // These comparisons are designed to return false // when one of t, u, or v is a NaN if (u >= 0 && v >= 0 && w >= 0) { auto t = negate_when_right_handed(dot(n, c)) * inv_det; if (t >= ray.tmin && t <= ray.tmax) return std::make_optional(Intersection{ t, u, v }); } return std::nullopt; } }; } // namespace bvh #endif ================================================ FILE: External/bvh/utilities.hpp ================================================ #ifndef BVH_UTILITIES_HPP #define BVH_UTILITIES_HPP #include #include #include #include #include #include #include #include #include #include "bvh/bounding_box.hpp" namespace bvh { /// Safe function to reinterpret the bits of the given value as another type. template To as(From from) { static_assert(sizeof(To) == sizeof(From)); To to; std::memcpy(&to, &from, sizeof(from)); return to; } /// Equivalent to copysign(x, x * y). inline float product_sign(float x, float y) { return as(as(x) ^ (as(y) & UINT32_C(0x80000000))); } /// Equivalent to copysign(x, x * y). inline double product_sign(double x, double y) { return as(as(x) ^ (as(y) & UINT64_C(0x8000000000000000))); } #ifdef _MSC_VER #pragma fp_contract(on) #else #pragma STDC FP_CONTRACT ON #endif inline float fast_multiply_add(float x, float y, float z) { #ifdef FP_FAST_FMAF return std::fmaf(x, y, z); #else return x * y + z; #endif } inline double fast_multiply_add(double x, double y, double z) { #ifdef FP_FAST_FMA return std::fma(x, y, z); #else return x * y + z; #endif } /// Returns the mininum of two values. /// Guaranteed to return a non-NaN value if the right hand side is not a NaN. template const T& robust_min(const T& x, const T& y) { return x < y ? x : y; } /// Returns the maximum of two values. /// Guaranteed to return a non-NaN value if the right hand side is not a NaN. template const T& robust_max(const T& x, const T& y) { return x > y ? x : y; } template void atomic_max(std::atomic& x, T y) { auto z = x.load(); while (z < y && !x.compare_exchange_weak(z, y)) ; } /// Templates that contains signed and unsigned integer types of the given number of bits. template struct SizedIntegerType { static_assert(Bits <= 8); using Signed = int8_t; using Unsigned = uint8_t; }; template <> struct SizedIntegerType<64> { using Signed = int64_t; using Unsigned = uint64_t; }; template <> struct SizedIntegerType<32> { using Signed = int32_t; using Unsigned = uint32_t; }; template <> struct SizedIntegerType<16> { using Signed = int16_t; using Unsigned = uint16_t; }; /// Adds the given number of ULPs (Unit in the Last Place) to the floating-point argument. template ::value, int> = 0> T add_ulp_magnitude(T x, unsigned ulps) { using U = typename SizedIntegerType::Unsigned; return std::isfinite(x) ? as(as(x) + ulps) : x; } /// Computes the (rounded-up) compile-time log in base-2 of an unsigned integer. inline constexpr size_t round_up_log2(size_t i, size_t p = 0) { return (size_t(1) << p) >= i ? p : round_up_log2(i, p + 1); } /// Returns the number of bits that are equal to zero, /// starting from the most significant one. template ::value, int> = 0> size_t count_leading_zeros(T value) { static constexpr size_t bit_count = sizeof(T) * CHAR_BIT; size_t a = 0; size_t b = bit_count; auto all = T(-1); for (size_t i = 0; i < round_up_log2(bit_count); i++) { auto m = (a + b) / 2; auto mask = all << m; if (value & mask) a = m + 1; else b = m; } return bit_count - b; } /// Permutes primitives such that the primitive at index i is `primitives[indices[i]]`. /// Allows to remove indirections in the primitive intersectors. template std::unique_ptr permute_primitives(const Primitive* primitives, const size_t* indices, size_t primitive_count) { auto primitives_copy = std::make_unique(primitive_count); #pragma omp parallel for for (size_t i = 0; i < primitive_count; ++i) primitives_copy[i] = primitives[indices[i]]; return primitives_copy; } /// Computes the bounding box and the center of each primitive in given array. template std::pair[]>, std::unique_ptr[]>> compute_bounding_boxes_and_centers(const Primitive* primitives, size_t primitive_count) { auto bounding_boxes = std::make_unique[]>(primitive_count); auto centers = std::make_unique[]>(primitive_count); #pragma omp parallel for for (size_t i = 0; i < primitive_count; ++i) { bounding_boxes[i] = primitives[i].bounding_box(); centers[i] = primitives[i].center(); } return std::make_pair(std::move(bounding_boxes), std::move(centers)); } /// Computes the union of all the bounding boxes in the given array. template BoundingBox compute_bounding_boxes_union(const BoundingBox* bboxes, size_t count) { auto bbox = BoundingBox::empty(); #pragma omp declare reduction \ (bbox_extend:BoundingBox:omp_out.extend(omp_in)) \ initializer(omp_priv = BoundingBox::empty()) #pragma omp parallel for reduction(bbox_extend: bbox) for (size_t i = 0; i < count; ++i) bbox.extend(bboxes[i]); return bbox; } } // namespace bvh #endif ================================================ FILE: External/bvh/vector.hpp ================================================ #ifndef BVH_VECTOR_HPP #define BVH_VECTOR_HPP #include #include #include #include #include #include #include "bvh/platform.hpp" namespace bvh { template struct Vector; /// Helper class to set the elements of a vector. template struct VectorSetter { template bvh_always_inline static void set(Vector& v, Scalar s, Args... args) { v[I] = s; VectorSetter::set(v, args...); } }; template struct VectorSetter { bvh_always_inline static void set(Vector&) {} }; /// An N-dimensional vector class. template struct Vector { Scalar values[N]; Vector() = default; bvh_always_inline explicit Vector(Scalar s) { std::fill(values, values + N, s); } template N), int> = 0> bvh_always_inline explicit Vector(const Vector& other) { std::copy(other.values, other.values + N, values); } template bvh_always_inline Vector(Scalar first, Scalar second, Args... args) { set(first, second, args...); } template ::value, int> = 0> bvh_always_inline Vector(F f) { for (size_t i = 0; i < N; ++i) values[i] = f(i); } template bvh_always_inline void set(Args... args) { VectorSetter<0, Scalar, N>::set(*this, Scalar(args)...); } bvh_always_inline Vector operator - () const { return Vector([this] (size_t i) { return -values[i]; }); } bvh_always_inline Vector inverse() const { return Vector([this] (size_t i) { return Scalar(1) / values[i]; }); } bvh_always_inline Vector safe_inverse() const { static constexpr auto threshold = std::numeric_limits::epsilon(); return Vector([&] (size_t i) { return Scalar(1) / (std::fabs(values[i]) < threshold ? std::copysign(threshold, values[i]) : values[i]); }); } bvh_always_inline Vector& operator += (const Vector& other) { return *this = *this + other; } bvh_always_inline Vector& operator -= (const Vector& other) { return *this = *this - other; } bvh_always_inline Vector& operator *= (const Vector& other) { return *this = *this * other; } bvh_always_inline Scalar& operator [] (size_t i) { return values[i]; } bvh_always_inline Scalar operator [] (size_t i) const { return values[i]; } }; template bvh_always_inline inline Vector operator + (const Vector& a, const Vector& b) { return Vector([=] (size_t i) { return a[i] + b[i]; }); } template bvh_always_inline inline Vector operator - (const Vector& a, const Vector& b) { return Vector([=] (size_t i) { return a[i] - b[i]; }); } template bvh_always_inline inline Vector operator * (const Vector& a, const Vector& b) { return Vector([=] (size_t i) { return a[i] * b[i]; }); } template bvh_always_inline inline Vector min(const Vector& a, const Vector& b) { return Vector([=] (size_t i) { return std::min(a[i], b[i]); }); } template bvh_always_inline inline Vector max(const Vector& a, const Vector& b) { return Vector([=] (size_t i) { return std::max(a[i], b[i]); }); } template bvh_always_inline inline Vector operator * (const Vector& a, Scalar s) { return Vector([=] (size_t i) { return a[i] * s; }); } template bvh_always_inline inline Vector operator * (Scalar s, const Vector& b) { return b * s; } template bvh_always_inline inline Scalar dot(const Vector& a, const Vector& b) { Scalar sum = a[0] * b[0]; for (size_t i = 1; i < N; ++i) sum += a[i] * b[i]; return sum; } template bvh_always_inline inline Scalar length(const Vector& v) { return std::sqrt(dot(v, v)); } template bvh_always_inline inline Vector normalize(const Vector& v) { auto inv = Scalar(1) / length(v); return v * inv; } template using Vector3 = Vector; template bvh_always_inline inline Vector3 cross(const Vector3& a, const Vector3& b) { return Vector3([=] (size_t i) { size_t j = (i + 1) % 3; size_t k = (i + 2) % 3; return a[j] * b[k] - a[k] * b[j]; }); } } // namespace bvh #endif ================================================ FILE: External/stb/stb_rect_pack.h ================================================ // stb_rect_pack.h - v1.01 - public domain - rectangle packing // Sean Barrett 2014 // // Useful for e.g. packing rectangular textures into an atlas. // Does not do rotation. // // Before #including, // // #define STB_RECT_PACK_IMPLEMENTATION // // in the file that you want to have the implementation. // // Not necessarily the awesomest packing method, but better than // the totally naive one in stb_truetype (which is primarily what // this is meant to replace). // // Has only had a few tests run, may have issues. // // More docs to come. // // No memory allocations; uses qsort() and assert() from stdlib. // Can override those by defining STBRP_SORT and STBRP_ASSERT. // // This library currently uses the Skyline Bottom-Left algorithm. // // Please note: better rectangle packers are welcome! Please // implement them to the same API, but with a different init // function. // // Credits // // Library // Sean Barrett // Minor features // Martins Mozeiko // github:IntellectualKitty // // Bugfixes / warning fixes // Jeremy Jaussaud // Fabian Giesen // // Version history: // // 1.01 (2021-07-11) always use large rect mode, expose STBRP__MAXVAL in public section // 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles // 0.99 (2019-02-07) warning fixes // 0.11 (2017-03-03) return packing success/fail result // 0.10 (2016-10-25) remove cast-away-const to avoid warnings // 0.09 (2016-08-27) fix compiler warnings // 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0) // 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0) // 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort // 0.05: added STBRP_ASSERT to allow replacing assert // 0.04: fixed minor bug in STBRP_LARGE_RECTS support // 0.01: initial release // // LICENSE // // See end of file for license information. ////////////////////////////////////////////////////////////////////////////// // // INCLUDE SECTION // #ifndef STB_INCLUDE_STB_RECT_PACK_H #define STB_INCLUDE_STB_RECT_PACK_H #define STB_RECT_PACK_VERSION 1 #ifdef STBRP_STATIC #define STBRP_DEF static #else #define STBRP_DEF extern #endif #ifdef __cplusplus extern "C" { #endif typedef struct stbrp_context stbrp_context; typedef struct stbrp_node stbrp_node; typedef struct stbrp_rect stbrp_rect; typedef int stbrp_coord; #define STBRP__MAXVAL 0x7fffffff // Mostly for internal use, but this is the maximum supported coordinate value. STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects); // Assign packed locations to rectangles. The rectangles are of type // 'stbrp_rect' defined below, stored in the array 'rects', and there // are 'num_rects' many of them. // // Rectangles which are successfully packed have the 'was_packed' flag // set to a non-zero value and 'x' and 'y' store the minimum location // on each axis (i.e. bottom-left in cartesian coordinates, top-left // if you imagine y increasing downwards). Rectangles which do not fit // have the 'was_packed' flag set to 0. // // You should not try to access the 'rects' array from another thread // while this function is running, as the function temporarily reorders // the array while it executes. // // To pack into another rectangle, you need to call stbrp_init_target // again. To continue packing into the same rectangle, you can call // this function again. Calling this multiple times with multiple rect // arrays will probably produce worse packing results than calling it // a single time with the full rectangle array, but the option is // available. // // The function returns 1 if all of the rectangles were successfully // packed and 0 otherwise. struct stbrp_rect { // reserved for your use: int id; // input: stbrp_coord w, h; // output: stbrp_coord x, y; int was_packed; // non-zero if valid packing }; // 16 bytes, nominally STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes); // Initialize a rectangle packer to: // pack a rectangle that is 'width' by 'height' in dimensions // using temporary storage provided by the array 'nodes', which is 'num_nodes' long // // You must call this function every time you start packing into a new target. // // There is no "shutdown" function. The 'nodes' memory must stay valid for // the following stbrp_pack_rects() call (or calls), but can be freed after // the call (or calls) finish. // // Note: to guarantee best results, either: // 1. make sure 'num_nodes' >= 'width' // or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' // // If you don't do either of the above things, widths will be quantized to multiples // of small integers to guarantee the algorithm doesn't run out of temporary storage. // // If you do #2, then the non-quantized algorithm will be used, but the algorithm // may run out of temporary storage and be unable to pack some rectangles. STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem); // Optionally call this function after init but before doing any packing to // change the handling of the out-of-temp-memory scenario, described above. // If you call init again, this will be reset to the default (false). STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic); // Optionally select which packing heuristic the library should use. Different // heuristics will produce better/worse results for different data sets. // If you call init again, this will be reset to the default. enum { STBRP_HEURISTIC_Skyline_default=0, STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, STBRP_HEURISTIC_Skyline_BF_sortHeight }; ////////////////////////////////////////////////////////////////////////////// // // the details of the following structures don't matter to you, but they must // be visible so you can handle the memory allocations for them struct stbrp_node { stbrp_coord x,y; stbrp_node *next; }; struct stbrp_context { int width; int height; int align; int init_mode; int heuristic; int num_nodes; stbrp_node *active_head; stbrp_node *free_head; stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' }; #ifdef __cplusplus } #endif #endif ////////////////////////////////////////////////////////////////////////////// // // IMPLEMENTATION SECTION // #ifdef STB_RECT_PACK_IMPLEMENTATION #ifndef STBRP_SORT #include #define STBRP_SORT qsort #endif #ifndef STBRP_ASSERT #include #define STBRP_ASSERT assert #endif #ifdef _MSC_VER #define STBRP__NOTUSED(v) (void)(v) #define STBRP__CDECL __cdecl #else #define STBRP__NOTUSED(v) (void)sizeof(v) #define STBRP__CDECL #endif enum { STBRP__INIT_skyline = 1 }; STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) { switch (context->init_mode) { case STBRP__INIT_skyline: STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); context->heuristic = heuristic; break; default: STBRP_ASSERT(0); } } STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem) { if (allow_out_of_mem) // if it's ok to run out of memory, then don't bother aligning them; // this gives better packing, but may fail due to OOM (even though // the rectangles easily fit). @TODO a smarter approach would be to only // quantize once we've hit OOM, then we could get rid of this parameter. context->align = 1; else { // if it's not ok to run out of memory, then quantize the widths // so that num_nodes is always enough nodes. // // I.e. num_nodes * align >= width // align >= width / num_nodes // align = ceil(width/num_nodes) context->align = (context->width + context->num_nodes-1) / context->num_nodes; } } STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes) { int i; for (i=0; i < num_nodes-1; ++i) nodes[i].next = &nodes[i+1]; nodes[i].next = NULL; context->init_mode = STBRP__INIT_skyline; context->heuristic = STBRP_HEURISTIC_Skyline_default; context->free_head = &nodes[0]; context->active_head = &context->extra[0]; context->width = width; context->height = height; context->num_nodes = num_nodes; stbrp_setup_allow_out_of_mem(context, 0); // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) context->extra[0].x = 0; context->extra[0].y = 0; context->extra[0].next = &context->extra[1]; context->extra[1].x = (stbrp_coord) width; context->extra[1].y = (1<<30); context->extra[1].next = NULL; } // find minimum y position if it starts at x1 static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) { stbrp_node *node = first; int x1 = x0 + width; int min_y, visited_width, waste_area; STBRP__NOTUSED(c); STBRP_ASSERT(first->x <= x0); #if 0 // skip in case we're past the node while (node->next->x <= x0) ++node; #else STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency #endif STBRP_ASSERT(node->x <= x0); min_y = 0; waste_area = 0; visited_width = 0; while (node->x < x1) { if (node->y > min_y) { // raise min_y higher. // we've accounted for all waste up to min_y, // but we'll now add more waste for everything we've visted waste_area += visited_width * (node->y - min_y); min_y = node->y; // the first time through, visited_width might be reduced if (node->x < x0) visited_width += node->next->x - x0; else visited_width += node->next->x - node->x; } else { // add waste area int under_width = node->next->x - node->x; if (under_width + visited_width > width) under_width = width - visited_width; waste_area += under_width * (min_y - node->y); visited_width += under_width; } node = node->next; } *pwaste = waste_area; return min_y; } typedef struct { int x,y; stbrp_node **prev_link; } stbrp__findresult; static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height) { int best_waste = (1<<30), best_x, best_y = (1 << 30); stbrp__findresult fr; stbrp_node **prev, *node, *tail, **best = NULL; // align to multiple of c->align width = (width + c->align - 1); width -= width % c->align; STBRP_ASSERT(width % c->align == 0); // if it can't possibly fit, bail immediately if (width > c->width || height > c->height) { fr.prev_link = NULL; fr.x = fr.y = 0; return fr; } node = c->active_head; prev = &c->active_head; while (node->x + width <= c->width) { int y,waste; y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL // bottom left if (y < best_y) { best_y = y; best = prev; } } else { // best-fit if (y + height <= c->height) { // can only use it if it first vertically if (y < best_y || (y == best_y && waste < best_waste)) { best_y = y; best_waste = waste; best = prev; } } } prev = &node->next; node = node->next; } best_x = (best == NULL) ? 0 : (*best)->x; // if doing best-fit (BF), we also have to try aligning right edge to each node position // // e.g, if fitting // // ____________________ // |____________________| // // into // // | | // | ____________| // |____________| // // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned // // This makes BF take about 2x the time if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { tail = c->active_head; node = c->active_head; prev = &c->active_head; // find first node that's admissible while (tail->x < width) tail = tail->next; while (tail) { int xpos = tail->x - width; int y,waste; STBRP_ASSERT(xpos >= 0); // find the left position that matches this while (node->next->x <= xpos) { prev = &node->next; node = node->next; } STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); if (y + height <= c->height) { if (y <= best_y) { if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) { best_x = xpos; STBRP_ASSERT(y <= best_y); best_y = y; best_waste = waste; best = prev; } } } tail = tail->next; } } fr.prev_link = best; fr.x = best_x; fr.y = best_y; return fr; } static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height) { // find best position according to heuristic stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); stbrp_node *node, *cur; // bail if: // 1. it failed // 2. the best node doesn't fit (we don't always check this) // 3. we're out of memory if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { res.prev_link = NULL; return res; } // on success, create new node node = context->free_head; node->x = (stbrp_coord) res.x; node->y = (stbrp_coord) (res.y + height); context->free_head = node->next; // insert the new node into the right starting point, and // let 'cur' point to the remaining nodes needing to be // stiched back in cur = *res.prev_link; if (cur->x < res.x) { // preserve the existing one, so start testing with the next one stbrp_node *next = cur->next; cur->next = node; cur = next; } else { *res.prev_link = node; } // from here, traverse cur and free the nodes, until we get to one // that shouldn't be freed while (cur->next && cur->next->x <= res.x + width) { stbrp_node *next = cur->next; // move the current node to the free list cur->next = context->free_head; context->free_head = cur; cur = next; } // stitch the list back in node->next = cur; if (cur->x < res.x + width) cur->x = (stbrp_coord) (res.x + width); #ifdef _DEBUG cur = context->active_head; while (cur->x < context->width) { STBRP_ASSERT(cur->x < cur->next->x); cur = cur->next; } STBRP_ASSERT(cur->next == NULL); { int count=0; cur = context->active_head; while (cur) { cur = cur->next; ++count; } cur = context->free_head; while (cur) { cur = cur->next; ++count; } STBRP_ASSERT(count == context->num_nodes+2); } #endif return res; } static int STBRP__CDECL rect_height_compare(const void *a, const void *b) { const stbrp_rect *p = (const stbrp_rect *) a; const stbrp_rect *q = (const stbrp_rect *) b; if (p->h > q->h) return -1; if (p->h < q->h) return 1; return (p->w > q->w) ? -1 : (p->w < q->w); } static int STBRP__CDECL rect_original_order(const void *a, const void *b) { const stbrp_rect *p = (const stbrp_rect *) a; const stbrp_rect *q = (const stbrp_rect *) b; return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); } STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects) { int i, all_rects_packed = 1; // we use the 'was_packed' field internally to allow sorting/unsorting for (i=0; i < num_rects; ++i) { rects[i].was_packed = i; } // sort according to heuristic STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); for (i=0; i < num_rects; ++i) { if (rects[i].w == 0 || rects[i].h == 0) { rects[i].x = rects[i].y = 0; // empty rect needs no space } else { stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); if (fr.prev_link) { rects[i].x = (stbrp_coord) fr.x; rects[i].y = (stbrp_coord) fr.y; } else { rects[i].x = rects[i].y = STBRP__MAXVAL; } } } // unsort STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); // set was_packed flags and all_rects_packed status for (i=0; i < num_rects; ++i) { rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); if (!rects[i].was_packed) all_rects_packed = 0; } // return the all_rects_packed status return all_rects_packed; } #endif /* ------------------------------------------------------------------------------ This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------ ALTERNATIVE A - MIT License Copyright (c) 2017 Sean Barrett Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ ALTERNATIVE B - Public Domain (www.unlicense.org) This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ */ ================================================ FILE: External/tsl/bhopscotch_map.h ================================================ /** * MIT License * * Copyright (c) 2017 Thibaut Goetghebuer-Planchon * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #ifndef TSL_BHOPSCOTCH_MAP_H #define TSL_BHOPSCOTCH_MAP_H #include #include #include #include #include #include #include #include #include "hopscotch_hash.h" namespace tsl { /** * Similar to tsl::hopscotch_map but instead of using a list for overflowing elements it uses * a binary search tree. It thus needs an additional template parameter Compare. Compare should * be arithmetically coherent with KeyEqual. * * The binary search tree allows the map to have a worst-case scenario of O(log n) for search * and delete, even if the hash function maps all the elements to the same bucket. * For insert, the amortized worst case is O(log n), but the worst case is O(n) in case of rehash. * * This makes the map resistant to DoS attacks (but doesn't preclude you to have a good hash function, * as an element in the bucket array is faster to retrieve than in the tree). * * @copydoc hopscotch_map */ template, class KeyEqual = std::equal_to, class Compare = std::less, class Allocator = std::allocator>, unsigned int NeighborhoodSize = 62, bool StoreHash = false, class GrowthPolicy = tsl::hh::power_of_two_growth_policy<2>> class bhopscotch_map { private: template using has_is_transparent = tsl::detail_hopscotch_hash::has_is_transparent; class KeySelect { public: using key_type = Key; const key_type& operator()(const std::pair& key_value) const { return key_value.first; } const key_type& operator()(std::pair& key_value) { return key_value.first; } }; class ValueSelect { public: using value_type = T; const value_type& operator()(const std::pair& key_value) const { return key_value.second; } value_type& operator()(std::pair& key_value) { return key_value.second; } }; // TODO Not optimal as we have to use std::pair as ValueType which forbid // us to move the key in the bucket array, we have to use copy. Optimize. using overflow_container_type = std::map; using ht = detail_hopscotch_hash::hopscotch_hash, KeySelect, ValueSelect, Hash, KeyEqual, Allocator, NeighborhoodSize, StoreHash, GrowthPolicy, overflow_container_type>; public: using key_type = typename ht::key_type; using mapped_type = T; using value_type = typename ht::value_type; using size_type = typename ht::size_type; using difference_type = typename ht::difference_type; using hasher = typename ht::hasher; using key_equal = typename ht::key_equal; using key_compare = Compare; using allocator_type = typename ht::allocator_type; using reference = typename ht::reference; using const_reference = typename ht::const_reference; using pointer = typename ht::pointer; using const_pointer = typename ht::const_pointer; using iterator = typename ht::iterator; using const_iterator = typename ht::const_iterator; /* * Constructors */ bhopscotch_map() : bhopscotch_map(ht::DEFAULT_INIT_BUCKETS_SIZE) { } explicit bhopscotch_map(size_type bucket_count, const Hash& hash = Hash(), const KeyEqual& equal = KeyEqual(), const Allocator& alloc = Allocator(), const Compare& comp = Compare()) : m_ht(bucket_count, hash, equal, alloc, ht::DEFAULT_MAX_LOAD_FACTOR, comp) { } bhopscotch_map(size_type bucket_count, const Allocator& alloc) : bhopscotch_map(bucket_count, Hash(), KeyEqual(), alloc) { } bhopscotch_map(size_type bucket_count, const Hash& hash, const Allocator& alloc) : bhopscotch_map(bucket_count, hash, KeyEqual(), alloc) { } explicit bhopscotch_map(const Allocator& alloc) : bhopscotch_map(ht::DEFAULT_INIT_BUCKETS_SIZE, alloc) { } template bhopscotch_map(InputIt first, InputIt last, size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, const Hash& hash = Hash(), const KeyEqual& equal = KeyEqual(), const Allocator& alloc = Allocator()) : bhopscotch_map(bucket_count, hash, equal, alloc) { insert(first, last); } template bhopscotch_map(InputIt first, InputIt last, size_type bucket_count, const Allocator& alloc) : bhopscotch_map(first, last, bucket_count, Hash(), KeyEqual(), alloc) { } template bhopscotch_map(InputIt first, InputIt last, size_type bucket_count, const Hash& hash, const Allocator& alloc) : bhopscotch_map(first, last, bucket_count, hash, KeyEqual(), alloc) { } bhopscotch_map(std::initializer_list init, size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, const Hash& hash = Hash(), const KeyEqual& equal = KeyEqual(), const Allocator& alloc = Allocator()) : bhopscotch_map(init.begin(), init.end(), bucket_count, hash, equal, alloc) { } bhopscotch_map(std::initializer_list init, size_type bucket_count, const Allocator& alloc) : bhopscotch_map(init.begin(), init.end(), bucket_count, Hash(), KeyEqual(), alloc) { } bhopscotch_map(std::initializer_list init, size_type bucket_count, const Hash& hash, const Allocator& alloc) : bhopscotch_map(init.begin(), init.end(), bucket_count, hash, KeyEqual(), alloc) { } bhopscotch_map& operator=(std::initializer_list ilist) { m_ht.clear(); m_ht.reserve(ilist.size()); m_ht.insert(ilist.begin(), ilist.end()); return *this; } allocator_type get_allocator() const { return m_ht.get_allocator(); } /* * Iterators */ iterator begin() noexcept { return m_ht.begin(); } const_iterator begin() const noexcept { return m_ht.begin(); } const_iterator cbegin() const noexcept { return m_ht.cbegin(); } iterator end() noexcept { return m_ht.end(); } const_iterator end() const noexcept { return m_ht.end(); } const_iterator cend() const noexcept { return m_ht.cend(); } /* * Capacity */ bool empty() const noexcept { return m_ht.empty(); } size_type size() const noexcept { return m_ht.size(); } size_type max_size() const noexcept { return m_ht.max_size(); } /* * Modifiers */ void clear() noexcept { m_ht.clear(); } std::pair insert(const value_type& value) { return m_ht.insert(value); } template::value>::type* = nullptr> std::pair insert(P&& value) { return m_ht.insert(std::forward

(value)); } std::pair insert(value_type&& value) { return m_ht.insert(std::move(value)); } iterator insert(const_iterator hint, const value_type& value) { return m_ht.insert(hint, value); } template::value>::type* = nullptr> iterator insert(const_iterator hint, P&& value) { return m_ht.insert(hint, std::forward

(value)); } iterator insert(const_iterator hint, value_type&& value) { return m_ht.insert(hint, std::move(value)); } template void insert(InputIt first, InputIt last) { m_ht.insert(first, last); } void insert(std::initializer_list ilist) { m_ht.insert(ilist.begin(), ilist.end()); } template std::pair insert_or_assign(const key_type& k, M&& obj) { return m_ht.insert_or_assign(k, std::forward(obj)); } template std::pair insert_or_assign(key_type&& k, M&& obj) { return m_ht.insert_or_assign(std::move(k), std::forward(obj)); } template iterator insert_or_assign(const_iterator hint, const key_type& k, M&& obj) { return m_ht.insert_or_assign(hint, k, std::forward(obj)); } template iterator insert_or_assign(const_iterator hint, key_type&& k, M&& obj) { return m_ht.insert_or_assign(hint, std::move(k), std::forward(obj)); } /** * Due to the way elements are stored, emplace will need to move or copy the key-value once. * The method is equivalent to insert(value_type(std::forward(args)...)); * * Mainly here for compatibility with the std::unordered_map interface. */ template std::pair emplace(Args&&... args) { return m_ht.emplace(std::forward(args)...); } /** * Due to the way elements are stored, emplace_hint will need to move or copy the key-value once. * The method is equivalent to insert(hint, value_type(std::forward(args)...)); * * Mainly here for compatibility with the std::unordered_map interface. */ template iterator emplace_hint(const_iterator hint, Args&&... args) { return m_ht.emplace_hint(hint, std::forward(args)...); } template std::pair try_emplace(const key_type& k, Args&&... args) { return m_ht.try_emplace(k, std::forward(args)...); } template std::pair try_emplace(key_type&& k, Args&&... args) { return m_ht.try_emplace(std::move(k), std::forward(args)...); } template iterator try_emplace(const_iterator hint, const key_type& k, Args&&... args) { return m_ht.try_emplace(hint, k, std::forward(args)...); } template iterator try_emplace(const_iterator hint, key_type&& k, Args&&... args) { return m_ht.try_emplace(hint, std::move(k), std::forward(args)...); } iterator erase(iterator pos) { return m_ht.erase(pos); } iterator erase(const_iterator pos) { return m_ht.erase(pos); } iterator erase(const_iterator first, const_iterator last) { return m_ht.erase(first, last); } size_type erase(const key_type& key) { return m_ht.erase(key); } /** * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup to the value if you already have the hash. */ size_type erase(const key_type& key, std::size_t precalculated_hash) { return m_ht.erase(key, precalculated_hash); } /** * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent * and Compare::is_transparent exist. * If so, K must be hashable and comparable to Key. */ template::value && has_is_transparent::value>::type* = nullptr> size_type erase(const K& key) { return m_ht.erase(key); } /** * @copydoc erase(const K& key) * * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup to the value if you already have the hash. */ template::value && has_is_transparent::value>::type* = nullptr> size_type erase(const K& key, std::size_t precalculated_hash) { return m_ht.erase(key, precalculated_hash); } void swap(bhopscotch_map& other) { other.m_ht.swap(m_ht); } /* * Lookup */ T& at(const Key& key) { return m_ht.at(key); } /** * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ T& at(const Key& key, std::size_t precalculated_hash) { return m_ht.at(key, precalculated_hash); } const T& at(const Key& key) const { return m_ht.at(key); } /** * @copydoc at(const Key& key, std::size_t precalculated_hash) */ const T& at(const Key& key, std::size_t precalculated_hash) const { return m_ht.at(key, precalculated_hash); } /** * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent * and Compare::is_transparent exist. * If so, K must be hashable and comparable to Key. */ template::value && has_is_transparent::value>::type* = nullptr> T& at(const K& key) { return m_ht.at(key); } /** * @copydoc at(const K& key) * * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ template::value && has_is_transparent::value>::type* = nullptr> T& at(const K& key, std::size_t precalculated_hash) { return m_ht.at(key, precalculated_hash); } /** * @copydoc at(const K& key) */ template::value && has_is_transparent::value>::type* = nullptr> const T& at(const K& key) const { return m_ht.at(key); } /** * @copydoc at(const K& key, std::size_t precalculated_hash) */ template::value && has_is_transparent::value>::type* = nullptr> const T& at(const K& key, std::size_t precalculated_hash) const { return m_ht.at(key, precalculated_hash); } T& operator[](const Key& key) { return m_ht[key]; } T& operator[](Key&& key) { return m_ht[std::move(key)]; } size_type count(const Key& key) const { return m_ht.count(key); } /** * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ size_type count(const Key& key, std::size_t precalculated_hash) const { return m_ht.count(key, precalculated_hash); } /** * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent * and Compare::is_transparent exist. * If so, K must be hashable and comparable to Key. */ template::value && has_is_transparent::value>::type* = nullptr> size_type count(const K& key) const { return m_ht.count(key); } /** * @copydoc count(const K& key) const * * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ template::value && has_is_transparent::value>::type* = nullptr> size_type count(const K& key, std::size_t precalculated_hash) const { return m_ht.count(key, precalculated_hash); } iterator find(const Key& key) { return m_ht.find(key); } /** * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ iterator find(const Key& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } const_iterator find(const Key& key) const { return m_ht.find(key); } /** * @copydoc find(const Key& key, std::size_t precalculated_hash) */ const_iterator find(const Key& key, std::size_t precalculated_hash) const { return m_ht.find(key, precalculated_hash); } /** * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent * and Compare::is_transparent exist. * If so, K must be hashable and comparable to Key. */ template::value && has_is_transparent::value>::type* = nullptr> iterator find(const K& key) { return m_ht.find(key); } /** * @copydoc find(const K& key) * * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ template::value && has_is_transparent::value>::type* = nullptr> iterator find(const K& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } /** * @copydoc find(const K& key) */ template::value && has_is_transparent::value>::type* = nullptr> const_iterator find(const K& key) const { return m_ht.find(key); } /** * @copydoc find(const K& key, std::size_t precalculated_hash) */ template::value && has_is_transparent::value>::type* = nullptr> const_iterator find(const K& key, std::size_t precalculated_hash) const { return m_ht.find(key, precalculated_hash); } bool contains(const Key& key) const { return m_ht.contains(key); } /** * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ bool contains(const Key& key, std::size_t precalculated_hash) const { return m_ht.contains(key, precalculated_hash); } /** * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. * If so, K must be hashable and comparable to Key. */ template::value>::type* = nullptr> bool contains(const K& key) const { return m_ht.contains(key); } /** * @copydoc contains(const K& key) const * * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ template::value>::type* = nullptr> bool contains(const K& key, std::size_t precalculated_hash) const { return m_ht.contains(key, precalculated_hash); } std::pair equal_range(const Key& key) { return m_ht.equal_range(key); } /** * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ std::pair equal_range(const Key& key, std::size_t precalculated_hash) { return m_ht.equal_range(key, precalculated_hash); } std::pair equal_range(const Key& key) const { return m_ht.equal_range(key); } /** * @copydoc equal_range(const Key& key, std::size_t precalculated_hash) */ std::pair equal_range(const Key& key, std::size_t precalculated_hash) const { return m_ht.equal_range(key, precalculated_hash); } /** * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent * and Compare::is_transparent exist. * If so, K must be hashable and comparable to Key. */ template::value && has_is_transparent::value>::type* = nullptr> std::pair equal_range(const K& key) { return m_ht.equal_range(key); } /** * @copydoc equal_range(const K& key) * * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ template::value && has_is_transparent::value>::type* = nullptr> std::pair equal_range(const K& key, std::size_t precalculated_hash) { return m_ht.equal_range(key, precalculated_hash); } /** * @copydoc equal_range(const K& key) */ template::value && has_is_transparent::value>::type* = nullptr> std::pair equal_range(const K& key) const { return m_ht.equal_range(key); } /** * @copydoc equal_range(const K& key, std::size_t precalculated_hash) */ template::value && has_is_transparent::value>::type* = nullptr> std::pair equal_range(const K& key, std::size_t precalculated_hash) const { return m_ht.equal_range(key, precalculated_hash); } /* * Bucket interface */ size_type bucket_count() const { return m_ht.bucket_count(); } size_type max_bucket_count() const { return m_ht.max_bucket_count(); } /* * Hash policy */ float load_factor() const { return m_ht.load_factor(); } float max_load_factor() const { return m_ht.max_load_factor(); } void max_load_factor(float ml) { m_ht.max_load_factor(ml); } void rehash(size_type count_) { m_ht.rehash(count_); } void reserve(size_type count_) { m_ht.reserve(count_); } /* * Observers */ hasher hash_function() const { return m_ht.hash_function(); } key_equal key_eq() const { return m_ht.key_eq(); } key_compare key_comp() const { return m_ht.key_comp(); } /* * Other */ /** * Convert a const_iterator to an iterator. */ iterator mutable_iterator(const_iterator pos) { return m_ht.mutable_iterator(pos); } size_type overflow_size() const noexcept { return m_ht.overflow_size(); } friend bool operator==(const bhopscotch_map& lhs, const bhopscotch_map& rhs) { if(lhs.size() != rhs.size()) { return false; } for(const auto& element_lhs : lhs) { const auto it_element_rhs = rhs.find(element_lhs.first); if(it_element_rhs == rhs.cend() || element_lhs.second != it_element_rhs->second) { return false; } } return true; } friend bool operator!=(const bhopscotch_map& lhs, const bhopscotch_map& rhs) { return !operator==(lhs, rhs); } friend void swap(bhopscotch_map& lhs, bhopscotch_map& rhs) { lhs.swap(rhs); } private: ht m_ht; }; /** * Same as `tsl::bhopscotch_map`. */ template, class KeyEqual = std::equal_to, class Compare = std::less, class Allocator = std::allocator>, unsigned int NeighborhoodSize = 62, bool StoreHash = false> using bhopscotch_pg_map = bhopscotch_map; } // end namespace tsl #endif ================================================ FILE: External/tsl/bhopscotch_set.h ================================================ /** * MIT License * * Copyright (c) 2017 Thibaut Goetghebuer-Planchon * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #ifndef TSL_BHOPSCOTCH_SET_H #define TSL_BHOPSCOTCH_SET_H #include #include #include #include #include #include #include #include #include "hopscotch_hash.h" namespace tsl { /** * Similar to tsl::hopscotch_set but instead of using a list for overflowing elements it uses * a binary search tree. It thus needs an additional template parameter Compare. Compare should * be arithmetically coherent with KeyEqual. * * The binary search tree allows the set to have a worst-case scenario of O(log n) for search * and delete, even if the hash function maps all the elements to the same bucket. * For insert, the amortized worst case is O(log n), but the worst case is O(n) in case of rehash. * * This makes the set resistant to DoS attacks (but doesn't preclude you to have a good hash function, * as an element in the bucket array is faster to retrieve than in the tree). * * @copydoc hopscotch_set */ template, class KeyEqual = std::equal_to, class Compare = std::less, class Allocator = std::allocator, unsigned int NeighborhoodSize = 62, bool StoreHash = false, class GrowthPolicy = tsl::hh::power_of_two_growth_policy<2>> class bhopscotch_set { private: template using has_is_transparent = tsl::detail_hopscotch_hash::has_is_transparent; class KeySelect { public: using key_type = Key; const key_type& operator()(const Key& key) const { return key; } key_type& operator()(Key& key) { return key; } }; using overflow_container_type = std::set; using ht = tsl::detail_hopscotch_hash::hopscotch_hash; public: using key_type = typename ht::key_type; using value_type = typename ht::value_type; using size_type = typename ht::size_type; using difference_type = typename ht::difference_type; using hasher = typename ht::hasher; using key_equal = typename ht::key_equal; using key_compare = Compare; using allocator_type = typename ht::allocator_type; using reference = typename ht::reference; using const_reference = typename ht::const_reference; using pointer = typename ht::pointer; using const_pointer = typename ht::const_pointer; using iterator = typename ht::iterator; using const_iterator = typename ht::const_iterator; /* * Constructors */ bhopscotch_set() : bhopscotch_set(ht::DEFAULT_INIT_BUCKETS_SIZE) { } explicit bhopscotch_set(size_type bucket_count, const Hash& hash = Hash(), const KeyEqual& equal = KeyEqual(), const Allocator& alloc = Allocator(), const Compare& comp = Compare()) : m_ht(bucket_count, hash, equal, alloc, ht::DEFAULT_MAX_LOAD_FACTOR, comp) { } bhopscotch_set(size_type bucket_count, const Allocator& alloc) : bhopscotch_set(bucket_count, Hash(), KeyEqual(), alloc) { } bhopscotch_set(size_type bucket_count, const Hash& hash, const Allocator& alloc) : bhopscotch_set(bucket_count, hash, KeyEqual(), alloc) { } explicit bhopscotch_set(const Allocator& alloc) : bhopscotch_set(ht::DEFAULT_INIT_BUCKETS_SIZE, alloc) { } template bhopscotch_set(InputIt first, InputIt last, size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, const Hash& hash = Hash(), const KeyEqual& equal = KeyEqual(), const Allocator& alloc = Allocator()) : bhopscotch_set(bucket_count, hash, equal, alloc) { insert(first, last); } template bhopscotch_set(InputIt first, InputIt last, size_type bucket_count, const Allocator& alloc) : bhopscotch_set(first, last, bucket_count, Hash(), KeyEqual(), alloc) { } template bhopscotch_set(InputIt first, InputIt last, size_type bucket_count, const Hash& hash, const Allocator& alloc) : bhopscotch_set(first, last, bucket_count, hash, KeyEqual(), alloc) { } bhopscotch_set(std::initializer_list init, size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, const Hash& hash = Hash(), const KeyEqual& equal = KeyEqual(), const Allocator& alloc = Allocator()) : bhopscotch_set(init.begin(), init.end(), bucket_count, hash, equal, alloc) { } bhopscotch_set(std::initializer_list init, size_type bucket_count, const Allocator& alloc) : bhopscotch_set(init.begin(), init.end(), bucket_count, Hash(), KeyEqual(), alloc) { } bhopscotch_set(std::initializer_list init, size_type bucket_count, const Hash& hash, const Allocator& alloc) : bhopscotch_set(init.begin(), init.end(), bucket_count, hash, KeyEqual(), alloc) { } bhopscotch_set& operator=(std::initializer_list ilist) { m_ht.clear(); m_ht.reserve(ilist.size()); m_ht.insert(ilist.begin(), ilist.end()); return *this; } allocator_type get_allocator() const { return m_ht.get_allocator(); } /* * Iterators */ iterator begin() noexcept { return m_ht.begin(); } const_iterator begin() const noexcept { return m_ht.begin(); } const_iterator cbegin() const noexcept { return m_ht.cbegin(); } iterator end() noexcept { return m_ht.end(); } const_iterator end() const noexcept { return m_ht.end(); } const_iterator cend() const noexcept { return m_ht.cend(); } /* * Capacity */ bool empty() const noexcept { return m_ht.empty(); } size_type size() const noexcept { return m_ht.size(); } size_type max_size() const noexcept { return m_ht.max_size(); } /* * Modifiers */ void clear() noexcept { m_ht.clear(); } std::pair insert(const value_type& value) { return m_ht.insert(value); } std::pair insert(value_type&& value) { return m_ht.insert(std::move(value)); } iterator insert(const_iterator hint, const value_type& value) { return m_ht.insert(hint, value); } iterator insert(const_iterator hint, value_type&& value) { return m_ht.insert(hint, std::move(value)); } template void insert(InputIt first, InputIt last) { m_ht.insert(first, last); } void insert(std::initializer_list ilist) { m_ht.insert(ilist.begin(), ilist.end()); } /** * Due to the way elements are stored, emplace will need to move or copy the key-value once. * The method is equivalent to insert(value_type(std::forward(args)...)); * * Mainly here for compatibility with the std::unordered_map interface. */ template std::pair emplace(Args&&... args) { return m_ht.emplace(std::forward(args)...); } /** * Due to the way elements are stored, emplace_hint will need to move or copy the key-value once. * The method is equivalent to insert(hint, value_type(std::forward(args)...)); * * Mainly here for compatibility with the std::unordered_map interface. */ template iterator emplace_hint(const_iterator hint, Args&&... args) { return m_ht.emplace_hint(hint, std::forward(args)...); } iterator erase(iterator pos) { return m_ht.erase(pos); } iterator erase(const_iterator pos) { return m_ht.erase(pos); } iterator erase(const_iterator first, const_iterator last) { return m_ht.erase(first, last); } size_type erase(const key_type& key) { return m_ht.erase(key); } /** * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup to the value if you already have the hash. */ size_type erase(const key_type& key, std::size_t precalculated_hash) { return m_ht.erase(key, precalculated_hash); } /** * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent * and Compare::is_transparent exist. * If so, K must be hashable and comparable to Key. */ template::value && has_is_transparent::value>::type* = nullptr> size_type erase(const K& key) { return m_ht.erase(key); } /** * @copydoc erase(const K& key) * * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup to the value if you already have the hash. */ template::value && has_is_transparent::value>::type* = nullptr> size_type erase(const K& key, std::size_t precalculated_hash) { return m_ht.erase(key, precalculated_hash); } void swap(bhopscotch_set& other) { other.m_ht.swap(m_ht); } /* * Lookup */ size_type count(const Key& key) const { return m_ht.count(key); } /** * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ size_type count(const Key& key, std::size_t precalculated_hash) const { return m_ht.count(key, precalculated_hash); } /** * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent * and Compare::is_transparent exist. * If so, K must be hashable and comparable to Key. */ template::value && has_is_transparent::value>::type* = nullptr> size_type count(const K& key) const { return m_ht.count(key); } /** * @copydoc count(const K& key) const * * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ template::value && has_is_transparent::value>::type* = nullptr> size_type count(const K& key, std::size_t precalculated_hash) const { return m_ht.count(key, precalculated_hash); } iterator find(const Key& key) { return m_ht.find(key); } /** * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ iterator find(const Key& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } const_iterator find(const Key& key) const { return m_ht.find(key); } /** * @copydoc find(const Key& key, std::size_t precalculated_hash) */ const_iterator find(const Key& key, std::size_t precalculated_hash) const { return m_ht.find(key, precalculated_hash); } /** * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent * and Compare::is_transparent exist. * If so, K must be hashable and comparable to Key. */ template::value && has_is_transparent::value>::type* = nullptr> iterator find(const K& key) { return m_ht.find(key); } /** * @copydoc find(const K& key) * * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ template::value && has_is_transparent::value>::type* = nullptr> iterator find(const K& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } /** * @copydoc find(const K& key) */ template::value && has_is_transparent::value>::type* = nullptr> const_iterator find(const K& key) const { return m_ht.find(key); } /** * @copydoc find(const K& key) * * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ template::value && has_is_transparent::value>::type* = nullptr> const_iterator find(const K& key, std::size_t precalculated_hash) const { return m_ht.find(key, precalculated_hash); } bool contains(const Key& key) const { return m_ht.contains(key); } /** * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ bool contains(const Key& key, std::size_t precalculated_hash) const { return m_ht.contains(key, precalculated_hash); } /** * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. * If so, K must be hashable and comparable to Key. */ template::value>::type* = nullptr> bool contains(const K& key) const { return m_ht.contains(key); } /** * @copydoc contains(const K& key) const * * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ template::value>::type* = nullptr> bool contains(const K& key, std::size_t precalculated_hash) const { return m_ht.contains(key, precalculated_hash); } std::pair equal_range(const Key& key) { return m_ht.equal_range(key); } /** * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ std::pair equal_range(const Key& key, std::size_t precalculated_hash) { return m_ht.equal_range(key, precalculated_hash); } std::pair equal_range(const Key& key) const { return m_ht.equal_range(key); } /** * @copydoc equal_range(const Key& key, std::size_t precalculated_hash) */ std::pair equal_range(const Key& key, std::size_t precalculated_hash) const { return m_ht.equal_range(key, precalculated_hash); } /** * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent * and Compare::is_transparent exist. * If so, K must be hashable and comparable to Key. */ template::value && has_is_transparent::value>::type* = nullptr> std::pair equal_range(const K& key) { return m_ht.equal_range(key); } /** * @copydoc equal_range(const K& key) * * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ template::value && has_is_transparent::value>::type* = nullptr> std::pair equal_range(const K& key, std::size_t precalculated_hash) { return m_ht.equal_range(key, precalculated_hash); } /** * @copydoc equal_range(const K& key) */ template::value && has_is_transparent::value>::type* = nullptr> std::pair equal_range(const K& key) const { return m_ht.equal_range(key); } /** * @copydoc equal_range(const K& key, std::size_t precalculated_hash) */ template::value && has_is_transparent::value>::type* = nullptr> std::pair equal_range(const K& key, std::size_t precalculated_hash) const { return m_ht.equal_range(key, precalculated_hash); } /* * Bucket interface */ size_type bucket_count() const { return m_ht.bucket_count(); } size_type max_bucket_count() const { return m_ht.max_bucket_count(); } /* * Hash policy */ float load_factor() const { return m_ht.load_factor(); } float max_load_factor() const { return m_ht.max_load_factor(); } void max_load_factor(float ml) { m_ht.max_load_factor(ml); } void rehash(size_type count_) { m_ht.rehash(count_); } void reserve(size_type count_) { m_ht.reserve(count_); } /* * Observers */ hasher hash_function() const { return m_ht.hash_function(); } key_equal key_eq() const { return m_ht.key_eq(); } key_compare key_comp() const { return m_ht.key_comp(); } /* * Other */ /** * Convert a const_iterator to an iterator. */ iterator mutable_iterator(const_iterator pos) { return m_ht.mutable_iterator(pos); } size_type overflow_size() const noexcept { return m_ht.overflow_size(); } friend bool operator==(const bhopscotch_set& lhs, const bhopscotch_set& rhs) { if(lhs.size() != rhs.size()) { return false; } for(const auto& element_lhs : lhs) { const auto it_element_rhs = rhs.find(element_lhs); if(it_element_rhs == rhs.cend()) { return false; } } return true; } friend bool operator!=(const bhopscotch_set& lhs, const bhopscotch_set& rhs) { return !operator==(lhs, rhs); } friend void swap(bhopscotch_set& lhs, bhopscotch_set& rhs) { lhs.swap(rhs); } private: ht m_ht; }; /** * Same as `tsl::bhopscotch_set`. */ template, class KeyEqual = std::equal_to, class Compare = std::less, class Allocator = std::allocator, unsigned int NeighborhoodSize = 62, bool StoreHash = false> using bhopscotch_pg_set = bhopscotch_set; } // end namespace tsl #endif ================================================ FILE: External/tsl/hopscotch_growth_policy.h ================================================ /** * MIT License * * Copyright (c) 2018 Thibaut Goetghebuer-Planchon * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #ifndef TSL_HOPSCOTCH_GROWTH_POLICY_H #define TSL_HOPSCOTCH_GROWTH_POLICY_H #include #include #include #include #include #include #include #include #include #include /** * Only activate tsl_hh_assert if TSL_DEBUG is defined. * This way we avoid the performance hit when NDEBUG is not defined with assert as tsl_hh_assert is used a lot * (people usually compile with "-O3" and not "-O3 -DNDEBUG"). */ #ifdef TSL_DEBUG # define tsl_hh_assert(expr) assert(expr) #else # define tsl_hh_assert(expr) (static_cast(0)) #endif /** * If exceptions are enabled, throw the exception passed in parameter, otherwise call std::terminate. */ #if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || (defined (_MSC_VER) && defined (_CPPUNWIND))) && !defined(TSL_NO_EXCEPTIONS) # define TSL_HH_THROW_OR_TERMINATE(ex, msg) throw ex(msg) #else # define TSL_HH_NO_EXCEPTIONS # ifdef NDEBUG # define TSL_HH_THROW_OR_TERMINATE(ex, msg) std::terminate() # else # include # define TSL_HH_THROW_OR_TERMINATE(ex, msg) do { std::cerr << msg << std::endl; std::terminate(); } while(0) # endif #endif namespace tsl { namespace hh { /** * Grow the hash table by a factor of GrowthFactor keeping the bucket count to a power of two. It allows * the table to use a mask operation instead of a modulo operation to map a hash to a bucket. * * GrowthFactor must be a power of two >= 2. */ template class power_of_two_growth_policy { public: /** * Called on the hash table creation and on rehash. The number of buckets for the table is passed in parameter. * This number is a minimum, the policy may update this value with a higher value if needed (but not lower). * * If 0 is given, min_bucket_count_in_out must still be 0 after the policy creation and * bucket_for_hash must always return 0 in this case. */ explicit power_of_two_growth_policy(std::size_t& min_bucket_count_in_out) { if(min_bucket_count_in_out > max_bucket_count()) { TSL_HH_THROW_OR_TERMINATE(std::length_error, "The hash table exceeds its maximum size."); } if(min_bucket_count_in_out > 0) { min_bucket_count_in_out = round_up_to_power_of_two(min_bucket_count_in_out); m_mask = min_bucket_count_in_out - 1; } else { m_mask = 0; } } /** * Return the bucket [0, bucket_count()) to which the hash belongs. * If bucket_count() is 0, it must always return 0. */ std::size_t bucket_for_hash(std::size_t hash) const noexcept { return hash & m_mask; } /** * Return the bucket count to use when the bucket array grows on rehash. */ std::size_t next_bucket_count() const { if((m_mask + 1) > max_bucket_count() / GrowthFactor) { TSL_HH_THROW_OR_TERMINATE(std::length_error, "The hash table exceeds its maximum size."); } return (m_mask + 1) * GrowthFactor; } /** * Return the maximum number of buckets supported by the policy. */ std::size_t max_bucket_count() const { // Largest power of two. return (std::numeric_limits::max() / 2) + 1; } /** * Reset the growth policy as if it was created with a bucket count of 0. * After a clear, the policy must always return 0 when bucket_for_hash is called. */ void clear() noexcept { m_mask = 0; } private: static std::size_t round_up_to_power_of_two(std::size_t value) { if(is_power_of_two(value)) { return value; } if(value == 0) { return 1; } --value; for(std::size_t i = 1; i < sizeof(std::size_t) * CHAR_BIT; i *= 2) { value |= value >> i; } return value + 1; } static constexpr bool is_power_of_two(std::size_t value) { return value != 0 && (value & (value - 1)) == 0; } private: static_assert(is_power_of_two(GrowthFactor) && GrowthFactor >= 2, "GrowthFactor must be a power of two >= 2."); std::size_t m_mask; }; /** * Grow the hash table by GrowthFactor::num / GrowthFactor::den and use a modulo to map a hash * to a bucket. Slower but it can be useful if you want a slower growth. */ template> class mod_growth_policy { public: explicit mod_growth_policy(std::size_t& min_bucket_count_in_out) { if(min_bucket_count_in_out > max_bucket_count()) { TSL_HH_THROW_OR_TERMINATE(std::length_error, "The hash table exceeds its maximum size."); } if(min_bucket_count_in_out > 0) { m_mod = min_bucket_count_in_out; } else { m_mod = 1; } } std::size_t bucket_for_hash(std::size_t hash) const noexcept { return hash % m_mod; } std::size_t next_bucket_count() const { if(m_mod == max_bucket_count()) { TSL_HH_THROW_OR_TERMINATE(std::length_error, "The hash table exceeds its maximum size."); } const double next_bucket_count = std::ceil(double(m_mod) * REHASH_SIZE_MULTIPLICATION_FACTOR); if(!std::isnormal(next_bucket_count)) { TSL_HH_THROW_OR_TERMINATE(std::length_error, "The hash table exceeds its maximum size."); } if(next_bucket_count > double(max_bucket_count())) { return max_bucket_count(); } else { return std::size_t(next_bucket_count); } } std::size_t max_bucket_count() const { return MAX_BUCKET_COUNT; } void clear() noexcept { m_mod = 1; } private: static constexpr double REHASH_SIZE_MULTIPLICATION_FACTOR = 1.0 * GrowthFactor::num / GrowthFactor::den; static const std::size_t MAX_BUCKET_COUNT = std::size_t(double( std::numeric_limits::max() / REHASH_SIZE_MULTIPLICATION_FACTOR )); static_assert(REHASH_SIZE_MULTIPLICATION_FACTOR >= 1.1, "Growth factor should be >= 1.1."); std::size_t m_mod; }; namespace detail { #if SIZE_MAX >= ULLONG_MAX #define TSL_HH_NB_PRIMES 51 #elif SIZE_MAX >= ULONG_MAX #define TSL_HH_NB_PRIMES 40 #else #define TSL_HH_NB_PRIMES 23 #endif static constexpr const std::array PRIMES = {{ 1u, 5u, 17u, 29u, 37u, 53u, 67u, 79u, 97u, 131u, 193u, 257u, 389u, 521u, 769u, 1031u, 1543u, 2053u, 3079u, 6151u, 12289u, 24593u, 49157u, #if SIZE_MAX >= ULONG_MAX 98317ul, 196613ul, 393241ul, 786433ul, 1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul, 50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul, 1610612741ul, 3221225473ul, 4294967291ul, #endif #if SIZE_MAX >= ULLONG_MAX 6442450939ull, 12884901893ull, 25769803751ull, 51539607551ull, 103079215111ull, 206158430209ull, 412316860441ull, 824633720831ull, 1649267441651ull, 3298534883309ull, 6597069766657ull, #endif }}; template static constexpr std::size_t mod(std::size_t hash) { return hash % PRIMES[IPrime]; } // MOD_PRIME[iprime](hash) returns hash % PRIMES[iprime]. This table allows for faster modulo as the // compiler can optimize the modulo code better with a constant known at the compilation. static constexpr const std::array MOD_PRIME = {{ &mod<0>, &mod<1>, &mod<2>, &mod<3>, &mod<4>, &mod<5>, &mod<6>, &mod<7>, &mod<8>, &mod<9>, &mod<10>, &mod<11>, &mod<12>, &mod<13>, &mod<14>, &mod<15>, &mod<16>, &mod<17>, &mod<18>, &mod<19>, &mod<20>, &mod<21>, &mod<22>, #if SIZE_MAX >= ULONG_MAX &mod<23>, &mod<24>, &mod<25>, &mod<26>, &mod<27>, &mod<28>, &mod<29>, &mod<30>, &mod<31>, &mod<32>, &mod<33>, &mod<34>, &mod<35>, &mod<36>, &mod<37> , &mod<38>, &mod<39>, #endif #if SIZE_MAX >= ULLONG_MAX &mod<40>, &mod<41>, &mod<42>, &mod<43>, &mod<44>, &mod<45>, &mod<46>, &mod<47>, &mod<48>, &mod<49>, &mod<50>, #endif }}; } /** * Grow the hash table by using prime numbers as bucket count. Slower than tsl::hh::power_of_two_growth_policy in * general but will probably distribute the values around better in the buckets with a poor hash function. * * To allow the compiler to optimize the modulo operation, a lookup table is used with constant primes numbers. * * With a switch the code would look like: * \code * switch(iprime) { // iprime is the current prime of the hash table * case 0: hash % 5ul; * break; * case 1: hash % 17ul; * break; * case 2: hash % 29ul; * break; * ... * } * \endcode * * Due to the constant variable in the modulo the compiler is able to optimize the operation * by a series of multiplications, substractions and shifts. * * The 'hash % 5' could become something like 'hash - (hash * 0xCCCCCCCD) >> 34) * 5' in a 64 bits environment. */ class prime_growth_policy { public: explicit prime_growth_policy(std::size_t& min_bucket_count_in_out) { auto it_prime = std::lower_bound(detail::PRIMES.begin(), detail::PRIMES.end(), min_bucket_count_in_out); if(it_prime == detail::PRIMES.end()) { TSL_HH_THROW_OR_TERMINATE(std::length_error, "The hash table exceeds its maximum size."); } m_iprime = static_cast(std::distance(detail::PRIMES.begin(), it_prime)); if(min_bucket_count_in_out > 0) { min_bucket_count_in_out = *it_prime; } else { min_bucket_count_in_out = 0; } } std::size_t bucket_for_hash(std::size_t hash) const noexcept { return detail::MOD_PRIME[m_iprime](hash); } std::size_t next_bucket_count() const { if(m_iprime + 1 >= detail::PRIMES.size()) { TSL_HH_THROW_OR_TERMINATE(std::length_error, "The hash table exceeds its maximum size."); } return detail::PRIMES[m_iprime + 1]; } std::size_t max_bucket_count() const { return detail::PRIMES.back(); } void clear() noexcept { m_iprime = 0; } private: unsigned int m_iprime; static_assert(std::numeric_limits::max() >= detail::PRIMES.size(), "The type of m_iprime is not big enough."); }; } } #endif ================================================ FILE: External/tsl/hopscotch_hash.h ================================================ /** * MIT License * * Copyright (c) 2017 Thibaut Goetghebuer-Planchon * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #ifndef TSL_HOPSCOTCH_HASH_H #define TSL_HOPSCOTCH_HASH_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "hopscotch_growth_policy.h" #if (defined(__GNUC__) && (__GNUC__ == 4) && (__GNUC_MINOR__ < 9)) # define TSL_HH_NO_RANGE_ERASE_WITH_CONST_ITERATOR #endif namespace tsl { namespace detail_hopscotch_hash { template struct make_void { using type = void; }; template struct has_is_transparent : std::false_type { }; template struct has_is_transparent::type> : std::true_type { }; template struct has_key_compare : std::false_type { }; template struct has_key_compare::type> : std::true_type { }; template struct is_power_of_two_policy: std::false_type { }; template struct is_power_of_two_policy>: std::true_type { }; template static T numeric_cast(U value, const char* error_message = "numeric_cast() failed.") { T ret = static_cast(value); if(static_cast(ret) != value) { TSL_HH_THROW_OR_TERMINATE(std::runtime_error, error_message); } const bool is_same_signedness = (std::is_unsigned::value && std::is_unsigned::value) || (std::is_signed::value && std::is_signed::value); if(!is_same_signedness && (ret < T{}) != (value < U{})) { TSL_HH_THROW_OR_TERMINATE(std::runtime_error, error_message); } return ret; } /* * smallest_type_for_min_bits::type returns the smallest type that can fit MinBits. */ static const std::size_t SMALLEST_TYPE_MAX_BITS_SUPPORTED = 64; template class smallest_type_for_min_bits { }; template class smallest_type_for_min_bits 0) && (MinBits <= 8)>::type> { public: using type = std::uint_least8_t; }; template class smallest_type_for_min_bits 8) && (MinBits <= 16)>::type> { public: using type = std::uint_least16_t; }; template class smallest_type_for_min_bits 16) && (MinBits <= 32)>::type> { public: using type = std::uint_least32_t; }; template class smallest_type_for_min_bits 32) && (MinBits <= 64)>::type> { public: using type = std::uint_least64_t; }; /* * Each bucket may store up to three elements: * - An aligned storage to store a value_type object with placement-new. * - An (optional) hash of the value in the bucket. * - An unsigned integer of type neighborhood_bitmap used to tell us which buckets in the neighborhood of the * current bucket contain a value with a hash belonging to the current bucket. * * For a bucket 'bct', a bit 'i' (counting from 0 and from the least significant bit to the most significant) * set to 1 means that the bucket 'bct + i' contains a value with a hash belonging to bucket 'bct'. * The bits used for that, start from the third least significant bit. * The two least significant bits are reserved: * - The least significant bit is set to 1 if there is a value in the bucket storage. * - The second least significant bit is set to 1 if there is an overflow. More than NeighborhoodSize values * give the same hash, all overflow values are stored in the m_overflow_elements list of the map. * * Details regarding hopscotch hashing an its implementation can be found here: * https://tessil.github.io/2016/08/29/hopscotch-hashing.html */ static const std::size_t NB_RESERVED_BITS_IN_NEIGHBORHOOD = 2; using truncated_hash_type = std::uint_least32_t; /** * Helper class that stores a truncated hash if StoreHash is true and nothing otherwise. */ template class hopscotch_bucket_hash { public: bool bucket_hash_equal(std::size_t /*hash*/) const noexcept { return true; } truncated_hash_type truncated_bucket_hash() const noexcept { return 0; } protected: void copy_hash(const hopscotch_bucket_hash& ) noexcept { } void set_hash(truncated_hash_type /*hash*/) noexcept { } }; template<> class hopscotch_bucket_hash { public: bool bucket_hash_equal(std::size_t hash) const noexcept { return m_hash == truncated_hash_type(hash); } truncated_hash_type truncated_bucket_hash() const noexcept { return m_hash; } protected: void copy_hash(const hopscotch_bucket_hash& bucket) noexcept { m_hash = bucket.m_hash; } void set_hash(truncated_hash_type hash) noexcept { m_hash = hash; } private: truncated_hash_type m_hash; }; template class hopscotch_bucket: public hopscotch_bucket_hash { private: static const std::size_t MIN_NEIGHBORHOOD_SIZE = 4; static const std::size_t MAX_NEIGHBORHOOD_SIZE = SMALLEST_TYPE_MAX_BITS_SUPPORTED - NB_RESERVED_BITS_IN_NEIGHBORHOOD; static_assert(NeighborhoodSize >= 4, "NeighborhoodSize should be >= 4."); // We can't put a variable in the message, ensure coherence static_assert(MIN_NEIGHBORHOOD_SIZE == 4, ""); static_assert(NeighborhoodSize <= 62, "NeighborhoodSize should be <= 62."); // We can't put a variable in the message, ensure coherence static_assert(MAX_NEIGHBORHOOD_SIZE == 62, ""); static_assert(!StoreHash || NeighborhoodSize <= 30, "NeighborhoodSize should be <= 30 if StoreHash is true."); // We can't put a variable in the message, ensure coherence static_assert(MAX_NEIGHBORHOOD_SIZE - 32 == 30, ""); using bucket_hash = hopscotch_bucket_hash; public: using value_type = ValueType; using neighborhood_bitmap = typename smallest_type_for_min_bits::type; hopscotch_bucket() noexcept: bucket_hash(), m_neighborhood_infos(0) { tsl_hh_assert(empty()); } hopscotch_bucket(const hopscotch_bucket& bucket) noexcept(std::is_nothrow_copy_constructible::value): bucket_hash(bucket), m_neighborhood_infos(0) { if(!bucket.empty()) { ::new (static_cast(std::addressof(m_value))) value_type(bucket.value()); } m_neighborhood_infos = bucket.m_neighborhood_infos; } hopscotch_bucket(hopscotch_bucket&& bucket) noexcept(std::is_nothrow_move_constructible::value) : bucket_hash(std::move(bucket)), m_neighborhood_infos(0) { if(!bucket.empty()) { ::new (static_cast(std::addressof(m_value))) value_type(std::move(bucket.value())); } m_neighborhood_infos = bucket.m_neighborhood_infos; } hopscotch_bucket& operator=(const hopscotch_bucket& bucket) noexcept(std::is_nothrow_copy_constructible::value) { if(this != &bucket) { remove_value(); bucket_hash::operator=(bucket); if(!bucket.empty()) { ::new (static_cast(std::addressof(m_value))) value_type(bucket.value()); } m_neighborhood_infos = bucket.m_neighborhood_infos; } return *this; } hopscotch_bucket& operator=(hopscotch_bucket&& ) = delete; ~hopscotch_bucket() noexcept { if(!empty()) { destroy_value(); } } neighborhood_bitmap neighborhood_infos() const noexcept { return neighborhood_bitmap(m_neighborhood_infos >> NB_RESERVED_BITS_IN_NEIGHBORHOOD); } void set_overflow(bool has_overflow) noexcept { if(has_overflow) { m_neighborhood_infos = neighborhood_bitmap(m_neighborhood_infos | 2); } else { m_neighborhood_infos = neighborhood_bitmap(m_neighborhood_infos & ~2); } } bool has_overflow() const noexcept { return (m_neighborhood_infos & 2) != 0; } bool empty() const noexcept { return (m_neighborhood_infos & 1) == 0; } void toggle_neighbor_presence(std::size_t ineighbor) noexcept { tsl_hh_assert(ineighbor <= NeighborhoodSize); m_neighborhood_infos = neighborhood_bitmap( m_neighborhood_infos ^ (1ull << (ineighbor + NB_RESERVED_BITS_IN_NEIGHBORHOOD))); } bool check_neighbor_presence(std::size_t ineighbor) const noexcept { tsl_hh_assert(ineighbor <= NeighborhoodSize); if(((m_neighborhood_infos >> (ineighbor + NB_RESERVED_BITS_IN_NEIGHBORHOOD)) & 1) == 1) { return true; } return false; } value_type& value() noexcept { tsl_hh_assert(!empty()); return *reinterpret_cast(std::addressof(m_value)); } const value_type& value() const noexcept { tsl_hh_assert(!empty()); return *reinterpret_cast(std::addressof(m_value)); } template void set_value_of_empty_bucket(truncated_hash_type hash, Args&&... value_type_args) { tsl_hh_assert(empty()); ::new (static_cast(std::addressof(m_value))) value_type(std::forward(value_type_args)...); set_empty(false); this->set_hash(hash); } void swap_value_into_empty_bucket(hopscotch_bucket& empty_bucket) { tsl_hh_assert(empty_bucket.empty()); if(!empty()) { ::new (static_cast(std::addressof(empty_bucket.m_value))) value_type(std::move(value())); empty_bucket.copy_hash(*this); empty_bucket.set_empty(false); destroy_value(); set_empty(true); } } void remove_value() noexcept { if(!empty()) { destroy_value(); set_empty(true); } } void clear() noexcept { if(!empty()) { destroy_value(); } m_neighborhood_infos = 0; tsl_hh_assert(empty()); } static truncated_hash_type truncate_hash(std::size_t hash) noexcept { return truncated_hash_type(hash); } private: void set_empty(bool is_empty) noexcept { if(is_empty) { m_neighborhood_infos = neighborhood_bitmap(m_neighborhood_infos & ~1); } else { m_neighborhood_infos = neighborhood_bitmap(m_neighborhood_infos | 1); } } void destroy_value() noexcept { tsl_hh_assert(!empty()); value().~value_type(); } private: using storage = typename std::aligned_storage::type; neighborhood_bitmap m_neighborhood_infos; storage m_value; }; /** * Internal common class used by (b)hopscotch_map and (b)hopscotch_set. * * ValueType is what will be stored by hopscotch_hash (usually std::pair for a map and Key for a set). * * KeySelect should be a FunctionObject which takes a ValueType in parameter and returns a reference to the key. * * ValueSelect should be a FunctionObject which takes a ValueType in parameter and returns a reference to the value. * ValueSelect should be void if there is no value (in a set for example). * * OverflowContainer will be used as containers for overflown elements. Usually it should be a list * or a set/map. */ template class hopscotch_hash: private Hash, private KeyEqual, private GrowthPolicy { private: template using has_mapped_type = typename std::integral_constant::value>; static_assert(noexcept(std::declval().bucket_for_hash(std::size_t(0))), "GrowthPolicy::bucket_for_hash must be noexcept."); static_assert(noexcept(std::declval().clear()), "GrowthPolicy::clear must be noexcept."); public: template class hopscotch_iterator; using key_type = typename KeySelect::key_type; using value_type = ValueType; using size_type = std::size_t; using difference_type = std::ptrdiff_t; using hasher = Hash; using key_equal = KeyEqual; using allocator_type = Allocator; using reference = value_type&; using const_reference = const value_type&; using pointer = value_type*; using const_pointer = const value_type*; using iterator = hopscotch_iterator; using const_iterator = hopscotch_iterator; private: using hopscotch_bucket = tsl::detail_hopscotch_hash::hopscotch_bucket; using neighborhood_bitmap = typename hopscotch_bucket::neighborhood_bitmap; using buckets_allocator = typename std::allocator_traits::template rebind_alloc; using buckets_container_type = std::vector; using overflow_container_type = OverflowContainer; static_assert(std::is_same::value, "OverflowContainer should have ValueType as type."); static_assert(std::is_same::value, "Invalid allocator, not the same type as the value_type."); using iterator_buckets = typename buckets_container_type::iterator; using const_iterator_buckets = typename buckets_container_type::const_iterator; using iterator_overflow = typename overflow_container_type::iterator; using const_iterator_overflow = typename overflow_container_type::const_iterator; public: /** * The `operator*()` and `operator->()` methods return a const reference and const pointer respectively to the * stored value type. * * In case of a map, to get a modifiable reference to the value associated to a key (the `.second` in the * stored pair), you have to call `value()`. */ template class hopscotch_iterator { friend class hopscotch_hash; private: using iterator_bucket = typename std::conditional::type; using iterator_overflow = typename std::conditional::type; hopscotch_iterator(iterator_bucket buckets_iterator, iterator_bucket buckets_end_iterator, iterator_overflow overflow_iterator) noexcept : m_buckets_iterator(buckets_iterator), m_buckets_end_iterator(buckets_end_iterator), m_overflow_iterator(overflow_iterator) { } public: using iterator_category = std::forward_iterator_tag; using value_type = const typename hopscotch_hash::value_type; using difference_type = std::ptrdiff_t; using reference = value_type&; using pointer = value_type*; hopscotch_iterator() noexcept { } // Copy constructor from iterator to const_iterator. template::type* = nullptr> hopscotch_iterator(const hopscotch_iterator& other) noexcept : m_buckets_iterator(other.m_buckets_iterator), m_buckets_end_iterator(other.m_buckets_end_iterator), m_overflow_iterator(other.m_overflow_iterator) { } hopscotch_iterator(const hopscotch_iterator& other) = default; hopscotch_iterator(hopscotch_iterator&& other) = default; hopscotch_iterator& operator=(const hopscotch_iterator& other) = default; hopscotch_iterator& operator=(hopscotch_iterator&& other) = default; const typename hopscotch_hash::key_type& key() const { if(m_buckets_iterator != m_buckets_end_iterator) { return KeySelect()(m_buckets_iterator->value()); } return KeySelect()(*m_overflow_iterator); } template::value>::type* = nullptr> typename std::conditional< IsConst, const typename U::value_type&, typename U::value_type&>::type value() const { if(m_buckets_iterator != m_buckets_end_iterator) { return U()(m_buckets_iterator->value()); } return U()(*m_overflow_iterator); } reference operator*() const { if(m_buckets_iterator != m_buckets_end_iterator) { return m_buckets_iterator->value(); } return *m_overflow_iterator; } pointer operator->() const { if(m_buckets_iterator != m_buckets_end_iterator) { return std::addressof(m_buckets_iterator->value()); } return std::addressof(*m_overflow_iterator); } hopscotch_iterator& operator++() { if(m_buckets_iterator == m_buckets_end_iterator) { ++m_overflow_iterator; return *this; } do { ++m_buckets_iterator; } while(m_buckets_iterator != m_buckets_end_iterator && m_buckets_iterator->empty()); return *this; } hopscotch_iterator operator++(int) { hopscotch_iterator tmp(*this); ++*this; return tmp; } friend bool operator==(const hopscotch_iterator& lhs, const hopscotch_iterator& rhs) { return lhs.m_buckets_iterator == rhs.m_buckets_iterator && lhs.m_overflow_iterator == rhs.m_overflow_iterator; } friend bool operator!=(const hopscotch_iterator& lhs, const hopscotch_iterator& rhs) { return !(lhs == rhs); } private: iterator_bucket m_buckets_iterator; iterator_bucket m_buckets_end_iterator; iterator_overflow m_overflow_iterator; }; public: template::value>::type* = nullptr> hopscotch_hash(size_type bucket_count, const Hash& hash, const KeyEqual& equal, const Allocator& alloc, float max_load_factor) : Hash(hash), KeyEqual(equal), GrowthPolicy(bucket_count), m_buckets_data(alloc), m_overflow_elements(alloc), m_buckets(static_empty_bucket_ptr()), m_nb_elements(0) { if(bucket_count > max_bucket_count()) { TSL_HH_THROW_OR_TERMINATE(std::length_error, "The map exceeds its maximum size."); } if(bucket_count > 0) { static_assert(NeighborhoodSize - 1 > 0, ""); // Can't directly construct with the appropriate size in the initializer // as m_buckets_data(bucket_count, alloc) is not supported by GCC 4.8 m_buckets_data.resize(bucket_count + NeighborhoodSize - 1); m_buckets = m_buckets_data.data(); } this->max_load_factor(max_load_factor); // Check in the constructor instead of outside of a function to avoid compilation issues // when value_type is not complete. static_assert(std::is_nothrow_move_constructible::value || std::is_copy_constructible::value, "value_type must be either copy constructible or nothrow move constructible."); } template::value>::type* = nullptr> hopscotch_hash(size_type bucket_count, const Hash& hash, const KeyEqual& equal, const Allocator& alloc, float max_load_factor, const typename OC::key_compare& comp) : Hash(hash), KeyEqual(equal), GrowthPolicy(bucket_count), m_buckets_data(alloc), m_overflow_elements(comp, alloc), m_buckets(static_empty_bucket_ptr()), m_nb_elements(0) { if(bucket_count > max_bucket_count()) { TSL_HH_THROW_OR_TERMINATE(std::length_error, "The map exceeds its maximum size."); } if(bucket_count > 0) { static_assert(NeighborhoodSize - 1 > 0, ""); // Can't directly construct with the appropriate size in the initializer // as m_buckets_data(bucket_count, alloc) is not supported by GCC 4.8 m_buckets_data.resize(bucket_count + NeighborhoodSize - 1); m_buckets = m_buckets_data.data(); } this->max_load_factor(max_load_factor); // Check in the constructor instead of outside of a function to avoid compilation issues // when value_type is not complete. static_assert(std::is_nothrow_move_constructible::value || std::is_copy_constructible::value, "value_type must be either copy constructible or nothrow move constructible."); } hopscotch_hash(const hopscotch_hash& other): Hash(other), KeyEqual(other), GrowthPolicy(other), m_buckets_data(other.m_buckets_data), m_overflow_elements(other.m_overflow_elements), m_buckets(m_buckets_data.empty()?static_empty_bucket_ptr(): m_buckets_data.data()), m_nb_elements(other.m_nb_elements), m_min_load_threshold_rehash(other.m_min_load_threshold_rehash), m_max_load_threshold_rehash(other.m_max_load_threshold_rehash), m_max_load_factor(other.m_max_load_factor) { } hopscotch_hash(hopscotch_hash&& other) noexcept( std::is_nothrow_move_constructible::value && std::is_nothrow_move_constructible::value && std::is_nothrow_move_constructible::value && std::is_nothrow_move_constructible::value && std::is_nothrow_move_constructible::value ): Hash(std::move(static_cast(other))), KeyEqual(std::move(static_cast(other))), GrowthPolicy(std::move(static_cast(other))), m_buckets_data(std::move(other.m_buckets_data)), m_overflow_elements(std::move(other.m_overflow_elements)), m_buckets(m_buckets_data.empty()?static_empty_bucket_ptr(): m_buckets_data.data()), m_nb_elements(other.m_nb_elements), m_min_load_threshold_rehash(other.m_min_load_threshold_rehash), m_max_load_threshold_rehash(other.m_max_load_threshold_rehash), m_max_load_factor(other.m_max_load_factor) { other.GrowthPolicy::clear(); other.m_buckets_data.clear(); other.m_overflow_elements.clear(); other.m_buckets = static_empty_bucket_ptr(); other.m_nb_elements = 0; other.m_min_load_threshold_rehash = 0; other.m_max_load_threshold_rehash = 0; } hopscotch_hash& operator=(const hopscotch_hash& other) { if(&other != this) { Hash::operator=(other); KeyEqual::operator=(other); GrowthPolicy::operator=(other); m_buckets_data = other.m_buckets_data; m_overflow_elements = other.m_overflow_elements; m_buckets = m_buckets_data.empty()?static_empty_bucket_ptr(): m_buckets_data.data(); m_nb_elements = other.m_nb_elements; m_min_load_threshold_rehash = other.m_min_load_threshold_rehash; m_max_load_threshold_rehash = other.m_max_load_threshold_rehash; m_max_load_factor = other.m_max_load_factor; } return *this; } hopscotch_hash& operator=(hopscotch_hash&& other) { other.swap(*this); other.clear(); return *this; } allocator_type get_allocator() const { return m_buckets_data.get_allocator(); } /* * Iterators */ iterator begin() noexcept { auto begin = m_buckets_data.begin(); while(begin != m_buckets_data.end() && begin->empty()) { ++begin; } return iterator(begin, m_buckets_data.end(), m_overflow_elements.begin()); } const_iterator begin() const noexcept { return cbegin(); } const_iterator cbegin() const noexcept { auto begin = m_buckets_data.cbegin(); while(begin != m_buckets_data.cend() && begin->empty()) { ++begin; } return const_iterator(begin, m_buckets_data.cend(), m_overflow_elements.cbegin()); } iterator end() noexcept { return iterator(m_buckets_data.end(), m_buckets_data.end(), m_overflow_elements.end()); } const_iterator end() const noexcept { return cend(); } const_iterator cend() const noexcept { return const_iterator(m_buckets_data.cend(), m_buckets_data.cend(), m_overflow_elements.cend()); } /* * Capacity */ bool empty() const noexcept { return m_nb_elements == 0; } size_type size() const noexcept { return m_nb_elements; } size_type max_size() const noexcept { return m_buckets_data.max_size(); } /* * Modifiers */ void clear() noexcept { for(auto& bucket: m_buckets_data) { bucket.clear(); } m_overflow_elements.clear(); m_nb_elements = 0; } std::pair insert(const value_type& value) { return insert_impl(value); } template::value>::type* = nullptr> std::pair insert(P&& value) { return insert_impl(value_type(std::forward

(value))); } std::pair insert(value_type&& value) { return insert_impl(std::move(value)); } iterator insert(const_iterator hint, const value_type& value) { if(hint != cend() && compare_keys(KeySelect()(*hint), KeySelect()(value))) { return mutable_iterator(hint); } return insert(value).first; } template::value>::type* = nullptr> iterator insert(const_iterator hint, P&& value) { return emplace_hint(hint, std::forward

(value)); } iterator insert(const_iterator hint, value_type&& value) { if(hint != cend() && compare_keys(KeySelect()(*hint), KeySelect()(value))) { return mutable_iterator(hint); } return insert(std::move(value)).first; } template void insert(InputIt first, InputIt last) { if(std::is_base_of::iterator_category>::value) { const auto nb_elements_insert = std::distance(first, last); const std::size_t nb_elements_in_buckets = m_nb_elements - m_overflow_elements.size(); const std::size_t nb_free_buckets = m_max_load_threshold_rehash - nb_elements_in_buckets; tsl_hh_assert(m_nb_elements >= m_overflow_elements.size()); tsl_hh_assert(m_max_load_threshold_rehash >= nb_elements_in_buckets); if(nb_elements_insert > 0 && nb_free_buckets < std::size_t(nb_elements_insert)) { reserve(nb_elements_in_buckets + std::size_t(nb_elements_insert)); } } for(; first != last; ++first) { insert(*first); } } template std::pair insert_or_assign(const key_type& k, M&& obj) { return insert_or_assign_impl(k, std::forward(obj)); } template std::pair insert_or_assign(key_type&& k, M&& obj) { return insert_or_assign_impl(std::move(k), std::forward(obj)); } template iterator insert_or_assign(const_iterator hint, const key_type& k, M&& obj) { if(hint != cend() && compare_keys(KeySelect()(*hint), k)) { auto it = mutable_iterator(hint); it.value() = std::forward(obj); return it; } return insert_or_assign(k, std::forward(obj)).first; } template iterator insert_or_assign(const_iterator hint, key_type&& k, M&& obj) { if(hint != cend() && compare_keys(KeySelect()(*hint), k)) { auto it = mutable_iterator(hint); it.value() = std::forward(obj); return it; } return insert_or_assign(std::move(k), std::forward(obj)).first; } template std::pair emplace(Args&&... args) { return insert(value_type(std::forward(args)...)); } template iterator emplace_hint(const_iterator hint, Args&&... args) { return insert(hint, value_type(std::forward(args)...)); } template std::pair try_emplace(const key_type& k, Args&&... args) { return try_emplace_impl(k, std::forward(args)...); } template std::pair try_emplace(key_type&& k, Args&&... args) { return try_emplace_impl(std::move(k), std::forward(args)...); } template iterator try_emplace(const_iterator hint, const key_type& k, Args&&... args) { if(hint != cend() && compare_keys(KeySelect()(*hint), k)) { return mutable_iterator(hint); } return try_emplace(k, std::forward(args)...).first; } template iterator try_emplace(const_iterator hint, key_type&& k, Args&&... args) { if(hint != cend() && compare_keys(KeySelect()(*hint), k)) { return mutable_iterator(hint); } return try_emplace(std::move(k), std::forward(args)...).first; } /** * Here to avoid `template size_type erase(const K& key)` being used when * we use an iterator instead of a const_iterator. */ iterator erase(iterator pos) { return erase(const_iterator(pos)); } iterator erase(const_iterator pos) { const std::size_t ibucket_for_hash = bucket_for_hash(hash_key(pos.key())); if(pos.m_buckets_iterator != pos.m_buckets_end_iterator) { auto it_bucket = m_buckets_data.begin() + std::distance(m_buckets_data.cbegin(), pos.m_buckets_iterator); erase_from_bucket(*it_bucket, ibucket_for_hash); return ++iterator(it_bucket, m_buckets_data.end(), m_overflow_elements.begin()); } else { auto it_next_overflow = erase_from_overflow(pos.m_overflow_iterator, ibucket_for_hash); return iterator(m_buckets_data.end(), m_buckets_data.end(), it_next_overflow); } } iterator erase(const_iterator first, const_iterator last) { if(first == last) { return mutable_iterator(first); } auto to_delete = erase(first); while(to_delete != last) { to_delete = erase(to_delete); } return to_delete; } template size_type erase(const K& key) { return erase(key, hash_key(key)); } template size_type erase(const K& key, std::size_t hash) { const std::size_t ibucket_for_hash = bucket_for_hash(hash); hopscotch_bucket* bucket_found = find_in_buckets(key, hash, m_buckets + ibucket_for_hash); if(bucket_found != nullptr) { erase_from_bucket(*bucket_found, ibucket_for_hash); return 1; } if(m_buckets[ibucket_for_hash].has_overflow()) { auto it_overflow = find_in_overflow(key); if(it_overflow != m_overflow_elements.end()) { erase_from_overflow(it_overflow, ibucket_for_hash); return 1; } } return 0; } void swap(hopscotch_hash& other) { using std::swap; swap(static_cast(*this), static_cast(other)); swap(static_cast(*this), static_cast(other)); swap(static_cast(*this), static_cast(other)); swap(m_buckets_data, other.m_buckets_data); swap(m_overflow_elements, other.m_overflow_elements); swap(m_buckets, other.m_buckets); swap(m_nb_elements, other.m_nb_elements); swap(m_min_load_threshold_rehash, other.m_min_load_threshold_rehash); swap(m_max_load_threshold_rehash, other.m_max_load_threshold_rehash); swap(m_max_load_factor, other.m_max_load_factor); } /* * Lookup */ template::value>::type* = nullptr> typename U::value_type& at(const K& key) { return at(key, hash_key(key)); } template::value>::type* = nullptr> typename U::value_type& at(const K& key, std::size_t hash) { return const_cast(static_cast(this)->at(key, hash)); } template::value>::type* = nullptr> const typename U::value_type& at(const K& key) const { return at(key, hash_key(key)); } template::value>::type* = nullptr> const typename U::value_type& at(const K& key, std::size_t hash) const { using T = typename U::value_type; const T* value = find_value_impl(key, hash, m_buckets + bucket_for_hash(hash)); if(value == nullptr) { TSL_HH_THROW_OR_TERMINATE(std::out_of_range, "Couldn't find key."); } else { return *value; } } template::value>::type* = nullptr> typename U::value_type& operator[](K&& key) { using T = typename U::value_type; const std::size_t hash = hash_key(key); const std::size_t ibucket_for_hash = bucket_for_hash(hash); T* value = find_value_impl(key, hash, m_buckets + ibucket_for_hash); if(value != nullptr) { return *value; } else { return insert_value(ibucket_for_hash, hash, std::piecewise_construct, std::forward_as_tuple(std::forward(key)), std::forward_as_tuple()).first.value(); } } template size_type count(const K& key) const { return count(key, hash_key(key)); } template size_type count(const K& key, std::size_t hash) const { return count_impl(key, hash, m_buckets + bucket_for_hash(hash)); } template iterator find(const K& key) { return find(key, hash_key(key)); } template iterator find(const K& key, std::size_t hash) { return find_impl(key, hash, m_buckets + bucket_for_hash(hash)); } template const_iterator find(const K& key) const { return find(key, hash_key(key)); } template const_iterator find(const K& key, std::size_t hash) const { return find_impl(key, hash, m_buckets + bucket_for_hash(hash)); } template bool contains(const K& key) const { return contains(key, hash_key(key)); } template bool contains(const K& key, std::size_t hash) const { return count(key, hash) != 0; } template std::pair equal_range(const K& key) { return equal_range(key, hash_key(key)); } template std::pair equal_range(const K& key, std::size_t hash) { iterator it = find(key, hash); return std::make_pair(it, (it == end())?it:std::next(it)); } template std::pair equal_range(const K& key) const { return equal_range(key, hash_key(key)); } template std::pair equal_range(const K& key, std::size_t hash) const { const_iterator it = find(key, hash); return std::make_pair(it, (it == cend())?it:std::next(it)); } /* * Bucket interface */ size_type bucket_count() const { /* * So that the last bucket can have NeighborhoodSize neighbors, the size of the bucket array is a little * bigger than the real number of buckets when not empty. * We could use some of the buckets at the beginning, but it is faster this way as we avoid extra checks. */ if(m_buckets_data.empty()) { return 0; } return m_buckets_data.size() - NeighborhoodSize + 1; } size_type max_bucket_count() const { const std::size_t max_bucket_count = std::min(GrowthPolicy::max_bucket_count(), m_buckets_data.max_size()); return max_bucket_count - NeighborhoodSize + 1; } /* * Hash policy */ float load_factor() const { if(bucket_count() == 0) { return 0; } return float(m_nb_elements)/float(bucket_count()); } float max_load_factor() const { return m_max_load_factor; } void max_load_factor(float ml) { m_max_load_factor = std::max(0.1f, std::min(ml, 0.95f)); m_min_load_threshold_rehash = size_type(float(bucket_count())*MIN_LOAD_FACTOR_FOR_REHASH); m_max_load_threshold_rehash = size_type(float(bucket_count())*m_max_load_factor); } void rehash(size_type count_) { count_ = std::max(count_, size_type(std::ceil(float(size())/max_load_factor()))); rehash_impl(count_); } void reserve(size_type count_) { rehash(size_type(std::ceil(float(count_)/max_load_factor()))); } /* * Observers */ hasher hash_function() const { return static_cast(*this); } key_equal key_eq() const { return static_cast(*this); } /* * Other */ iterator mutable_iterator(const_iterator pos) { if(pos.m_buckets_iterator != pos.m_buckets_end_iterator) { // Get a non-const iterator auto it = m_buckets_data.begin() + std::distance(m_buckets_data.cbegin(), pos.m_buckets_iterator); return iterator(it, m_buckets_data.end(), m_overflow_elements.begin()); } else { // Get a non-const iterator auto it = mutable_overflow_iterator(pos.m_overflow_iterator); return iterator(m_buckets_data.end(), m_buckets_data.end(), it); } } size_type overflow_size() const noexcept { return m_overflow_elements.size(); } template::value>::type* = nullptr> typename U::key_compare key_comp() const { return m_overflow_elements.key_comp(); } private: template std::size_t hash_key(const K& key) const { return Hash::operator()(key); } template bool compare_keys(const K1& key1, const K2& key2) const { return KeyEqual::operator()(key1, key2); } std::size_t bucket_for_hash(std::size_t hash) const { const std::size_t bucket = GrowthPolicy::bucket_for_hash(hash); tsl_hh_assert(bucket < m_buckets_data.size() || (bucket == 0 && m_buckets_data.empty())); return bucket; } template::value>::type* = nullptr> void rehash_impl(size_type count_) { hopscotch_hash new_map = new_hopscotch_hash(count_); if(!m_overflow_elements.empty()) { new_map.m_overflow_elements.swap(m_overflow_elements); new_map.m_nb_elements += new_map.m_overflow_elements.size(); for(const value_type& value : new_map.m_overflow_elements) { const std::size_t ibucket_for_hash = new_map.bucket_for_hash(new_map.hash_key(KeySelect()(value))); new_map.m_buckets[ibucket_for_hash].set_overflow(true); } } #ifndef TSL_HH_NO_EXCEPTIONS try { #endif const bool use_stored_hash = USE_STORED_HASH_ON_REHASH(new_map.bucket_count()); for(auto it_bucket = m_buckets_data.begin(); it_bucket != m_buckets_data.end(); ++it_bucket) { if(it_bucket->empty()) { continue; } const std::size_t hash = use_stored_hash? it_bucket->truncated_bucket_hash(): new_map.hash_key(KeySelect()(it_bucket->value())); const std::size_t ibucket_for_hash = new_map.bucket_for_hash(hash); new_map.insert_value(ibucket_for_hash, hash, std::move(it_bucket->value())); erase_from_bucket(*it_bucket, bucket_for_hash(hash)); } #ifndef TSL_HH_NO_EXCEPTIONS } /* * The call to insert_value may throw an exception if an element is added to the overflow * list and the memory allocation fails. Rollback the elements in this case. */ catch(...) { m_overflow_elements.swap(new_map.m_overflow_elements); const bool use_stored_hash = USE_STORED_HASH_ON_REHASH(new_map.bucket_count()); for(auto it_bucket = new_map.m_buckets_data.begin(); it_bucket != new_map.m_buckets_data.end(); ++it_bucket) { if(it_bucket->empty()) { continue; } const std::size_t hash = use_stored_hash? it_bucket->truncated_bucket_hash(): hash_key(KeySelect()(it_bucket->value())); const std::size_t ibucket_for_hash = bucket_for_hash(hash); // The elements we insert were not in the overflow list before the switch. // They will not be go in the overflow list if we rollback the switch. insert_value(ibucket_for_hash, hash, std::move(it_bucket->value())); } throw; } #endif new_map.swap(*this); } template::value && !std::is_nothrow_move_constructible::value>::type* = nullptr> void rehash_impl(size_type count_) { hopscotch_hash new_map = new_hopscotch_hash(count_); const bool use_stored_hash = USE_STORED_HASH_ON_REHASH(new_map.bucket_count()); for(const hopscotch_bucket& bucket: m_buckets_data) { if(bucket.empty()) { continue; } const std::size_t hash = use_stored_hash? bucket.truncated_bucket_hash(): new_map.hash_key(KeySelect()(bucket.value())); const std::size_t ibucket_for_hash = new_map.bucket_for_hash(hash); new_map.insert_value(ibucket_for_hash, hash, bucket.value()); } for(const value_type& value: m_overflow_elements) { const std::size_t hash = new_map.hash_key(KeySelect()(value)); const std::size_t ibucket_for_hash = new_map.bucket_for_hash(hash); new_map.insert_value(ibucket_for_hash, hash, value); } new_map.swap(*this); } #ifdef TSL_HH_NO_RANGE_ERASE_WITH_CONST_ITERATOR iterator_overflow mutable_overflow_iterator(const_iterator_overflow it) { return std::next(m_overflow_elements.begin(), std::distance(m_overflow_elements.cbegin(), it)); } #else iterator_overflow mutable_overflow_iterator(const_iterator_overflow it) { return m_overflow_elements.erase(it, it); } #endif // iterator is in overflow list iterator_overflow erase_from_overflow(const_iterator_overflow pos, std::size_t ibucket_for_hash) { #ifdef TSL_HH_NO_RANGE_ERASE_WITH_CONST_ITERATOR auto it_next = m_overflow_elements.erase(mutable_overflow_iterator(pos)); #else auto it_next = m_overflow_elements.erase(pos); #endif m_nb_elements--; // Check if we can remove the overflow flag tsl_hh_assert(m_buckets[ibucket_for_hash].has_overflow()); for(const value_type& value: m_overflow_elements) { const std::size_t bucket_for_value = bucket_for_hash(hash_key(KeySelect()(value))); if(bucket_for_value == ibucket_for_hash) { return it_next; } } m_buckets[ibucket_for_hash].set_overflow(false); return it_next; } /** * bucket_for_value is the bucket in which the value is. * ibucket_for_hash is the bucket where the value belongs. */ void erase_from_bucket(hopscotch_bucket& bucket_for_value, std::size_t ibucket_for_hash) noexcept { const std::size_t ibucket_for_value = std::distance(m_buckets_data.data(), &bucket_for_value); tsl_hh_assert(ibucket_for_value >= ibucket_for_hash); bucket_for_value.remove_value(); m_buckets[ibucket_for_hash].toggle_neighbor_presence(ibucket_for_value - ibucket_for_hash); m_nb_elements--; } template std::pair insert_or_assign_impl(K&& key, M&& obj) { auto it = try_emplace_impl(std::forward(key), std::forward(obj)); if(!it.second) { it.first.value() = std::forward(obj); } return it; } template std::pair try_emplace_impl(P&& key, Args&&... args_value) { const std::size_t hash = hash_key(key); const std::size_t ibucket_for_hash = bucket_for_hash(hash); // Check if already presents auto it_find = find_impl(key, hash, m_buckets + ibucket_for_hash); if(it_find != end()) { return std::make_pair(it_find, false); } return insert_value(ibucket_for_hash, hash, std::piecewise_construct, std::forward_as_tuple(std::forward

(key)), std::forward_as_tuple(std::forward(args_value)...)); } template std::pair insert_impl(P&& value) { const std::size_t hash = hash_key(KeySelect()(value)); const std::size_t ibucket_for_hash = bucket_for_hash(hash); // Check if already presents auto it_find = find_impl(KeySelect()(value), hash, m_buckets + ibucket_for_hash); if(it_find != end()) { return std::make_pair(it_find, false); } return insert_value(ibucket_for_hash, hash, std::forward

(value)); } template std::pair insert_value(std::size_t ibucket_for_hash, std::size_t hash, Args&&... value_type_args) { if((m_nb_elements - m_overflow_elements.size()) >= m_max_load_threshold_rehash) { rehash(GrowthPolicy::next_bucket_count()); ibucket_for_hash = bucket_for_hash(hash); } std::size_t ibucket_empty = find_empty_bucket(ibucket_for_hash); if(ibucket_empty < m_buckets_data.size()) { do { tsl_hh_assert(ibucket_empty >= ibucket_for_hash); // Empty bucket is in range of NeighborhoodSize, use it if(ibucket_empty - ibucket_for_hash < NeighborhoodSize) { auto it = insert_in_bucket(ibucket_empty, ibucket_for_hash, hash, std::forward(value_type_args)...); return std::make_pair(iterator(it, m_buckets_data.end(), m_overflow_elements.begin()), true); } } // else, try to swap values to get a closer empty bucket while(swap_empty_bucket_closer(ibucket_empty)); } // Load factor is too low or a rehash will not change the neighborhood, put the value in overflow list if(size() < m_min_load_threshold_rehash || !will_neighborhood_change_on_rehash(ibucket_for_hash)) { auto it = insert_in_overflow(ibucket_for_hash, std::forward(value_type_args)...); return std::make_pair(iterator(m_buckets_data.end(), m_buckets_data.end(), it), true); } rehash(GrowthPolicy::next_bucket_count()); ibucket_for_hash = bucket_for_hash(hash); return insert_value(ibucket_for_hash, hash, std::forward(value_type_args)...); } /* * Return true if a rehash will change the position of a key-value in the neighborhood of * ibucket_neighborhood_check. In this case a rehash is needed instead of puting the value in overflow list. */ bool will_neighborhood_change_on_rehash(size_t ibucket_neighborhood_check) const { std::size_t expand_bucket_count = GrowthPolicy::next_bucket_count(); GrowthPolicy expand_growth_policy(expand_bucket_count); const bool use_stored_hash = USE_STORED_HASH_ON_REHASH(expand_bucket_count); for(size_t ibucket = ibucket_neighborhood_check; ibucket < m_buckets_data.size() && (ibucket - ibucket_neighborhood_check) < NeighborhoodSize; ++ibucket) { tsl_hh_assert(!m_buckets[ibucket].empty()); const size_t hash = use_stored_hash? m_buckets[ibucket].truncated_bucket_hash(): hash_key(KeySelect()(m_buckets[ibucket].value())); if(bucket_for_hash(hash) != expand_growth_policy.bucket_for_hash(hash)) { return true; } } return false; } /* * Return the index of an empty bucket in m_buckets_data. * If none, the returned index equals m_buckets_data.size() */ std::size_t find_empty_bucket(std::size_t ibucket_start) const { const std::size_t limit = std::min(ibucket_start + MAX_PROBES_FOR_EMPTY_BUCKET, m_buckets_data.size()); for(; ibucket_start < limit; ibucket_start++) { if(m_buckets[ibucket_start].empty()) { return ibucket_start; } } return m_buckets_data.size(); } /* * Insert value in ibucket_empty where value originally belongs to ibucket_for_hash * * Return bucket iterator to ibucket_empty */ template iterator_buckets insert_in_bucket(std::size_t ibucket_empty, std::size_t ibucket_for_hash, std::size_t hash, Args&&... value_type_args) { tsl_hh_assert(ibucket_empty >= ibucket_for_hash ); tsl_hh_assert(m_buckets[ibucket_empty].empty()); m_buckets[ibucket_empty].set_value_of_empty_bucket(hopscotch_bucket::truncate_hash(hash), std::forward(value_type_args)...); tsl_hh_assert(!m_buckets[ibucket_for_hash].empty()); m_buckets[ibucket_for_hash].toggle_neighbor_presence(ibucket_empty - ibucket_for_hash); m_nb_elements++; return m_buckets_data.begin() + ibucket_empty; } template::value>::type* = nullptr> iterator_overflow insert_in_overflow(std::size_t ibucket_for_hash, Args&&... value_type_args) { auto it = m_overflow_elements.emplace(m_overflow_elements.end(), std::forward(value_type_args)...); m_buckets[ibucket_for_hash].set_overflow(true); m_nb_elements++; return it; } template::value>::type* = nullptr> iterator_overflow insert_in_overflow(std::size_t ibucket_for_hash, Args&&... value_type_args) { auto it = m_overflow_elements.emplace(std::forward(value_type_args)...).first; m_buckets[ibucket_for_hash].set_overflow(true); m_nb_elements++; return it; } /* * Try to swap the bucket ibucket_empty_in_out with a bucket preceding it while keeping the neighborhood * conditions correct. * * If a swap was possible, the position of ibucket_empty_in_out will be closer to 0 and true will re returned. */ bool swap_empty_bucket_closer(std::size_t& ibucket_empty_in_out) { tsl_hh_assert(ibucket_empty_in_out >= NeighborhoodSize); const std::size_t neighborhood_start = ibucket_empty_in_out - NeighborhoodSize + 1; for(std::size_t to_check = neighborhood_start; to_check < ibucket_empty_in_out; to_check++) { neighborhood_bitmap neighborhood_infos = m_buckets[to_check].neighborhood_infos(); std::size_t to_swap = to_check; while(neighborhood_infos != 0 && to_swap < ibucket_empty_in_out) { if((neighborhood_infos & 1) == 1) { tsl_hh_assert(m_buckets[ibucket_empty_in_out].empty()); tsl_hh_assert(!m_buckets[to_swap].empty()); m_buckets[to_swap].swap_value_into_empty_bucket(m_buckets[ibucket_empty_in_out]); tsl_hh_assert(!m_buckets[to_check].check_neighbor_presence(ibucket_empty_in_out - to_check)); tsl_hh_assert(m_buckets[to_check].check_neighbor_presence(to_swap - to_check)); m_buckets[to_check].toggle_neighbor_presence(ibucket_empty_in_out - to_check); m_buckets[to_check].toggle_neighbor_presence(to_swap - to_check); ibucket_empty_in_out = to_swap; return true; } to_swap++; neighborhood_infos = neighborhood_bitmap(neighborhood_infos >> 1); } } return false; } template::value>::type* = nullptr> typename U::value_type* find_value_impl(const K& key, std::size_t hash, hopscotch_bucket* bucket_for_hash) { return const_cast( static_cast(this)->find_value_impl(key, hash, bucket_for_hash)); } /* * Avoid the creation of an iterator to just get the value for operator[] and at() in maps. Faster this way. * * Return null if no value for the key (TODO use std::optional when available). */ template::value>::type* = nullptr> const typename U::value_type* find_value_impl(const K& key, std::size_t hash, const hopscotch_bucket* bucket_for_hash) const { const hopscotch_bucket* bucket_found = find_in_buckets(key, hash, bucket_for_hash); if(bucket_found != nullptr) { return std::addressof(ValueSelect()(bucket_found->value())); } if(bucket_for_hash->has_overflow()) { auto it_overflow = find_in_overflow(key); if(it_overflow != m_overflow_elements.end()) { return std::addressof(ValueSelect()(*it_overflow)); } } return nullptr; } template size_type count_impl(const K& key, std::size_t hash, const hopscotch_bucket* bucket_for_hash) const { if(find_in_buckets(key, hash, bucket_for_hash) != nullptr) { return 1; } else if(bucket_for_hash->has_overflow() && find_in_overflow(key) != m_overflow_elements.cend()) { return 1; } else { return 0; } } template iterator find_impl(const K& key, std::size_t hash, hopscotch_bucket* bucket_for_hash) { hopscotch_bucket* bucket_found = find_in_buckets(key, hash, bucket_for_hash); if(bucket_found != nullptr) { return iterator(m_buckets_data.begin() + std::distance(m_buckets_data.data(), bucket_found), m_buckets_data.end(), m_overflow_elements.begin()); } if(!bucket_for_hash->has_overflow()) { return end(); } return iterator(m_buckets_data.end(), m_buckets_data.end(), find_in_overflow(key)); } template const_iterator find_impl(const K& key, std::size_t hash, const hopscotch_bucket* bucket_for_hash) const { const hopscotch_bucket* bucket_found = find_in_buckets(key, hash, bucket_for_hash); if(bucket_found != nullptr) { return const_iterator(m_buckets_data.cbegin() + std::distance(m_buckets_data.data(), bucket_found), m_buckets_data.cend(), m_overflow_elements.cbegin()); } if(!bucket_for_hash->has_overflow()) { return cend(); } return const_iterator(m_buckets_data.cend(), m_buckets_data.cend(), find_in_overflow(key)); } template hopscotch_bucket* find_in_buckets(const K& key, std::size_t hash, hopscotch_bucket* bucket_for_hash) { const hopscotch_bucket* bucket_found = static_cast(this)->find_in_buckets(key, hash, bucket_for_hash); return const_cast(bucket_found); } /** * Return a pointer to the bucket which has the value, nullptr otherwise. */ template const hopscotch_bucket* find_in_buckets(const K& key, std::size_t hash, const hopscotch_bucket* bucket_for_hash) const { (void) hash; // Avoid warning of unused variable when StoreHash is false; // TODO Try to optimize the function. // I tried to use ffs and __builtin_ffs functions but I could not reduce the time the function // takes with -march=native neighborhood_bitmap neighborhood_infos = bucket_for_hash->neighborhood_infos(); while(neighborhood_infos != 0) { if((neighborhood_infos & 1) == 1) { // Check StoreHash before calling bucket_hash_equal. Functionally it doesn't change anythin. // If StoreHash is false, bucket_hash_equal is a no-op. Avoiding the call is there to help // GCC optimizes `hash` parameter away, it seems to not be able to do without this hint. if((!StoreHash || bucket_for_hash->bucket_hash_equal(hash)) && compare_keys(KeySelect()(bucket_for_hash->value()), key)) { return bucket_for_hash; } } ++bucket_for_hash; neighborhood_infos = neighborhood_bitmap(neighborhood_infos >> 1); } return nullptr; } template::value>::type* = nullptr> iterator_overflow find_in_overflow(const K& key) { return std::find_if(m_overflow_elements.begin(), m_overflow_elements.end(), [&](const value_type& value) { return compare_keys(key, KeySelect()(value)); }); } template::value>::type* = nullptr> const_iterator_overflow find_in_overflow(const K& key) const { return std::find_if(m_overflow_elements.cbegin(), m_overflow_elements.cend(), [&](const value_type& value) { return compare_keys(key, KeySelect()(value)); }); } template::value>::type* = nullptr> iterator_overflow find_in_overflow(const K& key) { return m_overflow_elements.find(key); } template::value>::type* = nullptr> const_iterator_overflow find_in_overflow(const K& key) const { return m_overflow_elements.find(key); } template::value>::type* = nullptr> hopscotch_hash new_hopscotch_hash(size_type bucket_count) { return hopscotch_hash(bucket_count, static_cast(*this), static_cast(*this), get_allocator(), m_max_load_factor); } template::value>::type* = nullptr> hopscotch_hash new_hopscotch_hash(size_type bucket_count) { return hopscotch_hash(bucket_count, static_cast(*this), static_cast(*this), get_allocator(), m_max_load_factor, m_overflow_elements.key_comp()); } public: static const size_type DEFAULT_INIT_BUCKETS_SIZE = 0; static constexpr float DEFAULT_MAX_LOAD_FACTOR = (NeighborhoodSize <= 30)?0.8f:0.9f; private: static const std::size_t MAX_PROBES_FOR_EMPTY_BUCKET = 12*NeighborhoodSize; static constexpr float MIN_LOAD_FACTOR_FOR_REHASH = 0.1f; /** * We can only use the hash on rehash if the size of the hash type is the same as the stored one or * if we use a power of two modulo. In the case of the power of two modulo, we just mask * the least significant bytes, we just have to check that the truncated_hash_type didn't truncated * too much bytes. */ template::value>::type* = nullptr> static bool USE_STORED_HASH_ON_REHASH(size_type /*bucket_count*/) { return StoreHash; } template::value>::type* = nullptr> static bool USE_STORED_HASH_ON_REHASH(size_type bucket_count) { (void) bucket_count; if(StoreHash && is_power_of_two_policy::value) { tsl_hh_assert(bucket_count > 0); return (bucket_count - 1) <= std::numeric_limits::max(); } else { return false; } } /** * Return an always valid pointer to an static empty hopscotch_bucket. */ hopscotch_bucket* static_empty_bucket_ptr() { static hopscotch_bucket empty_bucket; return &empty_bucket; } private: buckets_container_type m_buckets_data; overflow_container_type m_overflow_elements; /** * Points to m_buckets_data.data() if !m_buckets_data.empty() otherwise points to static_empty_bucket_ptr. * This variable is useful to avoid the cost of checking if m_buckets_data is empty when trying * to find an element. * * TODO Remove m_buckets_data and only use a pointer+size instead of a pointer+vector to save some space in the hopscotch_hash object. */ hopscotch_bucket* m_buckets; size_type m_nb_elements; /** * Min size of the hash table before a rehash can occurs automatically (except if m_max_load_threshold_rehash os reached). * If the neighborhood of a bucket is full before the min is reacher, the elements are put into m_overflow_elements. */ size_type m_min_load_threshold_rehash; /** * Max size of the hash table before a rehash occurs automatically to grow the table. */ size_type m_max_load_threshold_rehash; float m_max_load_factor; }; } // end namespace detail_hopscotch_hash } // end namespace tsl #endif ================================================ FILE: External/tsl/hopscotch_map.h ================================================ /** * MIT License * * Copyright (c) 2017 Thibaut Goetghebuer-Planchon * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #ifndef TSL_HOPSCOTCH_MAP_H #define TSL_HOPSCOTCH_MAP_H #include #include #include #include #include #include #include #include #include "hopscotch_hash.h" namespace tsl { /** * Implementation of a hash map using the hopscotch hashing algorithm. * * The Key and the value T must be either nothrow move-constructible, copy-constructible or both. * * The size of the neighborhood (NeighborhoodSize) must be > 0 and <= 62 if StoreHash is false. * When StoreHash is true, 32-bits of the hash will be stored alongside the neighborhood limiting * the NeighborhoodSize to <= 30. There is no memory usage difference between * 'NeighborhoodSize 62; StoreHash false' and 'NeighborhoodSize 30; StoreHash true'. * * Storing the hash may improve performance on insert during the rehash process if the hash takes time * to compute. It may also improve read performance if the KeyEqual function takes time (or incurs a cache-miss). * If used with simple Hash and KeyEqual it may slow things down. * * StoreHash can only be set if the GrowthPolicy is set to tsl::power_of_two_growth_policy. * * GrowthPolicy defines how the map grows and consequently how a hash value is mapped to a bucket. * By default the map uses tsl::power_of_two_growth_policy. This policy keeps the number of buckets * to a power of two and uses a mask to map the hash to a bucket instead of the slow modulo. * You may define your own growth policy, check tsl::power_of_two_growth_policy for the interface. * * If the destructors of Key or T throw an exception, behaviour of the class is undefined. * * Iterators invalidation: * - clear, operator=, reserve, rehash: always invalidate the iterators. * - insert, emplace, emplace_hint, operator[]: if there is an effective insert, invalidate the iterators * if a displacement is needed to resolve a collision (which mean that most of the time, * insert will invalidate the iterators). Or if there is a rehash. * - erase: iterator on the erased element is the only one which become invalid. */ template, class KeyEqual = std::equal_to, class Allocator = std::allocator>, unsigned int NeighborhoodSize = 62, bool StoreHash = false, class GrowthPolicy = tsl::hh::power_of_two_growth_policy<2>> class hopscotch_map { private: template using has_is_transparent = tsl::detail_hopscotch_hash::has_is_transparent; class KeySelect { public: using key_type = Key; const key_type& operator()(const std::pair& key_value) const { return key_value.first; } key_type& operator()(std::pair& key_value) { return key_value.first; } }; class ValueSelect { public: using value_type = T; const value_type& operator()(const std::pair& key_value) const { return key_value.second; } value_type& operator()(std::pair& key_value) { return key_value.second; } }; using overflow_container_type = std::list, Allocator>; using ht = detail_hopscotch_hash::hopscotch_hash, KeySelect, ValueSelect, Hash, KeyEqual, Allocator, NeighborhoodSize, StoreHash, GrowthPolicy, overflow_container_type>; public: using key_type = typename ht::key_type; using mapped_type = T; using value_type = typename ht::value_type; using size_type = typename ht::size_type; using difference_type = typename ht::difference_type; using hasher = typename ht::hasher; using key_equal = typename ht::key_equal; using allocator_type = typename ht::allocator_type; using reference = typename ht::reference; using const_reference = typename ht::const_reference; using pointer = typename ht::pointer; using const_pointer = typename ht::const_pointer; using iterator = typename ht::iterator; using const_iterator = typename ht::const_iterator; /* * Constructors */ hopscotch_map() : hopscotch_map(ht::DEFAULT_INIT_BUCKETS_SIZE) { } explicit hopscotch_map(size_type bucket_count, const Hash& hash = Hash(), const KeyEqual& equal = KeyEqual(), const Allocator& alloc = Allocator()) : m_ht(bucket_count, hash, equal, alloc, ht::DEFAULT_MAX_LOAD_FACTOR) { } hopscotch_map(size_type bucket_count, const Allocator& alloc) : hopscotch_map(bucket_count, Hash(), KeyEqual(), alloc) { } hopscotch_map(size_type bucket_count, const Hash& hash, const Allocator& alloc) : hopscotch_map(bucket_count, hash, KeyEqual(), alloc) { } explicit hopscotch_map(const Allocator& alloc) : hopscotch_map(ht::DEFAULT_INIT_BUCKETS_SIZE, alloc) { } template hopscotch_map(InputIt first, InputIt last, size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, const Hash& hash = Hash(), const KeyEqual& equal = KeyEqual(), const Allocator& alloc = Allocator()) : hopscotch_map(bucket_count, hash, equal, alloc) { insert(first, last); } template hopscotch_map(InputIt first, InputIt last, size_type bucket_count, const Allocator& alloc) : hopscotch_map(first, last, bucket_count, Hash(), KeyEqual(), alloc) { } template hopscotch_map(InputIt first, InputIt last, size_type bucket_count, const Hash& hash, const Allocator& alloc) : hopscotch_map(first, last, bucket_count, hash, KeyEqual(), alloc) { } hopscotch_map(std::initializer_list init, size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, const Hash& hash = Hash(), const KeyEqual& equal = KeyEqual(), const Allocator& alloc = Allocator()) : hopscotch_map(init.begin(), init.end(), bucket_count, hash, equal, alloc) { } hopscotch_map(std::initializer_list init, size_type bucket_count, const Allocator& alloc) : hopscotch_map(init.begin(), init.end(), bucket_count, Hash(), KeyEqual(), alloc) { } hopscotch_map(std::initializer_list init, size_type bucket_count, const Hash& hash, const Allocator& alloc) : hopscotch_map(init.begin(), init.end(), bucket_count, hash, KeyEqual(), alloc) { } hopscotch_map& operator=(std::initializer_list ilist) { m_ht.clear(); m_ht.reserve(ilist.size()); m_ht.insert(ilist.begin(), ilist.end()); return *this; } allocator_type get_allocator() const { return m_ht.get_allocator(); } /* * Iterators */ iterator begin() noexcept { return m_ht.begin(); } const_iterator begin() const noexcept { return m_ht.begin(); } const_iterator cbegin() const noexcept { return m_ht.cbegin(); } iterator end() noexcept { return m_ht.end(); } const_iterator end() const noexcept { return m_ht.end(); } const_iterator cend() const noexcept { return m_ht.cend(); } /* * Capacity */ bool empty() const noexcept { return m_ht.empty(); } size_type size() const noexcept { return m_ht.size(); } size_type max_size() const noexcept { return m_ht.max_size(); } /* * Modifiers */ void clear() noexcept { m_ht.clear(); } std::pair insert(const value_type& value) { return m_ht.insert(value); } template::value>::type* = nullptr> std::pair insert(P&& value) { return m_ht.insert(std::forward

(value)); } std::pair insert(value_type&& value) { return m_ht.insert(std::move(value)); } iterator insert(const_iterator hint, const value_type& value) { return m_ht.insert(hint, value); } template::value>::type* = nullptr> iterator insert(const_iterator hint, P&& value) { return m_ht.insert(hint, std::forward

(value)); } iterator insert(const_iterator hint, value_type&& value) { return m_ht.insert(hint, std::move(value)); } template void insert(InputIt first, InputIt last) { m_ht.insert(first, last); } void insert(std::initializer_list ilist) { m_ht.insert(ilist.begin(), ilist.end()); } template std::pair insert_or_assign(const key_type& k, M&& obj) { return m_ht.insert_or_assign(k, std::forward(obj)); } template std::pair insert_or_assign(key_type&& k, M&& obj) { return m_ht.insert_or_assign(std::move(k), std::forward(obj)); } template iterator insert_or_assign(const_iterator hint, const key_type& k, M&& obj) { return m_ht.insert_or_assign(hint, k, std::forward(obj)); } template iterator insert_or_assign(const_iterator hint, key_type&& k, M&& obj) { return m_ht.insert_or_assign(hint, std::move(k), std::forward(obj)); } /** * Due to the way elements are stored, emplace will need to move or copy the key-value once. * The method is equivalent to insert(value_type(std::forward(args)...)); * * Mainly here for compatibility with the std::unordered_map interface. */ template std::pair emplace(Args&&... args) { return m_ht.emplace(std::forward(args)...); } /** * Due to the way elements are stored, emplace_hint will need to move or copy the key-value once. * The method is equivalent to insert(hint, value_type(std::forward(args)...)); * * Mainly here for compatibility with the std::unordered_map interface. */ template iterator emplace_hint(const_iterator hint, Args&&... args) { return m_ht.emplace_hint(hint, std::forward(args)...); } template std::pair try_emplace(const key_type& k, Args&&... args) { return m_ht.try_emplace(k, std::forward(args)...); } template std::pair try_emplace(key_type&& k, Args&&... args) { return m_ht.try_emplace(std::move(k), std::forward(args)...); } template iterator try_emplace(const_iterator hint, const key_type& k, Args&&... args) { return m_ht.try_emplace(hint, k, std::forward(args)...); } template iterator try_emplace(const_iterator hint, key_type&& k, Args&&... args) { return m_ht.try_emplace(hint, std::move(k), std::forward(args)...); } iterator erase(iterator pos) { return m_ht.erase(pos); } iterator erase(const_iterator pos) { return m_ht.erase(pos); } iterator erase(const_iterator first, const_iterator last) { return m_ht.erase(first, last); } size_type erase(const key_type& key) { return m_ht.erase(key); } /** * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup to the value if you already have the hash. */ size_type erase(const key_type& key, std::size_t precalculated_hash) { return m_ht.erase(key, precalculated_hash); } /** * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. * If so, K must be hashable and comparable to Key. */ template::value>::type* = nullptr> size_type erase(const K& key) { return m_ht.erase(key); } /** * @copydoc erase(const K& key) * * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup to the value if you already have the hash. */ template::value>::type* = nullptr> size_type erase(const K& key, std::size_t precalculated_hash) { return m_ht.erase(key, precalculated_hash); } void swap(hopscotch_map& other) { other.m_ht.swap(m_ht); } /* * Lookup */ T& at(const Key& key) { return m_ht.at(key); } /** * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ T& at(const Key& key, std::size_t precalculated_hash) { return m_ht.at(key, precalculated_hash); } const T& at(const Key& key) const { return m_ht.at(key); } /** * @copydoc at(const Key& key, std::size_t precalculated_hash) */ const T& at(const Key& key, std::size_t precalculated_hash) const { return m_ht.at(key, precalculated_hash); } /** * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. * If so, K must be hashable and comparable to Key. */ template::value>::type* = nullptr> T& at(const K& key) { return m_ht.at(key); } /** * @copydoc at(const K& key) * * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ template::value>::type* = nullptr> T& at(const K& key, std::size_t precalculated_hash) { return m_ht.at(key, precalculated_hash); } /** * @copydoc at(const K& key) */ template::value>::type* = nullptr> const T& at(const K& key) const { return m_ht.at(key); } /** * @copydoc at(const K& key, std::size_t precalculated_hash) */ template::value>::type* = nullptr> const T& at(const K& key, std::size_t precalculated_hash) const { return m_ht.at(key, precalculated_hash); } T& operator[](const Key& key) { return m_ht[key]; } T& operator[](Key&& key) { return m_ht[std::move(key)]; } size_type count(const Key& key) const { return m_ht.count(key); } /** * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ size_type count(const Key& key, std::size_t precalculated_hash) const { return m_ht.count(key, precalculated_hash); } /** * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. * If so, K must be hashable and comparable to Key. */ template::value>::type* = nullptr> size_type count(const K& key) const { return m_ht.count(key); } /** * @copydoc count(const K& key) const * * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ template::value>::type* = nullptr> size_type count(const K& key, std::size_t precalculated_hash) const { return m_ht.count(key, precalculated_hash); } iterator find(const Key& key) { return m_ht.find(key); } /** * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ iterator find(const Key& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } const_iterator find(const Key& key) const { return m_ht.find(key); } /** * @copydoc find(const Key& key, std::size_t precalculated_hash) */ const_iterator find(const Key& key, std::size_t precalculated_hash) const { return m_ht.find(key, precalculated_hash); } /** * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. * If so, K must be hashable and comparable to Key. */ template::value>::type* = nullptr> iterator find(const K& key) { return m_ht.find(key); } /** * @copydoc find(const K& key) * * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ template::value>::type* = nullptr> iterator find(const K& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } /** * @copydoc find(const K& key) */ template::value>::type* = nullptr> const_iterator find(const K& key) const { return m_ht.find(key); } /** * @copydoc find(const K& key) * * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ template::value>::type* = nullptr> const_iterator find(const K& key, std::size_t precalculated_hash) const { return m_ht.find(key, precalculated_hash); } bool contains(const Key& key) const { return m_ht.contains(key); } /** * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ bool contains(const Key& key, std::size_t precalculated_hash) const { return m_ht.contains(key, precalculated_hash); } /** * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. * If so, K must be hashable and comparable to Key. */ template::value>::type* = nullptr> bool contains(const K& key) const { return m_ht.contains(key); } /** * @copydoc contains(const K& key) const * * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ template::value>::type* = nullptr> bool contains(const K& key, std::size_t precalculated_hash) const { return m_ht.contains(key, precalculated_hash); } std::pair equal_range(const Key& key) { return m_ht.equal_range(key); } /** * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ std::pair equal_range(const Key& key, std::size_t precalculated_hash) { return m_ht.equal_range(key, precalculated_hash); } std::pair equal_range(const Key& key) const { return m_ht.equal_range(key); } /** * @copydoc equal_range(const Key& key, std::size_t precalculated_hash) */ std::pair equal_range(const Key& key, std::size_t precalculated_hash) const { return m_ht.equal_range(key, precalculated_hash); } /** * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. * If so, K must be hashable and comparable to Key. */ template::value>::type* = nullptr> std::pair equal_range(const K& key) { return m_ht.equal_range(key); } /** * @copydoc equal_range(const K& key) * * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ template::value>::type* = nullptr> std::pair equal_range(const K& key, std::size_t precalculated_hash) { return m_ht.equal_range(key, precalculated_hash); } /** * @copydoc equal_range(const K& key) */ template::value>::type* = nullptr> std::pair equal_range(const K& key) const { return m_ht.equal_range(key); } /** * @copydoc equal_range(const K& key, std::size_t precalculated_hash) */ template::value>::type* = nullptr> std::pair equal_range(const K& key, std::size_t precalculated_hash) const { return m_ht.equal_range(key, precalculated_hash); } /* * Bucket interface */ size_type bucket_count() const { return m_ht.bucket_count(); } size_type max_bucket_count() const { return m_ht.max_bucket_count(); } /* * Hash policy */ float load_factor() const { return m_ht.load_factor(); } float max_load_factor() const { return m_ht.max_load_factor(); } void max_load_factor(float ml) { m_ht.max_load_factor(ml); } void rehash(size_type count_) { m_ht.rehash(count_); } void reserve(size_type count_) { m_ht.reserve(count_); } /* * Observers */ hasher hash_function() const { return m_ht.hash_function(); } key_equal key_eq() const { return m_ht.key_eq(); } /* * Other */ /** * Convert a const_iterator to an iterator. */ iterator mutable_iterator(const_iterator pos) { return m_ht.mutable_iterator(pos); } size_type overflow_size() const noexcept { return m_ht.overflow_size(); } friend bool operator==(const hopscotch_map& lhs, const hopscotch_map& rhs) { if(lhs.size() != rhs.size()) { return false; } for(const auto& element_lhs : lhs) { const auto it_element_rhs = rhs.find(element_lhs.first); if(it_element_rhs == rhs.cend() || element_lhs.second != it_element_rhs->second) { return false; } } return true; } friend bool operator!=(const hopscotch_map& lhs, const hopscotch_map& rhs) { return !operator==(lhs, rhs); } friend void swap(hopscotch_map& lhs, hopscotch_map& rhs) { lhs.swap(rhs); } private: ht m_ht; }; /** * Same as `tsl::hopscotch_map`. */ template, class KeyEqual = std::equal_to, class Allocator = std::allocator>, unsigned int NeighborhoodSize = 62, bool StoreHash = false> using hopscotch_pg_map = hopscotch_map; } // end namespace tsl #endif ================================================ FILE: External/tsl/hopscotch_set.h ================================================ /** * MIT License * * Copyright (c) 2017 Thibaut Goetghebuer-Planchon * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #ifndef TSL_HOPSCOTCH_SET_H #define TSL_HOPSCOTCH_SET_H #include #include #include #include #include #include #include #include #include "hopscotch_hash.h" namespace tsl { /** * Implementation of a hash set using the hopscotch hashing algorithm. * * The Key must be either nothrow move-constructible, copy-constructible or both. * * The size of the neighborhood (NeighborhoodSize) must be > 0 and <= 62 if StoreHash is false. * When StoreHash is true, 32-bits of the hash will be stored alongside the neighborhood limiting * the NeighborhoodSize to <= 30. There is no memory usage difference between * 'NeighborhoodSize 62; StoreHash false' and 'NeighborhoodSize 30; StoreHash true'. * * Storing the hash may improve performance on insert during the rehash process if the hash takes time * to compute. It may also improve read performance if the KeyEqual function takes time (or incurs a cache-miss). * If used with simple Hash and KeyEqual it may slow things down. * * StoreHash can only be set if the GrowthPolicy is set to tsl::power_of_two_growth_policy. * * GrowthPolicy defines how the set grows and consequently how a hash value is mapped to a bucket. * By default the set uses tsl::power_of_two_growth_policy. This policy keeps the number of buckets * to a power of two and uses a mask to set the hash to a bucket instead of the slow modulo. * You may define your own growth policy, check tsl::power_of_two_growth_policy for the interface. * * If the destructor of Key throws an exception, behaviour of the class is undefined. * * Iterators invalidation: * - clear, operator=, reserve, rehash: always invalidate the iterators. * - insert, emplace, emplace_hint, operator[]: if there is an effective insert, invalidate the iterators * if a displacement is needed to resolve a collision (which mean that most of the time, * insert will invalidate the iterators). Or if there is a rehash. * - erase: iterator on the erased element is the only one which become invalid. */ template, class KeyEqual = std::equal_to, class Allocator = std::allocator, unsigned int NeighborhoodSize = 62, bool StoreHash = false, class GrowthPolicy = tsl::hh::power_of_two_growth_policy<2>> class hopscotch_set { private: template using has_is_transparent = tsl::detail_hopscotch_hash::has_is_transparent; class KeySelect { public: using key_type = Key; const key_type& operator()(const Key& key) const { return key; } key_type& operator()(Key& key) { return key; } }; using overflow_container_type = std::list; using ht = detail_hopscotch_hash::hopscotch_hash; public: using key_type = typename ht::key_type; using value_type = typename ht::value_type; using size_type = typename ht::size_type; using difference_type = typename ht::difference_type; using hasher = typename ht::hasher; using key_equal = typename ht::key_equal; using allocator_type = typename ht::allocator_type; using reference = typename ht::reference; using const_reference = typename ht::const_reference; using pointer = typename ht::pointer; using const_pointer = typename ht::const_pointer; using iterator = typename ht::iterator; using const_iterator = typename ht::const_iterator; /* * Constructors */ hopscotch_set() : hopscotch_set(ht::DEFAULT_INIT_BUCKETS_SIZE) { } explicit hopscotch_set(size_type bucket_count, const Hash& hash = Hash(), const KeyEqual& equal = KeyEqual(), const Allocator& alloc = Allocator()) : m_ht(bucket_count, hash, equal, alloc, ht::DEFAULT_MAX_LOAD_FACTOR) { } hopscotch_set(size_type bucket_count, const Allocator& alloc) : hopscotch_set(bucket_count, Hash(), KeyEqual(), alloc) { } hopscotch_set(size_type bucket_count, const Hash& hash, const Allocator& alloc) : hopscotch_set(bucket_count, hash, KeyEqual(), alloc) { } explicit hopscotch_set(const Allocator& alloc) : hopscotch_set(ht::DEFAULT_INIT_BUCKETS_SIZE, alloc) { } template hopscotch_set(InputIt first, InputIt last, size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, const Hash& hash = Hash(), const KeyEqual& equal = KeyEqual(), const Allocator& alloc = Allocator()) : hopscotch_set(bucket_count, hash, equal, alloc) { insert(first, last); } template hopscotch_set(InputIt first, InputIt last, size_type bucket_count, const Allocator& alloc) : hopscotch_set(first, last, bucket_count, Hash(), KeyEqual(), alloc) { } template hopscotch_set(InputIt first, InputIt last, size_type bucket_count, const Hash& hash, const Allocator& alloc) : hopscotch_set(first, last, bucket_count, hash, KeyEqual(), alloc) { } hopscotch_set(std::initializer_list init, size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, const Hash& hash = Hash(), const KeyEqual& equal = KeyEqual(), const Allocator& alloc = Allocator()) : hopscotch_set(init.begin(), init.end(), bucket_count, hash, equal, alloc) { } hopscotch_set(std::initializer_list init, size_type bucket_count, const Allocator& alloc) : hopscotch_set(init.begin(), init.end(), bucket_count, Hash(), KeyEqual(), alloc) { } hopscotch_set(std::initializer_list init, size_type bucket_count, const Hash& hash, const Allocator& alloc) : hopscotch_set(init.begin(), init.end(), bucket_count, hash, KeyEqual(), alloc) { } hopscotch_set& operator=(std::initializer_list ilist) { m_ht.clear(); m_ht.reserve(ilist.size()); m_ht.insert(ilist.begin(), ilist.end()); return *this; } allocator_type get_allocator() const { return m_ht.get_allocator(); } /* * Iterators */ iterator begin() noexcept { return m_ht.begin(); } const_iterator begin() const noexcept { return m_ht.begin(); } const_iterator cbegin() const noexcept { return m_ht.cbegin(); } iterator end() noexcept { return m_ht.end(); } const_iterator end() const noexcept { return m_ht.end(); } const_iterator cend() const noexcept { return m_ht.cend(); } /* * Capacity */ bool empty() const noexcept { return m_ht.empty(); } size_type size() const noexcept { return m_ht.size(); } size_type max_size() const noexcept { return m_ht.max_size(); } /* * Modifiers */ void clear() noexcept { m_ht.clear(); } std::pair insert(const value_type& value) { return m_ht.insert(value); } std::pair insert(value_type&& value) { return m_ht.insert(std::move(value)); } iterator insert(const_iterator hint, const value_type& value) { return m_ht.insert(hint, value); } iterator insert(const_iterator hint, value_type&& value) { return m_ht.insert(hint, std::move(value)); } template void insert(InputIt first, InputIt last) { m_ht.insert(first, last); } void insert(std::initializer_list ilist) { m_ht.insert(ilist.begin(), ilist.end()); } /** * Due to the way elements are stored, emplace will need to move or copy the key-value once. * The method is equivalent to insert(value_type(std::forward(args)...)); * * Mainly here for compatibility with the std::unordered_map interface. */ template std::pair emplace(Args&&... args) { return m_ht.emplace(std::forward(args)...); } /** * Due to the way elements are stored, emplace_hint will need to move or copy the key-value once. * The method is equivalent to insert(hint, value_type(std::forward(args)...)); * * Mainly here for compatibility with the std::unordered_map interface. */ template iterator emplace_hint(const_iterator hint, Args&&... args) { return m_ht.emplace_hint(hint, std::forward(args)...); } iterator erase(iterator pos) { return m_ht.erase(pos); } iterator erase(const_iterator pos) { return m_ht.erase(pos); } iterator erase(const_iterator first, const_iterator last) { return m_ht.erase(first, last); } size_type erase(const key_type& key) { return m_ht.erase(key); } /** * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup to the value if you already have the hash. */ size_type erase(const key_type& key, std::size_t precalculated_hash) { return m_ht.erase(key, precalculated_hash); } /** * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. * If so, K must be hashable and comparable to Key. */ template::value>::type* = nullptr> size_type erase(const K& key) { return m_ht.erase(key); } /** * @copydoc erase(const K& key) * * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup to the value if you already have the hash. */ template::value>::type* = nullptr> size_type erase(const K& key, std::size_t precalculated_hash) { return m_ht.erase(key, precalculated_hash); } void swap(hopscotch_set& other) { other.m_ht.swap(m_ht); } /* * Lookup */ size_type count(const Key& key) const { return m_ht.count(key); } /** * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ size_type count(const Key& key, std::size_t precalculated_hash) const { return m_ht.count(key, precalculated_hash); } /** * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. * If so, K must be hashable and comparable to Key. */ template::value>::type* = nullptr> size_type count(const K& key) const { return m_ht.count(key); } /** * @copydoc count(const K& key) const * * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ template::value>::type* = nullptr> size_type count(const K& key, std::size_t precalculated_hash) const { return m_ht.count(key, precalculated_hash); } iterator find(const Key& key) { return m_ht.find(key); } /** * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ iterator find(const Key& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } const_iterator find(const Key& key) const { return m_ht.find(key); } /** * @copydoc find(const Key& key, std::size_t precalculated_hash) */ const_iterator find(const Key& key, std::size_t precalculated_hash) const { return m_ht.find(key, precalculated_hash); } /** * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. * If so, K must be hashable and comparable to Key. */ template::value>::type* = nullptr> iterator find(const K& key) { return m_ht.find(key); } /** * @copydoc find(const K& key) * * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ template::value>::type* = nullptr> iterator find(const K& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } /** * @copydoc find(const K& key) */ template::value>::type* = nullptr> const_iterator find(const K& key) const { return m_ht.find(key); } /** * @copydoc find(const K& key) * * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ template::value>::type* = nullptr> const_iterator find(const K& key, std::size_t precalculated_hash) const { return m_ht.find(key, precalculated_hash); } bool contains(const Key& key) const { return m_ht.contains(key); } /** * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ bool contains(const Key& key, std::size_t precalculated_hash) const { return m_ht.contains(key, precalculated_hash); } /** * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. * If so, K must be hashable and comparable to Key. */ template::value>::type* = nullptr> bool contains(const K& key) const { return m_ht.contains(key); } /** * @copydoc contains(const K& key) const * * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ template::value>::type* = nullptr> bool contains(const K& key, std::size_t precalculated_hash) const { return m_ht.contains(key, precalculated_hash); } std::pair equal_range(const Key& key) { return m_ht.equal_range(key); } /** * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ std::pair equal_range(const Key& key, std::size_t precalculated_hash) { return m_ht.equal_range(key, precalculated_hash); } std::pair equal_range(const Key& key) const { return m_ht.equal_range(key); } /** * @copydoc equal_range(const Key& key, std::size_t precalculated_hash) */ std::pair equal_range(const Key& key, std::size_t precalculated_hash) const { return m_ht.equal_range(key, precalculated_hash); } /** * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. * If so, K must be hashable and comparable to Key. */ template::value>::type* = nullptr> std::pair equal_range(const K& key) { return m_ht.equal_range(key); } /** * @copydoc equal_range(const K& key) * * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same * as hash_function()(key). Useful to speed-up the lookup if you already have the hash. */ template::value>::type* = nullptr> std::pair equal_range(const K& key, std::size_t precalculated_hash) { return m_ht.equal_range(key, precalculated_hash); } /** * @copydoc equal_range(const K& key) */ template::value>::type* = nullptr> std::pair equal_range(const K& key) const { return m_ht.equal_range(key); } /** * @copydoc equal_range(const K& key, std::size_t precalculated_hash) */ template::value>::type* = nullptr> std::pair equal_range(const K& key, std::size_t precalculated_hash) const { return m_ht.equal_range(key, precalculated_hash); } /* * Bucket interface */ size_type bucket_count() const { return m_ht.bucket_count(); } size_type max_bucket_count() const { return m_ht.max_bucket_count(); } /* * Hash policy */ float load_factor() const { return m_ht.load_factor(); } float max_load_factor() const { return m_ht.max_load_factor(); } void max_load_factor(float ml) { m_ht.max_load_factor(ml); } void rehash(size_type count_) { m_ht.rehash(count_); } void reserve(size_type count_) { m_ht.reserve(count_); } /* * Observers */ hasher hash_function() const { return m_ht.hash_function(); } key_equal key_eq() const { return m_ht.key_eq(); } /* * Other */ /** * Convert a const_iterator to an iterator. */ iterator mutable_iterator(const_iterator pos) { return m_ht.mutable_iterator(pos); } size_type overflow_size() const noexcept { return m_ht.overflow_size(); } friend bool operator==(const hopscotch_set& lhs, const hopscotch_set& rhs) { if(lhs.size() != rhs.size()) { return false; } for(const auto& element_lhs : lhs) { const auto it_element_rhs = rhs.find(element_lhs); if(it_element_rhs == rhs.cend()) { return false; } } return true; } friend bool operator!=(const hopscotch_set& lhs, const hopscotch_set& rhs) { return !operator==(lhs, rhs); } friend void swap(hopscotch_set& lhs, hopscotch_set& rhs) { lhs.swap(rhs); } private: ht m_ht; }; /** * Same as `tsl::hopscotch_set`. */ template, class KeyEqual = std::equal_to, class Allocator = std::allocator, unsigned int NeighborhoodSize = 62, bool StoreHash = false> using hopscotch_pg_set = hopscotch_set; } // end namespace tsl #endif ================================================ FILE: NEWorld.Base/Common/Console.cpp ================================================ // // Core: Console.cpp // NEWorld: A Free Game with Similar Rules to Minecraft. // Copyright (C) 2015-2018 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published // by the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // NEWorld is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General // Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // #include "Console.h" #if (__CYGWIN__ || _WIN32) #include #include #include namespace LColorFunc { // Microsoft Windows static HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE); std::ostream& black(std::ostream& s) noexcept { SetConsoleTextAttribute(hStdout, 0); return s; } std::ostream& lblack(std::ostream& s) noexcept { SetConsoleTextAttribute(hStdout, FOREGROUND_INTENSITY); return s; } std::ostream& red(std::ostream& s) noexcept { SetConsoleTextAttribute(hStdout, FOREGROUND_RED); return s; } std::ostream& lred(std::ostream& s) noexcept { SetConsoleTextAttribute(hStdout, FOREGROUND_RED | FOREGROUND_INTENSITY); return s; } std::ostream& green(std::ostream& s) noexcept { SetConsoleTextAttribute(hStdout, FOREGROUND_GREEN); return s; } std::ostream& lgreen(std::ostream& s) noexcept { SetConsoleTextAttribute(hStdout, FOREGROUND_GREEN | FOREGROUND_INTENSITY); return s; } std::ostream& blue(std::ostream& s) noexcept { SetConsoleTextAttribute(hStdout, FOREGROUND_BLUE); return s; } std::ostream& lblue(std::ostream& s) noexcept { SetConsoleTextAttribute(hStdout, FOREGROUND_BLUE | FOREGROUND_INTENSITY); return s; } std::ostream& yellow(std::ostream& s) noexcept { SetConsoleTextAttribute(hStdout, FOREGROUND_RED | FOREGROUND_GREEN); return s; } std::ostream& lyellow(std::ostream& s) noexcept { SetConsoleTextAttribute(hStdout, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY); return s; } std::ostream& magenta(std::ostream& s) noexcept { SetConsoleTextAttribute(hStdout, FOREGROUND_RED | FOREGROUND_BLUE); return s; } std::ostream& lmagenta(std::ostream& s) noexcept { SetConsoleTextAttribute(hStdout, FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY); return s; } std::ostream& cyan(std::ostream& s) noexcept { SetConsoleTextAttribute(hStdout, FOREGROUND_GREEN | FOREGROUND_BLUE); return s; } std::ostream& lcyan(std::ostream& s) noexcept { SetConsoleTextAttribute(hStdout, FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY); return s; } std::ostream& white(std::ostream& s) noexcept { SetConsoleTextAttribute(hStdout, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); return s; } std::ostream& lwhite(std::ostream& s) noexcept { SetConsoleTextAttribute(hStdout, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY); return s; } } #else // *nix namespace LColorFunc { std::ostream& black(std::ostream& s) noexcept { return s << "\033[21;30m"; } std::ostream& lblack(std::ostream& s) noexcept { return s << "\033[1;30m"; } std::ostream& red(std::ostream& s) noexcept { return s << "\033[21;31m"; } std::ostream& lred(std::ostream& s) noexcept { return s << "\033[1;31m"; } std::ostream& green(std::ostream& s) noexcept { return s << "\033[21;32m"; } std::ostream& lgreen(std::ostream& s) noexcept { return s << "\033[1;32m"; } std::ostream& blue(std::ostream& s) noexcept { return s << "\033[21;34m"; } std::ostream& lblue(std::ostream& s) noexcept { return s << "\033[1;34m"; } std::ostream& yellow(std::ostream& s) noexcept { return s << "\033[21;33m"; } std::ostream& lyellow(std::ostream& s) noexcept { return s << "\033[1;33m"; } std::ostream& magenta(std::ostream& s) noexcept { return s << "\033[21;35m"; } std::ostream& lmagenta(std::ostream& s) noexcept { return s << "\033[1;35m"; } std::ostream& cyan(std::ostream& s) noexcept { return s << "\033[21;36m"; } std::ostream& lcyan(std::ostream& s) noexcept { return s << "\033[1;36m"; } std::ostream& white(std::ostream& s) noexcept { return s << "\033[21;37m"; } std::ostream& lwhite(std::ostream& s) noexcept { return s << "\033[1;37m"; } } #endif ================================================ FILE: NEWorld.Base/Common/Console.h ================================================ // // Core: Console.h // NEWorld: A Free Game with Similar Rules to Minecraft. // Copyright (C) 2015-2018 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published // by the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // NEWorld is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General // Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // #pragma once #include namespace LColorFunc { using ColorFunc = std::ostream& (*)(std::ostream& s) noexcept; std::ostream& black(std::ostream& s) noexcept; std::ostream& lblack(std::ostream& s) noexcept; std::ostream& red(std::ostream& s) noexcept; std::ostream& lred(std::ostream& s) noexcept; std::ostream& green(std::ostream& s) noexcept; std::ostream& lgreen(std::ostream& s) noexcept; std::ostream& blue(std::ostream& s) noexcept; std::ostream& lblue(std::ostream& s) noexcept; std::ostream& yellow(std::ostream& s) noexcept; std::ostream& lyellow(std::ostream& s) noexcept; std::ostream& magenta(std::ostream& s) noexcept; std::ostream& lmagenta(std::ostream& s) noexcept; std::ostream& cyan(std::ostream& s) noexcept; std::ostream& lcyan(std::ostream& s) noexcept; std::ostream& white(std::ostream& s) noexcept; std::ostream& lwhite(std::ostream& s) noexcept; } namespace LColor { constexpr const char* black = "&0"; constexpr const char* red = "&1"; constexpr const char* yellow = "&2"; constexpr const char* green = "&3"; constexpr const char* cyan = "&4"; constexpr const char* blue = "&5"; constexpr const char* magenta = "&6"; constexpr const char* white = "&7"; constexpr const char* lblack = "&8"; constexpr const char* lred = "&9"; constexpr const char* lyellow = "&a"; constexpr const char* lgreen = "&b"; constexpr const char* lcyan = "&c"; constexpr const char* lblue = "&d"; constexpr const char* lmagenta = "&e"; constexpr const char* lwhite = "&f"; } ================================================ FILE: NEWorld.Base/Common/Logger.cpp ================================================ // // Core: Logger.cpp // NEWorld: A Free Game with Similar Rules to Minecraft. // Copyright (C) 2015-2018 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published // by the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // NEWorld is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General // Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // #include #include #include #include #include #include #include #include "Logger.h" #include "Console.h" namespace { constexpr std::array levelTags = { "[verbose]", "[debug]", "[info]", "[warning]", "[error]", "[fatal]" }; std::vector fSink{}; Logger::Level coutLevel = Logger::Level::verbose; Logger::Level cerrLevel = Logger::Level::fatal; Logger::Level fileLevel = Logger::Level::info; Logger::Level lineLevel = Logger::Level::error; template std::string convert(int arg) { char arr[13]; int siz = 0u; while (arg) { arr[siz++] = arg % 10 + '0'; // NOLINT arg /= 10; } std::string ret(length - siz, '0'); ret.reserve(length); for (int i = siz - 1; i >= 0; i--) ret += arr[i]; return ret; } std::string getTimeString(const char dateSplit, const char midSplit, const char timeSplit) { time_t timer = time(nullptr); tm currtime; #if _MSC_VER localtime_s(&currtime, &timer); // MSVC #else localtime_r(&timer, &currtime); // POSIX #endif return convert<4u>(currtime.tm_year + 1900) + dateSplit + convert<2u>(currtime.tm_mon) + dateSplit + convert<2u>(currtime.tm_mday) + midSplit + convert<2u>(currtime.tm_hour) + timeSplit + convert<2u>(currtime.tm_min) + timeSplit + convert<2u>(currtime.tm_sec); } void setLogColor(const Logger::Level& level, std::stringstream& content) { switch (level) { case Logger::Level::verbose: case Logger::Level::debug: return (content << LColor::white, (void)0); case Logger::Level::info: return (content << LColor::lwhite, (void)0); case Logger::Level::warning: return (content << LColor::lyellow, (void)0); case Logger::Level::error: return (content << LColor::lred, (void)0); case Logger::Level::fatal: return (content << LColor::red, (void)0); default: return; } } constexpr char styleChar = '&'; LColorFunc::ColorFunc queryColorFunc(const char style) { using namespace LColorFunc; static std::map map = { {'0', black}, {'1', red}, {'2', yellow}, {'3', green}, {'4', cyan}, {'5', blue}, {'6', magenta}, {'7', white}, {'8', lblack}, {'9', lred}, {'a', lyellow}, {'b', lgreen}, {'c', lcyan}, {'d', lblue}, {'e', lmagenta}, {'f', lwhite}, }; const auto chNorm = (style >= 'A' && style <= 'F') ? style - 'A' + 'a' : style; const auto sRes = map.find(chNorm); return (sRes != map.end()) ? sRes->second : ColorFunc(); } void chConsumeStyled(std::ostream& ostream, const char ch) { if (const auto cf = queryColorFunc(ch); cf) return (ostream << cf, (void)0); if (ch == styleChar) ostream << styleChar; // Escaped to `stylechar` else ostream << styleChar << ch; // Wrong color code } template auto transitStyleString(const std::string& str, Fn fn) { std::string_view vw{ str }; std::string::size_type pos1 = 0; std::stringstream ss{}; for (;;) { const auto pos2 = vw.find(styleChar, pos1); if (std::string::npos == pos2) return (ss << vw.substr(pos1, str.size()), ss.str()); ss << vw.substr(pos1, pos2 - pos1); if (pos2 < str.size()) fn(ss, str[pos2 + 1]); pos1 = pos2 + 2; } } void lockedFlush(std::ostream& stream, const std::string& string) { static std::mutex mutex; std::lock_guard lock(mutex); stream << string; } void flushConsole(const Logger::Level level, const std::string& string) { const auto line = transitStyleString(string, chConsumeStyled); if (level >= cerrLevel) lockedFlush(std::cerr, line); else if (level >= coutLevel) lockedFlush(std::cout, line); } void flushFiles(const Logger::Level level, const std::string& string) { if (level >= fileLevel) { const auto line = transitStyleString(string, [](auto&&, auto&&) {}); for (auto& it : fSink) { lockedFlush(it, line); if (level >= cerrLevel) it.flush(); } } } } void Logger::addFileSink(const std::string& path, const std::string& prefix) { std::filesystem::create_directory(path); fSink.emplace_back(path + prefix + "_" + getTimeString('-', '_', '-') + ".log"); } Logger::Logger(const char* fileName, const char* funcName, int lineNumber, Level level, const char* mgr) :mLevel(level), mFileName(fileName), mFuncName(funcName), mLineNumber(lineNumber) { mContent << LColor::white << getTimeString('-', ' ', ':') << " [" << mgr << ']'; setLogColor(level, mContent); mContent << levelTags[static_cast(level)] << ' '; } Logger::~Logger() { if (mLevel >= lineLevel) { mContent << std::endl << "\tSource :\t" << mFileName << std::endl << "\tAt Line :\t" << mLineNumber << std::endl << "\tFunction :\t" << mFuncName << std::endl; } mContent << std::endl; const auto string = mContent.str(); if (!fileOnly) flushConsole(mLevel, string); flushFiles(mLevel, string); } ================================================ FILE: NEWorld.Base/Common/Logger.h ================================================ // // Core: Logger.h // NEWorld: A Free Game with Similar Rules to Minecraft. // Copyright (C) 2015-2018 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published // by the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // NEWorld is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General // Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // #pragma once #include #include #include class Logger { public: enum class Level { verbose, debug, info, warning, error, fatal }; Logger(const char* fileName, const char* funcName, int lineNumber, Level level, const char* mgr); ~Logger(); template Logger& operator<<(const T& rhs) { mContent << rhs; return *this; } template Logger& operator<<(const std::vector& rhs) { for (auto& item : rhs) mContent << item << " "; return *this; } static void addFileSink(const std::string& path, const std::string& prefix); private: Level mLevel; int mLineNumber; const char* mFileName; const char* mFuncName; bool fileOnly{ false }; std::stringstream mContent; }; #define loggerstream(level) Logger(__FILE__, __FUNCTION__, __LINE__, Logger::Level::level, "NEWorld") // Information for tracing #define verbosestream loggerstream(verbose) // Information for developers #define debugstream loggerstream(debug) // Information for engine users #define infostream loggerstream(info) // Problems that may affect facility, performance or stability but may not lead the Game to crash immediately #define warningstream loggerstream(warning) // The Game crashes, but may be resumed by ways such as reloading the world which don't restart the program #define errorstream loggerstream(error) // Unrecoverable error and program termination is required #define fatalstream loggerstream(fatal) ================================================ FILE: NEWorld.Base/Math/Vector2.h ================================================ #pragma once #include #include #include template>> struct Vec2 { union { T Data[2]; struct { T X, Y; }; }; constexpr Vec2() noexcept = default; constexpr Vec2(T x, T y) noexcept : X(x), Y(y) {} constexpr explicit Vec2(T v) noexcept : X(v), Y(v) {} template>> constexpr Vec2(const Vec2 &r) noexcept // NOLINT : X(T(r.X)), Y(T(r.Y)) {} constexpr Vec2 operator+(const Vec2 &r) const noexcept { return {X + r.X, Y + r.Y}; } constexpr Vec2 operator-(const Vec2 &r) const noexcept { return {X - r.X, Y - r.Y}; } template>> constexpr Vec2 operator*(U r) const noexcept { return {X * r, Y * r}; } template>> constexpr Vec2 operator/(U r) const noexcept { return {X / r, Y / r}; } constexpr Vec2 &operator+=(const Vec2 &r) noexcept { return (X += r.X, Y += r.Y, *this); } constexpr Vec2 &operator-=(const Vec2 &r) noexcept { return (X -= r.X, Y -= r.Y, *this); } template>> constexpr Vec2 &operator*=(U r) noexcept { return (X *= r, Y *= r, *this); } template>> constexpr Vec2 &operator/=(U r) noexcept { return (X /= r, Y /= r, *this); } template>> constexpr Vec2 operator<<(T r) const noexcept { return {X << r, Y << r}; } template>> constexpr Vec2 operator<<(const Vec2 &r) const noexcept { return {X << r.X, Y << r.Y}; } template>> constexpr Vec2 &operator<<=(T r) noexcept { return (X <<= r, Y <<= r, *this); } template>> constexpr Vec2 &operator<<=(const Vec2 &r) noexcept { return (X <<= r.X, Y <<= r.Y, *this); } template>> constexpr Vec2 operator>>(T r) const noexcept { return {X >> r, Y >> r}; } template>> constexpr Vec2 operator>>(const Vec2 &r) const noexcept { return {X >> r.X, Y >> r.Y}; } template>> constexpr Vec2 &operator>>=(T r) noexcept { return (X >>= r, Y >>= r, *this); } template>> constexpr Vec2 &operator>>=(const Vec2 &r) noexcept { return (X >>= r.X, Y >>= r.Y, *this); } template>> constexpr Vec2 operator|(T r) const noexcept { return {X | r, Y | r}; } template>> constexpr Vec2 operator|(const Vec2 &r) const noexcept { return {X | r.X, Y | r.Y}; } template>> constexpr Vec2 &operator|=(T r) noexcept { return (X |= r, Y |= r, *this); } template>> constexpr Vec2 &operator|=(const Vec2 &r) noexcept { return (X |= r.X, Y |= r.Y, *this); } template>> constexpr Vec2 operator&(T r) const noexcept { return {X & r, Y & r}; } template>> constexpr Vec2 operator&(const Vec2 &r) const noexcept { return {X & r.X, Y & r.Y}; } template>> constexpr Vec2 &operator&=(T r) noexcept { return (X &= r, Y &= r, *this); } template>> constexpr Vec2 &operator&=(const Vec2 &r) noexcept { return (X &= r.X, Y &= r.Y, *this); } template>> constexpr Vec2 operator^(T r) const noexcept { return {X ^ r, Y ^ r}; } template>> constexpr Vec2 operator^(const Vec2 &r) const noexcept { return {X ^ r.X, Y ^ r.Y}; } template>> constexpr Vec2 &operator^=(T r) noexcept { return (X ^= r, Y ^= r, *this); } template>> constexpr Vec2 &operator^=(const Vec2 &r) noexcept { return (X ^= r.X, Y ^= r.Y, *this); } constexpr Vec2 operator-() const noexcept { return {-X, -Y}; } [[nodiscard]] constexpr T LengthSquared() const noexcept { return X * X + Y * Y; } constexpr bool operator==(const Vec2 &r) const noexcept { return (X == r.X) && (Y == r.Y); } constexpr bool operator<(const Vec2 &r) const noexcept { return LengthSquared() < r.LengthSquared(); } constexpr bool operator>(const Vec2 &r) const noexcept { return LengthSquared() > r.LengthSquared(); } constexpr bool operator<=(const Vec2 &r) const noexcept { return LengthSquared() <= r.LengthSquared(); } constexpr bool operator>=(const Vec2 &r) const noexcept { return LengthSquared() >= r.LengthSquared(); } [[nodiscard]] constexpr T Dot(const Vec2 &r) const noexcept { return X * r.X + Y * r.Y; } [[nodiscard]] T Length() const noexcept { return std::sqrt(LengthSquared()); } void Normalize() noexcept { (*this) /= Length(); } }; template constexpr T Dot(const Vec2 &l, const Vec2 &r) noexcept { return l.Dot(r); } template>> constexpr Vec2 operator*(U l, const Vec2 &r) noexcept { return r * l; } template double EuclideanDistanceSquared(const Vec2 &l, const Vec2 &r) noexcept { return (l - r).LengthSquared(); } template double EuclideanDistance(const Vec2 &l, const Vec2 &r) noexcept { return (l - r).Length(); } template double DistanceSquared(const Vec2 &l, const Vec2 &r) noexcept { return (l - r).LengthSquared(); } template double Distance(const Vec2 &l, const Vec2 &r) noexcept { return (l - r).Length(); } template constexpr T ChebyshevDistance(const Vec2 &l, const Vec2 &r) noexcept { return std::max(std::abs(l.X - r.X), std::abs(l.Y - r.Y)); } template constexpr T ManhattanDistance(const Vec2 &l, const Vec2 &r) noexcept { return std::abs(l.X - r.X) + std::abs(l.Y - r.Y); } using Char2 = Vec2; using Byte2 = Vec2; using Short2 = Vec2; using UShort2 = Vec2; using Int2 = Vec2; using UInt2 = Vec2; using Long2 = Vec2; using ULong2 = Vec2; using Float2 = Vec2; using Double2 = Vec2; ================================================ FILE: NEWorld.Base/Math/Vector3.h ================================================ #pragma once #include #include #include template>> struct Vec3 { union { T Data[3]; struct { T X, Y, Z; }; }; constexpr Vec3() noexcept = default; constexpr Vec3(T x, T y, T z) noexcept : X(x), Y(y), Z(z) {} constexpr explicit Vec3(T v) noexcept : X(v), Y(v), Z(v) {} template>> constexpr Vec3(const Vec3 &r) noexcept // NOLINT : X(T(r.X)), Y(T(r.Y)), Z(T(r.Z)) {} template constexpr Vec3(const Vec3 &r, F transform) noexcept // NOLINT : X(transform(r.X)), Y(transform(r.Y)), Z(transform(r.Z)) {} constexpr Vec3 operator+(const Vec3 &r) const noexcept { return {X + r.X, Y + r.Y, Z + r.Z}; } constexpr Vec3 operator-(const Vec3 &r) const noexcept { return {X - r.X, Y - r.Y, Z - r.Z}; } template>> constexpr Vec3 operator*(U r) const noexcept { return {X * r, Y * r, Z * r}; } template>> constexpr Vec3 operator/(U r) const noexcept { return {X / r, Y / r, Z / r}; } constexpr Vec3 &operator+=(const Vec3 &r) noexcept { return (X += r.X, Y += r.Y, Z += r.Z, *this); } constexpr Vec3 &operator-=(const Vec3 &r) noexcept { return (X -= r.X, Y -= r.Y, Z -= r.Z, *this); } template>> constexpr Vec3 &operator*=(U r) noexcept { return (X *= r, Y *= r, Z *= r, *this); } template>> constexpr Vec3 &operator/=(U r) noexcept { return (X /= r, Y /= r, Z /= r, *this); } constexpr Vec3 operator*(const Vec3 &r) const noexcept { return {Y * r.Z - Z * r.Y, Z * r.X - X * r.Z, X * r.Y - Y * r.X}; } template>> constexpr Vec3 operator<<(T r) const noexcept { return {X << r, Y << r, Z << r}; } template>> constexpr Vec3 operator<<(const Vec3 &r) const noexcept { return {X << r.X, Y << r.Y, Z << r.Z}; } template>> constexpr Vec3 &operator<<=(T r) noexcept { return (X <<= r, Y <<= r, Z <<= r, *this); } template>> constexpr Vec3 &operator<<=(const Vec3 &r) noexcept { return (X <<= r.X, Y <<= r.Y, Z <<= r.Z, *this); } template>> constexpr Vec3 operator>>(T r) const noexcept { return {X >> r, Y >> r, Z >> r}; } template>> constexpr Vec3 operator>>(const Vec3 &r) const noexcept { return {X >> r.X, Y >> r.Y, Z >> r.Z}; } template>> constexpr Vec3 &operator>>=(T r) noexcept { return (X >>= r, Y >>= r, Z >>= r, *this); } template>> constexpr Vec3 &operator>>=(const Vec3 &r) noexcept { return (X >>= r.X, Y >>= r.Y, Z >>= r.Z, *this); } template>> constexpr Vec3 operator|(T r) const noexcept { return {X | r, Y | r, Z | r}; } template>> constexpr Vec3 operator|(const Vec3 &r) const noexcept { return {X | r.X, Y | r.Y, Z | r.Z}; } template>> constexpr Vec3 &operator|=(T r) noexcept { return (X |= r, Y |= r, Z |= r, *this); } template>> constexpr Vec3 &operator|=(const Vec3 &r) noexcept { return (X |= r.X, Y |= r.Y, Z |= r.Z, *this); } template>> constexpr Vec3 operator&(T r) const noexcept { return {X & r, Y & r, Z & r}; } template>> constexpr Vec3 operator&(const Vec3 &r) const noexcept { return {X & r.X, Y & r.Y, Z & r.Z}; } template>> constexpr Vec3 &operator&=(T r) noexcept { return (X &= r, Y &= r, Z &= r, *this); } template>> constexpr Vec3 &operator&=(const Vec3 &r) noexcept { return (X &= r.X, Y &= r.Y, Z &= r.Z, *this); } template>> constexpr Vec3 operator^(T r) const noexcept { return {X ^ r, Y ^ r, Z ^ r}; } template>> constexpr Vec3 operator^(const Vec3 &r) const noexcept { return {X ^ r.X, Y ^ r.Y, Z ^ r.Z}; } template>> constexpr Vec3 &operator^=(T r) noexcept { return (X ^= r, Y ^= r, Z ^= r, *this); } template>> constexpr Vec3 &operator^=(const Vec3 &r) noexcept { return (X ^= r.X, Y ^= r.Y, Z ^= r.Z, *this); } constexpr Vec3 operator-() const noexcept { return {-X, -Y, -Z}; } [[nodiscard]] constexpr T LengthSquared() const noexcept { return X * X + Y * Y + Z * Z; } constexpr bool operator==(const Vec3 &r) const noexcept { return (X == r.X) && (Y == r.Y) && (Z == r.Z); } constexpr bool operator!=(const Vec3& r) const noexcept { return !(*this == r); } constexpr bool operator<(const Vec3 &r) const noexcept { return LengthSquared() < r.LengthSquared(); } constexpr bool operator>(const Vec3 &r) const noexcept { return LengthSquared() > r.LengthSquared(); } constexpr bool operator<=(const Vec3 &r) const noexcept { return LengthSquared() <= r.LengthSquared(); } constexpr bool operator>=(const Vec3 &r) const noexcept { return LengthSquared() >= r.LengthSquared(); } [[nodiscard]] constexpr T Dot(const Vec3 &r) const noexcept { return X * r.X + Y * r.Y + Z * r.Z; } [[nodiscard]] T Length() const noexcept { return std::sqrt(LengthSquared()); } void Normalize() noexcept { (*this) /= Length(); } }; template constexpr T Dot(const Vec3 &l, const Vec3 &r) noexcept { return l.Dot(r); } template>> constexpr Vec3 operator*(U l, const Vec3 &r) noexcept { return r * l; } template double EuclideanDistanceSquared(const Vec3 &l, const Vec3 &r) noexcept { return (l - r).LengthSquared(); } template double EuclideanDistance(const Vec3 &l, const Vec3 &r) noexcept { return (l - r).Length(); } template double DistanceSquared(const Vec3 &l, const Vec3 &r) noexcept { return (l - r).LengthSquared(); } template double Distance(const Vec3 &l, const Vec3 &r) noexcept { return (l - r).Length(); } template constexpr T ChebyshevDistance(const Vec3 &l, const Vec3 &r) noexcept { return std::max(std::max(std::abs(l.X - r.X), std::abs(l.Y - r.Y)), std::abs(l.Z - r.Z)); } template constexpr T ManhattanDistance(const Vec3 &l, const Vec3 &r) noexcept { return std::abs(l.X - r.X) + std::abs(l.Y - r.Y) + std::abs(l.Z - r.Z); } template void Cursor(const Vec3 &min, const Vec3 &max, F func) noexcept { Vec3 vec{}; for (vec.X = min.X; vec.X < max.X; ++vec.X) for (vec.Y = min.Y; vec.Y < max.Y; ++vec.Y) for (vec.Z = min.Z; vec.Z < max.Z; ++vec.Z) func(vec); } template void CursorEQ(const Vec3 &min, const Vec3 &max, F func) noexcept { Vec3 vec{}; for (vec.X = min.X; vec.X <= max.X; ++vec.X) for (vec.Y = min.Y; vec.Y <= max.Y; ++vec.Y) for (vec.Z = min.Z; vec.Z <= max.Z; ++vec.Z) func(vec); } using Char3 = Vec3; using Byte3 = Vec3; using Short3 = Vec3; using UShort3 = Vec3; using Int3 = Vec3; using UInt3 = Vec3; using Long3 = Vec3; using ULong3 = Vec3; using Float3 = Vec3; using Double3 = Vec3; ================================================ FILE: NEWorld.Base/Math/Vector4.h ================================================ #pragma once #include #include #include template>> struct Vec4 { union { T Data[4]; struct { T X, Y, Z, W; }; }; constexpr Vec4() noexcept = default; constexpr Vec4(T x, T y, T z, T w) noexcept : X(x), Y(y), Z(z), W(w) {} constexpr explicit Vec4(T v) noexcept : X(v), Y(v), Z(v), W(v) {} template>> constexpr Vec4(const Vec4 &r) noexcept // NOLINT : X(T(r.X)), Y(T(r.Y)), Z(T(r.Z)), W(T(r.W)) {} template constexpr Vec4(const Vec4 &r, F transform) noexcept // NOLINT : X(transform(r.X)), Y(transform(r.Y)), Z(transform(r.Z)), W(transform(r.W)) {} constexpr Vec4 operator+(const Vec4 &r) const noexcept { return {X + r.X, Y + r.Y, Z + r.Z, W + r.W}; } constexpr Vec4 operator-(const Vec4 &r) const noexcept { return {X - r.X, Y - r.Y, Z - r.Z, W - r.W}; } template>> constexpr Vec4 operator*(U r) const noexcept { return {X * r, Y * r, Z * r, W * r}; } template>> constexpr Vec4 operator/(U r) const noexcept { return {X / r, Y / r, Z / r, W / r}; } constexpr Vec4 &operator+=(const Vec4 &r) noexcept { return (X += r.X, Y += r.Y, Z += r.Z, W += r.W, *this); } constexpr Vec4 &operator-=(const Vec4 &r) noexcept { return (X -= r.X, Y -= r.Y, Z -= r.Z, W -= r.W, *this); } template>> constexpr Vec4 &operator*=(U r) noexcept { return (X *= r, Y *= r, Z *= r, W *= r, *this); } template>> constexpr Vec4 &operator/=(U r) noexcept { return (X /= r, Y /= r, Z /= r, W /= r, *this); } template>> constexpr Vec4 operator<<(T r) const noexcept { return {X << r, Y << r, Z << r, W << r}; } template>> constexpr Vec4 operator<<(const Vec4 &r) const noexcept { return {X << r.X, Y << r.Y, Z << r.Z, W << r.W}; } template>> constexpr Vec4 &operator<<=(T r) noexcept { return (X <<= r, Y <<= r, Z <<= r, W <<= r, *this); } template>> constexpr Vec4 &operator<<=(const Vec4 &r) noexcept { return (X <<= r.X, Y <<= r.Y, Z <<= r.Z, W <<= r.W, *this); } template>> constexpr Vec4 operator>>(T r) const noexcept { return {X >> r, Y >> r, Z >> r, W >> r}; } template>> constexpr Vec4 operator>>(const Vec4 &r) const noexcept { return {X >> r.X, Y >> r.Y, Z >> r.Z, W >> r.W}; } template>> constexpr Vec4 &operator>>=(T r) noexcept { return (X >>= r, Y >>= r, Z >>= r, W >>= r, *this); } template>> constexpr Vec4 &operator>>=(const Vec4 &r) noexcept { return (X >>= r.X, Y >>= r.Y, Z >>= r.Z, W >>= r.W, *this); } template>> constexpr Vec4 operator|(T r) const noexcept { return {X | r, Y | r, Z | r, W | r}; } template>> constexpr Vec4 operator|(const Vec4 &r) const noexcept { return {X | r.X, Y | r.Y, Z | r.Z, W | r.W}; } template>> constexpr Vec4 &operator|=(T r) noexcept { return (X |= r, Y |= r, Z |= r, W |= r, *this); } template>> constexpr Vec4 &operator|=(const Vec4 &r) noexcept { return (X |= r.X, Y |= r.Y, Z |= r.Z, W |= r.W, *this); } template>> constexpr Vec4 operator&(T r) const noexcept { return {X & r, Y & r, Z & r, W & r}; } template>> constexpr Vec4 operator&(const Vec4 &r) const noexcept { return {X & r.X, Y & r.Y, Z & r.Z, W & r.W}; } template>> constexpr Vec4 &operator&=(T r) noexcept { return (X &= r, Y &= r, Z &= r, W &= r, *this); } template>> constexpr Vec4 &operator&=(const Vec4 &r) noexcept { return (X &= r.X, Y &= r.Y, Z &= r.Z, W &= r.W, *this); } template>> constexpr Vec4 operator^(T r) const noexcept { return {X ^ r, Y ^ r, Z ^ r, W ^ r}; } template>> constexpr Vec4 operator^(const Vec4 &r) const noexcept { return {X ^ r.X, Y ^ r.Y, Z ^ r.Z, W ^ r.W}; } template>> constexpr Vec4 &operator^=(T r) noexcept { return (X ^= r, Y ^= r, Z ^= r, W ^= r.W, *this); } template>> constexpr Vec4 &operator^=(const Vec4 &r) noexcept { return (X ^= r.X, Y ^= r.Y, Z ^= r.Z, W ^= r.W, *this); } constexpr Vec4 operator-() const noexcept { return {-X, -Y, -Z, -W}; } [[nodiscard]] constexpr T LengthSquared() const noexcept { return X * X + Y * Y + Z * Z + W * W; } constexpr bool operator==(const Vec4 &r) const noexcept { return (X == r.X) && (Y == r.Y) && (Z == r.Z) && (W == r.W); } constexpr bool operator<(const Vec4 &r) const noexcept { return LengthSquared() < r.LengthSquared(); } constexpr bool operator>(const Vec4 &r) const noexcept { return LengthSquared() > r.LengthSquared(); } constexpr bool operator<=(const Vec4 &r) const noexcept { return LengthSquared() <= r.LengthSquared(); } constexpr bool operator>=(const Vec4 &r) const noexcept { return LengthSquared() >= r.LengthSquared(); } [[nodiscard]] constexpr T Dot(const Vec4 &r) const noexcept { return X * r.X + Y * r.Y + Z * r.Z + W * r.W; } [[nodiscard]] T Length() const noexcept { return std::sqrt(LengthSquared()); } void Normalize() noexcept { (*this) /= Length(); } }; template constexpr T Dot(const Vec4 &l, const Vec4 &r) noexcept { return l.Dot(r); } template>> constexpr Vec4 operator*(U l, const Vec4 &r) noexcept { return r * l; } template double EuclideanDistanceSquared(const Vec4 &l, const Vec4 &r) noexcept { return (l - r).LengthSquared(); } template double EuclideanDistance(const Vec4 &l, const Vec4 &r) noexcept { return (l - r).Length(); } template double DistanceSquared(const Vec4 &l, const Vec4 &r) noexcept { return (l - r).LengthSquared(); } template double Distance(const Vec4 &l, const Vec4 &r) noexcept { return (l - r).Length(); } template constexpr T ChebyshevDistance(const Vec4 &l, const Vec4 &r) noexcept { return std::max(std::max(std::max(std::abs(l.X - r.X), std::abs(l.Y - r.Y)),std::abs(l.Z - r.Z)), std::abs(l.W - r.W)); } template constexpr T ManhattanDistance(const Vec4 &l, const Vec4 &r) noexcept { return std::abs(l.X - r.X) + std::abs(l.Y - r.Y) + std::abs(l.Z - r.Z) + std::abs(l.W - r.W); } template void Cursor(const Vec4 &min, const Vec4 &max, F func) noexcept { Vec4 vec{}; for (vec.X = min.X; vec.X < max.X; ++vec.X) for (vec.Y = min.Y; vec.Y < max.Y; ++vec.Y) for (vec.Z = min.Z; vec.Z < max.Z; ++vec.Z) for (vec.W = min.W; vec.W < max.W; ++vec.W) func(vec); } using Char4 = Vec4; using Byte4 = Vec4; using Short4 = Vec4; using UShort4 = Vec4; using Int4 = Vec4; using UInt4 = Vec4; using Long4 = Vec4; using ULong4 = Vec4; using Float4 = Vec4; using Double4 = Vec4; ================================================ FILE: NEWorld.Base/System/FileSystem.h ================================================ #pragma once #if __has_include() #include namespace NEWorld { namespace filesystem = std::filesystem; } #elif __has_include() #include namespace NEWorld { namespace filesystem = boost::filesystem; } #else #error No available filesystem library configured #endif ================================================ FILE: NEWorld.Base/System/MessageBus.cpp ================================================ #include "MessageBus.h" std::vector> MessageBus::SlotBase::PrepareInvokeList() { std::vector> invokes{}; std::lock_guard lk{mLock}; invokes.reserve(mListeners.size()); for (auto& x : mListeners) { if (auto ptr = x.lock(); ptr) { invokes.push_back(std::move(ptr)); } } if (mListeners.size() > 4 * invokes.size()) { mListeners.clear(); if (mListeners.capacity() > 8 * invokes.size()) { mListeners.shrink_to_fit(); } for (auto& x : invokes) { mListeners.push_back(x); } } return invokes; } MessageBus& MessageBus::Default() { static MessageBus defaultBus{}; return defaultBus; } ================================================ FILE: NEWorld.Base/System/MessageBus.h ================================================ #pragma once #include #include #include #include #include #include #include class MessageBus { class SlotBase : public kls::PmrBase { protected: std::vector> PrepareInvokeList(); std::mutex mLock; std::vector> mListeners; }; public: template class Slot final : public SlotBase { class ListenerBase : public kls::PmrBase { using Invoke = void (*)(ListenerBase *, void *, const Tm &) noexcept; public: explicit ListenerBase(const Invoke fn) noexcept : Function(fn) {} Invoke Function; }; template class Listener final : public ListenerBase { public: explicit Listener(Fn package) : ListenerBase(Invoke), mPackage(std::move(package)) {} private: static void Invoke(ListenerBase *ths, void *s, const Tm &arg) noexcept { static_cast(ths)->mPackage(s, arg); } Fn mPackage; }; public: void Send(void *sender, const Tm &arg) { const auto invokes = PrepareInvokeList(); for (const auto &x: invokes) { const auto x2 = static_cast(x.get()); if (x2->Function) { std::invoke(x2->Function, x2, sender, arg); } } } template std::shared_ptr Listen(Fn fn) { const auto s = std::make_shared>(std::move(fn)); { std::lock_guard lk{mLock}; mListeners.push_back(s); } return s; } }; template Slot *Get(const std::string &name) noexcept { const auto search = mSlots.find(name); if (search != mSlots.end()) { return dynamic_cast *>(search->second.get()); } auto ptr = std::make_unique>(); const auto ret = ptr.get(); mSlots.insert_or_assign(name, std::move(ptr)); return ret; } static MessageBus &Default(); private: std::unordered_map> mSlots; }; using NullArg = int; ================================================ FILE: NEWorld.Game/Audio/Audio.cpp ================================================ #include "Audio.h" ================================================ FILE: NEWorld.Game/Audio/Audio.h ================================================ #pragma once namespace NEWorld::Audio { } ================================================ FILE: NEWorld.Game/AudioSystem.cpp ================================================ #include"AudioSystem.h" namespace AudioSystem { //ALDevice Device; //Gain ALfloat BGMGain = 0.1f;//背景音乐 ALfloat SoundGain = 0.17f;//音效 //Set ALenum DopplerModel = AL_INVERSE_DISTANCE_CLAMPED;//设置OpenAL的距离模型 ALfloat DopplerFactor = 1.0f;//多普勒因子 ALfloat SpeedOfSound = Air_SpeedOfSound;//声速 //Update bool FallBefore = false;//OnGround bool DownWaterBefore = false;//InWater int BGMNum = 0; //Buffer ALuint BGM[10]; ALuint Run = -1; ALuint Click = -1; ALuint Fall = -1; ALuint BlockClick = -1; ALuint DownWater = -1; //Source ALuint SBGM = -1; ALuint SRun = -1; ALuint SClick = -1; ALuint SFall = -1; ALuint SBlockClick = -1; ALuint SDownWater = -1; void Init() { //初始化设备 /*ALDeviceList *DL = Device.GetALDeviceList(); Device.InitAL(DL->GetDeviceName(DL->GetDefaultDevice())); delete DL; //开启所有功能 alEnable(AL_DOPPLER_FACTOR); alEnable(AL_DISTANCE_MODEL); alEnable(AL_SPEED_OF_SOUND); //背景音乐 char BGMName[256]; for (size_t i = 0; i < 10; i++) { BGM[i] = -1; } for (size_t i = 0; i < 10; i++) { sprintf_s(BGMName, "Audio\\BGM%d.wav", i); if (Device.load(BGMName, &BGM[BGMNum])) { BGMNum++; } } //行走and跑步声音 if (!Device.load("Audio\\Run.wav", &Run))Run = -1; //鼠标单击 if (!Device.load("Audio\\Click.wav", &Click))Click = -1; //掉落 if (!Device.load("Audio\\Fall.wav", &Fall))Fall = -1; //击打方块 if (!Device.load("Audio\\BlockClick.wav", &BlockClick))BlockClick = -1; //下水 if (!Device.load("Audio\\DownWater.wav", &DownWater))DownWater = -1; //播放BGM int size = GetTickCount64() % BGMNum; ALfloat Pos[] = { 0.0,0.0,0.0 }; ALfloat Vel[] = { 0.0,0.0,0.0 }; SBGM = Device.Play(BGM[size], false, BGMGain, Pos, Vel);*/ } void Update(ALfloat PlayerPos[3], bool BFall, bool BBlockClick, ALfloat BlockPos[3], int BRun, bool BDownWater) { //设置全局常量 /*alDopplerFactor(DopplerFactor); alDistanceModel(DopplerModel); alSpeedOfSound(SpeedOfSound); //更新音量 if (SBGM != -1)alSourcef(SBGM,AL_GAIN,BGMGain); if (SRun != -1)alSourcef(SRun, AL_GAIN, SoundGain); if (SClick != -1)alSourcef(SClick, AL_GAIN, SoundGain); if (SFall != -1)alSourcef(SFall, AL_GAIN, SoundGain); if (SBlockClick != -1)alSourcef(SBlockClick, AL_GAIN, SoundGain); if (SDownWater != -1)alSourcef(SDownWater, AL_GAIN, SoundGain); //更新环境 if (SBGM != -1)EFX::set(SBGM); if (SRun != -1)EFX::set(SRun); if (SClick != -1)EFX::set(SClick); if (SFall != -1)EFX::set(SFall); if (SBlockClick != -1)EFX::set(SBlockClick); if (SDownWater != -1)EFX::set(SDownWater); //更新玩家位置 PlayerPos[1] += 0.74; ALfloat Vel[] = { 0.0,0.0,0.0 }; ALfloat Ori[] = { 0.0,0.0,-1.0, 0.0,1.0,0.0 }; Device.Updatelistener(PlayerPos, Vel, Ori); //更新BGM位置 ALint state; alGetSourcei(SBGM, AL_SOURCE_STATE, &state); if (state == AL_STOPPED) { Device.Stop(SBGM); int size = GetTickCount64() % BGMNum; ALfloat Pos[] = { 0.0,0.0,0.0 }; ALfloat Vel[] = { 0.0,0.0,0.0 }; SBGM = Device.Play(BGM[size], false, BGMGain, Pos, Vel); } Device.Updatesource(SBGM, PlayerPos, Vel); //下落 PlayerPos[1] -= 1.54; if (BFall != FallBefore) { if (BFall) { SFall = Device.Play(Fall, false, SoundGain, PlayerPos, Vel); } FallBefore = BFall; } else { if(SFall!=-1)Device.Stop(SFall); SFall = -1; } //击打方块 if (BBlockClick) { if (SBlockClick==-1) { SBlockClick = Device.Play(BlockClick, true, SoundGain, BlockPos, Vel); } } else { if(SBlockClick!=-1)Device.Stop(SBlockClick); SBlockClick = -1; } //奔跑 if ((BRun!=0)&&BFall) { if (SRun == -1) { SRun = Device.Play(Run, true, SoundGain, PlayerPos, Vel); } Device.Updatesource(SRun, PlayerPos, Vel); alSourcef(SRun, AL_PITCH, BRun*0.5f); } else { if(SRun!=-1)Device.Stop(SRun); SRun = -1; } //下水 if (BDownWater != DownWaterBefore) { if (SDownWater == -1)SDownWater = Device.Play(DownWater, false, SoundGain, PlayerPos, Vel); DownWaterBefore = BDownWater; } else { if (SDownWater != -1) { ALint state; alGetSourcei(SDownWater, AL_SOURCE_STATE, &state); if (state == AL_STOPPED) { Device.Stop(SDownWater); SDownWater = -1; } } }*/ } void ClickEvent() { /*ALfloat Pos[] = { 0.0,0.0,0.0 }; ALfloat Vel[] = { 0.0,0.0,0.0 }; SClick = Device.Play(Click, false, SoundGain, Pos, Vel); SleepMs(50); Device.Stop(SClick); SClick = -1;*/ } void UnInit() { /*if (SBGM != -1)Device.Stop(SBGM); if (SRun != -1)Device.Stop(SRun); if (SClick != -1)Device.Stop(SClick); if (SFall != -1)Device.Stop(SFall); if (SBlockClick != -1)Device.Stop(SBlockClick); if (SDownWater != -1)Device.Stop(SDownWater); for (size_t i = 0; i < 10; i++) { if (BGM[i] != -1)Device.unload(BGM[i]); } if (Run != -1)Device.unload(Run); if (Click != -1)Device.unload(Click); if (Fall != -1)Device.unload(Fall); if (BlockClick != -1)Device.unload(BlockClick); if (DownWater != -1)Device.unload(DownWater); Device.ShutdownAL();*/ } } ================================================ FILE: NEWorld.Game/AudioSystem.h ================================================ #pragma once #include namespace AudioSystem { //extern ALDevice Device; //Gain extern ALfloat BGMGain;//背景音乐 extern ALfloat SoundGain;//音效 //Set extern ALenum DopplerModel;//设置OpenAL的距离模型 extern ALfloat DopplerFactor;//多普勒因子 extern ALfloat SpeedOfSound;//声速 const ALfloat Air_SpeedOfSound = 343.3f; const ALfloat Water_SpeedOfSound = 1473.0f; //Update extern bool FallBefore;//OnGround extern bool DownWaterBefore;//InWater extern int BGMNum; //Buffer extern ALuint BGM[10]; extern ALuint Run; extern ALuint Click; extern ALuint Fall; extern ALuint BlockClick; extern ALuint DownWater; //Source extern ALuint SBGM; extern ALuint SRun; extern ALuint SClick; extern ALuint SFall; extern ALuint SBlockClick; extern ALuint SDownWater; void Init(); void Update(ALfloat PlayerPos[3], bool BFall, bool BBlockClick, ALfloat BlockPos[3], int Run, bool BDownWater); void ClickEvent(); void UnInit(); } ================================================ FILE: NEWorld.Game/Command.h ================================================ #pragma once #include #include class Command { public: Command(std::string _identifier, std::function &)> _execute) : identifier( std::move(std::move(_identifier))), execute(std::move(std::move(_execute))) {}; std::string identifier; std::function &)> execute; }; ================================================ FILE: NEWorld.Game/ControlContext.h ================================================ #pragma once #include "stdinclude.h" #include #include #include "Math/Vector2.h" #include "FunctionsKit.h" #include "Common/Logger.h" #include "System/MessageBus.h" class ControlContext { public: struct KeyState { bool Pressed : 1; uint16_t LastPressedFrame : 15; // 0 represents never pressed }; enum class Action { PLACE_BLOCK, PICK_BLOCK }; struct Frame { bool LeftMouse{}, MiddleMouse{}, RightMouse{}; double MouseScroll{}; Double2 MousePosition{}; double Time{}; std::array KeyStates{}; }; ControlContext(GLFWwindow* window) :mWindow(window) { if (!Listener) Listener = MessageBus::Default().Get>("KeyEvents")->Listen( [this](void*, std::pair keyAndAction) { auto [key, action] = keyAndAction; KeyStates[key].Pressed = action != GLFW_RELEASE; if (KeyStates[key].Pressed) KeyStates[key].LastPressedFrame = mFrameCounter; }); } ControlContext(ControlContext&) = delete; ControlContext operator=(ControlContext&) = delete; Frame Current, Last; void Update() { Last = Current; mFrameCounter++; if (mFrameCounter > 1<<15) mFrameCounter = 1; // loop back to squeeze into 7 bits glfwGetCursorPos(mWindow, &Current.MousePosition.X, &Current.MousePosition.Y); Current.LeftMouse = glfwGetMouseButton(mWindow, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS; Current.RightMouse = glfwGetMouseButton(mWindow, GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS; Current.MiddleMouse = glfwGetMouseButton(mWindow, GLFW_MOUSE_BUTTON_MIDDLE) == GLFW_PRESS; Current.Time = timer(); Current.MouseScroll = MouseScroll; std::copy(KeyStates.begin(), KeyStates.end(), Current.KeyStates.begin()); } [[nodiscard]] bool KeyPressed(int key) const noexcept { return Current.KeyStates[key].Pressed; } [[nodiscard]] bool KeyJustDoublePressed(int key, int intervalInFrames = 8) const noexcept { assert(intervalInFrames <= 1<<15); if (!Current.KeyStates[key].Pressed || Last.KeyStates[key].Pressed) return false; auto lastPressed = Last.KeyStates[key].LastPressedFrame; if (lastPressed == 0) return false; auto currentFrame = mFrameCounter; if (currentFrame < lastPressed) currentFrame += 1<<15; return currentFrame - lastPressed < intervalInFrames; } [[nodiscard]] bool KeyJustPressed(int key) const noexcept { return Current.KeyStates[key].Pressed && !Last.KeyStates[key].Pressed; } [[nodiscard]] bool ShouldDo(Action action) { switch (action) { case Action::PLACE_BLOCK: return (Current.RightMouse && !Last.RightMouse) || KeyPressed(GLFW_KEY_TAB); case Action::PICK_BLOCK: return Current.LeftMouse || KeyPressed(GLFW_KEY_ENTER); default: return false; } } static void MouseScrollCallback(GLFWwindow*, double, double yOffset) { MouseScroll += yOffset; } private: GLFWwindow* mWindow; uint16_t mFrameCounter = 1; inline static std::shared_ptr Listener; inline static double MouseScroll = 0; inline static std::array KeyStates; }; ================================================ FILE: NEWorld.Game/Definitions.cpp ================================================ #include "Definitions.h" //Global Vars float FOVyNormal = 60.0f; float mousemove = 0.2f; int viewdistance = 8; int cloudwidth = 10; double selectPrecision = 32; int selectDistance = 8; float walkspeed = 0.15f; float runspeed = 0.3f; int MaxAirJumps = 3 - 1; bool SmoothLighting = true; bool NiceGrass = true; int linelength = 10; int linedist = 30; float skycolorR = 0.7f; float skycolorG = 1.0f; float skycolorB = 1.0f; float FOVyRunning = 8.0f; float FOVyExt; double stretch = 1.0f; int Multisample = 0; bool vsync = false; int gametime = 0; //float daylight; int windowwidth; int windowheight; //������Ϸ bool multiplayer = false; std::string serverip; unsigned short port = 30001; TextureID BlockTextures; TextureID tex_select, tex_unselect, tex_mainmenu[6]; TextureID DestroyImage[11]; TextureID DefaultSkin; Mutex_t Mutex; double lastUpdate; bool updateThreadRun, updateThreadPaused; bool shouldGetScreenshot; bool shouldGetThumbnail; bool FirstUpdateThisFrame; double SpeedupAnimTimer; double TouchdownAnimTimer; double screenshotAnimTimer; double bagAnimTimer; double bagAnimDuration = 0.5; //OpenGL int GLVersionMajor, GLVersionMinor, GLVersionRev; //GLFW GLFWwindow *MainWindow; GLFWcursor *MouseCursor; #ifdef NEWORLD_DEBUG_PERFORMANCE_REC int c_getChunkPtrFromCPA; int c_getChunkPtrFromSearch; int c_getHeightFromHMap; int c_getHeightFromWorldGen; #endif ================================================ FILE: NEWorld.Game/Definitions.h ================================================ #pragma once #ifdef NDEBUG #pragma comment(linker, "/SUBSYSTEM:\"WINDOWS\" /ENTRY:\"mainCRTStartup\"") #endif #include "Typedefs.h" #include "FunctionsKit.h" #include //Global Vars const unsigned int VERSION = 39; constexpr double MaxUpdateFPS = 30; constexpr const char* MAJOR_VERSION = "Alpha 0."; constexpr const char* MINOR_VERSION = "5"; constexpr const char* EXT_VERSION = " Technical Perview"; constexpr int DefaultWindowWidth = 852; //默认窗口宽度 constexpr int DefaultWindowHeight = 480; //默认窗口高度 extern float FOVyNormal; extern float mousemove; extern int viewdistance; extern int cloudwidth; extern double selectPrecision; extern int selectDistance; extern float walkspeed; extern float runspeed; extern int MaxAirJumps; extern bool SmoothLighting; extern bool NiceGrass; extern int linelength; extern int linedist; extern float skycolorR; extern float skycolorG; extern float skycolorB; extern float FOVyRunning; extern float FOVyExt; extern int Multisample; extern bool vsync; extern double stretch; extern int gametime; const int gameTimeMax = 43200; extern int windowwidth; extern int windowheight; extern TextureID BlockTextures; extern TextureID tex_select, tex_unselect, tex_title, tex_mainmenu[6]; extern TextureID DestroyImage[11]; extern TextureID DefaultSkin; extern Mutex_t Mutex; extern double lastUpdate; extern bool updateThreadRun, updateThreadPaused; extern bool mpclient, mpserver; extern bool shouldGetScreenshot; extern bool shouldGetThumbnail; extern bool FirstUpdateThisFrame; extern double SpeedupAnimTimer; extern double TouchdownAnimTimer; extern double screenshotAnimTimer; extern double bagAnimTimer; extern double bagAnimDuration; extern int GLVersionMajor, GLVersionMinor, GLVersionRev; extern GLFWwindow *MainWindow; extern GLFWcursor *MouseCursor; void AppCleanUp(); ================================================ FILE: NEWorld.Game/Dispatch.cpp ================================================ #include "Dispatch.h" #include namespace { auto sessDefault = kls::coroutine::CreateScalingFIFOExecutor(1, std::thread::hardware_concurrency(), 5000); } kls::coroutine::IExecutor * GetSessionDefault() { return sessDefault.get(); } ================================================ FILE: NEWorld.Game/Dispatch.h ================================================ #pragma once #include "kls/coroutine/Executor.h" kls::coroutine::IExecutor * GetSessionDefault(); ================================================ FILE: NEWorld.Game/Frustum.cpp ================================================ #include "Frustum.h" #define _USE_MATH_DEFINES #include #include void Frustum::LoadIdentity() { memset(proj, 0, sizeof(proj)); memset(modl, 0, sizeof(modl)); modl[0] = modl[5] = modl[10] = modl[15] = 1.0f; } inline void Frustum::MultMatrix(float *a, float *b) { float sum[16]; MultMatrixTo(sum, a, b); memcpy(a, sum, sizeof(sum)); } void Frustum::SetPerspective(float FOV, float aspect, float Znear, float Zfar) { const auto ViewAngleH = FOV * static_cast(M_PI) / 180.0f; const auto ViewAngleV = atan(tan(ViewAngleH / 2.0f) * aspect) * 2.0f; proj[0] = 1.0f / tan(ViewAngleV / 2); proj[5] = proj[0] * aspect; proj[10] = -(Zfar + Znear) / (Zfar - Znear); proj[11] = -1; proj[14] = -2 * Zfar * Znear / (Zfar - Znear); } void Frustum::SetOrtho(float left, float right, float top, float bottom, float Znear, float Zfar) { proj[0] = 2 / (right - left); proj[5] = 2 / (bottom - top); proj[10] = 2 / (Znear - Zfar); proj[15] = 1.0f; } void Frustum::MultRotate(float angle, float x, float y, float z) { float m[16], sum[16]; memset(m, 0, sizeof(m)); const auto length = sqrtf(x * x + y * y + z * z); x /= length; y /= length; z /= length; const auto alpha = angle * static_cast(M_PI) / 180.0f; const auto s = sin(alpha); const auto c = cos(alpha); const auto t = 1.0f - c; m[0] = t * x * x + c; m[1] = t * x * y + s * z; m[2] = t * x * z - s * y; m[4] = t * x * y - s * z; m[5] = t * y * y + c; m[6] = t * y * z + s * x; m[8] = t * x * z + s * y; m[9] = t * y * z - s * x; m[10] = t * z * z + c; m[15] = 1.0f; MultMatrixTo(sum, m, modl); memcpy(modl, sum, sizeof(sum)); } inline void Frustum::normalize(int side) { const auto magnitude = sqrtf( frus[side + 0] * frus[side + 0] + frus[side + 1] * frus[side + 1] + frus[side + 2] * frus[side + 2]); frus[side + 0] /= magnitude; frus[side + 1] /= magnitude; frus[side + 2] /= magnitude; frus[side + 3] /= magnitude; } void Frustum::update() { MultMatrixTo(clip, modl, proj); frus[0] = clip[3] - clip[0]; frus[1] = clip[7] - clip[4]; frus[2] = clip[11] - clip[8]; frus[3] = clip[15] - clip[12]; normalize(0); frus[4] = clip[3] + clip[0]; frus[5] = clip[7] + clip[4]; frus[6] = clip[11] + clip[8]; frus[7] = clip[15] + clip[12]; normalize(4); frus[8] = clip[3] + clip[1]; frus[9] = clip[7] + clip[5]; frus[10] = clip[11] + clip[9]; frus[11] = clip[15] + clip[13]; normalize(8); frus[12] = clip[3] - clip[1]; frus[13] = clip[7] - clip[5]; frus[14] = clip[11] - clip[9]; frus[15] = clip[15] - clip[13]; normalize(12); frus[16] = clip[3] - clip[2]; frus[17] = clip[7] - clip[6]; frus[18] = clip[11] - clip[10]; frus[19] = clip[15] - clip[14]; normalize(16); frus[20] = clip[3] + clip[2]; frus[21] = clip[7] + clip[6]; frus[22] = clip[11] + clip[10]; frus[23] = clip[15] + clip[14]; normalize(20); } bool Frustum::FrustumTest(const ChunkBox &aabb) { for (auto i = 0; i < 24; i += 4) { if (frus[i] * aabb.xmin + frus[i + 1] * aabb.ymin + frus[i + 2] * aabb.zmin + frus[i + 3] <= 0.0f && frus[i] * aabb.xmax + frus[i + 1] * aabb.ymin + frus[i + 2] * aabb.zmin + frus[i + 3] <= 0.0f && frus[i] * aabb.xmin + frus[i + 1] * aabb.ymax + frus[i + 2] * aabb.zmin + frus[i + 3] <= 0.0f && frus[i] * aabb.xmax + frus[i + 1] * aabb.ymax + frus[i + 2] * aabb.zmin + frus[i + 3] <= 0.0f && frus[i] * aabb.xmin + frus[i + 1] * aabb.ymin + frus[i + 2] * aabb.zmax + frus[i + 3] <= 0.0f && frus[i] * aabb.xmax + frus[i + 1] * aabb.ymin + frus[i + 2] * aabb.zmax + frus[i + 3] <= 0.0f && frus[i] * aabb.xmin + frus[i + 1] * aabb.ymax + frus[i + 2] * aabb.zmax + frus[i + 3] <= 0.0f && frus[i] * aabb.xmax + frus[i + 1] * aabb.ymax + frus[i + 2] * aabb.zmax + frus[i + 3] <= 0.0f) { return false; } } return true; } ================================================ FILE: NEWorld.Game/Frustum.h ================================================ #pragma once class Frustum { private: float frus[24], clip[16]; float proj[16], modl[16]; public: //AABB with Float32 coords struct ChunkBox { float xmin, ymin, zmin; float xmax, ymax, zmax; }; float *getProjMatrix() { return proj; } float *getModlMatrix() { return modl; } void LoadIdentity(); void MultMatrixTo(float *sum, float *a, float *b) { sum[0] = a[0] * b[0] + a[1] * b[4] + a[2] * b[8] + a[3] * b[12]; sum[1] = a[0] * b[1] + a[1] * b[5] + a[2] * b[9] + a[3] * b[13]; sum[2] = a[0] * b[2] + a[1] * b[6] + a[2] * b[10] + a[3] * b[14]; sum[3] = a[0] * b[3] + a[1] * b[7] + a[2] * b[11] + a[3] * b[15]; sum[4] = a[4] * b[0] + a[5] * b[4] + a[6] * b[8] + a[7] * b[12]; sum[5] = a[4] * b[1] + a[5] * b[5] + a[6] * b[9] + a[7] * b[13]; sum[6] = a[4] * b[2] + a[5] * b[6] + a[6] * b[10] + a[7] * b[14]; sum[7] = a[4] * b[3] + a[5] * b[7] + a[6] * b[11] + a[7] * b[15]; sum[8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[8] + a[11] * b[12]; sum[9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[9] + a[11] * b[13]; sum[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10] + a[11] * b[14]; sum[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11] * b[15]; sum[12] = a[12] * b[0] + a[13] * b[4] + a[14] * b[8] + a[15] * b[12]; sum[13] = a[12] * b[1] + a[13] * b[5] + a[14] * b[9] + a[15] * b[13]; sum[14] = a[12] * b[2] + a[13] * b[6] + a[14] * b[10] + a[15] * b[14]; sum[15] = a[12] * b[3] + a[13] * b[7] + a[14] * b[11] + a[15] * b[15]; } inline void MultMatrix(float *a, float *b); void SetPerspective(float FOV, float aspect, float Znear, float Zfar); void SetOrtho(float left, float right, float top, float bottom, float Znear, float Zfar); void MultRotate(float angle, float x, float y, float z); inline void normalize(int side); void update(); bool FrustumTest(const ChunkBox &aabb); }; ================================================ FILE: NEWorld.Game/FunctionsKit.cpp ================================================ #include "FunctionsKit.h" #if __has_include() #define WIN32_LEAN_AND_MEAN #define NOMINMAX #define NEWORLD_WIN32 #include #endif unsigned int g_seed = 0; std::vector split(const std::string &str, const std::string &pattern) { std::vector ret; if (pattern.empty()) return ret; size_t start = 0, index = str.find_first_of(pattern, 0); while (index != str.npos) { if (start != index) ret.push_back(str.substr(start, index - start)); start = index + 1; index = str.find_first_of(pattern, start); } if (!str.substr(start).empty()) ret.push_back(str.substr(start)); return ret; } unsigned int MByteToWChar(wchar_t *dst, const char *src, unsigned int n) { #ifdef NEWORLD_WIN32 return MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, n, dst, n); #else return mbstowcs(dst, src, n); #endif } unsigned int WCharToMByte(char *dst, const wchar_t *src, unsigned int n) { #ifdef NEWORLD_WIN32 return WideCharToMultiByte(CP_UTF8, WC_COMPOSITECHECK, src, n, dst, n, nullptr, nullptr); #else return wcstombs(dst, src, n); #endif } ================================================ FILE: NEWorld.Game/FunctionsKit.h ================================================ #pragma once #include "stdinclude.h" #include "Typedefs.h" #include #include #include #include extern double stretch; //常用函数 extern unsigned int g_seed; inline unsigned int fastRand() { g_seed = (214013 * g_seed + 2531011); return (g_seed >> 16u) & 0x7FFFu; } inline void fastSrand(int seed) { g_seed = seed; } std::vector split(const std::string &str, const std::string &pattern); inline std::string boolstr(bool b) { return b ? "True" : "False"; } inline double rnd() { return static_cast(fastRand()) / static_cast(0x7FFFu); } inline int RoundInt(double d) { return static_cast(lround(d)); } inline Mutex_t MutexCreate() { return new std::mutex; } inline void MutexDestroy(Mutex_t _hMutex) { delete _hMutex; } inline void MutexLock(Mutex_t _hMutex) { _hMutex->lock(); } inline void MutexUnlock(Mutex_t _hMutex) { _hMutex->unlock(); } unsigned int MByteToWChar(wchar_t *dst, const char *src, unsigned int n); unsigned int WCharToMByte(char *dst, const wchar_t *src, unsigned int n); inline unsigned int wstrlen(const wchar_t *wstr) { return wcslen(wstr); } inline void SleepMs(unsigned int ms) { std::this_thread::sleep_for(std::chrono::milliseconds(ms)); } inline double timer() { return static_cast(std::chrono::duration_cast( std::chrono::high_resolution_clock::now().time_since_epoch()).count()) / 1000.0; } //计算距离的平方 inline int DistanceSquare(int ix, int iy, int iz, int x, int y, int z) { return (ix - x) * (ix - x) + (iy - y) * (iy - y) + (iz - z) * (iz - z); } ================================================ FILE: NEWorld.Game/GUI/GUI.cpp ================================================ #include #include "GUI.h" #include #include #include "Noesis.h" #include "System/MessageBus.h" #include "Common/Logger.h" #include namespace GUI { static Noesis::Key mapKey(int glfwKey) { using namespace Noesis; static std::unordered_map keyTable = { {GLFW_KEY_SPACE ,Key_Space}, {GLFW_KEY_MINUS ,Key_Subtract}, {GLFW_KEY_0, Key_D0}, {GLFW_KEY_1, Key_D1}, {GLFW_KEY_2, Key_D2}, {GLFW_KEY_3, Key_D3}, {GLFW_KEY_4, Key_D4}, {GLFW_KEY_5, Key_D5}, {GLFW_KEY_6, Key_D6}, {GLFW_KEY_7, Key_D7}, {GLFW_KEY_8, Key_D8}, {GLFW_KEY_9, Key_D9}, {GLFW_KEY_A, Key_A}, {GLFW_KEY_B, Key_B}, {GLFW_KEY_C, Key_C}, {GLFW_KEY_D, Key_D}, {GLFW_KEY_E, Key_E}, {GLFW_KEY_F, Key_F}, {GLFW_KEY_G, Key_G}, {GLFW_KEY_H, Key_H}, {GLFW_KEY_I, Key_I}, {GLFW_KEY_J, Key_J}, {GLFW_KEY_K, Key_K}, {GLFW_KEY_L, Key_L}, {GLFW_KEY_M, Key_M}, {GLFW_KEY_N, Key_N}, {GLFW_KEY_O, Key_O}, {GLFW_KEY_P, Key_P}, {GLFW_KEY_Q, Key_Q}, {GLFW_KEY_R, Key_R}, {GLFW_KEY_S, Key_S}, {GLFW_KEY_T, Key_T}, {GLFW_KEY_U, Key_U}, {GLFW_KEY_V, Key_V}, {GLFW_KEY_W, Key_W}, {GLFW_KEY_X, Key_X}, {GLFW_KEY_Y, Key_Y}, {GLFW_KEY_Z, Key_Z} }; return keyTable[glfwKey]; } void Scene::update() { // TODO: change to use glfw callback + message bus? if (mView) { static bool leftPressed = false, rightPressed = false; static double lastPressedTime = 0; double xpos, ypos; glfwGetCursorPos(MainWindow, &xpos, &ypos); mView->MouseMove(xpos, ypos); mView->SetSize(windowwidth, windowheight); const int leftCurrentlyPressed = glfwGetMouseButton(MainWindow, GLFW_MOUSE_BUTTON_LEFT); if (leftCurrentlyPressed == GLFW_PRESS && !leftPressed) { mView->MouseButtonDown(xpos, ypos, Noesis::MouseButton_Left); auto curTime = timer(); if (curTime - lastPressedTime < 0.25) { mView->MouseDoubleClick(xpos, ypos, Noesis::MouseButton_Left); } leftPressed = true; lastPressedTime = curTime; } else if (leftCurrentlyPressed != GLFW_PRESS && leftPressed) { leftPressed = false; mView->MouseButtonUp(xpos, ypos, Noesis::MouseButton_Left); } const int rightCurrentlyPressed = glfwGetMouseButton(MainWindow, GLFW_MOUSE_BUTTON_RIGHT); if (rightCurrentlyPressed == GLFW_PRESS && !rightPressed) { mView->MouseButtonDown(xpos, ypos, Noesis::MouseButton_Right); rightPressed = true; } else if (rightCurrentlyPressed != GLFW_PRESS && rightPressed) { rightPressed = false; mView->MouseButtonUp(xpos, ypos, Noesis::MouseButton_Right); } } onUpdate(); } kls::coroutine::ValueAsync Scene::render() { mFPS.update(); if (mView) { // Update view (layout, animations, ...) mView->Update(timer() - mEnterTimeInSec); glPushAttrib(GL_ENABLE_BIT | GL_TEXTURE_BIT | GL_DEPTH_BUFFER_BIT); // Offscreen rendering phase populates textures needed by the on-screen rendering mView->GetRenderer()->UpdateRenderTree(); mView->GetRenderer()->RenderOffscreen(); glPopAttrib(); glBindFramebuffer(GL_FRAMEBUFFER, 0); glViewport(0, 0, windowwidth, windowheight); glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glClearStencil(0); glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); } co_await onRender(); if (mView) { glPushAttrib(GL_ENABLE_BIT | GL_TEXTURE_BIT | GL_DEPTH_BUFFER_BIT); // Rendering is done in the active framebuffer mView->GetRenderer()->Render(); glPopAttrib(); } } Scene::~Scene() { if (mView) { mView->GetRenderer()->Shutdown(); mView.Reset(); } } void Scene::loadView() { mRoot = Noesis::GUI::LoadXaml(mXamlPath); if (!mRoot) { errorstream << "UI failed to load!"; return; } onViewBinding(); mView = Noesis::GUI::CreateView(mRoot); mView->SetFlags(Noesis::RenderFlags_PPAA | Noesis::RenderFlags_LCD); mView->GetRenderer()->Init(GUI::renderDevice); // high dpi support float scale; glfwGetWindowContentScale(MainWindow, &scale, nullptr); mView->SetScale(scale); } kls::coroutine::ValueAsync Scene::singleLoop() { update(); co_await render(); glfwSwapBuffers(MainWindow); glfwPollEvents(); } void Scene::load() { loadView(); glfwSetInputMode(MainWindow, GLFW_CURSOR, mHasCursor ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_DISABLED); if (mXamlPath) { mListeners.push_back(MessageBus::Default().Get>("KeyEvents")->Listen([this](void*, std::pair keyAndAction) { auto [key, action] = keyAndAction; if (action == GLFW_PRESS) mView->KeyDown(mapKey(key)); else if (action == GLFW_RELEASE) mView->KeyUp(mapKey(key)); if (key == GLFW_KEY_F5 && action == GLFW_PRESS) { // reload view. might leak memory but it's for debug only infostream << "Reloading View"; loadView(); } })); mListeners.push_back(MessageBus::Default().Get("InputEvents")->Listen([this](void*, int k) { mView->Char(k); })); } onLoad(); } std::deque> scenes; void pushScene(std::unique_ptr scene) { scene->load(); scenes.emplace_back(std::move(scene)); } void popScene() { scenes.pop_back(); } void clearScenes() { while (!scenes.empty()) popScene(); } void appStart() { glfwSetInputMode(MainWindow, GLFW_CURSOR, GLFW_CURSOR_NORMAL); glClearColor(0.0, 0.0, 0.0, 1.0); glDisable(GL_CULL_FACE); kls::coroutine::run_blocking([]() -> kls::coroutine::ValueAsync<> { while (!scenes.empty()) { auto ¤tScene = scenes.back(); co_await currentScene->singleLoop(); if (currentScene->shouldLeave()) GUI::popScene(); if (glfwWindowShouldClose(MainWindow)) { clearScenes(); } } }); AppCleanUp(); } } ================================================ FILE: NEWorld.Game/GUI/GUI.h ================================================ #pragma once #include "Definitions.h" #include #include #include #include #include #include namespace GUI { class FpsCounter { public: int getFPS() const noexcept { return mFPS; } void update() noexcept { check(); frame(); } void check() noexcept { if (timer() - mLastTimeInSec >= 1.0) { mFPS = mFPSCounter; mFPSCounter = 0; mLastTimeInSec = timer(); } } void frame() noexcept { mFPSCounter++; } private: int mFPSCounter = 0; int mFPS = 0; double mLastTimeInSec = 0; }; class Scene { public: Scene(const char* xaml, bool hasCursor = true) : mXamlPath(xaml), mHasCursor(hasCursor), mEnterTimeInSec(timer()) {} virtual ~Scene(); void load(); kls::coroutine::ValueAsync singleLoop(); void requestLeave() noexcept { mShouldLeave = true; } bool shouldLeave() const noexcept { return mShouldLeave; } protected: virtual kls::coroutine::ValueAsync onRender() { co_return; } virtual void onUpdate() {} virtual void onLoad() {} virtual void onViewBinding() {} Noesis::Ptr mRoot; Noesis::Ptr mView; FpsCounter mFPS; private: kls::coroutine::ValueAsync render(); void update(); void loadView(); bool mShouldLeave = false; const char* mXamlPath; bool mHasCursor; double mEnterTimeInSec; std::vector> mListeners; }; void pushScene(std::unique_ptr scene); void popScene(); void clearScenes(); void appStart(); } ================================================ FILE: NEWorld.Game/GUI/InventorySlot.cpp ================================================ #include "InventorySlot.h" #include #include "Definitions.h" #include "Textures.h" #include "Typedefs.h" #include "Universe/World/Blocks.h" #include #include #include #include #include const Noesis::DependencyProperty* InventorySlot::AmountProperty; const Noesis::DependencyProperty* InventorySlot::SelectedProperty; Noesis::Ptr InventorySlot::CachedBlockTextures; std::unordered_map> InventorySlot::CachedItemTextures; InventorySlot::InventorySlot() { Noesis::GUI::LoadComponent(this, "InventorySlot.xaml"); if (!CachedBlockTextures) { CachedBlockTextures = Noesis::MakePtr(NoesisApp::GLFactory::WrapTexture( BlockTextures, 256, 256, 0, false, true )); } } void InventorySlot::clearCache() { CachedItemTextures.clear(); } Noesis::ImageSource* InventorySlot::getTextureForItem(Item i) { if (i == Blocks::ENV) return nullptr; // find from cache first auto itemTextureIter = CachedItemTextures.find(i); if (itemTextureIter != CachedItemTextures.end()) return (*itemTextureIter).second.GetPtr(); const auto tcX = Textures::getTexcoordX(i, 1) * 256; const auto tcY = Textures::getTexcoordY(i, 1) * 256; return CachedItemTextures[i] = Noesis::MakePtr( CachedBlockTextures.GetPtr(), Noesis::Int32Rect(tcX, tcY, 32, 32) // TODO: refactor ); } ================================================ FILE: NEWorld.Game/GUI/InventorySlot.h ================================================ #include #include "Typedefs.h" #include "NsGui/CroppedBitmap.h" #include "NsGui/Image.h" #include "NsGui/ImageSource.h" #include "NsGui/IntegrationAPI.h" #include "NsGui/TextureSource.h" #include "NsGui/UIElementData.h" #include "NsGui/UserControl.h" #include "Universe/Entity/PlayerEntity.h" class InventorySlot : public Noesis::UserControl { public: InventorySlot(); int getAmount() const noexcept { return mQuantity; } void setAmount(int value) { mQuantity = value; SetValue(AmountProperty, value); } Item getItem() const noexcept { return mItem; } void setItem(Item i) { if (mItem == i) return; mItem = i; FindName("ItemTexture")->SetSource(getTextureForItem(i)); } void setItemStack(ItemStack stack) { setItem(stack.item); setAmount(stack.amount); } bool isSelected() const noexcept { return mSelected; } void setSelected(bool selected) { mSelected = selected; SetValue(SelectedProperty, selected); } static const Noesis::DependencyProperty* AmountProperty; static const Noesis::DependencyProperty* SelectedProperty; static void clearCache(); private: static Noesis::ImageSource* getTextureForItem(Item i); static Noesis::Ptr CachedBlockTextures; static std::unordered_map> CachedItemTextures; Item mItem; bool mSelected; int mQuantity; NS_IMPLEMENT_INLINE_REFLECTION(InventorySlot, UserControl, "NEWorld.InventorySlot") { Noesis::UIElementData* data = NsMeta(Noesis::TypeOf()); data->RegisterProperty(AmountProperty, "Amount", Noesis::PropertyMetadata::Create(0)); data->RegisterProperty(SelectedProperty, "Selected", Noesis::PropertyMetadata::Create(false)); } }; ================================================ FILE: NEWorld.Game/GUI/Menus/MainMenu.cpp ================================================ #include "Menus.h" #include "../GUI.h" #include "NsRender/GLFactory.h" #include "NsGui/Grid.h" #include "NsGui/Button.h" #include "GameView.h" #include "Frustum.h" #include "NsApp/NotifyPropertyChangedBase.h" #include "NsGui/ObservableCollection.h" #include #include "GameSettings.h" #include "Universe/World/Chunk.h" #include "NsGui/TextBox.h" #include "NsGui/ListView.h" #include "Renderer/Renderer.h" namespace Menus { class WorldModel : public NoesisApp::NotifyPropertyChangedBase { public: WorldModel(std::string name) : mName(std::move(name)) {} const char* name() const { return mName.c_str(); } private: std::string mName; NS_IMPLEMENT_INLINE_REFLECTION(WorldModel, NotifyPropertyChangedBase) { NsProp("Name", &WorldModel::name); } }; class GameMenuViewModel : public NoesisApp::NotifyPropertyChangedBase { public: GameMenuViewModel() { for (auto&& x : std::filesystem::directory_iterator("./Worlds/")) { if (is_directory(x)) { mWorlds.Add(Noesis::MakePtr(x.path().filename().string())); } } } enum class State { MAIN_MENU, SETTINGS, SELECT_WORLD }; const char* getState() const { switch (mState) { case State::MAIN_MENU: return "MainMenu"; case State::SETTINGS: return "Settings"; case State::SELECT_WORLD: return "SelectWorld"; default: assert(false); } return nullptr; } void setState(State state) noexcept { if(mState!=state) { mState = state; OnPropertyChanged("State"); } } #define SETTING_ITEM(TYPE, MEMBER_NAME, PROP_NAME) \ TYPE get##PROP_NAME() const noexcept { return MEMBER_NAME; }\ void set##PROP_NAME(TYPE value) noexcept {\ if (value == MEMBER_NAME) return;\ MEMBER_NAME = value;\ OnPropertyChanged(#PROP_NAME);\ } SETTING_ITEM(int, mFOV, FOV); SETTING_ITEM(float, mMouseSensitivity, MouseSensitivity); SETTING_ITEM(bool, mNiceGrass, NiceGrass); SETTING_ITEM(bool, mSmoothLighting, SmoothLighting); SETTING_ITEM(bool, mVSync, VSync); SETTING_ITEM(bool, mShadows, Shadows); #undef SETTING_ITEM int getRenderDistance() const { return mRenderDistance; } int getRenderDistanceTick() const noexcept { return mRenderDistanceTick; } void setRenderDistanceTick(int renderDistanceTick) noexcept { if (mRenderDistanceTick != renderDistanceTick) { mRenderDistanceTick = renderDistanceTick; mRenderDistance = 1 << renderDistanceTick; // 2^renderDistanceTick OnPropertyChanged("RenderDistanceTick"); OnPropertyChanged("RenderDistance"); } } const Noesis::ObservableCollection* getWorlds() const { return &mWorlds; } private: State mState = State::MAIN_MENU; float& mFOV = FOVyNormal; float& mMouseSensitivity = mousemove; int& mRenderDistance = viewdistance; int mRenderDistanceTick = int(std::log2(viewdistance)); bool& mNiceGrass = NiceGrass; bool& mVSync = vsync; bool& mSmoothLighting = SmoothLighting; bool& mShadows = Renderer::AdvancedRender; Noesis::ObservableCollection mWorlds; NS_IMPLEMENT_INLINE_REFLECTION(GameMenuViewModel, NotifyPropertyChangedBase) { NsProp("State", &GameMenuViewModel::getState); // settings NsProp("FOV", &GameMenuViewModel::getFOV, &GameMenuViewModel::setFOV); NsProp("RenderDistanceTick", &GameMenuViewModel::getRenderDistanceTick, &GameMenuViewModel::setRenderDistanceTick); NsProp("RenderDistance", &GameMenuViewModel::getRenderDistance); NsProp("MouseSensitivity", &GameMenuViewModel::getMouseSensitivity, &GameMenuViewModel::setMouseSensitivity); NsProp("NiceGrass", &GameMenuViewModel::getNiceGrass, &GameMenuViewModel::setNiceGrass); NsProp("VSync", &GameMenuViewModel::getVSync, &GameMenuViewModel::setVSync); NsProp("SmoothLighting", &GameMenuViewModel::getSmoothLighting, &GameMenuViewModel::setSmoothLighting); NsProp("Shadows", &GameMenuViewModel::getShadows, &GameMenuViewModel::setShadows); // Select World NsProp("Worlds", &GameMenuViewModel::getWorlds); } }; class MainMenu : public GUI::Scene { public: MainMenu() : Scene("MainMenu.xaml"){} private: Noesis::Ptr mViewModel; void onViewBinding() override { if(!mViewModel) mViewModel = Noesis::MakePtr(); mRoot->SetDataContext(mViewModel); // Main Menu mRoot->FindName("startGame")->Click() += [this](Noesis::BaseComponent*, const Noesis::RoutedEventArgs&) { mViewModel->setState(GameMenuViewModel::State::SELECT_WORLD); }; mRoot->FindName("settings")->Click() += [this](Noesis::BaseComponent*, const Noesis::RoutedEventArgs&) { mViewModel->setState(GameMenuViewModel::State::SETTINGS); }; mRoot->FindName("exit")->Click() += [this](Noesis::BaseComponent*, const Noesis::RoutedEventArgs&) { requestLeave(); }; // Settings mRoot->FindName("Save")->Click() += [this](Noesis::BaseComponent*, const Noesis::RoutedEventArgs&) { mViewModel->setState(GameMenuViewModel::State::MAIN_MENU); GameSettings::getInstance().saveOptions(); }; // Select World View mRoot->FindName("Back")->Click() += [this](Noesis::BaseComponent*, const Noesis::RoutedEventArgs&) { mViewModel->setState(GameMenuViewModel::State::MAIN_MENU); }; mRoot->FindName("Create")->Click() += [this](Noesis::BaseComponent*, const Noesis::RoutedEventArgs&) { World::worldname = mRoot->FindName("NewWorldNameTextBox")->GetText(); pushGameView(); }; auto worldList = mRoot->FindName("WorldList"); worldList->MouseDoubleClick() += [this, worldList](Noesis::BaseComponent*, const Noesis::MouseButtonEventArgs&) { World::worldname = static_cast(worldList->GetSelectedItem())->name(); pushGameView(); }; } kls::coroutine::ValueAsync onRender() override { co_return drawBackground(); } void drawBackground() { static Frustum frus; static auto startTimer = timer(); const auto elapsed = timer() - startTimer; frus.LoadIdentity(); frus.SetPerspective(90.0f, static_cast(windowwidth) / windowheight, 0.1f, 10.0f); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glMultMatrixf(frus.getProjMatrix()); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glRotated(elapsed * 4.0, 0.1, 1.0, 0.1); glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glDepthFunc(GL_LEQUAL); glDisable(GL_CULL_FACE); glEnable(GL_TEXTURE_2D); glColor4f(1.0f, 1.0f, 1.0f, 1.0f); //Begin to draw a cube glBindTexture(GL_TEXTURE_2D, tex_mainmenu[0]); glBegin(GL_QUADS); glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(1.0f, 1.0f, -1.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(1.0f, -1.0f, -1.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); glEnd(); glBindTexture(GL_TEXTURE_2D, tex_mainmenu[1]); glBegin(GL_QUADS); glTexCoord2f(0.0f, 1.0f); glVertex3f(1.0f, 1.0f, -1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(1.0f, 1.0f, 1.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(1.0f, -1.0f, 1.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(1.0f, -1.0f, -1.0f); glEnd(); glBindTexture(GL_TEXTURE_2D, tex_mainmenu[2]); glBegin(GL_QUADS); glTexCoord2f(0.0f, 1.0f); glVertex3f(1.0f, 1.0f, 1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(1.0f, -1.0f, 1.0f); glEnd(); glBindTexture(GL_TEXTURE_2D, tex_mainmenu[3]); glBegin(GL_QUADS); glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); glEnd(); glBindTexture(GL_TEXTURE_2D, tex_mainmenu[4]); glBegin(GL_QUADS); glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, -1.0f, 1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(1.0f, -1.0f, -1.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(1.0f, -1.0f, 1.0f); glEnd(); glBindTexture(GL_TEXTURE_2D, tex_mainmenu[5]); glBegin(GL_QUADS); glTexCoord2f(0.0f, 1.0f); glVertex3f(1.0f, 1.0f, 1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(1.0f, 1.0f, -1.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, 1.0f, -1.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f); glEnd(); } }; std::unique_ptr startMenu() { return std::make_unique(); } } ================================================ FILE: NEWorld.Game/GUI/Menus/Menus.h ================================================ #include #include "GUI/GUI.h" namespace Menus { std::unique_ptr startMenu(); } ================================================ FILE: NEWorld.Game/GUI/Noesis.cpp ================================================ #include "Noesis.h" #define GLEW_NO_GLU #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Common/Logger.h" #include "NsGui/BaseValueConverter.h" #include "NsRender/GLFactory.h" #include "GUI/InventorySlot.h" #include "NsApp/ChangePropertyAction.h" Noesis::Ptr GUI::renderDevice; void GUI::noesisSetup() { Noesis::SetLogHandler([](const char* file, uint32_t line, uint32_t level, const char* channel, const char* msg) { Logger::Level prefixes[] = { Logger::Level::debug, Logger::Level::debug, Logger::Level::info, Logger::Level::warning, Logger::Level::error }; Logger(file, "", line, prefixes[level], "Noesis") << msg; }); // Sets the active license Noesis::GUI::SetLicense(NS_LICENSE_NAME, NS_LICENSE_KEY); if (std::string(NS_LICENSE_NAME).empty()) { errorstream << "Noesis License not set."; errorstream << "Please add a valid license into External/Noesis/Include/NoesisLicense.h"; errorstream << "You can get one from https://www.noesisengine.com/trial/"; } // Noesis initialization. This must be the first step before using any NoesisGUI functionality Noesis::GUI::Init(); Noesis::Ptr xamlProvider = *new NoesisApp::LocalXamlProvider("Assets/GUI"); Noesis::Ptr fontProvider = *new NoesisApp::LocalFontProvider("Assets/Fonts"); Noesis::Ptr textureProvider = *new NoesisApp::LocalTextureProvider("Assets/Textures"); NoesisApp::SetThemeProviders(xamlProvider, fontProvider, textureProvider); Noesis::GUI::LoadApplicationResources("Theme/NEWorld.xaml"); Noesis::RegisterComponent(); Noesis::RegisterComponent(); Noesis::RegisterComponent(); Noesis::RegisterComponent(); Noesis::RegisterComponent(); Noesis::RegisterComponent(); Noesis::RegisterComponent(); Noesis::RegisterComponent>(); Noesis::RegisterComponent(); Noesis::TypeOf(); // Force the creation of its reflection type Noesis::TypeOf(); // Force the creation of its reflection type // Setup Render Device glPushAttrib(GL_ALL_ATTRIB_BITS); renderDevice = NoesisApp::GLFactory::CreateDevice(false); glPopAttrib(); } void GUI::noesisShutdown() { InventorySlot::clearCache(); GUI::renderDevice.Reset(); Noesis::GUI::Shutdown(); } ================================================ FILE: NEWorld.Game/GUI/Noesis.h ================================================ #pragma once #include "NsCore/Ptr.h" #include "NsRender/RenderDevice.h" namespace GUI { void noesisSetup(); void noesisShutdown(); extern Noesis::Ptr renderDevice; } ================================================ FILE: NEWorld.Game/GameSettings.cpp ================================================ #include "GameSettings.h" #include #include #include #include "Definitions.h" #include "Globalization.h" #include "AudioSystem.h" #include "Renderer/Renderer.h" constexpr const char* SettingsFile = "./Configs/options.ini"; void GameSettings::loadOptions() { std::unordered_map options; std::ifstream filein("./Configs/options.ini", std::ios::in); if (!filein.is_open()) return; std::string name, value; while (!filein.eof()) { filein >> name >> value; options[name] = value; } filein.close(); loadOption(options, "Language", Globalization::Cur_Lang); loadOption(options, "FOV", FOVyNormal); loadOption(options, "RenderDistance", viewdistance); loadOption(options, "Sensitivity", mousemove); loadOption(options, "CloudWidth", cloudwidth); loadOption(options, "SmoothLighting", SmoothLighting); loadOption(options, "FancyGrass", NiceGrass); loadOption(options, "MultiSample", Multisample); loadOption(options, "AdvancedRender", Renderer::AdvancedRender); loadOption(options, "ShadowMapRes", Renderer::ShadowRes); loadOption(options, "ShadowDistance", Renderer::MaxShadowDist); loadOption(options, "VerticalSync", vsync); loadOption(options, "GainOfBGM", AudioSystem::BGMGain); loadOption(options, "GainOfSound", AudioSystem::SoundGain); } void GameSettings::saveOptions() { std::map options; std::ofstream fileout("./Configs/options.ini", std::ios::out); if (!fileout.is_open()) return; saveOption(fileout, "Language", Globalization::Cur_Lang); saveOption(fileout, "FOV", FOVyNormal); saveOption(fileout, "RenderDistance", viewdistance); saveOption(fileout, "Sensitivity", mousemove); saveOption(fileout, "CloudWidth", cloudwidth); saveOption(fileout, "SmoothLighting", SmoothLighting); saveOption(fileout, "FancyGrass", NiceGrass); saveOption(fileout, "MultiSample", Multisample); saveOption(fileout, "AdvancedRender", Renderer::AdvancedRender); saveOption(fileout, "ShadowMapRes", Renderer::ShadowRes); saveOption(fileout, "ShadowDistance", Renderer::MaxShadowDist); saveOption(fileout, "VerticalSync", vsync); saveOption(fileout, "GainOfBGM", AudioSystem::BGMGain); saveOption(fileout, "GainOfSound", AudioSystem::SoundGain); fileout.close(); } ================================================ FILE: NEWorld.Game/GameSettings.h ================================================ #pragma once #include #include #include class GameSettings { public: GameSettings() { loadOptions(); } static GameSettings& getInstance() { static GameSettings settings; return settings; } void loadOptions(); void saveOptions(); private: template void loadOption(std::unordered_map& m, const char* name, T& value) { if (m.find(name) == m.end()) return; std::stringstream ss; ss << m[name]; ss >> value; } template void saveOption(std::ostream& out, const char* name, T& value) { out << std::string(name) << " " << value << std::endl; } }; ================================================ FILE: NEWorld.Game/GameView.cpp ================================================ #include "GameView.h" #include #include #include #include "Universe/World/Blocks.h" #include "Textures.h" #include "Renderer/Renderer.h" #include "Universe/World/World.h" #include "Renderer/World/WorldRenderer.h" #include "Particles.h" #include "GUI/GUI.h" #include "Command.h" #include "Setup.h" #include "Universe/Game.h" #include "Common/Logger.h" #include "NsApp/NotifyPropertyChangedBase.h" #include "NsGui/Button.h" #include "NsGui/Image.h" #include "GUI/InventorySlot.h" #include "NsGui/TextBlock.h" #include "GUI/Menus/Menus.h" #include "NsGui/StackPanel.h" #include "NsGui/UIElementCollection.h" #include "NsGui/WrapPanel.h" #include "ControlContext.h" namespace NoesisApp { class Window; } class GameView; // pretty hacky. try to remove later. GameView *currentGame = nullptr; class GameViewViewModel : public NoesisApp::NotifyPropertyChangedBase { public: const char *getDebugInfo() const { return mDebugInfo.c_str(); } void setDebugInfo(std::string debugInfo) { if (debugInfo != mDebugInfo) { mDebugInfo = std::move(debugInfo); OnPropertyChanged("DebugInfo"); } } bool getGamePaused() const { return mGamePaused; } void setGamePaused(bool gamePaused) { if (gamePaused != mGamePaused) { mGamePaused = gamePaused; OnPropertyChanged("GamePaused"); } } bool getBagOpen() const { return mBagOpen; } void setBagOpen(bool bagOpen) { if (bagOpen != mBagOpen) { mBagOpen = bagOpen; OnPropertyChanged("BagOpen"); } } double getHP() const { return mHealth; } double getHPMax() const { return mHealthMax; } void notifyHPChanges(PlayerEntity *player) { if (mHealth != player->getHealth() || mHealthMax != player->getMaxHealth()) { mHealth = player->getHealth(); mHealthMax = player->getMaxHealth(); OnPropertyChanged("HP"); OnPropertyChanged("HPMax"); } } private: std::string mDebugInfo; bool mGamePaused = false; bool mBagOpen = false; double mHealth, mHealthMax; NS_IMPLEMENT_INLINE_REFLECTION(GameViewViewModel, NotifyPropertyChangedBase) { NsProp("DebugInfo", &GameViewViewModel::getDebugInfo); NsProp("GamePaused", &GameViewViewModel::getGamePaused); NsProp("BagOpen", &GameViewViewModel::getBagOpen); NsProp("HP", &GameViewViewModel::getHP); NsProp("HPMax", &GameViewViewModel::getHPMax); } }; class GameView : public virtual GUI::Scene, public Game { private: ControlContext mControls{MainWindow}; GUI::FpsCounter mUpsCounter; InventorySlot *mHotBar[10]; InventorySlot *mInventory[4][10]; Noesis::Ptr mViewModel; std::thread mUpdateThread; Frustum mFrustum; WorldRenderer::ChunksRenderer& mChunksRenderer = WorldRenderer::ChunksRenderer::Default(); struct ItemMoveContext { int row, col; int quantity; }; std::optional mInventoryMoveFrom; public: GameView() : Scene("InGame.xaml", false), mViewModel(Noesis::MakePtr()) {} void gameThread() { //Wait until start... MutexLock(Mutex); while (!updateThreadRun) { MutexUnlock(Mutex); SleepMs(1); MutexLock(Mutex); } MutexUnlock(Mutex); //Thread start MutexLock(Mutex); lastUpdate = timer(); while (updateThreadRun) { MutexUnlock(Mutex); std::this_thread::yield(); MutexLock(Mutex); while (updateThreadPaused) { MutexUnlock(Mutex); std::this_thread::yield(); MutexLock(Mutex); lastUpdate = timer(); } FirstUpdateThisFrame = true; double currentTime = timer(); if (currentTime - lastUpdate >= 5.0) lastUpdate = currentTime; while (currentTime - lastUpdate >= 1.0 / MaxUpdateFPS && mUpsCounter.getFPS() < 60) { lastUpdate += 1.0 / MaxUpdateFPS; mUpsCounter.frame(); updateGame(); FirstUpdateThisFrame = false; } mUpsCounter.check(); } MutexUnlock(Mutex); } kls::coroutine::ValueAsync gameRender() { //画场景 const auto currentTime = timer(); const auto camera = mPlayer->renderUpdate( mControls, mBagOpened || mViewModel->getGamePaused(), mControlsForUpdate.Current.Time ); const double xpos = camera.position.X, ypos = camera.position.Y, zpos = camera.position.Z; if (mPlayer->isRunning()) { if (FOVyExt < 9.8) { FOVyExt = 10.0f - (10.0f - FOVyExt) * static_cast(pow(0.8, (currentTime - SpeedupAnimTimer) * 30)); SpeedupAnimTimer = currentTime; } else FOVyExt = 10.0; } else { if (FOVyExt > 0.2) { FOVyExt *= static_cast(pow(0.8, (currentTime - SpeedupAnimTimer) * 30)); SpeedupAnimTimer = currentTime; } else FOVyExt = 0.0; } SpeedupAnimTimer = currentTime; //更新区块VBO mFrustum.LoadIdentity(); mFrustum.SetPerspective(FOVyNormal + FOVyExt, static_cast(windowwidth) / windowheight, 0.05f, viewdistance * 16.0f); mFrustum.MultRotate(static_cast(camera.lookUpDown), 1, 0, 0); mFrustum.MultRotate(360.0f - static_cast(camera.heading), 0, 1, 0); mFrustum.update(); mChunksRenderer.FrustumUpdate({xpos, ypos, zpos}, mFrustum); co_await mChunksRenderer.Update( Int3{ RoundInt(mPlayer->getPosition().X), RoundInt(mPlayer->getPosition().Y), RoundInt(mPlayer->getPosition().Z) } ); MutexUnlock(Mutex); glFlush(); glDepthFunc(GL_LEQUAL); glEnable(GL_CULL_FACE); glCullFace(GL_BACK); //daylight = clamp((1.0 - cos((double)gametime / gameTimeMax * 2.0 * M_PI) * 2.0) / 2.0, 0.05, 1.0); //Renderer::sunlightXrot = 90 * daylight; if (Renderer::AdvancedRender) { //Build shadow map if (!DebugShadow) ShadowMaps::BuildShadowMap(mChunksRenderer, xpos, ypos, zpos, currentTime); else ShadowMaps::RenderShadowMap(mChunksRenderer, xpos, ypos, zpos, currentTime); } glClearColor(skycolorR, skycolorG, skycolorB, 1.0); if (!DebugShadow) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_TEXTURE_2D); MutexLock(Mutex); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glMultMatrixf(mFrustum.getProjMatrix()); glMatrixMode(GL_MODELVIEW); auto playerChunk = mPlayer->getChunkPosition(); MutexUnlock(Mutex); auto frameRenderer = mChunksRenderer.List(playerChunk, viewdistance); // currentTime glBindTexture(GL_TEXTURE_2D, BlockTextures); // 渲染层1 glLoadIdentity(); glRotated(camera.lookUpDown, 1, 0, 0); glRotated(360.0 - camera.heading, 0, 1, 0); glDisable(GL_BLEND); Renderer::EnableShaders(); if (!DebugShadow) frameRenderer.Render(xpos, ypos, zpos, 0); Renderer::DisableShaders(); glEnable(GL_BLEND); MutexLock(Mutex); if (mBlockDestructionProgress > 0.0) { auto breakingBlock = mCurrentSelection->second; glTranslated(breakingBlock.X - xpos, breakingBlock.Y - ypos, breakingBlock.Z - zpos); renderDestroy(mBlockDestructionProgress, 0, 0, 0); glTranslated(-breakingBlock.X + xpos, -breakingBlock.Y + ypos, -breakingBlock.Z + zpos); } glBindTexture(GL_TEXTURE_2D, BlockTextures); Particles::renderall(xpos, ypos, zpos); RenderEntities(); glDisable(GL_TEXTURE_2D); if (mShouldRenderGUI && mCurrentSelection) { auto selectingBlock = mCurrentSelection->second; glTranslated(selectingBlock.X - xpos, selectingBlock.Y - ypos, selectingBlock.Z - zpos); drawBorder(0, 0, 0); glTranslated(-selectingBlock.X + xpos, -selectingBlock.Y + ypos, -selectingBlock.Z + zpos); } MutexUnlock(Mutex); // 渲染层2&3 glLoadIdentity(); glRotated(camera.lookUpDown, 1, 0, 0); glRotated(360.0 - camera.heading, 0, 1, 0); glEnable(GL_TEXTURE_2D); glEnable(GL_CULL_FACE); glBindTexture(GL_TEXTURE_2D, BlockTextures); Renderer::EnableShaders(); if (!DebugShadow) frameRenderer.Render(xpos, ypos, zpos, 1); glDisable(GL_CULL_FACE); if (!DebugShadow) frameRenderer.Render(xpos, ypos, zpos, 2); Renderer::DisableShaders(); glLoadIdentity(); glRotated(camera.lookUpDown, 1, 0, 0); glRotated(360.0 - camera.heading, 0, 1, 0); glTranslated(-xpos, -ypos, -zpos); MutexLock(Mutex); glEnable(GL_CULL_FACE); glEnable(GL_TEXTURE_2D); //Time_renderscene = timer() - Time_renderscene; //Time_renderGUI_ = timer(); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0, windowwidth, windowheight, 0, -1.0, 1.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); if (World::GetBlock({RoundInt(xpos), RoundInt(ypos), RoundInt(zpos)}) == Blocks::WATER) { glColor4f(1.0f, 1.0f, 1.0f, 1.0f); glBindTexture(GL_TEXTURE_2D, BlockTextures); const auto tcX = Textures::getTexcoordX(Blocks::WATER, 1); const auto tcY = Textures::getTexcoordY(Blocks::WATER, 1); glBegin(GL_QUADS); glTexCoord2d(tcX, tcY + 1 / 8.0); glVertex2i(0, 0); glTexCoord2d(tcX, tcY); glVertex2i(0, windowheight); glTexCoord2d(tcX + 1 / 8.0, tcY); glVertex2i(windowwidth, windowheight); glTexCoord2d(tcX + 1 / 8.0, tcY + 1 / 8.0); glVertex2i(windowwidth, 0); glEnd(); } glDisable(GL_TEXTURE_2D); if (currentTime - screenshotAnimTimer <= 1.0 && !shouldGetScreenshot) { const auto col = 1.0f - static_cast(currentTime - screenshotAnimTimer); glColor4f(1.0f, 1.0f, 1.0f, col); glBegin(GL_QUADS); glVertex2i(0, 0); glVertex2i(0, windowheight); glVertex2i(windowwidth, windowheight); glVertex2i(windowwidth, 0); glEnd(); } glEnable(GL_TEXTURE_2D); if (shouldGetScreenshot) { shouldGetScreenshot = false; screenshotAnimTimer = currentTime; auto t = time(nullptr); char tmp[64]; const auto timeinfo = localtime(&t); strftime(tmp, sizeof(tmp), "%Y年%m月%d日%H时%M分%S秒", timeinfo); std::stringstream ss; ss << "Screenshots/" << tmp << ".bmp"; saveScreenshot(0, 0, windowwidth, windowheight, ss.str()); } if (shouldGetThumbnail) { shouldGetThumbnail = false; createThumbnail(); } } void RenderEntities() { for (auto &entity: mEntities) entity->render(); } kls::coroutine::ValueAsync onRender() override { MutexLock(Mutex); co_await gameRender(); MutexUnlock(Mutex); } static void drawBorder(int x, int y, int z) { //绘制选择边框 static auto eps = 0.002f; //实际上这个边框应该比方块大一些,否则很难看 glEnable(GL_LINE_SMOOTH); glLineWidth(1); glColor3f(0.2f, 0.2f, 0.2f); glBegin(GL_LINES); // Left Face glVertex3f((0.5f + eps) + x, -(0.5f + eps) + y, -(0.5f + eps) + z); glVertex3f((0.5f + eps) + x, (0.5f + eps) + y, -(0.5f + eps) + z); glVertex3f((0.5f + eps) + x, (0.5f + eps) + y, -(0.5f + eps) + z); glVertex3f((0.5f + eps) + x, (0.5f + eps) + y, (0.5f + eps) + z); glVertex3f((0.5f + eps) + x, (0.5f + eps) + y, (0.5f + eps) + z); glVertex3f((0.5f + eps) + x, -(0.5f + eps) + y, (0.5f + eps) + z); glVertex3f((0.5f + eps) + x, -(0.5f + eps) + y, -(0.5f + eps) + z); glVertex3f((0.5f + eps) + x, -(0.5f + eps) + y, (0.5f + eps) + z); // Front Face glVertex3f(-(0.5f + eps) + x, -(0.5f + eps) + y, (0.5f + eps) + z); glVertex3f((0.5f + eps) + x, -(0.5f + eps) + y, (0.5f + eps) + z); glVertex3f((0.5f + eps) + x, -(0.5f + eps) + y, (0.5f + eps) + z); glVertex3f((0.5f + eps) + x, (0.5f + eps) + y, (0.5f + eps) + z); glVertex3f((0.5f + eps) + x, (0.5f + eps) + y, (0.5f + eps) + z); glVertex3f(-(0.5f + eps) + x, (0.5f + eps) + y, (0.5f + eps) + z); glVertex3f(-(0.5f + eps) + x, -(0.5f + eps) + y, (0.5f + eps) + z); glVertex3f(-(0.5f + eps) + x, (0.5f + eps) + y, (0.5f + eps) + z); // Right Face glVertex3f(-(0.5f + eps) + x, -(0.5f + eps) + y, -(0.5f + eps) + z); glVertex3f(-(0.5f + eps) + x, -(0.5f + eps) + y, (0.5f + eps) + z); glVertex3f(-(0.5f + eps) + x, -(0.5f + eps) + y, (0.5f + eps) + z); glVertex3f(-(0.5f + eps) + x, (0.5f + eps) + y, (0.5f + eps) + z); glVertex3f(-(0.5f + eps) + x, (0.5f + eps) + y, (0.5f + eps) + z); glVertex3f(-(0.5f + eps) + x, (0.5f + eps) + y, -(0.5f + eps) + z); glVertex3f(-(0.5f + eps) + x, -(0.5f + eps) + y, -(0.5f + eps) + z); glVertex3f(-(0.5f + eps) + x, (0.5f + eps) + y, -(0.5f + eps) + z); // Back Face glVertex3f(-(0.5f + eps) + x, -(0.5f + eps) + y, -(0.5f + eps) + z); glVertex3f(-(0.5f + eps) + x, (0.5f + eps) + y, -(0.5f + eps) + z); glVertex3f(-(0.5f + eps) + x, (0.5f + eps) + y, -(0.5f + eps) + z); glVertex3f((0.5f + eps) + x, (0.5f + eps) + y, -(0.5f + eps) + z); glVertex3f((0.5f + eps) + x, (0.5f + eps) + y, -(0.5f + eps) + z); glVertex3f((0.5f + eps) + x, -(0.5f + eps) + y, -(0.5f + eps) + z); glVertex3f(-(0.5f + eps) + x, -(0.5f + eps) + y, -(0.5f + eps) + z); glVertex3f((0.5f + eps) + x, -(0.5f + eps) + y, -(0.5f + eps) + z); glEnd(); glDisable(GL_LINE_SMOOTH); } void debugInfo() const { std::stringstream ss; if (DebugMode) { ss << "NEWorld v" << VERSION << " [OpenGL " << GLVersionMajor << "." << GLVersionMinor << " " << GLVersionRev << "]" << std::endl << "Fps:" << mFPS.getFPS() << " Ups:" << mUpsCounter.getFPS() << std::endl << "Debug Mode:" << boolstr(DebugMode) << std::endl; if (Renderer::AdvancedRender) { ss << "Shadow View:" << boolstr(DebugShadow) << std::endl; } ss << "X: " << mPlayer->getPosition().X << " Y: " << mPlayer->getPosition().Y << " Z: " << mPlayer->getPosition().Z << std::endl << "Direction:" << mPlayer->getHeading() << " Head:" << mPlayer->getLookUpDown() << std::endl << "Jump speed:" << mPlayer->getCurrentJumpSpeed() << std::endl << "Stats:"; if (mPlayer->isFlying()) ss << " Flying"; if (mPlayer->isOnGround()) ss << " On_ground"; if (mPlayer->isNearWall()) ss << " Near_wall"; if (mPlayer->isCrossWall()) ss << " Cross_Wall"; ss << std::endl; auto h = gametime / (30 * 60); auto m = gametime % (30 * 60) / 30; auto s = gametime % 30 * 2; ss << "Time: " << (h < 10 ? "0" : "") << h << ":" << (m < 10 ? "0" : "") << m << ":" << (s < 10 ? "0" : "") << s << " (" << gametime << "/" << gameTimeMax << ")" << std::endl; ss << "load:" << World::chunks.size() << " unload:" << World::unloadedChunks << " render:" << WorldRenderer::chunkBuildRenders << " update:" << World::updatedChunks; #ifdef NEWORLD_DEBUG_PERFORMANCE_REC ss << c_getChunkPtrFromCPA << " CPA requests" << std::endl; ss << c_getChunkPtrFromSearch << " search requests" << std::endl; ss << c_getHeightFromHMap << " heightmap requests" << std::endl; ss << c_getHeightFromWorldGen << " worldgen requests" << std::endl; #endif } else { ss << "v" << VERSION << " Fps:" << mFPS.getFPS(); } mViewModel->setDebugInfo(ss.str()); } static void renderDestroy(float level, int x, int y, int z) { static auto eps = 0.002f; glColor4f(1.0f, 1.0f, 1.0f, 1.0f); if (level < 100.0) glBindTexture(GL_TEXTURE_2D, DestroyImage[int(level / 10) + 1]); else glBindTexture(GL_TEXTURE_2D, DestroyImage[10]); glBegin(GL_QUADS); glTexCoord2f(0.0f, 0.0f); glVertex3f(-(0.5f + eps) + x, -(0.5f + eps) + y, (0.5f + eps) + z); glTexCoord2f(1.0f, 0.0f); glVertex3f((0.5f + eps) + x, -(0.5f + eps) + y, (0.5f + eps) + z); glTexCoord2f(1.0f, 1.0f); glVertex3f((0.5f + eps) + x, (0.5f + eps) + y, (0.5f + eps) + z); glTexCoord2f(0.0f, 1.0f); glVertex3f(-(0.5f + eps) + x, (0.5f + eps) + y, (0.5f + eps) + z); glTexCoord2f(1.0f, 0.0f); glVertex3f(-(0.5f + eps) + x, -(0.5f + eps) + y, -(0.5f + eps) + z); glTexCoord2f(1.0f, 1.0f); glVertex3f(-(0.5f + eps) + x, (0.5f + eps) + y, -(0.5f + eps) + z); glTexCoord2f(0.0f, 1.0f); glVertex3f((0.5f + eps) + x, (0.5f + eps) + y, -(0.5f + eps) + z); glTexCoord2f(0.0f, 0.0f); glVertex3f((0.5f + eps) + x, -(0.5f + eps) + y, -(0.5f + eps) + z); glTexCoord2f(1.0f, 0.0f); glVertex3f((0.5f + eps) + x, -(0.5f + eps) + y, -(0.5f + eps) + z); glTexCoord2f(1.0f, 1.0f); glVertex3f((0.5f + eps) + x, (0.5f + eps) + y, -(0.5f + eps) + z); glTexCoord2f(0.0f, 1.0f); glVertex3f((0.5f + eps) + x, (0.5f + eps) + y, (0.5f + eps) + z); glTexCoord2f(0.0f, 0.0f); glVertex3f((0.5f + eps) + x, -(0.5f + eps) + y, (0.5f + eps) + z); glTexCoord2f(0.0f, 0.0f); glVertex3f(-(0.5f + eps) + x, -(0.5f + eps) + y, -(0.5f + eps) + z); glTexCoord2f(1.0f, 0.0f); glVertex3f(-(0.5f + eps) + x, -(0.5f + eps) + y, (0.5f + eps) + z); glTexCoord2f(1.0f, 1.0f); glVertex3f(-(0.5f + eps) + x, (0.5f + eps) + y, (0.5f + eps) + z); glTexCoord2f(0.0f, 1.0f); glVertex3f(-(0.5f + eps) + x, (0.5f + eps) + y, -(0.5f + eps) + z); glTexCoord2f(0.0f, 1.0f); glVertex3f(-(0.5f + eps) + x, (0.5f + eps) + y, -(0.5f + eps) + z); glTexCoord2f(0.0f, 0.0f); glVertex3f(-(0.5f + eps) + x, (0.5f + eps) + y, (0.5f + eps) + z); glTexCoord2f(1.0f, 0.0f); glVertex3f((0.5f + eps) + x, (0.5f + eps) + y, (0.5f + eps) + z); glTexCoord2f(1.0f, 1.0f); glVertex3f((0.5f + eps) + x, (0.5f + eps) + y, -(0.5f + eps) + z); glTexCoord2f(1.0f, 1.0f); glVertex3f(-(0.5f + eps) + x, -(0.5f + eps) + y, -(0.5f + eps) + z); glTexCoord2f(0.0f, 1.0f); glVertex3f((0.5f + eps) + x, -(0.5f + eps) + y, -(0.5f + eps) + z); glTexCoord2f(0.0f, 0.0f); glVertex3f((0.5f + eps) + x, -(0.5f + eps) + y, (0.5f + eps) + z); glTexCoord2f(1.0f, 0.0f); glVertex3f(-(0.5f + eps) + x, -(0.5f + eps) + y, (0.5f + eps) + z); glEnd(); } static void saveScreenshot(int x, int y, int w, int h, std::string filename) { Textures::TEXTURE_RGB scrBuffer; auto bufw = w, bufh = h; while (bufw % 4 != 0) { bufw += 1; } while (bufh % 4 != 0) { bufh += 1; } scrBuffer.sizeX = bufw; scrBuffer.sizeY = bufh; scrBuffer.buffer = std::unique_ptr(new ubyte[bufw * bufh * 3]); glReadPixels(x, y, bufw, bufh, GL_RGB, GL_UNSIGNED_BYTE, scrBuffer.buffer.get()); Textures::SaveRGBImage(std::move(filename), scrBuffer); } void createThumbnail() { std::stringstream ss; ss << "Worlds/" << World::worldname << "/Thumbnail.bmp"; saveScreenshot(0, 0, windowwidth, windowheight, ss.str()); } void onViewBinding() override { mRoot->SetDataContext(mViewModel); mRoot->FindName("Resume")->Click() += [this](Noesis::BaseComponent *, const Noesis::RoutedEventArgs &) { mViewModel->setGamePaused(false); updateThreadPaused = false; glfwSetInputMode(MainWindow, GLFW_CURSOR, GLFW_CURSOR_DISABLED); }; mRoot->FindName("Exit")->Click() += [this](Noesis::BaseComponent *, const Noesis::RoutedEventArgs &) { mViewModel->setGamePaused(false); updateThreadPaused = false; requestLeave(); pushScene(Menus::startMenu()); }; auto hotbar = mRoot->FindName("Hotbar"); for (auto &slot: mHotBar) { hotbar->GetChildren()->Add(slot = new InventorySlot()); } auto inventory = mRoot->FindName("Inventory"); for (int row = 0; row < 4; ++row) { for (int i = 0; i < 10; ++i) { inventory->GetChildren()->Add(mInventory[row][i] = new InventorySlot()); mInventory[row][i]->PreviewMouseUp() += [this, row, i](Noesis::BaseComponent *, const Noesis::MouseButtonEventArgs &args) { auto playerInventory = mPlayer->getInventory(); auto &thisAmount = playerInventory[row][i].amount; auto &thisItem = playerInventory[row][i].item; bool rightClick = args.changedButton == Noesis::MouseButton_Right; if (mInventoryMoveFrom.has_value()) { // if has already selected one auto &from = mInventoryMoveFrom.value(); auto &fromAmount = playerInventory[from.row][from.col].amount; auto &fromItem = playerInventory[from.row][from.col].item; if (thisItem != fromItem && thisItem != Blocks::ENV) { // different item - swap std::swap(fromAmount, thisAmount); std::swap(fromItem, thisItem); from.quantity = 0; } else { // same item or empty - stack const auto moveAmount = rightClick ? 1 : std::min(255 - thisAmount, std::min(from.quantity, int(fromAmount))); thisItem = fromItem; fromAmount -= moveAmount; thisAmount += moveAmount; from.quantity -= moveAmount; } if (fromAmount == 0) fromItem = Blocks::ENV; if (from.quantity == 0) mInventoryMoveFrom.reset(); // done transfer } else if (thisAmount != 0) { // if not selected and this one is selectable mInventoryMoveFrom = ItemMoveContext{ row, i, args.changedButton == Noesis::MouseButton_Left ? thisAmount : std::max(thisAmount / 2, 1) }; } }; } } } void onLoad() override { glEnable(GL_LINE_SMOOTH); glEnable(GL_TEXTURE_2D); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glfwSwapBuffers(MainWindow); glfwPollEvents(); Mutex = MutexCreate(); //MutexLock(Mutex); currentGame = this; mUpdateThread = std::thread([this] { gameThread(); }); //初始化游戏状态 InitGame(); infostream << "Init world..."; World::Init(); registerCommands(); mShouldRenderGUI = true; glDepthFunc(GL_LEQUAL); glEnable(GL_CULL_FACE); setupNormalFog(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glfwSwapBuffers(MainWindow); glfwPollEvents(); infostream << "Game start!"; //这才是游戏开始! glfwSetInputMode(MainWindow, GLFW_CURSOR, GLFW_CURSOR_DISABLED); infostream << "Main loop started"; updateThreadRun = true; lastUpdate = timer(); } void onUpdate() override { mControls.Update(); debugInfo(); auto inventory = mPlayer->getInventory(); for (int i = 0; i < 10; ++i) { mHotBar[i]->setItemStack(inventory[3][i]); mHotBar[i]->setSelected(i == mPlayer->getCurrentHotbarSelection()); } for (int row = 0; row < 4; ++row) { for (int i = 0; i < 10; ++i) { mInventory[row][i]->setItemStack(inventory[row][i]); mInventory[row][i]->setSelected(mInventoryMoveFrom.has_value() && row == mInventoryMoveFrom.value().row && i == mInventoryMoveFrom.value().col); } } mViewModel->notifyHPChanges(mPlayer); mViewModel->setBagOpen(mBagOpened); static bool wasBagOpen = false; if (mViewModel->getBagOpen()) { if (!wasBagOpen) { wasBagOpen = true; glfwSetInputMode(MainWindow, GLFW_CURSOR, GLFW_CURSOR_NORMAL); } } else if (wasBagOpen) { wasBagOpen = false; glfwSetInputMode(MainWindow, GLFW_CURSOR, GLFW_CURSOR_DISABLED); } if (glfwGetKey(MainWindow, GLFW_KEY_ESCAPE) == 1) { mViewModel->setGamePaused(true); updateThreadPaused = true; glfwSetInputMode(MainWindow, GLFW_CURSOR, GLFW_CURSOR_NORMAL); createThumbnail(); } } ~GameView() override { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glfwSwapBuffers(MainWindow); glfwPollEvents(); infostream << "Terminate threads"; updateThreadRun = false; //MutexUnlock(Mutex); mUpdateThread.join(); currentGame = nullptr; MutexDestroy(Mutex); saveGame(); World::destroyAllChunks(); commands.clear(); } }; void pushGameView() { GUI::pushScene(std::make_unique()); } ================================================ FILE: NEWorld.Game/GameView.h ================================================ #pragma once void pushGameView(); ================================================ FILE: NEWorld.Game/Globalization.cpp ================================================ #include #include "Globalization.h" #include #include #include namespace Globalization { int count; std::string Cur_Lang = "zh_CN", Cur_Symbol = "", Cur_Name = ""; std::map Lines; std::map keys; bool LoadLang(const std::string& lang) { std::ifstream f("./Assets/Translations/"+lang+".lang"); if (f.bad()) { return false; } Lines.clear(); Cur_Lang = lang; getline(f, Cur_Symbol); getline(f, Cur_Name); for (auto i = 0; i> file; auto i = 0; for (auto& v: file) { keys[v] = i++; } count = i; } catch (...) { return false; } return LoadLang(Cur_Lang); } return false; } std::string GetStrbyid(int id) { return Lines[id].str; } std::string GetStrbyKey(std::string key) { return Lines[keys[key]].str; } } ================================================ FILE: NEWorld.Game/Globalization.h ================================================ #pragma once #include namespace Globalization { struct Line { std::string str; int id; }; extern std::string Cur_Lang; bool LoadLang(const std::string& lang); bool Load(); std::string GetStrbyid(int id); std::string GetStrbyKey(std::string key); } ================================================ FILE: NEWorld.Game/Items.cpp ================================================ #include "Items.h" #include "Textures.h" ItemInfo itemsinfo[] = {STICK, APPLE}; void loadItemsTextures() { itemsinfo[BuiltInItems::STICK - theFirstItem].texture = Textures::LoadRGBTexture("./Assets/Textures/Items/stick.bmp"); itemsinfo[BuiltInItems::APPLE - theFirstItem].texture = Textures::LoadRGBTexture("./Assets/Textures/Items/apple.bmp"); } ================================================ FILE: NEWorld.Game/Items.h ================================================ #pragma once #include "Definitions.h" class ItemInfo { public: ItemInfo(Item itemid, TextureID itemtexture = 0) : id(itemid), texture(itemtexture) {} Item id; TextureID texture; }; enum BuiltInItems { STICK = 30000, APPLE }; extern ItemInfo itemsinfo[]; const Item theFirstItem = STICK; void loadItemsTextures(); inline bool isBlock(Item i) { return i < theFirstItem; } inline TextureID getItemTexture(Item i) { if (isBlock(i)) return BlockTextures; else return itemsinfo[i - theFirstItem].texture; } ================================================ FILE: NEWorld.Game/Menus.h ================================================ #pragma once #include "Globalization.h" using Globalization::GetStrbyKey; namespace Menus { void options(); void Renderoptions(); void Shaderoptions(); void GUIoptions(); void worldmenu(); void createworldmenu(); void multiplayermenu(); void languagemenu(); void Information(); void Soundmenu(); } ================================================ FILE: NEWorld.Game/NEWorld.cpp ================================================ //============================== Initialize ================================// //==============================初始化(包括闪屏)================================// #include "Definitions.h" #include "Renderer/Renderer.h" #include "Universe/World/World.h" #include "Globalization.h" #include "Setup.h" #include "GUI/GUI.h" #include "AudioSystem.h" #include #include "System/MessageBus.h" #include "System/FileSystem.h" #include "GUI/Menus/Menus.h" #include "Common/Logger.h" #include "GUI/Noesis.h" #include "GameSettings.h" void loadOptions(); void saveOptions(); //============================== Main Program ================================// //============================== 主程序 ================================// void ApplicationBeforeLaunch() { setlocale(LC_ALL, "zh_CN.UTF-8"); GameSettings::getInstance().loadOptions(); Globalization::Load(); NEWorld::filesystem::create_directories("./Configs"); NEWorld::filesystem::create_directories("./Worlds"); NEWorld::filesystem::create_directories("./Screenshots"); NEWorld::filesystem::create_directories("./Mods"); } void ApplicationAfterLaunch() { loadTextures(); //初始化音频系统 AudioSystem::Init(); } #include "Universe/Entity/Entity.h" int main() { ApplicationBeforeLaunch(); windowwidth = DefaultWindowWidth; windowheight = DefaultWindowHeight; if (glfwInit() == 1) { infostream << "Initialize GLFW"; } else { infostream << "GLFW initialization failed"; } createWindow(); setupScreen(); GUI::noesisSetup(); glDisable(GL_CULL_FACE); //splashScreen(); ApplicationAfterLaunch(); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glDisable(GL_LINE_SMOOTH); GUI::pushScene(Menus::startMenu()); GUI::appStart(); //结束程序,删了也没关系 ←_←(吐槽FB和glfw中) //不对啊这不是FB!!!这是正宗的C++!!!!!! //楼上的楼上在瞎说!!!别信他的!!! //……所以你是不是应该说“吐槽C艹”中?——地鼠 GUI::noesisShutdown(); glfwTerminate(); //反初始化音频系统 AudioSystem::UnInit(); return 0; } void AppCleanUp() { World::saveAllChunks(); World::destroyAllChunks(); } ================================================ FILE: NEWorld.Game/Particles.cpp ================================================ #include "Particles.h" #include "Universe/World/World.h" #include "Textures.h" #include "Renderer/BufferBuilder.h" #include "Renderer/GL/Pipeline.h" namespace Particles { std::vector ptcs; int ptcsrendered; double pxpos, pypos, pzpos; Renderer::Pipeline &GetPipeline() { static auto pipeline = []() { using namespace Renderer; constexpr int sof = sizeof(float); PipelineBuilder builder{Topology::TriangleList}; builder.SetBinding(1, 7 * sof, 0); builder.AddAttribute(DataType::Float32, 1, 1, 2, 0); builder.AddAttribute(DataType::Float32, 1, 2, 2, 2 * sof); builder.AddAttribute(DataType::Float32, 1, 3, 3, 4 * sof); builder.SetShader(ShaderType::Vertex, CompileFile(ShaderType::Vertex, "./Assets/Shaders/Particle.vsh", {})); builder.SetShader(ShaderType::Fragment, CompileFile(ShaderType::Fragment, "./Assets/Shaders/Particle.fsh", {})); auto result = builder.Build(); result->SetUniform(0, 0); result->BindIndexBuffer(GetDefaultQuadIndex(), IndexType::U32); return result; }(); return pipeline; } void update(Particle &ptc) { const auto psz = ptc.psize; ptc.hb.min.values[0] = ptc.xpos - psz; ptc.hb.max.values[0] = ptc.xpos + psz; ptc.hb.min.values[1] = ptc.ypos - psz; ptc.hb.min.values[1] = ptc.ypos + psz; ptc.hb.min.values[2] = ptc.zpos - psz; ptc.hb.min.values[2] = ptc.zpos + psz; double dx = ptc.xsp; double dy = ptc.ysp; double dz = ptc.zsp; auto Hitboxes = World::getHitboxes(ptc.hb.extend(AABB::Move(ptc.hb, { dx, dy, dz }))); const int hitnum = Hitboxes.size(); for (auto i = 0; i < hitnum; i++) { dy = AABB::MaxMove(ptc.hb, Hitboxes[i], dy, 1); } ptc.hb = AABB::Move(ptc.hb, { 0.0, dy, 0.0 }); for (auto i = 0; i < hitnum; i++) { dx = AABB::MaxMove(ptc.hb, Hitboxes[i], dx,0); } ptc.hb = AABB::Move(ptc.hb, { dx, 0.0, 0.0 }); for (auto i = 0; i < hitnum; i++) { dz = AABB::MaxMove(ptc.hb, Hitboxes[i], dz, 2); } ptc.hb = AABB::Move(ptc.hb, { 0.0, 0.0, dz }); ptc.xpos += dx; ptc.ypos += dy; ptc.zpos += dz; if (dy != ptc.ysp) ptc.ysp = 0.0; ptc.xsp *= 0.6f; ptc.zsp *= 0.6f; ptc.ysp -= 0.01f; ptc.lasts -= 1; } void updateall() { for (auto iter = ptcs.begin(); iter < ptcs.end();) { if (!iter->exist) continue; update(*iter); if (iter->lasts <= 0) { iter->exist = false; iter = ptcs.erase(iter); } else { iter++; } } } void render(Renderer::BufferBuilder<> &builder, Particle &ptc) { //if (!Frustum::aabbInFrustum(ptc.hb)) return; ptcsrendered++; const auto size = static_cast(BLOCKTEXTURE_UNITSIZE) / BLOCKTEXTURE_SIZE * (ptc.psize <= 1.0f ? ptc.psize : 1.0f); const auto col = World::getbrightness(RoundInt(ptc.xpos), RoundInt(ptc.ypos), RoundInt(ptc.zpos)) / static_cast(World::BRIGHTNESSMAX); const auto col1 = col * 0.5f; const auto col2 = col * 0.7f; const auto tcx = ptc.tcX; const auto tcy = ptc.tcY; const auto psize = ptc.psize; const auto palpha = (ptc.lasts < 30 ? ptc.lasts / 30.0 : 1.0); const auto xpos = ptc.xpos - pxpos; const auto ypos = ptc.ypos - pypos; const auto zpos = ptc.zpos - pzpos; builder.put<24>( tcx, tcy, col1, palpha, xpos - psize, ypos - psize, zpos + psize, tcx + size, tcy, col1, palpha, xpos + psize, ypos - psize, zpos + psize, tcx + size, tcy + size, col1, palpha, xpos + psize, ypos + psize, zpos + psize, tcx, tcy + size, col1, palpha, xpos - psize, ypos + psize, zpos + psize, tcx, tcy, col1, palpha, xpos - psize, ypos + psize, zpos - psize, tcx + size, tcy, col1, palpha, xpos + psize, ypos + psize, zpos - psize, tcx + size, tcy + size, col1, palpha, xpos + psize, ypos - psize, zpos - psize, tcx, tcy + size, col1, palpha, xpos - psize, ypos - psize, zpos - psize, tcx, tcy, col, palpha, xpos + psize, ypos + psize, zpos - psize, tcx + size, tcy, col, palpha, xpos - psize, ypos + psize, zpos - psize, tcx + size, tcy + size, col, palpha, xpos - psize, ypos + psize, zpos + psize, tcx, tcy + size, col, palpha, xpos + psize, ypos + psize, zpos + psize, tcx, tcy, col, palpha, xpos - psize, ypos - psize, zpos - psize, tcx + size, tcy, col, palpha, xpos + psize, ypos - psize, zpos - psize, tcx + size, tcy + size, col, palpha, xpos + psize, ypos - psize, zpos + psize, tcx, tcy + size, col, palpha, xpos - psize, ypos - psize, zpos + psize, tcx, tcy, col2, palpha, xpos + psize, ypos + psize, zpos - psize, tcx + size, tcy, col2, palpha, xpos + psize, ypos + psize, zpos + psize, tcx + size, tcy + size, col2, palpha, xpos + psize, ypos - psize, zpos + psize, tcx, tcy + size, col2, palpha, xpos + psize, ypos - psize, zpos - psize, tcx, tcy, col2, palpha, xpos - psize, ypos - psize, zpos - psize, tcx + size, tcy, col2, palpha, xpos - psize, ypos - psize, zpos + psize, tcx + size, tcy + size, col2, palpha, xpos - psize, ypos + psize, zpos + psize, tcx, tcy + size, col2, palpha, xpos - psize, ypos + psize, zpos - psize ); } void renderall(double xpos, double ypos, double zpos) { pxpos = xpos; pypos = ypos; pzpos = zpos; ptcsrendered = 0; Renderer::BufferBuilder builder{}; for (auto &ptc: ptcs) { if (!ptc.exist) continue; render(builder, ptc); } VBOID vbo{0}; vtxCount vts{0}; builder.flush(vbo, vts); if (vts != 0) { GetPipeline()->Use(); GetPipeline()->BindVertexBuffer(1, vbo, 0); GetPipeline()->DrawIndexed(vts + vts / 2, 0); glDeleteBuffers(1, &vbo); glBindVertexArray(0); } } void throwParticle(Block pt, float x, float y, float z, float xs, float ys, float zs, float psz, int last) { const auto tcX1 = static_cast(Textures::getTexcoordX(pt, 2)); const auto tcY1 = static_cast(Textures::getTexcoordY(pt, 2)); Particle ptc; ptc.exist = true; ptc.xpos = x; ptc.ypos = y; ptc.zpos = z; ptc.xsp = xs; ptc.ysp = ys; ptc.zsp = zs; ptc.psize = psz; ptc.hb.min.values[0] = x - psz; ptc.hb.max.values[0] = x + psz; ptc.hb.min.values[1] = y - psz; ptc.hb.max.values[1] = y + psz; ptc.hb.min.values[2] = z - psz; ptc.hb.max.values[2] = z + psz; ptc.lasts = last; ptc.tcX = tcX1 + static_cast(rnd()) * (static_cast(BLOCKTEXTURE_UNITSIZE) / BLOCKTEXTURE_SIZE) * (1.0f - psz); ptc.tcY = tcY1 + static_cast(rnd()) * (static_cast(BLOCKTEXTURE_UNITSIZE) / BLOCKTEXTURE_SIZE) * (1.0f - psz); ptcs.push_back(ptc); } } ================================================ FILE: NEWorld.Game/Particles.h ================================================ #pragma once #include "Definitions.h" #include "Universe/Entity/bvh.h" namespace Particles { const int PARTICALE_MAX = 4096; struct Particle { bool exist = false; double xpos, ypos, zpos; float xsp, ysp, zsp, psize, tcX, tcY; int lasts; BoundingBox hb; }; extern std::vector ptcs; extern int ptcsrendered; void update(Particle &ptc); void updateall(); void renderall(double xpos, double ypos, double zpos); void throwParticle(Block pt, float x, float y, float z, float xs, float ys, float zs, float psz, int last); inline void throwParticle(Block pt, Int3 pos) { throwParticle( pt, float(pos.X + rnd() - 0.5f), float(pos.Y + rnd() - 0.2f), float(pos.Z + rnd() - 0.5f), float(rnd() * 0.2f - 0.1f), float(rnd() * 0.2f - 0.1f), float(rnd() * 0.2f - 0.1f), float(rnd() * 0.01f + 0.02f), int(rnd() * 30) + 30 ); } } ================================================ FILE: NEWorld.Game/Renderer/BufferBuilder.h ================================================ #pragma once #include #include #include "Definitions.h" #include #include #include #include "Dispatch.h" namespace Renderer { enum class BufferType { Vertex, Index, Indirect }; template class BufferBuilder { static constexpr ptrdiff_t size = 1024; static constexpr ptrdiff_t mask = size - 1; using Array = std::array; public: template void put(Elem... elem) noexcept { std::initializer_list v{static_cast(std::forward(elem))...}; const auto off = mTail & mask; const auto remain = size - off; if (off == 0) make_sector(); if (remain >= static_cast(v.size())) { std::copy(v.begin(), v.end(), mSectors.back()->begin() + off); } else { std::copy(v.begin(), v.begin() + remain, mSectors.back()->begin() + off); make_sector(); std::copy(v.begin() + remain, v.end(), mSectors.back()->begin()); } mTail += static_cast(v.size()); mVts += count; } // TODO: this is a temporary method kls::coroutine::ValueAsync flushAsync(VBOID& buffer, vtxCount& vts) noexcept { vts = static_cast(mVts); if (mVts != 0) { if (buffer != 0) glDeleteBuffers(1, &buffer); glCreateBuffers(1, &buffer); const auto segmentSize = static_cast(mSectors.size() * size * sizeof(B)); glNamedBufferStorage(buffer, segmentSize, nullptr, GL_MAP_WRITE_BIT); co_await CopyAndRelease(reinterpret_cast(glMapNamedBufferRange( buffer, 0, segmentSize, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_FLUSH_EXPLICIT_BIT ))); glFlushMappedNamedBufferRange(buffer, 0, segmentSize); glUnmapNamedBuffer(buffer); } } // TODO: this is a temporary method void flush(VBOID &buffer, vtxCount &vts) const noexcept { vts = static_cast(mVts); if (mVts != 0) { if (buffer != 0) glDeleteBuffers(1, &buffer); glCreateBuffers(1, &buffer); const auto segmentSize = static_cast(mSectors.size() * size * sizeof(B)); glNamedBufferStorage(buffer, segmentSize, nullptr, GL_MAP_WRITE_BIT); auto target = reinterpret_cast(glMapNamedBufferRange( buffer, 0, segmentSize, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_FLUSH_EXPLICIT_BIT )); for (auto &s: mSectors) target = std::copy(s->begin(), s->end(), target); glFlushMappedNamedBufferRange(buffer, 0, segmentSize); glUnmapNamedBuffer(buffer); } } private: void make_sector() noexcept { mSectors.emplace_back(kls::temp::make_unique()); } kls::coroutine::ValueAsync CopyAndRelease(B* target) { co_await kls::coroutine::SwitchTo(GetSessionDefault()); for (auto& s : mSectors) target = std::copy(s->begin(), s->end(), target); mSectors.clear(); } kls::temp::vector> mSectors{}; ptrdiff_t mTail{0u}, mVts{0u}; }; } ================================================ FILE: NEWorld.Game/Renderer/GL/Objects.cpp ================================================ ================================================ FILE: NEWorld.Game/Renderer/GL/Objects.h ================================================ #pragma once #include "stdinclude.h" #include "Temp/Vector.h" #include "Temp/Unordered.h" namespace Renderer { } ================================================ FILE: NEWorld.Game/Renderer/GL/Pipeline.cpp ================================================ #include #include "Pipeline.h" #include "FunctionsKit.h" #include #include #include "Renderer/BufferBuilder.h" namespace { auto CollapseDefines(const std::vector &defines) noexcept { kls::temp::set result{}; for (const auto &s: defines) result.insert(s); return result; } void PrintDebug(GLuint program) { GLint uniform_count = 0; glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &uniform_count); if (uniform_count != 0) { GLint max_name_len = 0; GLsizei length = 0; GLsizei count = 0; GLenum type = GL_NONE; glGetProgramiv(program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &max_name_len); auto uName = std::make_unique(max_name_len); for (GLint i = 0; i < uniform_count; ++i) { glGetActiveUniform(program, i, max_name_len, &length, &count, &type, uName.get()); infostream << uName.get() << ":" << glGetUniformLocation(program, uName.get()) << " count:" << count; } } } kls::temp::string Preprocess(std::string_view text, kls::temp::set defines) { kls::temp::istringstream input{kls::temp::string(text)}; kls::temp::stringstream output{}; kls::temp::istringstream line{}; kls::temp::string cur, var, macro; while (!input.eof()) { std::getline(input, cur); std::string_view lineView{cur}; if (cur.empty()) continue; if (lineView.starts_with( "#")) { //处理NEWorld预处理器标志 line.str(cur); line >> macro; if (macro == "##NEWORLD_SHADER_DEFINES") { line >> var >> macro; if (defines.find(var) != defines.end()) cur = "#define " + macro; else continue; } } output << cur << '\n'; } return output.str(); } GLenum MapShaderTypes(Renderer::ShaderType type) noexcept { switch (type) { case Renderer::ShaderType::Vertex: return GL_VERTEX_SHADER; case Renderer::ShaderType::Geometry: return GL_GEOMETRY_SHADER; case Renderer::ShaderType::Fragment: return GL_FRAGMENT_SHADER; case Renderer::ShaderType::Compute: return GL_COMPUTE_SHADER; } return GL_INVALID_ENUM; } void CheckErrorShader(GLuint res, const std::string &eMsg) { auto st = GL_TRUE; glGetShaderiv(res, GL_COMPILE_STATUS, &st); if (st == GL_FALSE) debugstream << eMsg; else return; int logLength, charsWritten; glGetShaderiv(res, GL_INFO_LOG_LENGTH, &logLength); if (logLength > 1) { const auto log = kls::temp::make_unique(logLength); glGetShaderInfoLog(res, logLength, &charsWritten, log.get()); throw std::runtime_error(log.get()); } } void CheckErrorProgram(GLuint res, const std::string &eMsg) { auto st = GL_TRUE; glGetProgramiv(res, GL_LINK_STATUS, &st); if (st == GL_FALSE) debugstream << eMsg; else return; int logLength, charsWritten; glGetProgramiv(res, GL_INFO_LOG_LENGTH, &logLength); if (logLength > 1) { const auto log = kls::temp::make_unique(logLength); glGetProgramInfoLog(res, logLength, &charsWritten, log.get()); throw std::runtime_error(log.get()); } } Renderer::Internal::Object *ToFakePtr(GLuint hdc) noexcept { return reinterpret_cast(static_cast(hdc)); } GLuint FromFakePtr(Renderer::Internal::Object *ptr) noexcept { return static_cast(reinterpret_cast(ptr)); } using GLObject = Renderer::Internal::Object; auto MakeProgram(const kls::temp::unordered_map &stages) { auto program = glCreateProgram(); auto deleter = [](GLObject *ptr) noexcept { if (ptr) { glDeleteProgram(FromFakePtr(ptr)); } }; auto result = std::unique_ptr(ToFakePtr(program), deleter); // TODO(implement checks) for (auto&&[type, shader]: stages) { glAttachShader(program, FromFakePtr(shader.get())); } glLinkProgram(program); //PrintDebug(program); CheckErrorProgram(program, "Shader linking error!"); return result; } GLenum MapDataType(Renderer::DataType type) { switch (type) { case Renderer::DataType::Int32: return GL_INT; case Renderer::DataType::Float16: return GL_HALF_FLOAT; case Renderer::DataType::Float32: return GL_FLOAT; case Renderer::DataType::Float64: return GL_DOUBLE; } throw std::runtime_error("Unknown DataType Enum"); } auto MakeVAO(const kls::temp::vector &attributes) { GLuint vao; glCreateVertexArrays(1, &vao); auto deleter = [](GLObject *ptr) noexcept { if (ptr) { auto vao = FromFakePtr(ptr); glDeleteVertexArrays(1, &vao); } }; auto result = std::unique_ptr(ToFakePtr(vao), deleter); // TODO(implement checks) for (auto&&[type, binding, location, width, offset]: attributes) { glEnableVertexArrayAttrib(vao, location); glVertexArrayAttribFormat(vao, location, width, MapDataType(type), GL_FALSE, offset); glVertexArrayAttribBinding(vao, location, binding); } return result; } auto ZipBinds(GLuint vao, const kls::temp::unordered_map> &binds) { std::vector result{}; for (auto&&[bind, zip]: binds) { if (result.size() <= bind) result.resize(bind + 1, 0); result[bind] = zip.first; glVertexArrayBindingDivisor(vao, bind, zip.second); } return result; } GLenum CastTopology(Renderer::Topology topology) { switch (topology) { case Renderer::Topology::PointList: return GL_POINTS; case Renderer::Topology::LineList: return GL_LINES; case Renderer::Topology::LineStrip: return GL_LINE_STRIP; case Renderer::Topology::TriangleList: return GL_TRIANGLES; case Renderer::Topology::TriangleStrip: return GL_TRIANGLE_STRIP; case Renderer::Topology::TriangleFan: return GL_TRIANGLE_FAN; case Renderer::Topology::Quad: return GL_QUADS; } throw std::runtime_error("Unknown Topology"); } GLenum CastIndexType(Renderer::IndexType index) { switch (index) { case Renderer::IndexType::U8: return GL_UNSIGNED_BYTE; case Renderer::IndexType::U16: return GL_UNSIGNED_SHORT; case Renderer::IndexType::U32: return GL_UNSIGNED_INT; } throw std::runtime_error("Unknown IndexType"); } class PipelineOGL : public Renderer::IPipeline { public: PipelineOGL(GLenum mode, std::vector strides, GLuint vao, GLuint program) noexcept: mMode(mode), mStrides(std::move(strides)), mVAO(vao), mProgram(program) {} ~PipelineOGL() noexcept { glDeleteProgram(mProgram); glDeleteVertexArrays(1, &mVAO); } void Use() override { glUseProgram(mProgram); glBindVertexArray(mVAO); } void BindVertexBuffer(int bind, GLuint buffer, int offset) override { glVertexArrayVertexBuffer(mVAO, bind, buffer, offset, mStrides[bind]); } void BindIndexBuffer(GLuint buffer, Renderer::IndexType type) override { mElement = CastIndexType(type); glVertexArrayElementBuffer(mVAO, buffer); } void Draw(int count, int first, int instance) override { glDrawArraysInstancedBaseInstance(mMode, first, count, instance, 0); } void DrawIndexed(int count, int first, int instance) override { intptr_t offset = first; if (mElement == GL_UNSIGNED_SHORT) offset *= 2; if (mElement == GL_UNSIGNED_INT) offset *= 4; glDrawElementsInstancedBaseVertexBaseInstance(mMode, count, mElement, nullptr, instance, 0, 0); } void SetUniform(GLint loc, float value) override { glProgramUniform1f(mProgram, loc, value); } void SetUniform(GLint loc, int value) override { glProgramUniform1i(mProgram, loc, value); } void SetUniform(GLint loc, float v0, float v1, float v2, float v3) override { glProgramUniform4f(mProgram, loc, v0, v1, v2, v3); } void SetUniform(GLint loc, float *value) override { glProgramUniformMatrix4fv(mProgram, loc, 1, GL_FALSE, value); } private: GLenum mMode; GLuint mVAO; GLuint mProgram; GLenum mElement{}; std::vector mStrides; }; kls::temp::string LoadFile(const std::string &path) { auto ss = kls::temp::ostringstream{}; std::ifstream input_file(path); if (!input_file.is_open()) { throw std::runtime_error("No such file:" + path); } ss << input_file.rdbuf(); return ss.str(); } } namespace Renderer { GLShader Compile(ShaderType type, std::string_view program, const std::vector &defines) { const auto preprocessed = Preprocess(program, CollapseDefines(defines)); //创建shader const auto mode = MapShaderTypes(type); if (mode == GL_INVALID_ENUM) throw std::runtime_error("Bad Shader Type Enum"); const auto res = glCreateShader(mode); const auto dataP = preprocessed.c_str(); const auto dataL = static_cast(preprocessed.size()); glShaderSource(res, 1, reinterpret_cast(&dataP), &dataL); glCompileShader(res); CheckErrorShader(res, "Shader compilation error! Shader: " + std::string(program)); return {ToFakePtr(res), [](auto ptr) noexcept { glDeleteShader(FromFakePtr(ptr)); }}; } GLShader CompileFile(ShaderType type, const std::string &path, const std::vector &defines) { const auto source = LoadFile(path); return Compile(type, source, defines); } Pipeline PipelineBuilder::Build() { auto program = MakeProgram(mStages); auto vao = MakeVAO(mSpecs); auto strides = ZipBinds(FromFakePtr(vao.get()), mBindings); auto object = std::make_shared( CastTopology(mTopology), std::move(strides), FromFakePtr(vao.release()), FromFakePtr(program.release()) ); return std::static_pointer_cast(std::move(object)); } GLuint GetDefaultQuadIndex() { static auto buffer = []() { BufferBuilder builder{}; for (auto i = 0; i < 262144; i += 4) builder.put<1>(i, i + 1, i + 2, i, i + 2, i + 3); GLuint ibo{0}; vtxCount count{0}; builder.flush(ibo, count); return ibo; }(); return buffer; } } ================================================ FILE: NEWorld.Game/Renderer/GL/Pipeline.h ================================================ #pragma once #include #include "stdinclude.h" #include namespace Renderer { enum class DataType { Int32, Float16, Float32, Float64 }; enum class ShaderType { Vertex, Geometry, Fragment, Compute }; enum class IndexType { U8, U16, U32 }; namespace Internal { struct AttributeSpec { DataType type; int binding, location, width, offset; constexpr AttributeSpec(DataType type, int binding, int location, int width, int offset) noexcept: type(type), binding(binding), location(location), width(width), offset(offset) {} }; class Object { }; } using GLShader = std::shared_ptr; GLShader Compile(ShaderType type, std::string_view program, const std::vector &defines); GLShader CompileFile(ShaderType type, const std::string &path, const std::vector &defines); struct IPipeline { virtual void Use() = 0; virtual void BindVertexBuffer(int bind, GLuint buffer, int offset = 0) = 0; virtual void BindIndexBuffer(GLuint buffer, Renderer::IndexType type) = 0; virtual void Draw(int count, int first = 0, int instance = 1) = 0; virtual void DrawIndexed(int count, int first = 0, int instance = 1) = 0; virtual void SetUniform(GLint loc, float value) = 0; virtual void SetUniform(GLint loc, int value) = 0; virtual void SetUniform(GLint loc, float v0, float v1, float v2, float v3) = 0; virtual void SetUniform(GLint loc, float *value) = 0; }; enum class Topology { PointList, LineList, LineStrip, TriangleList, TriangleStrip, TriangleFan, Quad }; using Pipeline = std::shared_ptr; class PipelineBuilder { public: explicit PipelineBuilder(Topology topology) noexcept: mTopology(topology) {} void SetBinding(int location, int stride, int divisor) noexcept { mBindings.insert_or_assign(location, std::pair(stride, divisor)); } bool AddAttribute(DataType type, int binding, int location, int width, int offset) noexcept { const auto binds = mBindings.find(binding); if (binds == mBindings.end()) return false; // 没有对应binding记录 const auto stride = binds->second.first; switch (type) { case DataType::Float16: if (offset + width * 2 > stride) return false; //超出stride长度 break; case DataType::Int32: case DataType::Float32: if (offset + width * 4 > stride) return false; //超出stride长度 break; case DataType::Float64: if (offset + width * 8 > stride) return false; //超出stride长度 break; default: return false; //未定义的数据类别 } mSpecs.emplace_back(type, binding, location, width, offset); return true; } void SetShader(ShaderType type, GLShader stage) noexcept { mStages.insert_or_assign(type, std::move(stage)); } Pipeline Build(); private: Topology mTopology; kls::temp::vector mSpecs; kls::temp::unordered_map mStages; kls::temp::unordered_map> mBindings; }; GLuint GetDefaultQuadIndex(); } ================================================ FILE: NEWorld.Game/Renderer/Renderer.cpp ================================================ #include "Renderer.h" #include #include #include "Frustum.h" #include "BufferBuilder.h" namespace Renderer { /* 好纠结啊好纠结,“高级”渲染模式里的所有数据要不要都用VertexAttribArray啊。。。 然而我还是比较懒。。。所以除了【附加】的顶点属性之外,其他属性(比如颜色、纹理坐标)都保留原来的算了。。。 说到为啥要用【附加】的顶点属性。。。这是由于Shadow Map的精度问题。。。 有的时候背光面的外圈会有亮光。。。很难看。。。所以要用Shader把背光面弄暗。。。 于是如何让shader知道这个面朝哪里呢?懒得用NormalArray的我就用了一个附加的顶点属性。。。 0.0f表示前面(z+),1.0f表示后面(z-),2.0f表示右面(x+),3.0f表示左面(x-),4.0f表示上面(y+),5.0f表示下面(y-) 你没有看错。。。这些值。。。全都是 浮! 点! 型! 的!!!!!!! 坑爹的GLSL不支持整型作为顶点属性。。。只好用浮点型代替了(╯‵□′)╯︵┻━┻ 然后为了解决浮点数的精度问题,我在shader里写了个四舍五入取整。。。 不说了。。。 等等我还没有签名呢。。。 --qiaozhanrong ==================================================== 留言板: 1楼. qiaozhanrong: 自己抢个沙发先 2楼. Null: 这就是你在源码里写这么一长串的理由?23333333333 3楼. qiaozhanrong: 无聊啊233333333333 4楼. [请输入姓名]: [请输入回复内容] [回复] ==================================================== */ bool AdvancedRender; int ShadowRes = 4096; int MaxShadowDist = 4; int shadowdist; float sunlightXrot, sunlightYrot; std::vector pipelines; int ActivePipeline; int index = 0, size = 0; unsigned int ShadowFBO, DepthTexture; struct IndirectBaseVertex { uint32_t count; uint32_t instanceCount; uint32_t firstIndex; uint32_t baseVertex; uint32_t baseInstance; }; void RenderBufferDirect(VBOID buffer, vtxCount vtxs) { pipelines[ActivePipeline]->BindVertexBuffer(1, buffer, 0); pipelines[ActivePipeline]->DrawIndexed(vtxs + vtxs / 2, 0); } Pipeline BuildPipeline( const std::string &vshPath, const std::string &fshPath, int tc, int cc, int ac, bool useAc, const std::vector &defines = {} ) noexcept { constexpr int sof = sizeof(float); PipelineBuilder builder{Topology::TriangleList}; builder.SetBinding(1, (tc + cc + ac + 3) * sof, 0); if (ac && useAc) builder.AddAttribute(DataType::Float32, 1, 1, ac, 0); if (tc) builder.AddAttribute(DataType::Float32, 1, 2, tc, ac * sof); if (cc) builder.AddAttribute(DataType::Float32, 1, 3, cc, (ac + tc) * sof); builder.AddAttribute(DataType::Float32, 1, 4, 3, (ac + tc + cc) * sof); builder.SetShader(ShaderType::Vertex, CompileFile(ShaderType::Vertex, vshPath, defines)); builder.SetShader(ShaderType::Fragment, CompileFile(ShaderType::Fragment, fshPath, defines)); auto result = builder.Build(); result->BindIndexBuffer(GetDefaultQuadIndex(), IndexType::U32); return result; } void initShaders() { std::vector defines{}; sunlightXrot = 30.0f; sunlightYrot = 60.0f; shadowdist = std::min(MaxShadowDist, viewdistance); pipelines = { BuildPipeline("./Assets/Shaders/Main.vsh", "./Assets/Shaders/Main.fsh", 2, 3, 1, true), BuildPipeline("./Assets/Shaders/Shadow.vsh", "./Assets/Shaders/Shadow.fsh", 0, 0, 0, false), BuildPipeline("./Assets/Shaders/Depth.vsh", "./Assets/Shaders/Depth.fsh", 0, 0, 0, false, defines), BuildPipeline("./Assets/Shaders/Simple.vsh", "./Assets/Shaders/Simple.fsh", 2, 3, 1, false) }; glGenTextures(1, &DepthTexture); glBindTexture(GL_TEXTURE_2D, DepthTexture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, ShadowRes, ShadowRes, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, DepthTexture); glActiveTexture(GL_TEXTURE0); glGenFramebuffersEXT(1, &ShadowFBO); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, ShadowFBO); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, DepthTexture, 0); if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != GL_FRAMEBUFFER_COMPLETE_EXT) { debugstream << "Frame buffer creation error!"; } glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); pipelines[MainShader]->SetUniform(0, 0); pipelines[MainShader]->SetUniform(1, 1); pipelines[MainShader]->SetUniform(5, skycolorR, skycolorG, skycolorB, 1.0f); pipelines[SimpleShader]->SetUniform(0, 0); } void destroyShaders() { glDeleteTextures(1, &DepthTexture); glDeleteFramebuffersEXT(1, &ShadowFBO); } void BindPipeline(int shaderID) { pipelines[shaderID]->Use(); ActivePipeline = shaderID; } void EnableAdvancedShaders() { shadowdist = std::min(MaxShadowDist, viewdistance); //Enable pipeline auto &pipeline = pipelines[MainShader]; BindPipeline(MainShader); //Calc matrix const auto scale = 16.0f * sqrt(3.0f); const auto length = shadowdist * scale; Frustum frus; frus.LoadIdentity(); frus.SetOrtho(-length, length, -length, length, -length, length); frus.MultRotate(sunlightXrot, 1.0f, 0.0f, 0.0f); frus.MultRotate(sunlightYrot, 0.0f, 1.0f, 0.0f); //Set uniform pipeline->SetUniform(6, viewdistance * 16.0f); pipeline->SetUniform(2, frus.getProjMatrix()); pipeline->SetUniform(3, frus.getModlMatrix()); } void EnableSimpleShaders() { BindPipeline(SimpleShader); } void EnableShaders() { if (AdvancedRender) EnableAdvancedShaders(); else EnableSimpleShaders(); } void DisableShaders() { glBindVertexArray(0); glUseProgram(0); } void StartShadowPass() { glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, ShadowFBO); glDrawBuffer(GL_NONE); glReadBuffer(GL_NONE); BindPipeline(ShadowShader); glViewport(0, 0, ShadowRes, ShadowRes); } void EndShadowPass() { glDrawBuffer(GL_NONE); glReadBuffer(GL_NONE); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); glDrawBuffer(GL_BACK); glReadBuffer(GL_BACK); glBindVertexArray(0); glViewport(0, 0, windowwidth, windowheight); } Pipeline &GetPipeline() { return pipelines[ActivePipeline]; } } ================================================ FILE: NEWorld.Game/Renderer/Renderer.h ================================================ #pragma once #include "Definitions.h" #include "GL/Pipeline.h" #include namespace Renderer { //我猜你肯定不敢看Renderer.cpp --qiaozhanrong //猜对了 --Null enum { MainShader, ShadowShader, DepthShader, SimpleShader }; const int ArraySize = 2621440; extern bool AdvancedRender; extern int ShadowRes; extern int MaxShadowDist; extern int shadowdist; extern float sunlightXrot, sunlightYrot; extern unsigned int DepthTexture; extern int ActivePipeline; void RenderBufferDirect(VBOID buffer, vtxCount vtxs); Pipeline& GetPipeline(); void initShaders(); void destroyShaders(); void EnableShaders(); void DisableShaders(); void StartShadowPass(); void EndShadowPass(); } ================================================ FILE: NEWorld.Game/Renderer/World/ChunkRenderer.cpp ================================================ #include "ChunkRenderer.h" #include "Renderer/Renderer.h" #include "Universe/World/World.h" #include "Renderer/BufferBuilder.h" #include "Math/Vector4.h" #include "Dispatch.h" namespace WorldRenderer { class ChunkRenderContext { static constexpr auto MX = 18 * 18; static constexpr auto MY = 18; public: ChunkRenderContext(World::Chunk *chunk) { Block *bF = mBStates; std::uint8_t *lF = mLumin; const auto cPos = chunk->GetPosition() * 16; Cursor(cPos - Int3{1}, cPos + Int3{17}, [&bF, &lF, chunk](const auto &v) { *(bF++) = World::GetBlock(v, Blocks::ROCK, chunk); *(lF++) = World::GetBrightness(v); }); } void Rebase(int x, int y, int z) noexcept { mBase = (x + 1) * MX + (y + 1) * MY + (z + 1); } auto State(int dx, int dy, int dz) noexcept { return mBStates[mBase + dx * MX + dy * MY + dz]; } Brightness Lumin(int dx, int dy, int dz) noexcept { return mLumin[mBase + dx * MX + dy * MY + dz]; } private: int mBase{0}; Block mBStates[18 * 18 * 18]; std::uint8_t mLumin[18 * 18 * 18]{0}; }; enum Face { Front, Back, Right, Left, Top, Bottom }; //TODO(simplify this function) static void renderblock(Renderer::BufferBuilder<> &builder, ChunkRenderContext &ctx, int x, int y, int z) { double tcx, tcy, size, EPS = 0.0; Block blk[7] = {ctx.State(0, 0, 0), ctx.State(0, 0, 1), ctx.State(0, 0, -1), ctx.State(1, 0, 0), ctx.State(-1, 0, 0), ctx.State(0, 1, 0), ctx.State(0, -1, 0)}; Brightness brt[7] = {ctx.Lumin(0, 0, 0), ctx.Lumin(0, 0, 1), ctx.Lumin(0, 0, -1), ctx.Lumin(1, 0, 0), ctx.Lumin(-1, 0, 0), ctx.Lumin(0, 1, 0), ctx.Lumin(0, -1, 0)}; size = 1 / 8.0f - EPS; if (NiceGrass && blk[0] == Blocks::GRASS && ctx.State(0, -1, 1) == Blocks::GRASS) { tcx = Textures::getTexcoordX(blk[0], 1) + EPS; tcy = Textures::getTexcoordY(blk[0], 1) + EPS; } else { tcx = Textures::getTexcoordX(blk[0], 2) + EPS; tcy = Textures::getTexcoordY(blk[0], 2) + EPS; } // Front Face if (!(BlockInfo(blk[1]).isOpaque() || (blk[1] == blk[0] && !BlockInfo(blk[0]).isOpaque())) || blk[0] == Blocks::LEAF) { Double4 col{double(brt[1])}; if (blk[0] != Blocks::GLOWSTONE && SmoothLighting) col = (col + Double4( ctx.Lumin(0, -1, 1) + ctx.Lumin(-1, 0, 1) + ctx.Lumin(-1, -1, 1), ctx.Lumin(0, -1, 1) + ctx.Lumin(1, 0, 1) + ctx.Lumin(1, -1, 1), ctx.Lumin(0, 1, 1) + ctx.Lumin(1, 0, 1) + ctx.Lumin(1, 1, 1), ctx.Lumin(0, 1, 1) + ctx.Lumin(-1, 0, 1) + ctx.Lumin(-1, 1, 1) )) / 4.0; col /= World::BRIGHTNESSMAX; if (blk[0] != Blocks::GLOWSTONE && !Renderer::AdvancedRender) col *= 0.5; // att, tex, col vert builder.put<4>( 0.0f, tcx, tcy, col.X, col.X, col.X, -0.5 + x, -0.5 + y, 0.5 + z, 0.0f, tcx + size, tcy, col.Y, col.Y, col.Y, 0.5 + x, -0.5 + y, 0.5 + z, 0.0f, tcx + size, tcy + size, col.Z, col.Z, col.Z, 0.5 + x, 0.5 + y, 0.5 + z, 0.0f, tcx, tcy + size, col.W, col.W, col.W, -0.5 + x, 0.5 + y, 0.5 + z ); } if (NiceGrass && blk[0] == Blocks::GRASS && ctx.State(0, -1, -1) == Blocks::GRASS) { tcx = Textures::getTexcoordX(blk[0], 1) + EPS; tcy = Textures::getTexcoordY(blk[0], 1) + EPS; } else { tcx = Textures::getTexcoordX(blk[0], 2) + EPS; tcy = Textures::getTexcoordY(blk[0], 2) + EPS; } // Back Face if (!(BlockInfo(blk[2]).isOpaque() || (blk[2] == blk[0] && !BlockInfo(blk[0]).isOpaque())) || blk[0] == Blocks::LEAF) { Double4 col{double(brt[2])}; if (blk[0] != Blocks::GLOWSTONE && SmoothLighting) col = (col + Double4( ctx.Lumin(0, -1, -1) + ctx.Lumin(-1, 0, -1) + ctx.Lumin(-1, -1, -1), ctx.Lumin(0, 1, -1) + ctx.Lumin(-1, 0, -1) + ctx.Lumin(-1, 1, -1), ctx.Lumin(0, 1, -1) + ctx.Lumin(1, 0, -1) + ctx.Lumin(1, 1, -1), ctx.Lumin(0, -1, -1) + ctx.Lumin(1, 0, -1) + ctx.Lumin(1, -1, -1) )) / 4.0; col /= World::BRIGHTNESSMAX; if (blk[0] != Blocks::GLOWSTONE && !Renderer::AdvancedRender) col *= 0.5; // att, tex, col vert builder.put<4>( 1.0f, tcx + size * 1.0, tcy + size * 0.0, col.X, col.X, col.X, -0.5 + x, -0.5 + y, -0.5 + z, 1.0f, tcx + size * 1.0, tcy + size * 1.0, col.Y, col.Y, col.Y, -0.5 + x, 0.5 + y, -0.5 + z, 1.0f, tcx + size * 0.0, tcy + size * 1.0, col.Z, col.Z, col.Z, 0.5 + x, 0.5 + y, -0.5 + z, 1.0f, tcx + size * 0.0, tcy + size * 0.0, col.W, col.W, col.W, 0.5 + x, -0.5 + y, -0.5 + z ); } if (NiceGrass && blk[0] == Blocks::GRASS && ctx.State(1, -1, 0) == Blocks::GRASS) { tcx = Textures::getTexcoordX(blk[0], 1) + EPS; tcy = Textures::getTexcoordY(blk[0], 1) + EPS; } else { tcx = Textures::getTexcoordX(blk[0], 2) + EPS; tcy = Textures::getTexcoordY(blk[0], 2) + EPS; } // Right face if (!(BlockInfo(blk[3]).isOpaque() || (blk[3] == blk[0] && !BlockInfo(blk[0]).isOpaque())) || blk[0] == Blocks::LEAF) { Double4 col{double(brt[3])}; if (blk[0] != Blocks::GLOWSTONE && SmoothLighting) col = (col + Double4( ctx.Lumin(1, -1, 0) + ctx.Lumin(1, 0, -1) + ctx.Lumin(1, -1, -1), ctx.Lumin(1, 1, 0) + ctx.Lumin(1, 0, -1) + ctx.Lumin(1, 1, -1), ctx.Lumin(1, 1, 0) + ctx.Lumin(1, 0, 1) + ctx.Lumin(1, 1, 1), ctx.Lumin(1, -1, 0) + ctx.Lumin(1, 0, 1) + ctx.Lumin(1, -1, 1) )) / 4.0; col /= World::BRIGHTNESSMAX; if (blk[0] != Blocks::GLOWSTONE && !Renderer::AdvancedRender) col *= 0.7; // att, tex, col vert builder.put<4>( 2.0f, tcx + size * 1.0, tcy + size * 0.0, col.X, col.X, col.X, 0.5 + x, -0.5 + y, -0.5 + z, 2.0f, tcx + size * 1.0, tcy + size * 1.0, col.Y, col.Y, col.Y, 0.5 + x, 0.5 + y, -0.5 + z, 2.0f, tcx + size * 0.0, tcy + size * 1.0, col.Z, col.Z, col.Z, 0.5 + x, 0.5 + y, 0.5 + z, 2.0f, tcx + size * 0.0, tcy + size * 0.0, col.W, col.W, col.W, 0.5 + x, -0.5 + y, 0.5 + z ); } if (NiceGrass && blk[0] == Blocks::GRASS && ctx.State(-1, -1, 0) == Blocks::GRASS) { tcx = Textures::getTexcoordX(blk[0], 1) + EPS; tcy = Textures::getTexcoordY(blk[0], 1) + EPS; } else { tcx = Textures::getTexcoordX(blk[0], 2) + EPS; tcy = Textures::getTexcoordY(blk[0], 2) + EPS; } // Left Face if (!(BlockInfo(blk[4]).isOpaque() || (blk[4] == blk[0] && !BlockInfo(blk[0]).isOpaque())) || blk[0] == Blocks::LEAF) { Double4 col{double(brt[4])}; if (blk[0] != Blocks::GLOWSTONE && SmoothLighting) col = (col + Double4( ctx.Lumin(-1, -1, 0) + ctx.Lumin(-1, 0, -1) + ctx.Lumin(-1, -1, -1), ctx.Lumin(-1, -1, 0) + ctx.Lumin(-1, 0, 1) + ctx.Lumin(-1, -1, 1), ctx.Lumin(-1, 1, 0) + ctx.Lumin(-1, 0, 1) + ctx.Lumin(-1, 1, 1), ctx.Lumin(-1, 1, 0) + ctx.Lumin(-1, 0, -1) + ctx.Lumin(-1, 1, -1) )) / 4.0; col /= World::BRIGHTNESSMAX; if (blk[0] != Blocks::GLOWSTONE && !Renderer::AdvancedRender) col *= 0.7; // att, tex, col vert builder.put<4>( 3.0f, tcx + size * 0.0, tcy + size * 0.0, col.X, col.X, col.X, -0.5 + x, -0.5 + y, -0.5 + z, 3.0f, tcx + size * 1.0, tcy + size * 0.0, col.Y, col.Y, col.Y, -0.5 + x, -0.5 + y, 0.5 + z, 3.0f, tcx + size * 1.0, tcy + size * 1.0, col.Z, col.Z, col.Z, -0.5 + x, 0.5 + y, 0.5 + z, 3.0f, tcx + size * 0.0, tcy + size * 1.0, col.W, col.W, col.W, -0.5 + x, 0.5 + y, -0.5 + z ); } tcx = Textures::getTexcoordX(blk[0], 1); tcy = Textures::getTexcoordY(blk[0], 1); // Top Face if (!(BlockInfo(blk[5]).isOpaque() || (blk[5] == blk[0] && !BlockInfo(blk[0]).isOpaque())) || blk[0] == Blocks::LEAF) { Double4 col{double(brt[5])}; if (blk[0] != Blocks::GLOWSTONE && SmoothLighting) col = (col + Double4( ctx.Lumin(0, 1, -1) + ctx.Lumin(-1, 1, 0) + ctx.Lumin(-1, 1, -1), ctx.Lumin(0, 1, 1) + ctx.Lumin(-1, 1, 0) + ctx.Lumin(-1, 1, 1), ctx.Lumin(0, 1, 1) + ctx.Lumin(1, 1, 0) + ctx.Lumin(1, 1, 1), ctx.Lumin(0, 1, -1) + ctx.Lumin(1, 1, 0) + ctx.Lumin(1, 1, -1) )) / 4.0; col /= World::BRIGHTNESSMAX; // att, tex, col vert builder.put<4>( 4.0f, tcx + size * 0.0, tcy + size * 1.0, col.X, col.X, col.X, -0.5 + x, 0.5 + y, -0.5 + z, 4.0f, tcx + size * 0.0, tcy + size * 0.0, col.Y, col.Y, col.Y, -0.5 + x, 0.5 + y, 0.5 + z, 4.0f, tcx + size * 1.0, tcy + size * 0.0, col.Z, col.Z, col.Z, 0.5 + x, 0.5 + y, 0.5 + z, 4.0f, tcx + size * 1.0, tcy + size * 1.0, col.W, col.W, col.W, 0.5 + x, 0.5 + y, -0.5 + z ); } tcx = Textures::getTexcoordX(blk[0], 3); tcy = Textures::getTexcoordY(blk[0], 3); // Bottom Face if (!(BlockInfo(blk[6]).isOpaque() || (blk[6] == blk[0] && !BlockInfo(blk[0]).isOpaque())) || blk[0] == Blocks::LEAF) { Double4 col{double(brt[6])}; if (blk[0] != Blocks::GLOWSTONE && SmoothLighting) col = (col + Double4( ctx.Lumin(0, -1, -1) + ctx.Lumin(-1, -1, 0) + ctx.Lumin(-1, -1, -1), ctx.Lumin(0, -1, -1) + ctx.Lumin(1, -1, 0) + ctx.Lumin(1, -1, -1), ctx.Lumin(0, -1, 1) + ctx.Lumin(1, -1, 0) + ctx.Lumin(1, -1, 1), ctx.Lumin(0, -1, 1) + ctx.Lumin(-1, -1, 0) + ctx.Lumin(-1, -1, 1) )) / 4.0; col /= World::BRIGHTNESSMAX; // att, tex, col vert builder.put<4>( 5.0f, tcx + size * 1.0, tcy + size * 1.0, col.X, col.X, col.X, -0.5 + x, -0.5 + y, -0.5 + z, 5.0f, tcx + size * 0.0, tcy + size * 1.0, col.Y, col.Y, col.Y, 0.5 + x, -0.5 + y, -0.5 + z, 5.0f, tcx + size * 0.0, tcy + size * 0.0, col.Z, col.Z, col.Z, 0.5 + x, -0.5 + y, 0.5 + z, 5.0f, tcx + size * 1.0, tcy + size * 0.0, col.W, col.W, col.W, -0.5 + x, -0.5 + y, 0.5 + z ); } } static void RenderPrimitive_Depth(Renderer::BufferBuilder<> &builder, QuadPrimitive_Depth &p) { const auto x = p.x, y = p.y, z = p.z, length = p.length; switch (p.direction) { case 0: return builder.put<4>(x + 0.5, y - 0.5, z - 0.5, x + 0.5, y + 0.5, z - 0.5, x + 0.5, y + 0.5, z + length + 0.5, x + 0.5, y - 0.5, z + length + 0.5); case 1: return builder.put<4>(x - 0.5, y + 0.5, z - 0.5, x - 0.5, y - 0.5, z - 0.5, x - 0.5, y - 0.5, z + length + 0.5, x - 0.5, y + 0.5, z + length + 0.5); case 2: return builder.put<4>(x + 0.5, y + 0.5, z - 0.5, x - 0.5, y + 0.5, z - 0.5, x - 0.5, y + 0.5, z + length + 0.5, x + 0.5, y + 0.5, z + length + 0.5); case 3: return builder.put<4>(x - 0.5, y - 0.5, z - 0.5, x + 0.5, y - 0.5, z - 0.5, x + 0.5, y - 0.5, z + length + 0.5, x - 0.5, y - 0.5, z + length + 0.5); case 4: return builder.put<4>(x - 0.5, y + 0.5, z + 0.5, x - 0.5, y - 0.5, z + 0.5, x + length + 0.5, y - 0.5, z + 0.5, x + length + 0.5, y + 0.5, z + 0.5); case 5: return builder.put<4>(x - 0.5, y - 0.5, z - 0.5, x - 0.5, y + 0.5, z - 0.5, x + length + 0.5, y + 0.5, z - 0.5, x + length + 0.5, y - 0.5, z - 0.5); } } static kls::coroutine::ValueAsync RenderDepthModelEvaluate(World::Chunk *c, Renderer::BufferBuilder<> &builder) { co_await kls::coroutine::SwitchTo(GetSessionDefault()); const auto cp = c->GetPosition(); auto x = 0, y = 0, z = 0; QuadPrimitive_Depth cur; Block bl, neighbour; auto valid = false; int cur_l_mx = bl = neighbour = 0; //Linear merge for depth model for (auto d = 0; d < 6; d++) { cur.direction = d; for (auto i = 0; i < 16; i++) for (auto j = 0; j < 16; j++) { for (auto k = 0; k < 16; k++) { //Get position if (d < 2) x = i, y = j, z = k; else if (d < 4) x = i, y = j, z = k; else x = k, y = i, z = j; //Get block ID bl = c->GetBlock({x, y, z}); //Get neighbour ID const auto xx = x + delta[d][0], yy = y + delta[d][1], zz = z + delta[d][2]; if (xx < 0 || xx >= 16 || yy < 0 || yy >= 16 || zz < 0 || zz >= 16) { neighbour = World::GetBlock(cp * 16 + Int3(xx, yy, zz)); } else { neighbour = c->GetBlock({(xx), (yy), (zz)}); } //Render if (bl == Blocks::ENV || bl == Blocks::GLASS || bl == neighbour && bl != Blocks::LEAF || BlockInfo(neighbour).isOpaque() || BlockInfo(bl).isTranslucent()) { //Not valid block if (valid) { if (BlockInfo(neighbour).isOpaque()) { if (cur_l_mx < cur.length) cur_l_mx = cur.length; cur_l_mx++; } else { RenderPrimitive_Depth(builder, cur); valid = false; } } continue; } if (valid) { if (cur_l_mx > cur.length) cur.length = cur_l_mx; cur.length++; } else { valid = true; cur.x = x; cur.y = y; cur.z = z; cur.length = cur_l_mx = 0; } } if (valid) { RenderPrimitive_Depth(builder, cur); valid = false; } } } } static kls::coroutine::ValueAsync RenderDepthModel(World::Chunk *c, ChunkRender &r) { if (Renderer::AdvancedRender) co_return; Renderer::BufferBuilder builder{}; co_await RenderDepthModelEvaluate(c, builder); co_await builder.flushAsync(r.Renders[3].Buffer, r.Renders[3].Count); } static kls::coroutine::ValueAsync BuildRenderEvaluate(World::Chunk *&c, Renderer::BufferBuilder<> b[]) { co_await kls::coroutine::SwitchTo(GetSessionDefault()); auto context = kls::temp::make_unique(c); for (int x = 0; x < 16; x++) { for (int y = 0; y < 16; y++) { for (int z = 0; z < 16; z++) { context->Rebase(x, y, z); const auto curr = context->State(0, 0, 0); if (curr == Blocks::ENV) continue; if (!BlockInfo(curr).isTranslucent()) renderblock(b[0], *context, x, y, z); if (BlockInfo(curr).isTranslucent() && BlockInfo(curr).isSolid()) renderblock(b[1], *context, x, y, z); if (!BlockInfo(curr).isSolid()) renderblock(b[2], *context, x, y, z); } } } } static kls::coroutine::ValueAsync RenderChunk(World::Chunk *c, ChunkRender &r) { Renderer::BufferBuilder<> b[3]{}; co_await BuildRenderEvaluate(c, b); co_await kls::coroutine::awaits( b[0].flushAsync(r.Renders[0].Buffer, r.Renders[0].Count), b[1].flushAsync(r.Renders[1].Buffer, r.Renders[1].Count), b[2].flushAsync(r.Renders[2].Buffer, r.Renders[2].Count) ); } bool ChunkRender::CheckBuild(const std::shared_ptr &c) { for (auto x = -1; x <= 1; x++) { for (auto y = -1; y <= 1; y++) { for (auto z = -1; z <= 1; z++) { if (x == 0 && y == 0 && z == 0) continue; if (World::ChunkOutOfBound(c->GetPosition() + Int3{x, y, z})) continue; if (!World::ChunkLoaded(c->GetPosition() + Int3{x, y, z})) return false; } } } return true; } kls::coroutine::ValueAsync ChunkRender::Rebuild(std::shared_ptr c) { World::rebuiltChunks++; World::updatedChunks++; co_await kls::coroutine::awaits( RenderChunk(c.get(), *this), RenderDepthModel(c.get(), *this) ); c->updated = false; Built = true; } } ================================================ FILE: NEWorld.Game/Renderer/World/ChunkRenderer.h ================================================ #pragma once #include "Definitions.h" #include "Textures.h" #include "Universe/World/World.h" #include namespace WorldRenderer { const int delta[6][3] = { {1, 0, 0}, {-1, 0, 0}, {0, 1, 0}, {0, -1, 0}, {0, 0, 1}, {0, 0, -1} }; struct RenderPair { VBOID Buffer{ 0 }; vtxCount Count{ 0 }; }; struct ChunkRender { bool Built; bool Visiable; Int3 Position; double LoadAnim; RenderPair Renders[4]; BoundingBox BaseBounds; std::weak_ptr Ref; explicit ChunkRender(const std::shared_ptr& c) : Built{ false }, Position(c->GetPosition()), LoadAnim(static_cast(Position.Y) * 16.0f + 16.0f), BaseBounds(c->getBaseAABB()), Ref(c) {} bool CheckBuild(const std::shared_ptr& c); kls::coroutine::ValueAsync Rebuild(std::shared_ptr c); Frustum::ChunkBox getRelativeAABB(const Double3& camera) { Frustum::ChunkBox ret{}; ret.xmin = static_cast(BaseBounds.min.values[0] - camera.X); ret.xmax = static_cast(BaseBounds.max.values[0] - camera.X); ret.ymin = static_cast(BaseBounds.min.values[1] /*- loadAnim*/ - camera.Y); ret.ymax = static_cast(BaseBounds.max.values[1] /*- loadAnim*/ - camera.Y); ret.zmin = static_cast(BaseBounds.min.values[2] - camera.Z); ret.zmax = static_cast(BaseBounds.max.values[2] - camera.Z); return ret; } }; //深度模型的面 | Face in depth model struct QuadPrimitive_Depth { int x, y, z, length, direction; QuadPrimitive_Depth() : x(0), y(0), z(0), length(0), direction(0) {} }; } ================================================ FILE: NEWorld.Game/Renderer/World/ShadowMaps.cpp ================================================ #include "ShadowMaps.h" #include "Universe/World/World.h" #include "Renderer/Renderer.h" namespace ShadowMaps { void BuildShadowMap( WorldRenderer::ChunksRenderer &chunksRenderer, double xpos, double ypos, double zpos, double curtime ) { const auto cx = World::GetChunkPos(static_cast(xpos)), cy = World::GetChunkPos( static_cast(ypos)), cz = World::GetChunkPos(static_cast(zpos)); Renderer::StartShadowPass(); glClear(GL_DEPTH_BUFFER_BIT); glEnableVertexAttribArray(4); glDisable(GL_TEXTURE_2D); glDisable(GL_FOG); glDisable(GL_BLEND); const auto scale = 16.0f * sqrt(3.0f); const auto length = Renderer::shadowdist * scale; glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(-length, length, -length, length, -length, length); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glRotated(Renderer::sunlightXrot, 1.0, 0.0, 0.0); glRotated(Renderer::sunlightYrot, 0.0, 1.0, 0.0); auto frame = chunksRenderer.List({cx, cy, cz}, Renderer::shadowdist + 1, /*curtime,*/ false); frame.Render(xpos, ypos, zpos, 3); glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0); glDisableVertexAttribArray(4); Renderer::EndShadowPass(); glEnable(GL_FOG); glEnable(GL_BLEND); } void RenderShadowMap( WorldRenderer::ChunksRenderer &chunksRenderer, double xpos, double ypos, double zpos, double curtime ) { const auto cx = World::GetChunkPos(static_cast(xpos)), cy = World::GetChunkPos( static_cast(ypos)), cz = World::GetChunkPos(static_cast(zpos)); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnableVertexAttribArray(4); glDisable(GL_TEXTURE_2D); glDisable(GL_FOG); glDisable(GL_BLEND); const auto scale = 16.0f * sqrt(3.0f); const auto length = Renderer::shadowdist * scale; glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(-length, length, -length, length, -length, length); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glRotated(Renderer::sunlightXrot, 1.0, 0.0, 0.0); glRotated(Renderer::sunlightYrot, 0.0, 1.0, 0.0); auto frame = chunksRenderer.List({cx, cy, cz}, Renderer::shadowdist + 1, /*curtime,*/ false); frame.Render(xpos, ypos, zpos, 3); glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0); glDisableVertexAttribArray(4); glEnable(GL_FOG); glEnable(GL_BLEND); } } ================================================ FILE: NEWorld.Game/Renderer/World/ShadowMaps.h ================================================ #pragma once #include "Renderer/World/WorldRenderer.h" namespace ShadowMaps { void BuildShadowMap( WorldRenderer::ChunksRenderer &chunksRenderer, double xpos, double ypos, double zpos, double curtime ); void RenderShadowMap( WorldRenderer::ChunksRenderer &chunksRenderer, double xpos, double ypos, double zpos, double curtime ); //void BuildCascadedShadowMaps(double xpos, double ypos, double zpos, double curtime); //void DrawShadowMap(int xi, int yi, int xa, int ya); } ================================================ FILE: NEWorld.Game/Renderer/World/WorldRenderer.cpp ================================================ #include #include "WorldRenderer.h" #include namespace WorldRenderer { int MaxChunkRenders = 64; int chunkBuildRenders; static constexpr auto ccOffset = Int3(7); // offset to a chunk center void ChunksRenderer::FrustumUpdate(Double3 camera, Frustum &frus) { for (auto &entry: mChunks) entry.Visiable = frus.FrustumTest(entry.getRelativeAABB(camera)); } // TODO(make it better, the function is bad) kls::coroutine::ValueAsync ChunksRenderer::Update(Int3 position) { struct SortEntry { int Distance; ChunkRender *Render; std::shared_ptr Locked; }; struct SortCmp { [[nodiscard]] constexpr bool operator()(const SortEntry &l, const SortEntry &r) const { return l.Distance > r.Distance; } }; // Pull in the added part { std::lock_guard lk{mMAdd}; mChunks.insert(mChunks.end(), mLAdd.begin(), mLAdd.end()); mLAdd.clear(); } // Sort the update priority list, also update the frustum results int invalidated = 0; { const auto cp = World::GetChunkPos(position); std::priority_queue, SortCmp> candidate; for (auto &entry: mChunks) { if (auto lock = entry.Ref.lock(); lock) { if (!lock->updated) continue; const auto c = lock->GetPosition(); if (ChebyshevDistance(c, cp) > viewdistance) continue; const auto distance = static_cast(DistanceSquared(c * 16 + ccOffset, position)); candidate.push(SortEntry{distance, &entry, std::move(lock)}); } else ++invalidated; } // walk the candidates and update max elements int released = 0; { kls::temp::vector> operations{}; while ((released < MaxChunkRenders) && (!candidate.empty())) { auto& top = candidate.top(); if (top.Render->CheckBuild(top.Locked)) { ++released; operations.push_back(top.Render->Rebuild(top.Locked)); } candidate.pop(); } co_await kls::coroutine::await_all(std::move(operations)); } chunkBuildRenders = released; } // purge the table if there are too many dead items. PurgeTable(invalidated); } void ChunksRenderer::PurgeTable(int invalidated) { kls::temp::vector toRelease; if (invalidated * 4 > mChunks.size()) { std::vector swap; for (auto& entry : mChunks) { if (!entry.Ref.expired()) { swap.push_back(std::move(entry)); } else if (entry.Built) { if (entry.Renders[0].Buffer != 0) toRelease.push_back(entry.Renders[0].Buffer); if (entry.Renders[1].Buffer != 0) toRelease.push_back(entry.Renders[1].Buffer); if (entry.Renders[2].Buffer != 0) toRelease.push_back(entry.Renders[2].Buffer); if (entry.Renders[3].Buffer != 0) toRelease.push_back(entry.Renders[3].Buffer); } } mChunks.swap(swap); } if (!toRelease.empty()) glDeleteBuffers(toRelease.size(), toRelease.data()); } FrameChunksRenderer::FrameChunksRenderer( const std::vector &list, Int3 cPos, int renderDist, bool frus ) { auto renderedChunks = 0; for (auto &entry: list) { if (!entry.Built) continue; if (ChebyshevDistance(cPos, entry.Position) <= renderDist) { if (!frus || entry.Visiable) { renderedChunks++; mFiltered.push_back(&entry); //RenderChunkList.emplace_back(chunk.get(), (curtime - lastUpdate) * 30.0); // position(c->GetPosition()), loadAnim(c->loadAnim * pow(0.6, TimeDelta)) { } } } } void FrameChunksRenderer::Render(double x, double y, double z, int buffer) { float m[16]; if (buffer != 3) { memset(m, 0, sizeof(m)); m[0] = m[5] = m[10] = m[15] = 1.0f; } for (auto cr: mFiltered) { if (cr->Renders[buffer].Count == 0) continue; const auto offset = Double3(cr->Position) * 16.0 - Double3(x, /*cr.loadAnim*/ +y, z); glPushMatrix(); glTranslated(offset.X, offset.Y, offset.Z); if (Renderer::AdvancedRender && buffer != 3) { m[12] = static_cast(offset.X); m[13] = static_cast(offset.Y); m[14] = static_cast(offset.Z); Renderer::GetPipeline()->SetUniform(4, m); } Renderer::RenderBufferDirect(cr->Renders[buffer].Buffer, cr->Renders[buffer].Count); glPopMatrix(); } glFlush(); } } ================================================ FILE: NEWorld.Game/Renderer/World/WorldRenderer.h ================================================ #pragma once #include "Renderer/Renderer.h" #include "ChunkRenderer.h" #include #include namespace WorldRenderer { class FrameChunksRenderer { public: explicit FrameChunksRenderer( const std::vector &list, Int3 cPos, int renderDist, bool frus = true ); void Render(double x, double y, double z, int buffer); private: kls::temp::vector mFiltered; }; class ChunksRenderer { public: kls::coroutine::ValueAsync Update(Int3 position); void FrustumUpdate(Double3 camera, Frustum &frus); void PurgeTable(int invalidated); auto List(Int3 cPos, int renderDist, bool frus = true) { return FrameChunksRenderer(mChunks, cPos, renderDist, frus); } void Add(const std::shared_ptr &c) { std::lock_guard lk{mMAdd}; mLAdd.emplace_back(c); } // TODO(Try to remove this) static ChunksRenderer &Default() { static ChunksRenderer instance{}; return instance; } private: kls::thread::SpinLock mMAdd; std::vector mLAdd; std::vector mChunks; }; extern int chunkBuildRenders; } ================================================ FILE: NEWorld.Game/Setup.cpp ================================================ #include #include "Setup.h" #include "ControlContext.h" #include "Definitions.h" #include "Textures.h" #include "Renderer/Renderer.h" #include "Universe/World/World.h" #include "Items.h" #include "Common/Logger.h" #include "System/MessageBus.h" void splashScreen() { auto splTex = Textures::LoadRGBTexture("./Assets/Textures/GUI/SplashScreen.bmp"); glEnable(GL_TEXTURE_2D); for (auto i = 0; i < 256; i += 2) { glfwSwapBuffers(MainWindow); glfwPollEvents(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glBindTexture(GL_TEXTURE_2D, splTex); glColor4f(static_cast(i) / 256, static_cast(i) / 256, static_cast(i) / 256, 1.0); glBegin(GL_QUADS); glTexCoord2f(0.0, 1.0); glVertex2i(-1, 1); glTexCoord2f(850.0f / 1024.0f, 1.0); glVertex2i(1, 1); glTexCoord2f(850.0f / 1024.0f, 1.0 - 480.0f / 1024.0f); glVertex2i(1, -1); glTexCoord2f(0.0, 1.0 - 480.0f / 1024.0f); glVertex2i(-1, -1); glEnd(); SleepMs(10); } glDeleteTextures(1, &splTex); glfwSwapBuffers(MainWindow); glfwPollEvents(); } void createWindow() { glfwSetErrorCallback([](int, const char *desc) { std::cout << desc << std::endl; }); std::stringstream title; title << "NEWorld " << MAJOR_VERSION << MINOR_VERSION << EXT_VERSION; if (Multisample != 0) glfwWindowHint(GLFW_SAMPLES, Multisample); MainWindow = glfwCreateWindow(windowwidth, windowheight, title.str().c_str(), NULL, NULL); // high dpi screens deserve a larger window float widthScale, heightScale; glfwGetWindowContentScale(MainWindow, &widthScale, &heightScale); windowwidth *= widthScale; windowheight *= heightScale; glfwSetWindowSize(MainWindow, windowwidth, windowheight); glfwSwapBuffers(MainWindow); MouseCursor = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); glfwMakeContextCurrent(MainWindow); glewInit(); glfwSetCursor(MainWindow, MouseCursor); glfwSetInputMode(MainWindow, GLFW_CURSOR, GLFW_CURSOR_NORMAL); glfwSetWindowSizeCallback(MainWindow, &WindowSizeFunc); glfwSetMouseButtonCallback(MainWindow, &MouseButtonFunc); glfwSetScrollCallback(MainWindow, &ControlContext::MouseScrollCallback); glfwSetCharCallback(MainWindow, &CharInputFunc); glfwSetKeyCallback(MainWindow, [](GLFWwindow* window, int key, int scancode, int action, int mods){ MessageBus::Default().Get>("KeyEvents")->Send(nullptr, std::make_pair(key, action)); }); } void message_callback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, GLchar const *message, void const *user_param) { auto const src_str = [source]() { switch (source) { case GL_DEBUG_SOURCE_API: return "API"; case GL_DEBUG_SOURCE_WINDOW_SYSTEM: return "WINDOW SYSTEM"; case GL_DEBUG_SOURCE_SHADER_COMPILER: return "SHADER COMPILER"; case GL_DEBUG_SOURCE_THIRD_PARTY: return "THIRD PARTY"; case GL_DEBUG_SOURCE_APPLICATION: return "APPLICATION"; case GL_DEBUG_SOURCE_OTHER: return "OTHER"; } return "UNKNOWN"; }(); auto const type_str = [type]() { switch (type) { case GL_DEBUG_TYPE_ERROR: return "ERROR"; case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: return "DEPRECATED_BEHAVIOR"; case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: return "UNDEFINED_BEHAVIOR"; case GL_DEBUG_TYPE_PORTABILITY: return "PORTABILITY"; case GL_DEBUG_TYPE_PERFORMANCE: return "PERFORMANCE"; case GL_DEBUG_TYPE_MARKER: return "MARKER"; case GL_DEBUG_TYPE_OTHER: return "OTHER"; } return "UNKNOWN"; }(); auto const severity_str = [severity]() { switch (severity) { case GL_DEBUG_SEVERITY_NOTIFICATION: return "NOTIFICATION"; case GL_DEBUG_SEVERITY_LOW: return "LOW"; case GL_DEBUG_SEVERITY_MEDIUM: return "MEDIUM"; case GL_DEBUG_SEVERITY_HIGH: return "HIGH"; } return "UNKNOWN"; }(); infostream << src_str << ", " << type_str << ", " << severity_str << ", " << id << ": " << message; } void setupScreen() { glEnable(GL_DEBUG_OUTPUT); glDebugMessageCallback(message_callback, nullptr); glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_NOTIFICATION, 0, nullptr, GL_FALSE); // TODO(this actually does matter, silenced for other debugging issues) glDebugMessageControl(GL_DONT_CARE, GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR, GL_DONT_CARE, 0, nullptr, GL_FALSE); //获取OpenGL版本 GLVersionMajor = glfwGetWindowAttrib(MainWindow, GLFW_CONTEXT_VERSION_MAJOR); GLVersionMinor = glfwGetWindowAttrib(MainWindow, GLFW_CONTEXT_VERSION_MINOR); GLVersionRev = glfwGetWindowAttrib(MainWindow, GLFW_CONTEXT_REVISION); //获取OpenGL函数地址 infostream << "GL Version: " << GLVersionMajor << '.' << GLVersionMinor << '.' << GLVersionRev; infostream << "GL Vendor: " << glGetString(GL_VENDOR); infostream << "GL Renderer: " << glGetString(GL_RENDERER); //渲染参数设置 glViewport(0, 0, windowwidth, windowheight); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glShadeModel(GL_SMOOTH); glDisable(GL_DITHER); glEnable(GL_CULL_FACE); glEnable(GL_TEXTURE_2D); glEnable(GL_DEPTH_TEST); glEnable(GL_ALPHA_TEST); glEnable(GL_BLEND); glEnable(GL_LINE_SMOOTH); glDepthFunc(GL_LEQUAL); glAlphaFunc(GL_GREATER, 0.0); //<--这家伙在卖萌?(往后面看看,卖萌的多着呢) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST); glHint(GL_FOG_HINT, GL_FASTEST); glHint(GL_LINE_SMOOTH_HINT, GL_FASTEST); if (Multisample != 0) glEnable(GL_MULTISAMPLE_ARB); glPixelStorei(GL_PACK_ALIGNMENT, 4); glPixelStorei(GL_UNPACK_ALIGNMENT, 4); glColor4f(0.0, 0.0, 0.0, 1.0); glClearColor(0.0, 0.0, 0.0, 1.0); glClearDepth(1.0); Renderer::initShaders(); if (vsync) glfwSwapInterval(1); else glfwSwapInterval(0); } void setupNormalFog() { float fogColor[4] = {skycolorR, skycolorG, skycolorB, 1.0f}; glEnable(GL_FOG); glFogi(GL_FOG_MODE, GL_LINEAR); glFogfv(GL_FOG_COLOR, fogColor); glFogf(GL_FOG_START, viewdistance * 16.0f - 32.0f); glFogf(GL_FOG_END, viewdistance * 16.0f); } void loadTextures() { //载入纹理 Textures::Init(); tex_select = Textures::LoadRGBATexture("./Assets/Textures/GUI/select.bmp", ""); tex_unselect = Textures::LoadRGBATexture("./Assets/Textures/GUI/unselect.bmp", ""); for (auto i = 0; i < 6; i++) { std::stringstream ss; ss << "./Assets/Textures/GUI/mainmenu" << i << ".bmp"; tex_mainmenu[i] = Textures::LoadRGBTexture(ss.str()); } DefaultSkin = Textures::LoadRGBATexture("./Assets/Textures/Player/skin_xiaoqiao.bmp", "./Assets/Textures/Player/skinmask_xiaoqiao.bmp"); for (auto gloop = 1; gloop <= 10; gloop++) { std::string result; result = std::to_string(gloop); const auto path = "./Assets/Textures/Blocks/destroy_" + result + ".bmp"; DestroyImage[gloop] = Textures::LoadRGBATexture(path, path); } BlockTextures = Textures::LoadRGBATexture("./Assets/Textures/Blocks/Terrain.bmp", "./Assets/Textures/Blocks/Terrainmask.bmp"); loadItemsTextures(); } void WindowSizeFunc(GLFWwindow *win, int width, int height) { if (width < 640) width = 640; if (height < 360) height = 360; windowwidth = width; windowheight = height > 0 ? height : 1; glfwSetWindowSize(win, width, height); setupScreen(); } void MouseButtonFunc(GLFWwindow *, int button, int action, int) { MessageBus::Default().Get>("Mouse")->Send(nullptr, std::make_pair(button, action)); } void CharInputFunc(GLFWwindow *, unsigned int c) { MessageBus::Default().Get("InputEvents")->Send(nullptr, c); if (c >= 128) { const auto pwszUnicode = new wchar_t[2]; pwszUnicode[0] = static_cast(c); pwszUnicode[1] = '\0'; auto pszMultiByte = static_cast(malloc(static_cast(4))); pszMultiByte = static_cast(realloc(pszMultiByte, WCharToMByte(pszMultiByte, pwszUnicode, 4))); free(pszMultiByte); delete[] pwszUnicode; } } ================================================ FILE: NEWorld.Game/Setup.h ================================================ #pragma once struct GLFWwindow; void splashScreen(); void createWindow(); void setupScreen(); void setupNormalFog(); void loadTextures(); void WindowSizeFunc(GLFWwindow *win, int width, int height); void MouseButtonFunc(GLFWwindow *, int button, int action, int); void CharInputFunc(GLFWwindow *, unsigned int c); void MouseScrollFunc(GLFWwindow *, double, double yoffset); ================================================ FILE: NEWorld.Game/Textures.cpp ================================================ #include "Textures.h" #include "Items.h" #include "Universe/World/Blocks.h" #include #include #include "Common/Logger.h" int BLOCKTEXTURE_SIZE, BLOCKTEXTURE_UNITSIZE, BLOCKTEXTURE_UNITS; namespace Textures { void Init() { BLOCKTEXTURE_SIZE = 256; BLOCKTEXTURE_UNITSIZE = 32; BLOCKTEXTURE_UNITS = 8; } ubyte getTextureIndex(Block blockname, ubyte side) { switch (blockname) { case Blocks::ROCK: return ROCK; case Blocks::GRASS: switch (side) { case 1: return GRASS_TOP; case 2: return GRASS_SIDE; case 3: return DIRT; } case Blocks::DIRT: return DIRT; case Blocks::STONE: return STONE; case Blocks::PLANK: return PLANK; case Blocks::WOOD: switch (side) { case 1: return WOOD_TOP; case 2: return WOOD_SIDE; case 3: return WOOD_TOP; } case Blocks::BEDROCK: return BEDROCK; case Blocks::LEAF: return LEAF; case Blocks::GLASS: return GLASS; case Blocks::WATER: return WATER; case Blocks::LAVA: return LAVA; case Blocks::GLOWSTONE: return GLOWSTONE; case Blocks::SAND: return SAND; case Blocks::CEMENT: return CEMENT; case Blocks::ICE: return ICE; case Blocks::COAL: return COAL; case Blocks::IRON: return IRON; case Blocks::TNT: return TNT; default: return NULLBLOCK; } } double getTexcoordX(Item item, ubyte side) { if (isBlock(item)) //如果为方块 return (getTextureIndex(item, side) & 7) / 8.0; else return NULLBLOCK; } double getTexcoordY(Item item, ubyte side) { if (isBlock(item)) //如果为方块 return (getTextureIndex(item, side) >> 3) / 8.0; else return NULLBLOCK; } void LoadRGBImage(TEXTURE_RGB &tex, std::string Filename) { unsigned int ind = 0; auto& bitmap = tex; //返回位图 bitmap.buffer = nullptr; bitmap.sizeX = bitmap.sizeY = 0; std::ifstream bmpfile(Filename, std::ios::binary | std::ios::in); //位图文件(二进制) if (!bmpfile.is_open()) { warningstream << "Cannot load " << Filename; return; } BITMAPINFOHEADER bih; //各种关于位图的参数 BITMAPFILEHEADER bfh; //各种关于文件的参数 //开始读取 bmpfile.read((char *) &bfh, sizeof(BITMAPFILEHEADER)); bmpfile.read((char *) &bih, sizeof(BITMAPINFOHEADER)); bitmap.sizeX = bih.biWidth; bitmap.sizeY = bih.biHeight; bitmap.buffer = std::unique_ptr(new unsigned char[bitmap.sizeX * bitmap.sizeY * 3]); bmpfile.read((char *) bitmap.buffer.get(), bitmap.sizeX * bitmap.sizeY * 3); bmpfile.close(); for (unsigned int i = 0; i < bitmap.sizeX * bitmap.sizeY; i++) { const auto t = bitmap.buffer[ind]; bitmap.buffer[ind] = bitmap.buffer[ind + 2]; bitmap.buffer[ind + 2] = t; ind += 3; } } void LoadRGBAImage(TEXTURE_RGBA &tex, std::string Filename, std::string MkFilename) { unsigned char *rgb = nullptr, *a = nullptr; unsigned int ind = 0; auto noMaskFile = (MkFilename == ""); auto& bitmap = tex; //·µ»ØÎ»Í¼ bitmap.buffer = nullptr; bitmap.sizeX = bitmap.sizeY = 0; std::ifstream bmpfile(Filename, std::ios::binary | std::ios::in); std::ifstream maskfile; if (!noMaskFile)maskfile.open(MkFilename, std::ios::binary | std::ios::in); if (!bmpfile.is_open()) { warningstream << "Cannot load bitmap " << Filename; return; } if (!noMaskFile && !maskfile.is_open()) { warningstream << "Cannot load bitmap " << MkFilename; return; } BITMAPFILEHEADER bfh; BITMAPINFOHEADER bih; if (!noMaskFile) { maskfile.read((char *) &bfh, sizeof(BITMAPFILEHEADER)); maskfile.read((char *) &bih, sizeof(BITMAPINFOHEADER)); } bmpfile.read((char *) &bfh, sizeof(BITMAPFILEHEADER)); bmpfile.read((char *) &bih, sizeof(BITMAPINFOHEADER)); bitmap.sizeX = bih.biWidth; bitmap.sizeY = bih.biHeight; bitmap.buffer = std::unique_ptr(new unsigned char[bitmap.sizeX * bitmap.sizeY * 4]); //¶ÁÈ¡Êý¾Ý rgb = new unsigned char[bitmap.sizeX * bitmap.sizeY * 3]; bmpfile.read((char *) rgb, bitmap.sizeX * bitmap.sizeY * 3); bmpfile.close(); if (!noMaskFile) { a = new unsigned char[bitmap.sizeX * bitmap.sizeY * 3]; maskfile.read((char *) a, bitmap.sizeX * bitmap.sizeY * 3); maskfile.close(); } //ºÏ²¢Óëת»» for (unsigned int i = 0; i < bitmap.sizeX * bitmap.sizeY; i++) { //°ÑBGR¸ñʽת»»ÎªRGB¸ñʽ bitmap.buffer[ind] = rgb[i * 3 + 2]; bitmap.buffer[ind + 1] = rgb[i * 3 + 1]; bitmap.buffer[ind + 2] = rgb[i * 3]; //Alpha if (noMaskFile) bitmap.buffer[ind + 3] = 255; else bitmap.buffer[ind + 3] = 255 - a[i * 3]; ind += 4; } } TextureID LoadRGBTexture(std::string Filename) { TEXTURE_RGB image; TextureID ret; LoadRGBImage(image, Filename); glGenTextures(1, &ret); glBindTexture(GL_TEXTURE_2D, ret); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST); Build2DMipmaps(GL_RGB, image.sizeX, image.sizeY, static_cast(log2(image.sizeX)), image.buffer.get()); return ret; } TextureID LoadFontTexture(std::string Filename) { TEXTURE_RGBA Texture; TEXTURE_RGB image; TextureID ret; LoadRGBImage(image, Filename); Texture.sizeX = image.sizeX; Texture.sizeY = image.sizeY; Texture.buffer = std::unique_ptr(new unsigned char[image.sizeX * image.sizeY * 4]); if (Texture.buffer == nullptr) { printf("[console][Warning] Cannot alloc memory when loading %s\n", Filename.c_str()); return 0; } auto ip = image.buffer.get(); auto tp = Texture.buffer.get(); for (unsigned int i = 0; i != image.sizeX * image.sizeY; i++) { *tp = 255; tp++; *tp = 255; tp++; *tp = 255; tp++; *tp = 255 - *ip; tp++; ip += 3; } glGenTextures(1, &ret); glBindTexture(GL_TEXTURE_2D, ret); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, Texture.sizeX, Texture.sizeY, 0, GL_RGBA, GL_UNSIGNED_BYTE, Texture.buffer.get()); return ret; } TextureID LoadRGBATexture(std::string Filename, std::string MkFilename) { TextureID ret; TEXTURE_RGBA image; LoadRGBAImage(image, Filename, MkFilename); glGenTextures(1, &ret); glBindTexture(GL_TEXTURE_2D, ret); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST); Build2DMipmaps(GL_RGBA, image.sizeX, image.sizeY, static_cast(log2(BLOCKTEXTURE_UNITSIZE)), image.buffer.get()); return ret; } void SaveRGBImage(std::string filename, TEXTURE_RGB &image) { BITMAPFILEHEADER bitmapfileheader; BITMAPINFOHEADER bitmapinfoheader; bitmapfileheader.bfSize = image.sizeX * image.sizeY * 3 + 54; bitmapinfoheader.biWidth = image.sizeX; bitmapinfoheader.biHeight = image.sizeY; bitmapinfoheader.biSizeImage = image.sizeX * image.sizeY * 3; for (unsigned int i = 0; i != image.sizeX * image.sizeY * 3; i += 3) { const auto t = image.buffer.get()[i]; image.buffer.get()[i] = image.buffer.get()[i + 2]; image.buffer.get()[i + 2] = t; } std::ofstream ofs(filename, std::ios::out | std::ios::binary); ofs.write((char *) &bitmapfileheader, sizeof(bitmapfileheader)); ofs.write((char *) &bitmapinfoheader, sizeof(bitmapinfoheader)); ofs.write((char *) image.buffer.get(), sizeof(ubyte) * image.sizeX * image.sizeY * 3); ofs.close(); } void Build2DMipmaps(GLenum format, int w, int h, int level, const ubyte *src) { auto sum = 0, scale = 1, cur_w = 0, cur_h = 0, cc = 0; if (format == GL_RGBA) cc = 4; else if (format == GL_RGB) cc = 3; const auto cur = new ubyte[w * h * cc]; memset(cur, 0, w * h * cc); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, level); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 0); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, level); glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, 0.0f); glTexImage2D(GL_TEXTURE_2D, 0, format, w, h, 0, format, GL_UNSIGNED_BYTE, src); for (auto i = 1; i <= level; i++) { scale <<= 1; cur_w = w / scale; cur_h = h / scale; for (auto y = 0; y < cur_h; y++) for (auto x = 0; x < cur_w; x++) { for (auto col = 0; col < cc; col++) { sum = 0; for (auto yy = 0; yy < scale; yy++) for (auto xx = 0; xx < scale; xx++) { sum += src[((y * scale + yy) * w + x * scale + xx) * cc + col]; } cur[(y * cur_w + x) * cc + col] = static_cast(sum / (scale * scale)); } } glTexImage2D(GL_TEXTURE_2D, i, format, cur_w, cur_h, 0, format, GL_UNSIGNED_BYTE, cur); } delete[] cur; } } ================================================ FILE: NEWorld.Game/Textures.h ================================================ #pragma once #include "Definitions.h" extern int BLOCKTEXTURE_SIZE, BLOCKTEXTURE_UNITSIZE, filter; const short BITMAP_ID = 0x4D42; namespace Textures { #pragma pack(push) #pragma pack(1) struct TEXTURE_RGB { unsigned int sizeX; unsigned int sizeY; std::unique_ptr buffer; }; struct TEXTURE_RGBA { unsigned int sizeX; unsigned int sizeY; std::unique_ptr buffer; }; struct BITMAPINFOHEADER { int biSize = 40, biWidth, biHeight; short biPlanes = 1, biBitCount = 24; int biCompression = 0, biSizeImage, biXPelsPerMeter = 0, biYPelsPerMeter = 0, biClrUsed = 0, biClrImportant = 0; }; struct BITMAPFILEHEADER { short bfType = BITMAP_ID; int bfSize; short bfReserved1 = 0, bfReserved2 = 0; int bfOffBits = 54; }; #pragma pack(pop) enum BlockTextureID { ROCK, GRASS_TOP, GRASS_SIDE, DIRT, STONE, PLANK, WOOD_TOP, WOOD_SIDE, BEDROCK, LEAF, GLASS, WATER, LAVA, GLOWSTONE, SAND, CEMENT, ICE, COAL, IRON, TNT, UNKNOWN }; const int NULLBLOCK = 63; void Init(); ubyte getTextureIndex(Block blockname, ubyte side); double getTexcoordX(Item item, ubyte side); double getTexcoordY(Item item, ubyte side); void LoadRGBImage(TEXTURE_RGB &tex, std::string Filename); void LoadRGBAImage(TEXTURE_RGBA &tex, std::string Filename, std::string MkFilename); TextureID LoadRGBTexture(std::string Filename); TextureID LoadFontTexture(std::string Filename); TextureID LoadRGBATexture(std::string Filename, std::string MkFilename); void SaveRGBImage(std::string filename, TEXTURE_RGB &image); void Build2DMipmaps(GLenum format, int w, int h, int level, const ubyte *src); } ================================================ FILE: NEWorld.Game/Tick.cpp ================================================ #include "Tick.h" #include #include #include std::shared_ptr CreateTickPipeline(std::vector> c) { struct ComponentHolder; struct ComponentConstructionInfo { int ActualDependencyCount{}; ComponentHolder* FutureHolder{}; std::vector ComponentsToRelease{}; }; struct ComponentHolder { const int ActualDependencyCount{}; std::shared_ptr Component{}; std::vector ComponentsToRelease{}; std::atomic_int AwaitingDependencies{ 0 }; constexpr ComponentHolder() noexcept = default; ComponentHolder(ComponentConstructionInfo&& construct, std::shared_ptr c) noexcept : ActualDependencyCount(construct.ActualDependencyCount), Component(std::move(c)), ComponentsToRelease(std::move(construct.ComponentsToRelease)) {} ComponentHolder(ComponentHolder&& o) noexcept : ActualDependencyCount(o.ActualDependencyCount), Component(std::move(o.Component)), ComponentsToRelease(std::move(o.ComponentsToRelease)) {} kls::coroutine::ValueAsync Launch() { co_await kls::coroutine::Redispatch(); co_await Component->Evaluate(); kls::temp::vector> childern{}; childern.reserve(ComponentsToRelease.size()); // reserve the max size // decrease the counter for all released component and spawn if necessary for (auto next : ComponentsToRelease) { if (next->AwaitingDependencies.fetch_sub(1) == 1) childern.push_back(next->Launch()); } co_await kls::coroutine::await_all(std::move(childern)); } }; class Pipeline : public TickPipeline { public: explicit Pipeline(std::vector> components) { std::unordered_map info{}; // construct the sorting base mComponents.reserve(components.size()); // this is done to ensure the references remains valid for (auto& c : components) { info.insert_or_assign(c.get(), ComponentConstructionInfo{ 0, &mComponents.emplace_back(), {} }); } // construct dependency graph for (auto& c : components) { for (auto& b : c->GetToEvalBefore()) { info[c.get()].ActualDependencyCount++; info[b].ComponentsToRelease.push_back(info[c.get()].FutureHolder); } for (auto& a : c->GetToEvalAfter()) { info[c.get()].ComponentsToRelease.push_back(info[a].FutureHolder); info[a].ActualDependencyCount++; } } // consolidates the component graph information for (auto& c : components) { const auto ptr = c.get(); auto& construct = info[ptr]; auto& holder = *construct.FutureHolder; // we have to reuse the address for constructing the new holder std::destroy_at(&holder); std::construct_at(&holder, std::move(construct), std::move(c)); if (holder.ActualDependencyCount == 0) mComponentRoots.push_back(&holder); } } kls::coroutine::ValueAsync Evaluate() override { // reset all component counters for (auto& c : mComponents) c.AwaitingDependencies.store(c.ActualDependencyCount); kls::temp::vector> childern{}; childern.reserve(mComponents.size()); // reserve the exact size // launch all the roots and await for the result for (auto root : mComponentRoots) childern.push_back(root->Launch()); co_await kls::coroutine::await_all(std::move(childern)); } private: std::vector mComponents{}; std::vector mComponentRoots{}; }; return std::make_shared(std::move(c)); } ================================================ FILE: NEWorld.Game/Tick.h ================================================ #pragma once #include #include #include struct TickComponent: kls::PmrBase { virtual std::vector GetToEvalAfter() = 0; virtual std::vector GetToEvalBefore() = 0; virtual kls::coroutine::ValueAsync Evaluate() = 0; }; struct TickPipeline: kls::PmrBase { virtual kls::coroutine::ValueAsync Evaluate() = 0; }; std::shared_ptr CreateTickPipeline(std::vector>); ================================================ FILE: NEWorld.Game/Typedefs.h ================================================ #pragma once //Types/constants define #include #include #include typedef unsigned char ubyte; typedef unsigned char blockprop; typedef unsigned char Brightness; typedef unsigned int TextureID; typedef uint32_t Item; typedef unsigned int VBOID; typedef int vtxCount; typedef uint64_t chunkid; typedef unsigned int onlineid; #ifdef NEWORLD_GAME typedef std::mutex *Mutex_t; #endif using Block = uint32_t; ================================================ FILE: NEWorld.Game/Universe/CommandHandler.h ================================================ #pragma once #include "Command.h" #include "Universe/World/Blocks.h" #include "Universe/World/World.h" #include "Items.h" std::vector commands; class CommandHandler { public: static bool doCommand(const std::vector &command) { for (auto &i: commands) { if (command[0] == i.identifier) return i.execute(command); } return false; } static void registerCommands() { commands.emplace_back("/setblock", [](const std::vector &command) { if (command.size() != 5) return false; const auto x = std::stoi(command[1]); const auto y = std::stoi(command[2]); const auto z = std::stoi(command[3]); const Block b = std::stoll(command[4]); World::SetBlock({x, y, z}, b); return true; }); commands.emplace_back("/tree", [](const std::vector &command) { if (command.size() != 4) return false; const auto x = std::stoi(command[1]); const auto y = std::stoi(command[2]); const auto z = std::stoi(command[3]); World::buildtree({x, y, z}); return true; }); commands.emplace_back("/explode", [](const std::vector &command) { if (command.size() != 5) return false; const auto x = std::stoi(command[1]); const auto y = std::stoi(command[2]); const auto z = std::stoi(command[3]); const auto r = std::stoi(command[4]); World::explode(x, y, z, r); return true; }); commands.emplace_back("/time", [](const std::vector &command) { if (command.size() != 2) return false; const auto time = std::stoi(command[4]); if (time < 0 || time > gameTimeMax) return false; gametime = time; return true; }); } }; ================================================ FILE: NEWorld.Game/Universe/Entity/Entity.cpp ================================================ #include "Entity.h" #include "Universe/World/World.h" Int3 Entity::getChunkPosition() const noexcept { return Int3(mPosition, World::GetChunkPos); } void Entity::move(const EntityBVH& bvh) { if (!doCollisionCheck()) { mVelocityForRendering = mVelocity; mPosition += mVelocity; return; } auto currentHitbox = bounding_box(); auto currentMovementHitbox = movement_bounding_box(); auto hitboxes = World::getHitboxes(currentMovementHitbox); const auto collisions = bvh.intersect(currentMovementHitbox); for (const auto& entity : collisions) { if (entity == this) continue; hitboxes.push_back(entity->bounding_box()); } auto actualMovement = getVelocity(); for (const auto& box : hitboxes) { actualMovement.Y = AABB::MaxMove(currentHitbox, box, actualMovement.Y, 1); } currentHitbox.min += Vector3(0.0, actualMovement.Y, 0.0); currentHitbox.max += Vector3(0.0, actualMovement.Y, 0.0); for (const auto& box : hitboxes) { actualMovement.X = AABB::MaxMove(currentHitbox, box, actualMovement.X, 0); } currentHitbox.min += Vector3(actualMovement.X, 0.0, 0.0); currentHitbox.max += Vector3(actualMovement.X, 0.0, 0.0); for (const auto& box : hitboxes) { actualMovement.Z = AABB::MaxMove(currentHitbox, box, actualMovement.Z, 2); } mPosition += actualMovement; mVelocityForRendering = actualMovement; afterMove(actualMovement); } ================================================ FILE: NEWorld.Game/Universe/Entity/Entity.h ================================================ #pragma once #include "bvh.h" #include "Definitions.h" #include "Math/Vector3.h" struct RenderProperties { Double3 position; double heading, lookUpDown; }; class Entity { public: Entity(Double3 position, Double3 size) : mPosition(position), mSize(size), mVelocity() {} virtual ~Entity() = default; [[nodiscard]] Vector3 center() const { return toBvhVec(mPosition); } [[nodiscard]] BoundingBox bounding_box() const { return { toBvhVec(mPosition - mSize / 2), toBvhVec(mPosition + mSize / 2) }; } [[nodiscard]] BoundingBox movement_bounding_box() const { return bounding_box().extend(AABB::Move(bounding_box(), mVelocity)); } [[nodiscard]] bool intersect(const BoundingBox& box) const { return AABB::Intersect(bounding_box(), box); } [[nodiscard]] Double3 getPosition() const noexcept { return mPosition; } [[nodiscard]] Double3 getSize() const noexcept { return mSize; } [[nodiscard]] Double3 getVelocity() const noexcept { return mVelocity; } [[nodiscard]] virtual bool doCollisionCheck() const noexcept { return true; } Int3 getChunkPosition() const noexcept; // Move the position by velocity. Will use World for collision check void move(const EntityBVH& bvh); virtual void afterMove(Double3 actualMovement) {} virtual void update() = 0; virtual void render() = 0; [[nodiscard]] double getHeading() const noexcept { return mHeading; } [[nodiscard]] double getLookUpDown() const noexcept { return mLookUpDown; } [[nodiscard]] virtual RenderProperties getPropertiesForRender(double timeDelta) const noexcept { // timeDelta is the time since the last update frame. auto cameraPosition = mPosition + (timeDelta * MaxUpdateFPS - 1) * mVelocityForRendering; return{ cameraPosition, mHeading + mXLookSpeed, std::clamp(mLookUpDown + mYLookSpeed, -90.0, 90.0) }; } protected: Double3 mPosition, mSize, mVelocity; double mLookUpDown = 90, mHeading = 0; double mXLookSpeed = 0, mYLookSpeed = 0; private: // The actual velocity used for movement in this frame // can be used for inter-frame interpolation. Double3 mVelocityForRendering; }; ================================================ FILE: NEWorld.Game/Universe/Entity/PlayerEntity.cpp ================================================ #include "PlayerEntity.h" #include #include #include "ControlContext.h" #include "Universe/World/Blocks.h" #include "Universe/World/World.h" void PlayerEntity::update() { // Process player's health if (mGameMode != GameMode::Creative) { if (mHealth > 0) { if (mPosition.Y < -100) mHealth -= (-100 - mPosition.Y) / 100; mHealth = std::min(mHealth + mHealSpeed, mMaxHealth); } else { spawn(); } } if (!mFlying && !mCrossWall && World::inWater(bounding_box())) { mVelocity *= 0.6; } } RenderProperties PlayerEntity::renderUpdate(const ControlContext& control, bool freeze, double lastUpdate) { if (freeze) { mYLookSpeed = mXLookSpeed = 0; return getPropertiesForRender(0); } if (isOnGround()) { //半蹲特效 if (mCurrentJumpSpeed < -0.005) { if (mCurrentJumpSpeed <= -(mHeight - 0.5f)) mHeightExt = -(mHeight - 0.5f); else mHeightExt = static_cast(mCurrentJumpSpeed); TouchdownAnimTimer = control.Current.Time; } else { if (mHeightExt <= -0.005) { mHeightExt *= static_cast(pow(0.8, (control.Current.Time - TouchdownAnimTimer) * 30)); TouchdownAnimTimer = control.Current.Time; } } } const auto timeDelta = control.Current.Time - lastUpdate; //转头!你治好了我多年的颈椎病! mXLookSpeed -= (control.Current.MousePosition.X - control.Last.MousePosition.X) * mousemove; mYLookSpeed += (control.Current.MousePosition.Y - control.Last.MousePosition.Y) * mousemove; if (control.KeyPressed(GLFW_KEY_RIGHT)) mXLookSpeed -= mousemove * 16 * timeDelta * 30.0; if (control.KeyPressed(GLFW_KEY_LEFT)) mXLookSpeed += mousemove * 16 * timeDelta * 30.0; if (control.KeyPressed(GLFW_KEY_UP)) mYLookSpeed -= mousemove * 16 * timeDelta * 30.0; if (control.KeyPressed(GLFW_KEY_DOWN)) mYLookSpeed += mousemove * 16 * timeDelta * 30.0; return getPropertiesForRender(timeDelta); } void PlayerEntity::controlUpdate(const ControlContext& control) { //更新方向 mHeading = fmod(mHeading + mXLookSpeed, 360.0); mLookUpDown = std::clamp(mLookUpDown + mYLookSpeed, -90.0, 90.0); mXLookSpeed = mYLookSpeed = 0.0; ProcessNavigate(control); HotbarItemSelect(control); if (control.KeyPressed(GLFW_KEY_SPACE)) StartJump(); if (control.KeyPressed(GLFW_KEY_LEFT_SHIFT) || control.KeyPressed(GLFW_KEY_RIGHT_SHIFT)) { if (mCrossWall || mFlying) mVelocity.Y -= walkspeed / 2; } mSpeedBoost = control.KeyPressed(GLFW_KEY_F) ? 10 : 1; //跳跃 ProcessJump(); } void PlayerEntity::ProcessNavigate(const ControlContext& control) { auto speed = getSpeed(); if (control.KeyJustDoublePressed(GLFW_KEY_W)) { mRunning = true; } if (control.KeyPressed(GLFW_KEY_W)) { mVelocity.X += -sin(mHeading * M_PI / 180.0) * speed; mVelocity.Z += -cos(mHeading * M_PI / 180.0) * speed; } else { mRunning = false; } if (control.KeyPressed(GLFW_KEY_S) == GLFW_PRESS) { mVelocity.X += sin(mHeading * M_PI / 180.0) * speed; mVelocity.Z += cos(mHeading * M_PI / 180.0) * speed; } if (control.KeyPressed(GLFW_KEY_A) == GLFW_PRESS) { mVelocity.X += sin((mHeading - 90) * M_PI / 180.0) * speed; mVelocity.Z += cos((mHeading - 90) * M_PI / 180.0) * speed; } if (control.KeyPressed(GLFW_KEY_D) == GLFW_PRESS) { mVelocity.X += -sin((mHeading - 90) * M_PI / 180.0) * speed; mVelocity.Z += -cos((mHeading - 90) * M_PI / 180.0) * speed; } if (!mFlying && !mCrossWall) { const auto horizontalSpeed = sqrt(mVelocity.X * mVelocity.X + mVelocity.Z * mVelocity.Z); if (horizontalSpeed > speed) { mVelocity.X *= speed / horizontalSpeed; mVelocity.Z *= speed / horizontalSpeed; } } } void PlayerEntity::ProcessJump() { if (!mInWater) { if (!mFlying && !mCrossWall) { mVelocity.Y = -0.001; if (mOnGround) { mCurrentJumpSpeed = 0.0; mAirJumps = 0; } else { //自由落体计算 mCurrentJumpSpeed -= 0.025; mVelocity.Y = mCurrentJumpSpeed + 0.5 * 0.6 / 900.0; } } else { mCurrentJumpSpeed = 0.0; mAirJumps = 0; } } else { mCurrentJumpSpeed = 0.0; mAirJumps = MaxAirJumps; if (mVelocity.Y <= 0.001 && !mFlying && !mCrossWall) { mVelocity.Y = -0.001; if (!mOnGround) mVelocity.Y -= 0.1; } } } void PlayerEntity::HotbarItemSelect(const ControlContext& control) { //切换方块 if (control.KeyPressed(GLFW_KEY_Z) && mIndexInHand > 0) mIndexInHand--; if (control.KeyPressed(GLFW_KEY_X) && mIndexInHand < 9) mIndexInHand++; auto deltaScroll = control.Last.MouseScroll - control.Current.MouseScroll; if (static_cast(mIndexInHand) + deltaScroll < 0)mIndexInHand = 9; else if (static_cast(mIndexInHand) + deltaScroll > 9)mIndexInHand = 0; else mIndexInHand += static_cast(deltaScroll); } void PlayerEntity::StartJump() { if (!mInWater) { if ((mOnGround || mAirJumps < MaxAirJumps) && !mFlying && !mCrossWall) { if (!mOnGround) { mCurrentJumpSpeed = 0.3; mAirJumps++; } else { mCurrentJumpSpeed = 0.25; mOnGround = false; } } if (mFlying || mCrossWall) { mVelocity.Y += walkspeed / 2; } } else { mVelocity.Y = walkspeed; } } void PlayerEntity::spawn() { mPosition = { 0,60,0 }; mVelocity = { 0,0,0 }; mHealth = mMaxHealth; mCurrentJumpSpeed = 0.0; memset(mInventory, 0, sizeof(mInventory)); for (size_t i = 0; i < 255; i++) { addItem(Blocks::ROCK); addItem(Blocks::GRASS); addItem(Blocks::DIRT); addItem(Blocks::STONE); addItem(Blocks::PLANK); addItem(Blocks::WOOD); addItem(Blocks::LEAF); addItem(Blocks::GLASS); addItem(Blocks::WATER); addItem(Blocks::LAVA); addItem(Blocks::GLOWSTONE); addItem(Blocks::SAND); addItem(Blocks::CEMENT); addItem(Blocks::ICE); addItem(Blocks::COAL); addItem(Blocks::IRON); addItem(Blocks::TNT); } } void PlayerEntity::afterMove(Double3 actualMovement) { // if we tried to go down but did not actually go down - we hit the ground/floor. if (actualMovement.Y != mVelocity.Y && mVelocity.Y < 0) { mOnGround = true; // fall damage if (mVelocity.Y < -0.4 && mGameMode == GameMode::Survival) { mHealth += mVelocity.Y * mDropDamage; // mVelocity.Y is negative } } else mOnGround = false; // when we hit the ceil if (actualMovement.Y != mVelocity.Y && mVelocity.Y > 0) mCurrentJumpSpeed = 0.0; // when we hit walls mNearWall = actualMovement.X != mVelocity.X || actualMovement.Z != mVelocity.Z; mVelocity = Double3(Int3(mVelocity * 100000)) / 100000.0; mVelocity *= 0.8; if (mFlying || mCrossWall) mVelocity.Y *= 0.8; if (mOnGround) { mVelocity *= .7; mVelocity.Y = 0; } } bool PlayerEntity::placeBlock(Int3 position, Block blockname) { if (!World::ChunkOutOfBound(getChunkPosition()) && (!AABB::Intersect(bounding_box(), AABB::BoxForBlock(position)) || mCrossWall || !BlockInfo(blockname).isSolid()) && !BlockInfo(World::GetBlock(position)).isSolid()) { World::PutBlock(position, blockname); return true; } return false; } bool PlayerEntity::addItem(Item itemname, short amount) { const auto InvMaxStack = 255; for (auto i = 3; i >= 0; i--) { for (auto j = 0; j != 10; j++) { if (mInventory[i][j].item == itemname && mInventory[i][j].amount < InvMaxStack) { if (amount + mInventory[i][j].amount <= InvMaxStack) { mInventory[i][j].amount += amount; return true; } amount -= InvMaxStack - mInventory[i][j].amount; mInventory[i][j].amount = InvMaxStack; } } } for (auto i = 3; i >= 0; i--) { for (auto j = 0; j != 10; j++) { if (mInventory[i][j].item == Blocks::ENV) { mInventory[i][j].item = itemname; if (amount <= InvMaxStack) { mInventory[i][j].amount = amount; return true; } mInventory[i][j].amount = InvMaxStack; amount -= InvMaxStack; } } } return false; } void PlayerEntity::setGameMode(GameMode _gamemode) { mGameMode = _gamemode; switch (_gamemode) { case GameMode::Survival: mFlying = false; mCrossWall = false; mCurrentJumpSpeed = 0.0; break; case GameMode::Creative: mFlying = true; mCurrentJumpSpeed = 0.0; break; } } ================================================ FILE: NEWorld.Game/Universe/Entity/PlayerEntity.h ================================================ #pragma once #include "ControlContext.h" #include "Entity.h" #include "Universe/World/Blocks.h" struct ItemStack { Item item; uint8_t amount; }; enum class GameMode { Survival, Creative }; class PlayerEntity: public Entity { public: PlayerEntity(Double3 pos) : Entity(pos, { .6,1.7,.6 }) {} PlayerEntity() : PlayerEntity({ 0,0,0 }) { spawn(); } void afterMove(Double3 velocity) override; void render() override {} void update() override; void controlUpdate(const ControlContext& control); // called by update thread RenderProperties renderUpdate(const ControlContext& control, bool freeze, double lastUpdate); // called by render thread void spawn(); bool addItem(Item itemName, short amount = 1); bool placeBlock(Int3 position, Block blockName); void setGameMode(GameMode gameMode); GameMode getGameMode() const noexcept { return mGameMode; } void setVelocity(Double3 vel) { mVelocity = vel; } void toggleCrossWall() noexcept { mCrossWall = !mCrossWall; } auto getInventory() noexcept { return mInventory; } ItemStack& getCurrentSelectedItem() noexcept { return mInventory[3][mIndexInHand]; } int getCurrentHotbarSelection() noexcept { return mIndexInHand; } double getHealth() const noexcept { return mHealth; } void setHealth(double health) noexcept { mHealth = health; } double getMaxHealth() const noexcept { return mMaxHealth; } double getCurrentJumpSpeed() const noexcept { return mCurrentJumpSpeed; } bool isRunning() const noexcept { return mRunning; } bool isOnGround() const noexcept { return mOnGround; } bool isNearWall() const noexcept { return mNearWall; } bool isFlying() const noexcept { return mFlying; } bool isCrossWall() const noexcept { return mCrossWall; } double getSpeed() const noexcept { // TODO: impl super-sprint return (mRunning ? runspeed : walkspeed) * mSpeedBoost; } RenderProperties getPropertiesForRender(double timeDelta) const noexcept override { auto props = Entity::getPropertiesForRender(timeDelta); props.position.Y += mHeightExt + mHeight - mSize.Y / 2; return props; } private: void ProcessInteract(const ControlContext& control); void ProcessJump(); void ProcessNavigate(const ControlContext& control); void HotbarItemSelect(const ControlContext& control); void StartJump(); int mAirJumps = 0; double mCurrentJumpSpeed = 0; GameMode mGameMode = GameMode::Survival; double mHealth = 20, mMaxHealth = 20; double mHealSpeed = 0.01; double mDropDamage = 5.0; double mSpeedBoost = 1; double mHeight = 1.7; // height of eyes double mHeightExt = 0; // status bool mOnGround = false; bool mRunning = false; bool mNearWall = false; bool mInWater = false; bool mFlying = false; bool mCrossWall = false; ItemStack mInventory[4][10]; ubyte mIndexInHand = 0; }; ================================================ FILE: NEWorld.Game/Universe/Entity/bvh.cpp ================================================ #include "bvh.h" #include #include #include #include "Entity.h" template struct AABBIntersector : public bvh::PrimitiveIntersector { AABBIntersector(const Bvh& bvh, const Primitive* primitives) : bvh::PrimitiveIntersector(bvh, primitives) {} std::vector result_indices; bool intersect(size_t index, const BoundingBox& box, bool is_leaf) { auto [p, i] = this->primitive_at(index); if (auto hit = p->intersect(box)) { if (is_leaf) result_indices.push_back(i); return true; } return false; } }; static void aabb_intersect(const Bvh& bvh, size_t index, AABBIntersector& intersector, const BoundingBox& box) { const auto& node = bvh.nodes[index]; if (node.is_leaf()) { for (int i = 0; i < node.primitive_count; ++i) { intersector.intersect(node.first_child_or_primitive + i, box, true); } return; } if (intersector.intersect(index, box, false)) { auto left = bvh.nodes[index].first_child_or_primitive; aabb_intersect(bvh, left, intersector, box); aabb_intersect(bvh, left + 1, intersector, box); } } EntityBVH::EntityBVH(const std::vector>& entities, bool for_movement) { std::vector bboxes; std::vector centers; for (const auto& e : entities) { bboxes.emplace_back(for_movement ? e->movement_bounding_box() : e->bounding_box()); centers.emplace_back(e->center()); mEntities.emplace_back(e.get()); } construct_bvh(bboxes, centers); } std::vector EntityBVH::intersect(const BoundingBox& box) const { AABBIntersector intersector(mBvh, mEntities.data()); aabb_intersect(mBvh, 0, intersector, box); std::vector entities; for (const auto index : intersector.result_indices) entities.push_back(mEntities[index]); return entities; } void EntityBVH::construct_bvh(const std::vector& bboxes, const std::vector& centers) { const auto global_bbox = bvh::compute_bounding_boxes_union(bboxes.data(), bboxes.size()); bvh::SweepSahBuilder builder(mBvh); builder.build(global_bbox, bboxes.data(), centers.data(), bboxes.size()); } ================================================ FILE: NEWorld.Game/Universe/Entity/bvh.h ================================================ #pragma once #include #include #include #include "Math/Vector3.h" using Scalar = double; using Vector3 = bvh::Vector3; using BoundingBox = bvh::BoundingBox; using Ray = bvh::Ray; using Bvh = bvh::Bvh; class Entity; inline Vector3 toBvhVec(const Double3& v) { return {v.X, v.Y, v.Z}; } class EntityBVH { public: EntityBVH(const std::vector>& entities, bool for_movement = false); std::vector intersect(const BoundingBox& box) const; private: void construct_bvh(const std::vector& bboxes, const std::vector& centers); Bvh mBvh; std::vector mEntities; }; namespace AABB { inline bool InClip(const BoundingBox& boxA, const BoundingBox& boxB, int axis) { if (boxA.min.values[axis] > boxB.min.values[axis] && boxA.min.values[axis] < boxB.max.values[axis] || boxA.max.values[axis] > boxB.min.values[axis] && boxA.max.values[axis] < boxB.max.values[axis]) return true; if (boxB.min.values[axis] > boxA.min.values[axis] && boxB.min.values[axis] < boxA.max.values[axis] || boxB.max.values[axis] > boxA.min.values[axis] && boxB.max.values[axis] < boxA.max.values[axis]) return true; return false; } inline bool Intersect(const BoundingBox& boxA, const BoundingBox& boxB) { return InClip(boxA, boxB, 0) && InClip(boxA, boxB, 1) && InClip(boxA, boxB, 2); } inline double MaxMove(const BoundingBox& boxA, const BoundingBox& boxB, double distance, int axis) { // perform collision from A to B if (!((axis == 0 || InClip(boxA, boxB, 0)) && (axis == 1 || InClip(boxA, boxB, 1)) && (axis == 2 || InClip(boxA, boxB, 2)))) return distance; if (boxA.min.values[axis] >= boxB.max.values[axis] && distance < 0.0) return std::max(boxB.max.values[axis] - boxA.min.values[axis], distance); if (boxA.max.values[axis] <= boxB.min.values[axis] && distance > 0.0) return std::min(boxB.min.values[axis] - boxA.max.values[axis], distance); return distance; } inline BoundingBox Move(BoundingBox box, Double3 direction) { box.min += toBvhVec(direction); box.max += toBvhVec(direction); return box; } inline BoundingBox BoxForBlock(Int3 pos) { return { {pos.X - 0.5, pos.Y - 0.5, pos.Z - 0.5}, { pos.X + 0.5, pos.Y + 0.5, pos.Z + 0.5 } }; } } ================================================ FILE: NEWorld.Game/Universe/Game.h ================================================ #pragma once #include "CommandHandler.h" #include "ControlContext.h" #include "Common/Logger.h" #include "Entity/Entity.h" #include "Entity/PlayerEntity.h" class Game : public CommandHandler { Vec3 mLastSelectedBlockPos{}; protected: std::vector> mEntities{}; PlayerEntity* mPlayer = nullptr; ControlContext mControlsForUpdate{ MainWindow }; bool DebugHitbox{}; bool DebugMode{}; bool DebugShadow{}; bool mShouldRenderGUI{}; float mBlockDestructionProgress{}; std::optional>> mCurrentSelection{}; bool mBagOpened = false; public: void InitGame() { infostream << "Init player..."; auto player = std::make_unique(); // TODO: try to load first mPlayer = player.get(); mEntities.emplace_back(std::move(player)); } void updateGame() { mControlsForUpdate.Update(); //时间 gametime++; if (glfwGetKey(MainWindow, GLFW_KEY_F8)) gametime += 30; if (gametime > gameTimeMax) gametime = 0; World::unloadedChunks = 0; World::rebuiltChunks = 0; World::updatedChunks = 0; //cpArray move const auto playerChunk = mPlayer->getChunkPosition(); const auto shiftedChunkStart = playerChunk - Int3(viewdistance + 2); World::cpArray.MoveTo(shiftedChunkStart); if (FirstUpdateThisFrame) { ChunkLoadUnload(); } //加载动画 for (const auto& cp : World::chunks) { if (cp->loadAnim <= 0.3f) cp->loadAnim = 0.0f; else cp->loadAnim *= 0.6f; } //随机状态更新 RandomTick(); if (!mBagOpened) { // Get camera position. Used delta=0 so there might be a little errors but shouldn't be noticeable. const auto props = mPlayer->getPropertiesForRender(0); ProcessInteract(props.position, props.heading, props.lookUpDown); mPlayer->controlUpdate(mControlsForUpdate); HotkeySettingsToggle(); } if (isPressed(GLFW_KEY_E) && mShouldRenderGUI) { mBagOpened = !mBagOpened; bagAnimTimer = timer(); if (mBagOpened) { shouldGetThumbnail = true; } } Particles::updateall(); EntitiesUpdate(); EntitiesMovement(); } void EntitiesUpdate() { // update all entities for (auto& entity : mEntities) { entity->update(); } } void EntitiesMovement() { // movement of entities if (mEntities.empty()) return; EntityBVH bvh(mEntities, true); for (auto& entity : mEntities) { entity->move(bvh); } } // handle ray/bounding box collision check to pick/place blocks void ProcessInteract(Double3 startPosition, double heading, double lookUpDown) { auto pos = startPosition; Int3 blockBefore = pos; mCurrentSelection = std::nullopt; for (auto i = 0; i < selectPrecision * selectDistance; i++) { //线段延伸 pos.X += sin(M_PI / 180 * (heading - 180)) * sin(M_PI / 180 * (lookUpDown + 90)) / selectPrecision; pos.Y += cos(M_PI / 180 * (lookUpDown + 90)) / selectPrecision; pos.Z += cos(M_PI / 180 * (heading - 180)) * sin(M_PI / 180 * (lookUpDown + 90)) / selectPrecision; const auto currentBlockPos = Int3(pos, RoundInt); const auto currentBlock = World::GetBlock(currentBlockPos); //碰到方块 if (BlockInfo(currentBlock).isSolid()) { mCurrentSelection = std::make_pair(currentBlock, currentBlockPos); break; } blockBefore = currentBlockPos; } if (!mCurrentSelection) return; auto& itemSelection = mPlayer->getCurrentSelectedItem(); if (mControlsForUpdate.ShouldDo(ControlContext::Action::PICK_BLOCK)) { Particles::throwParticle(mCurrentSelection->first, mCurrentSelection->second); // Reset progress if selecting a different block if (mCurrentSelection->second != mLastSelectedBlockPos) mBlockDestructionProgress = 0.0; else { double factor = itemSelection.item == STICK ? 4 : 30.0 / (BlockInfo(itemSelection.item).getHardness() + 0.1); factor = std::clamp(factor, 1.0, 1.7); mBlockDestructionProgress += BlockInfo(mCurrentSelection->first).getHardness() * (mPlayer->getGameMode() == GameMode::Creative ? 10.0f : 0.3f) * factor; } if (mBlockDestructionProgress >= 100.0) { for (auto j = 1; j <= 25; j++) { Particles::throwParticle(mCurrentSelection->first, mCurrentSelection->second); } World::PickBlock(mCurrentSelection->second); } } else mBlockDestructionProgress = 0.0; if (mControlsForUpdate.ShouldDo(ControlContext::Action::PLACE_BLOCK)) { if (itemSelection.amount > 0 && isBlock(itemSelection.item)) { //放置方块 if (mPlayer->placeBlock(blockBefore, itemSelection.item)) { itemSelection.amount--; if (itemSelection.amount == 0) itemSelection.item = Blocks::ENV; } } else { //使用物品 if (itemSelection.item == APPLE) { itemSelection.amount--; if (itemSelection.amount == 0) itemSelection.item = Blocks::ENV; mPlayer->setHealth(mPlayer->getMaxHealth()); } } } mLastSelectedBlockPos = mCurrentSelection->second; } void ChunkLoadUnload() const { World::sortChunkLoadUnloadList(mPlayer->getPosition()); for (const auto&[_, chunk] : World::ChunkUnloadList) World::DeleteChunk(chunk->GetPosition()); for (const auto&[_, pos]: World::ChunkLoadList) World::AddChunk(pos); } void HotkeySettingsToggle() {//各种设置切换 if (isPressed(GLFW_KEY_F1)) { mPlayer->setGameMode(mPlayer->getGameMode() == GameMode::Creative ? GameMode::Survival : GameMode::Creative); } if (isPressed(GLFW_KEY_F2)) shouldGetScreenshot = true; if (isPressed(GLFW_KEY_F3)) { DebugMode = !DebugMode; if (isPressed(GLFW_KEY_H)) { DebugHitbox = !DebugHitbox; DebugMode = true; } if (Renderer::AdvancedRender) { if (isPressed(GLFW_KEY_M)) { DebugShadow = !DebugShadow; DebugMode = true; } } else DebugShadow = false; } if (isPressed(GLFW_KEY_F4) && mPlayer->getGameMode() == GameMode::Creative) mPlayer->toggleCrossWall(); if (isPressed(GLFW_KEY_F5)) mShouldRenderGUI = !mShouldRenderGUI; if (isPressed(GLFW_KEY_F7)) mPlayer->spawn(); if (isPressed(GLFW_KEY_L)) World::saveAllChunks(); } bool isPressed(int key) { return mControlsForUpdate.KeyJustPressed(key); } void RandomTick() const { for (auto &chunk : World::chunks) { const auto cPos = chunk->GetPosition(); const auto bPos = Int3{std::min(15, int(rnd() * 16)), std::min(15, int(rnd() * 16)), std::min(15, int(rnd() * 16))}; const auto gPos = (cPos << World::ChunkEdgeSizeLog2) + bPos; const auto block = chunk->GetBlock(bPos); if (block != Blocks::ENV) { BlockInfo(block).OnRandomTick(gPos, block); } } } static void saveGame() { infostream << "Saving world"; World::saveAllChunks(); // TODO: save player info //if (!Player::save(World::worldname)) { // warningstream << "Failed saving player info!"; //} } static bool loadGame() { // TODO: load player info //if (!Player::load(World::worldname)) { // warningstream << "Failed loading player info!"; // return false; //} return true; } }; ================================================ FILE: NEWorld.Game/Universe/World/BlockRegistry.cpp ================================================ #include "BlockRegistry.h" #include #include #include #include #include namespace { std::once_flag gEnvBlockInit; std::vector gTable; std::unordered_map> gTypes; auto gEnvBlock = Blocks::BlockType("NEWorld.Blocks.Air", false, false, false, 0); // NOLINT constexpr const char* gConfigName = "new_world_block_id_table"; void TableExtract(const nlohmann::json& cfgTree) { auto&& entries = cfgTree[gConfigName]; gTable.resize(entries.size()); for (auto& [key, value] : entries.items()) { std::string name = key; const auto iter = gTypes.find(name); if (iter != gTypes.end()) { const auto index = static_cast(value); iter->second.second = index; gTable[index] = iter->second.first; } else { throw std::runtime_error("Required Block: (" + name + ") Not Found"); } } } void TableDump(nlohmann::json& cfgTree) { for (const auto& x : gTypes) { cfgTree[std::string(x.first)] = x.second.second; } } } void BlockRegistry::Register(Blocks::BlockType *type) { if (type) { gTypes.insert_or_assign(type->GetId(), std::pair{type, -1}); } std::call_once(gEnvBlockInit, []() { gTypes.insert_or_assign(gEnvBlock.GetId(), std::pair{&gEnvBlock, 0}); }); } bool BlockRegistry::LoadIdTable(const NEWorld::filesystem::path &path) { ClearIdTable(); std::ifstream config { path.string() }; if (config) { nlohmann::json cfgTree {}; config >> cfgTree; TableExtract(cfgTree); return true; } return false; } void BlockRegistry::CreateIdTableNull() { ClearIdTable(); gTable.emplace_back(&gEnvBlock); } void BlockRegistry::CreateIdTableAuto() { CreateIdTableNull(); for (auto& [_, pair] : gTypes) { if (pair.second) { pair.second = gTable.size(); gTable.emplace_back(pair.first); } } } bool BlockRegistry::AppendIdTable(const std::string_view &name) { const auto iter = gTypes.find(name); if (iter != gTypes.end()) { auto& [type, id] = iter->second; if (id == -1) { id = gTable.size(); gTable.emplace_back(type); return true; } } return false; } bool BlockRegistry::SaveIdTable(const NEWorld::filesystem::path &path) { std::ofstream config { path.string() }; if (config) { nlohmann::json cfgTree {}; TableDump(cfgTree); config << cfgTree; return true; } return false; } Blocks::BlockType *BlockRegistry::QueryTable(int id) noexcept { return gTable[id]; } int BlockRegistry::QueryId(const std::string_view &name) noexcept { const auto iter = gTypes.find(name); return iter != gTypes.end() ? iter->second.second: -1; } void BlockRegistry::ClearIdTable() noexcept { if (!gTable.empty()) { for (auto &x : gTypes) { x.second.second = -1; } gTable.clear(); } } ================================================ FILE: NEWorld.Game/Universe/World/BlockRegistry.h ================================================ #pragma once #include "Blocks.h" #include "System/FileSystem.h" class BlockRegistry { public: static void Register(Blocks::BlockType* type); static bool LoadIdTable(const NEWorld::filesystem::path& path); static void CreateIdTableNull(); static void CreateIdTableAuto(); static bool AppendIdTable(const std::string_view &name); static bool SaveIdTable(const NEWorld::filesystem::path& path); static void ClearIdTable() noexcept; static Blocks::BlockType* QueryTable(int id) noexcept; static int QueryId(const std::string_view& name) noexcept; }; ================================================ FILE: NEWorld.Game/Universe/World/Blocks.cpp ================================================ #include "Items.h" #include "Blocks.h" #include "World.h" namespace Blocks { BlockType::~BlockType() noexcept = default; bool BlockType::BeforeBlockPlace(const Int3& position, Block block) noexcept { return true; } void BlockType::AfterBlockPlace(const Int3& position, Block block) noexcept { } bool BlockType::BeforeBlockDestroy(const Int3& position, Block block) noexcept { return true; } void BlockType::AfterBlockDestroy(const Int3& position, Block block) noexcept { // should be an item drop here // TODO: replace when entity is implemented //Player::addItem(block); } void BlockType::OnRandomTick(const Int3& position, Block block) noexcept { } BlockType blockData[BLOCK_DEF_END + 1] = { // 方块名称 固体 不透明 半透明 可以爆炸 硬度 BlockType("NEWorld.Blocks.Air", false, false, false, 0), BlockType("NEWorld.Blocks.Rock", true, true, false, 2), BlockType("NEWorld.Blocks.Grass", true, true, false, 5), BlockType("NEWorld.Blocks.Dirt", true, true, false, 5), BlockType("NEWorld.Blocks.Stone", true, true, false, 2), BlockType("NEWorld.Blocks.Plank", true, true, false, 5), BlockType("NEWorld.Blocks.Wood", true, true, false, 5), BlockType("NEWorld.Blocks.Bedrock", true, true, false, 0), BlockType("NEWorld.Blocks.Leaf", true, false, false, 15), BlockType("NEWorld.Blocks.Glass", true, false, false, 30), BlockType("NEWorld.Blocks.Water", false, false, true, 0), BlockType("NEWorld.Blocks.Lava", false, false, true, 0), BlockType("NEWorld.Blocks.GlowStone", true, true, false, 10), BlockType("NEWorld.Blocks.Sand", true, true, false, 8), BlockType("NEWorld.Blocks.Cement", true, true, false, 0.5f), BlockType("NEWorld.Blocks.Ice", true, false, true, 25), BlockType("NEWorld.Blocks.Coal Block", true, true, false, 1), BlockType("NEWorld.Blocks.Iron Block", true, true, false, 0.5f), BlockType("NEWorld.Blocks.TNT", true, true, false, 25), BlockType("NEWorld.Blocks.Null Block", true, true, false, 0) }; class Grass : public BlockType { public: Grass() : BlockType(blockData[2]) { } void OnRandomTick(const Int3& position, Block) noexcept override { const auto top = position + Int3{0, 1, 0}; //草被覆盖 if (World::GetBlock(top) != Blocks::ENV) { World::SetBlock(position, Blocks::DIRT); World::updateblock(top.X, top.Y, top.Z, true); } } } gGrass; class Dirt : public BlockType { public: Dirt() : BlockType(blockData[3]) { } void OnRandomTick(const Int3& position, Block) noexcept override { const auto top = position + Int3{0, 1, 0}; static constexpr Int3 direction[] = { {1, 0, 0}, {-1, 0, 0}, {0, 0, 1}, {0, 0, -1}, {1, 1, 0}, {-1, 1, 0}, {0, 1, 1}, {0, 1, -1}, {1, -1, 0}, {-1, -1, 0}, {0, -1, 1}, {0, -1, -1} }; if (World::GetBlock(top, Blocks::NONEMPTY) == Blocks::ENV) { for (const auto& v : direction) { if (World::GetBlock(position + v) == Blocks::GRASS) { //长草 World::SetBlock(position, Blocks::GRASS); World::updateblock(top.X, top.Y, top.Z, true); return; } } } } } gDirt; class Leaf : public BlockType { public: Leaf() : BlockType(blockData[8]) { } void AfterBlockDestroy(const Int3& position, Block) noexcept override { //if (rnd() < 0.2) { // if (rnd() < 0.5)Player::addItem(APPLE); // else Player::addItem(STICK); //} //else { Player::addItem(Blocks::LEAF); } } } gLeaf; class Tnt : public BlockType { public: Tnt() : BlockType(blockData[18]) { } void AfterBlockPlace(const Int3& position, Block) noexcept override { World::explode(position.X, position.Y, position.Z, 8, World::GetChunk(World::GetChunkPos(position))); } } gTnt; BlockType* TypeIndex[BLOCK_DEF_END + 1] = { &blockData[0], &blockData[1], &gGrass, &gDirt, &blockData[4], &blockData[5], &blockData[6], &blockData[7], &gLeaf, &blockData[9], &blockData[10], &blockData[11], &blockData[12], &blockData[13], &blockData[14], &blockData[15], &blockData[16], &blockData[17], &gTnt, &blockData[19] }; } Blocks::BlockType& BlockInfo(int id) noexcept { return *Blocks::TypeIndex[id >= Blocks::BLOCK_DEF_END || id < 0 ? Blocks::BLOCK_DEF_END : id]; } ================================================ FILE: NEWorld.Game/Universe/World/Blocks.h ================================================ #pragma once #include #include #include #include "Definitions.h" #include "Globalization.h" namespace Blocks { enum BlockID { ENV, ROCK, GRASS, DIRT, STONE, PLANK, WOOD, BEDROCK, LEAF, GLASS, WATER, LAVA, GLOWSTONE, SAND, CEMENT, ICE, COAL, IRON, TNT, BLOCK_DEF_END }; const Block NONEMPTY = 1; class BlockType { private: std::string name; float Hardness; bool Solid; bool Opaque; bool Translucent; public: BlockType(std::string blockName, bool solid, bool opaque, bool translucent, float _hardness) :name(std::move(blockName)), Hardness(_hardness), Solid(solid), Opaque(opaque), Translucent(translucent) { }; virtual ~BlockType() noexcept; [[nodiscard]] std::string_view GetId() const noexcept { return std::string_view(name); } //获得方块名称 [[nodiscard]] std::string getBlockName() const { return Globalization::GetStrbyKey(name); } //是否是固体 [[nodiscard]] bool isSolid() const { return Solid; } //是否不透明 [[nodiscard]] bool isOpaque() const { return Opaque; } //是否半透明 [[nodiscard]] bool isTranslucent() const { return Translucent; } //获得硬度(数值越大硬度越小,最大100) [[nodiscard]] float getHardness() const { return Hardness; } //方块事件 virtual bool BeforeBlockPlace(const Int3& position, Block block) noexcept; virtual void AfterBlockPlace(const Int3& position, Block block) noexcept; virtual bool BeforeBlockDestroy(const Int3& position, Block block) noexcept; virtual void AfterBlockDestroy(const Int3& position, Block block) noexcept; virtual void OnRandomTick(const Int3& position, Block block) noexcept; }; } Blocks::BlockType& BlockInfo(int id) noexcept; ================================================ FILE: NEWorld.Game/Universe/World/Chunk.cpp ================================================ #include "Chunk.h" #include "World.h" namespace World { Chunk::~Chunk() { unloadedChunksCount++; } BoundingBox Chunk::getBaseAABB() { const auto min = Double3(GetPosition() * 16) - Double3(0.5); const auto max = Double3(GetPosition() * 16) + Double3(16.0 - 0.5); return BoundingBox{toBvhVec(min), toBvhVec(max)}; } } ================================================ FILE: NEWorld.Game/Universe/World/Chunk.h ================================================ #pragma once #include "Definitions.h" #include "Blocks.h" #include "Frustum.h" #include #include #include #include "Data/ChunkStorage.h" #include "Universe/Entity/bvh.h" class Object; namespace World { extern std::string worldname; extern Brightness BRIGHTNESSMIN; extern Brightness BRIGHTNESSMAX; //Maximum brightness extern Brightness skylight; constexpr chunkid GetChunkId(Int3 vec) noexcept { if (vec.Y == -128) vec.Y = 0; if (vec.Y <= 0) vec.Y = abs(vec.Y) + (1LL << 7); if (vec.X == -134217728) vec.X = 0; if (vec.X <= 0) vec.X = abs(vec.X) + (1LL << 27); if (vec.Z == -134217728) vec.Z = 0; if (vec.Z <= 0) vec.Z = abs(vec.Z) + (1LL << 27); return (chunkid(vec.Y) << 56) + (chunkid(vec.X) << 28) + vec.Z; } struct ChunkPosHash { chunkid operator()(Int3 pos) const noexcept { return GetChunkId(pos); } }; class ChunkData { static constexpr int GetIndex(const Int3 vec) noexcept { const auto v = UInt3(vec); return static_cast((v.X << ChunkPlaneSizeLog2) | (v.Y << ChunkEdgeSizeLog2) | v.Z); } public: ChunkData() = default; ChunkData(const ChunkData& o): mBrightness(o.mBrightness) { for (int i = 0; i < ChunkCubicSize; ++i) mBlock.Set(i, o.mBlock.Get(i)); } Block GetBlock(const Int3 vec) noexcept { return mBlock.Get(GetIndex(vec)); } Brightness GetBrightness(const Int3 vec) noexcept { return mBrightness[GetIndex(vec)]; } void SetBlock(const Int3 vec, Block block) noexcept { mBlock.Set(GetIndex(vec), block); } void SetBrightness(const Int3 vec, Brightness brightness) noexcept { mBrightness[GetIndex(vec)] = brightness; } private: std::array mBrightness{0}; Data::ChunkStorage mBlock{4, Data::Init}; }; class Chunk { private: bool mLazy; ChunkData *mData; chunkid mId; const Int3 mPos; BoundingBox mBounds; std::vector> mAttached {}; public: Chunk(Int3 pos, ChunkData *data, bool isShared = false) noexcept: mPos(pos), mId(GetChunkId(pos)), mLazy(isShared), mData(data), Modified(false), Empty(false), updated(false), loadAnim(0.0) { mBounds = getBaseAABB(); } ~Chunk(); [[nodiscard]] Int3 GetPosition() const noexcept { return mPos; } bool Empty, updated, Modified; double loadAnim; [[nodiscard]] chunkid GetId() const noexcept { return mId; } Block GetBlock(const Int3 vec) noexcept { return mData->GetBlock(vec); } Brightness GetBrightness(const Int3 vec) noexcept { return mData->GetBrightness(vec); } void SetBlock(const Int3 vec, Block block) noexcept { if (mLazy) { mLazy = false; mData = new ChunkData(*mData); } mData->SetBlock(vec, block); Modified = true; } void SetBrightness(const Int3 vec, Brightness brightness) noexcept { if (mLazy) { mLazy = false; mData = new ChunkData(*mData); } mData->SetBrightness(vec, brightness); Modified = true; } auto RawUnsafe() noexcept { return mData; } BoundingBox getBaseAABB(); void Attach(std::shared_ptr attachment) { mAttached.push_back(std::move(attachment)); } void Detach(const std::shared_ptr& attachment) { auto iter = std::find(mAttached.begin(), mAttached.end(), attachment); if (iter != mAttached.end()) mAttached.erase(iter); } }; } ================================================ FILE: NEWorld.Game/Universe/World/ChunkPtrArray.cpp ================================================ #include "ChunkPtrArray.h" #include namespace World { void ChunkPtrArray::Create(int s) { size = s; size2 = size * size; size3 = size * size * size; man = {size2, size, 1}; array = new Chunk *[size3]; memset(array, 0, size3 * sizeof(Chunk *)); } void ChunkPtrArray::Finalize() { delete[] array; array = nullptr; } void ChunkPtrArray::Move(const Int3 &delta) { if (delta.X || delta.Y || delta.Z) { Int3 iter{}; auto **arrTemp = new Chunk *[size3]; for (iter.X = 0; iter.X < size; iter.X++) { for (iter.Y = 0; iter.Y < size; iter.Y++) { for (iter.Z = 0; iter.Z < size; iter.Z++) { arrTemp[Dot(iter, man)] = Fetch(iter + delta); } } } delete[] array; array = arrTemp; origin += delta; } } } ================================================ FILE: NEWorld.Game/Universe/World/ChunkPtrArray.h ================================================ #pragma once #include "Math/Vector3.h" namespace World { class Chunk; class ChunkPtrArray { public: void Create(int s); void Finalize(); void Move(const Int3 &delta); void MoveTo(const Int3 &pos) { Move(pos - origin); } void Add(Chunk *c, const Int3 &pos) noexcept { Set(pos, c); } void Remove(const Int3 &pos) noexcept { Set(pos, nullptr); } [[nodiscard]] Chunk *Get(const Int3 &pos) const noexcept { return Fetch(pos - origin); } void Set(const Int3 &pos, Chunk *c) noexcept { Write(pos - origin, c); } private: [[nodiscard]] bool Has(const Int3 v) const noexcept { return v.X >= 0 && v.X < size && v.Z >= 0 && v.Z < size && v.Y >= 0 && v.Y < size; } [[nodiscard]] Chunk *Fetch(const Int3 v) const noexcept { return Has(v) ? array[Dot(v, man)] : nullptr; } void Write(const Int3 &pos, Chunk *c) noexcept { if (Has(pos)) array[Dot(pos, man)] = c; } Chunk **array = nullptr; Int3 origin{}, man{}; int size{}, size2{}, size3{}; }; } ================================================ FILE: NEWorld.Game/Universe/World/Data/BitStorage.h ================================================ #pragma once #include #include #include #include #include #include #include namespace World::Data { template T Exchange(T &v, const U as) noexcept { const auto r = v; v = as; return r; } template void AssertBetween(const T low, const U high, const V val) noexcept { assert(val >= low && val <= high); } class BitsDenseView { public: BitsDenseView(const unsigned int bits, const unsigned int size, uint64_t *const data) : mMask((1ull << bits) - 1ull), mData(data), mBits(bits), mSize(size), mDataSize(static_cast(ceil(double(size * bits) / 64.0) * sizeof(uint64_t))) { AssertBetween(1u, 32u, bits); } BitsDenseView(const BitsDenseView &r) noexcept = default; BitsDenseView(BitsDenseView &&r) noexcept : BitsDenseView(r) { r.mData = nullptr; } // NOLINT BitsDenseView &operator=(const BitsDenseView &r) noexcept = default; BitsDenseView &operator=(BitsDenseView &&r) noexcept { *this = r, r.mData = nullptr; return *this; } void Set(const unsigned int index, const uint64_t val) noexcept { AssertBetween(0u, mSize - 1, index); AssertBetween(0u, mMask, val); const auto bitOffset = index * mBits; const auto hByteBegin = bitOffset >> 6u; const auto hByteEnd = (bitOffset + (mBits - 1u)) >> 6u; const auto headBitLoc = bitOffset & 63u; const auto lowerRemains = mData[hByteBegin] & ~(mMask << headBitLoc); const auto lowerChanged = (val & mMask) << headBitLoc; mData[hByteBegin] = lowerRemains | lowerChanged; if (hByteBegin != hByteEnd) { const auto bitsLower = 64u - headBitLoc; const auto bitsUpper = mBits - bitsLower; const auto upperRemains = (mData[hByteEnd] >> bitsUpper) << bitsUpper; const auto upperChanged = (val & mMask) >> bitsLower; mData[hByteEnd] = upperRemains | upperChanged; } } [[nodiscard]] unsigned int Get(const unsigned int index) noexcept { AssertBetween(0u, mSize - 1, index); const auto bitOffset = index * mBits; const auto hByteBegin = bitOffset >> 6u; const auto hByteEnd = (bitOffset + (mBits - 1u)) >> 6u; const auto headBitLoc = bitOffset & 63u; if (hByteBegin == hByteEnd) { return static_cast((mData[hByteBegin] >> headBitLoc) & mMask); } else { const auto bitsLower = 64u - headBitLoc; const auto upper = (mData[hByteEnd] << bitsLower) & mMask; const auto lower = mData[hByteBegin] >> headBitLoc; return static_cast(upper | lower); } } template void Pack(const T *sparse) noexcept { uint64_t current = 0u; auto bS = 0u; auto bE = mBits; auto write = mData; for (const auto sE = sparse + mSize; sparse != sE; ++sparse) { const auto v = (*sparse) & mMask; current |= static_cast(v) << bS; if (bE >= 64u) { *(write++) = current; current = (bE -= 64u) ? v >> (64u - bS) : 0u; } bS = bE; bE += mBits; } if (bS) { *write = current; } } template void Unpack(T *sparse) noexcept { auto bS = 0u; auto bE = mBits; auto write = mData; uint64_t current = *(write++); for (const auto sE = sparse + mSize; sparse != sE; ++sparse) { *sparse = (current >> bS) & mMask; if (bE >= 64u) { bE -= 64u; current = *(write++); *sparse |= (current << (64u - bS)) & mMask; } bS = bE; bE += mBits; } } [[nodiscard]] auto Bits() const noexcept { return mBits; } [[nodiscard]] auto Size() const noexcept { return mSize; } [[nodiscard]] auto Raw() noexcept { return mData; } [[nodiscard]] auto Raw() const noexcept { return mData; } [[nodiscard]] auto RawSize() const noexcept { return mDataSize; } private: uint64_t mMask; uint64_t *mData; unsigned int mBits, mSize, mDataSize; }; class BitsSparseView { public: BitsSparseView(const int bits, const int size, std::byte *const data) noexcept : mFlag(GetFlag(bits)), mSize(size), mData(data) {} // NOLINT BitsSparseView(const BitsSparseView &r) noexcept = default; BitsSparseView(BitsSparseView &&r) noexcept : BitsSparseView(r) { r.mData = nullptr; } // NOLINT BitsSparseView &operator=(const BitsSparseView &r) noexcept = default; BitsSparseView &operator=(BitsSparseView &&r) noexcept { *this = r, r.mData = nullptr; return *this; } void Set(const unsigned int index, const int val) noexcept { switch (mFlag) { case 0: A(index) = val; return; case 1: A(index) = val; return; case 2: A(index) = val; } } [[nodiscard]] int Get(const unsigned int index) const noexcept { switch (mFlag) { case 0: return A(index); case 1: return A(index); case 2: return A(index); } return 0; } [[nodiscard]] int GetSet(const unsigned int index, const int val) noexcept { switch (mFlag) { case 0: return Exchange(A(index), val); case 1: return Exchange(A(index), val); case 2: return Exchange(A(index), val); } return 0; } [[nodiscard]] auto Size() const noexcept { return mSize; } [[nodiscard]] auto Raw() noexcept { return mData; } [[nodiscard]] auto Raw() const noexcept { return mData; } [[nodiscard]] auto MaxBits() const noexcept { return 8 * (1 << mFlag); } // NOLINT [[nodiscard]] auto RawSize() const noexcept { return mSize << mFlag; } // NOLINT protected: static constexpr int GetFlag(const int bits) noexcept { if (bits <= 8) return 0; if (bits <= 16) return 1; return 2; } private: template [[nodiscard]] T &A(const unsigned int x) noexcept { return reinterpret_cast(mData)[x]; } template [[nodiscard]] const T &A(const unsigned int x) const noexcept { return reinterpret_cast(mData)[x]; } static_assert(alignof(std::max_align_t) >= 4); static_assert(sizeof(int) >= 4); int mFlag, mSize; std::byte *mData; }; class BitsDense : public BitsDenseView { public: BitsDense(const unsigned int bits, const unsigned int size, const bool init) : BitsDenseView(bits, size, new uint64_t[size_t(ceil(double(size * bits) / 64.0))]) { if (init) std::memset(Raw(), 0, RawSize()); } BitsDense(const BitsDense &) = delete; BitsDense(BitsDense &&r) noexcept : BitsDenseView(std::move(r)) {} BitsDense &operator=(const BitsDense &) = delete; BitsDense &operator=(BitsDense &&r) noexcept { if (std::addressof(r) != this) { delete[] Raw(); static_cast(*this) = static_cast(std::move(r)); } return *this; } ~BitsDense() noexcept { delete[] Raw(); } }; class BitsSparse : public BitsSparseView { public: BitsSparse(const int bits, const int size, const bool init) : BitsSparseView(bits, size, new std::byte[size << GetFlag(bits)]) { // NOLINT if (init) std::memset(Raw(), 0, RawSize()); } BitsSparse(const BitsSparse &) = delete; BitsSparse(BitsSparse &&r) noexcept : BitsSparseView(std::move(r)) {} BitsSparse &operator=(const BitsSparse &) = delete; BitsSparse &operator=(BitsSparse &&r) noexcept { if (std::addressof(r) != this) { delete[] Raw(); static_cast(*this) = static_cast(std::move(r)); } return *this; } ~BitsSparse() noexcept { delete[] Raw(); } }; } ================================================ FILE: NEWorld.Game/Universe/World/Data/ChunkStorage.cpp ================================================ #include "ChunkStorage.h" namespace World::Data { ChunkStorage::ChunkStorage(int bits) noexcept :mT(CheckMode(bits)), mBit(bits) { switch (mT) { case 0: new(&mV.b4) BlocksSparse4(); new(&mP.p4) BlockPalette4(); return; case 1: new(&mV.b8) BlocksSparse8(); new(&mP.p8) BlockPalette8(bits); return; case 2: new(&mV.b16) BlocksSparse16(); new(&mP.p16) BlockPalette16(); return; } } ChunkStorage::ChunkStorage(int bits, InitializeT) noexcept :mT(CheckMode(bits)), mBit(bits) { switch (mT) { case 0: new(&mV.b4) BlocksSparse4(Init); new(&mP.p4) BlockPalette4(); return; case 1: new(&mV.b8) BlocksSparse8(Init); new(&mP.p8) BlockPalette8(bits); return; case 2: new(&mV.b16) BlocksSparse16(Init); new(&mP.p16) BlockPalette16(); return; } } ChunkStorage::~ChunkStorage() noexcept { switch (mT) { case 0: (mV.b4.~BlocksSparse4(), mP.p4.~BlockPalette4()); return; case 1: (mV.b8.~BlocksSparse8(), mP.p8.~BlockPalette8()); return; case 2: (mV.b16.~BlocksSparse16(), mP.p16.~BlockPalette16()); return; } } bool ChunkStorage::TrySet(const int index, const uintptr_t val) noexcept { switch (mT) { case 0: { auto id = mP.p4.fromVal(val); return (id != -1) && (mV.b4.Set(index, id), true); } case 1: { auto id = mP.p8.fromVal(val); return (id != -1) && (mV.b8.Set(index, id), true); } case 2: { auto id = mP.p16.fromVal(val); return (id != -1) && (mV.b16.Set(index, id), true); } } return false; } void ChunkStorage::Scale48() noexcept { BlocksSparse8 b8{Init}; BlockPalette8 p8{mP.p4}; for (int i = 0; i<4096; ++i) b8.Set(i, mV.b4.Get(i)); // mId is kept // close the 4-bit storage mV.b4.~BlocksSparse4(); mP.p4.~BlockPalette4(); // switch mode mBit = 5; mT = 1; // enable the 8-bit storage new(&mV.b8) BlocksSparse8(std::move(b8)); new(&mP.p8) BlockPalette8(std::move(p8)); } void ChunkStorage::Scale8H() noexcept { BlocksSparse16 b16{Init}; BlockPalette16 p16{}; for (int i = 0; i<4096; ++i) b16.Set(i, p16.fromVal(mP.p8.toVal(mV.b8.Get(i)))); // close the 8-bit storage mV.b8.~BlocksSparse8(); mP.p8.~BlockPalette8(); // switch mode mBit = 9; mT = 2; // enable the 16-bit storage new(&mV.b16) BlocksSparse16(std::move(b16)); new(&mP.p16) BlockPalette16(p16); } void ChunkStorage::UpScale() noexcept { switch (mT) { case 0: Scale48(); break; case 1: ((mBit<8) ? Scale88() : Scale8H()); break; } } void ChunkStorage::Set(const int index, const uintptr_t val) noexcept { if (!TrySet(index, val)) { UpScale(); TrySet(index, val); } } void ChunkStorage::Scale88() noexcept { (mP.p8.UpScale(), ++mBit); } } ================================================ FILE: NEWorld.Game/Universe/World/Data/ChunkStorage.h ================================================ #pragma once #include #include #include #include "BitStorage.h" namespace World { constexpr unsigned int ChunkEdgeSizeLog2 = 4; constexpr unsigned int ChunkPlaneSizeLog2 = ChunkEdgeSizeLog2 * 2u; constexpr unsigned int ChunkCubicSizeLog2 = ChunkEdgeSizeLog2 * 3u; constexpr unsigned int ChunkEdgeSize = 1u << ChunkEdgeSizeLog2; constexpr unsigned int ChunkPlaneSize = 1u << ChunkPlaneSizeLog2; constexpr unsigned int ChunkCubicSize = 1u << ChunkCubicSizeLog2; } namespace World::Data { static constexpr uintptr_t EmptyDataValue = ~uintptr_t(0); struct InitializeT { }; constexpr InitializeT Init; class BlocksSparse4 { struct Split { uint8_t upper: 4; uint8_t lower: 4; }; public: BlocksSparse4() = default; explicit BlocksSparse4(InitializeT) noexcept : mStorage(ChunkCubicSize >> 1, {0, 0}) {} [[nodiscard]] int Get(const int index) const noexcept { const auto sec = mStorage[index >> 1u]; // NOLINT return (index & 1) ? sec.upper : sec.lower; // NOLINT } void Set(const int index, const int val) noexcept { auto &sec = mStorage[index >> 1u]; // NOLINT if (index & 1) sec.upper = val; else sec.lower = val; // NOLINT } [[nodiscard]] auto Raw() noexcept { return mStorage.data(); } [[nodiscard]] auto Raw() const noexcept { return mStorage.data(); } private: std::vector mStorage; }; class BlocksSparse8 { public: BlocksSparse8() = default; explicit BlocksSparse8(InitializeT) noexcept : mStorage(ChunkCubicSize, 0) {} [[nodiscard]] int Get(const int index) const noexcept { return mStorage[index]; } void Set(const int index, const int val) noexcept { mStorage[index] = val; } [[nodiscard]] auto Raw() noexcept { return mStorage.data(); } [[nodiscard]] auto Raw() const noexcept { return mStorage.data(); } private: std::vector mStorage; }; class BlocksSparse16 { public: BlocksSparse16() = default; explicit BlocksSparse16(InitializeT) noexcept : mStorage(ChunkCubicSize, 0) {} [[nodiscard]] int Get(const int index) const noexcept { return mStorage[index]; } void Set(const int index, const int val) noexcept { mStorage[index] = val; } [[nodiscard]] auto Raw() noexcept { return mStorage.data(); } [[nodiscard]] auto Raw() const noexcept { return mStorage.data(); } private: std::vector mStorage; }; class BlockPalette4 { public: [[nodiscard]] uintptr_t toVal(const int id) const noexcept { return ((id < mSize) ? mT[id] : 0); } [[nodiscard]] int tryFromVal(const uintptr_t val) const noexcept { for (int i = 0; i < mSize; ++i) { if (mT[i] == val) return i; } return -1; } int fromVal(const uintptr_t val) noexcept { if (const auto v = tryFromVal(val); v == -1) { return (mSize < 16) ? (mT[mSize] = val, mSize++) : -1; } else return v; } [[nodiscard]] auto Size() const noexcept { return mSize; } [[nodiscard]] auto Raw() const noexcept { return mT.get(); } private: std::unique_ptr mT{new uintptr_t[16]}; int mSize{0}; }; class BlockPalette8 { static constexpr int LdRev(int size) noexcept { return (size >> 2) * 5; } // NOLINT, 0.8 Load Factor public: explicit BlockPalette8(int bits = 5) noexcept: mSize(0), mCurMax(1u << bits), mCurMask(mCurMax - 1) { // NOLINT StgCreate(); } explicit BlockPalette8(const BlockPalette4 &p4) noexcept: BlockPalette8(5) { mSize = p4.Size(); std::memcpy(mT.get(), p4.Raw(), p4.Size() * sizeof(uintptr_t)); ReHash(); } [[nodiscard]] uintptr_t toVal(const int id) const noexcept { return ((id < mSize) ? mT[id] : 0); } [[nodiscard]] int tryFromVal(const uintptr_t val) const noexcept { for (auto i = Hash(val);; ++i) { const auto m = i & mCurMask; // NOLINT const auto v = rLookUp[m]; if (v == val) return rMap[m]; if (v == EmptyDataValue) return -1; } } int fromVal(const uintptr_t val) noexcept { if (mSize == mCurMax) return -1; for (auto i = Hash(val);; ++i) { const auto m = i & mCurMask; // NOLINT const auto v = rLookUp[m]; if (v == val) return rMap[m]; if (v == EmptyDataValue) return (rLookUp[m] = val, mT[mSize] = val, rMap[m] = mSize++); } } void UpScale() noexcept { mCurMax <<= 1; // NOLINT mCurMask = mCurMax - 1; auto s = std::move(mT); StgCreate(); std::memcpy(mT.get(), s.get(), mSize * sizeof(uintptr_t)); ReHash(); } private: [[nodiscard]] uint8_t Hash(const uintptr_t v) const noexcept { return static_cast(v & mCurMask); } void ReHash() noexcept { for (int i = 0; i < mSize; ++i) { for (auto u = Hash(mT[i]);; ++u) { const auto m = u & mCurMask; // NOLINT const auto v = rLookUp[m]; if (v == EmptyDataValue) { (rLookUp[m] = mT[i], rMap[m] = i); break; } } } } void StgCreate() noexcept { mT = std::unique_ptr(new uintptr_t[mCurMax + LdRev(mCurMax) + LdRev(mCurMax >> 1)]); // NOLINT rMap = reinterpret_cast(mT.get() + mCurMax + LdRev(mCurMax)); rLookUp = mT.get() + mCurMax; std::memset(rLookUp, 0xFF, LdRev(mCurMax) * (sizeof(uintptr_t) + sizeof(uint8_t))); } std::unique_ptr mT{}; uintptr_t *rLookUp{}; uint8_t *rMap{}; int mSize, mCurMax, mCurMask; }; // TODO(Need to be worked on) class BlockPalette16 { public: [[nodiscard]] uintptr_t toVal(const int id) const noexcept { return id; } // NOLINT [[nodiscard]] int tryFromVal(const uintptr_t val) const noexcept { return static_cast(val); } // NOLINT int fromVal(const uintptr_t val) noexcept { return tryFromVal(val); } // NOLINT }; class ChunkStorage { public: explicit ChunkStorage(int bits) noexcept; ChunkStorage(int bits, InitializeT) noexcept; ~ChunkStorage() noexcept; [[nodiscard]] uintptr_t Get(const int index) const noexcept { switch (mT) { case 0: return mP.p4.toVal(mV.b4.Get(index)); case 1: return mP.p8.toVal(mV.b8.Get(index)); case 2: return mP.p16.toVal(mV.b16.Get(index)); } return 0; } void Set(int index, uintptr_t val) noexcept; private: bool TrySet(int index, uintptr_t val) noexcept; void Scale48() noexcept; void Scale88() noexcept; void Scale8H() noexcept; void UpScale() noexcept; static constexpr int CheckMode(int bits) noexcept { if (bits <= 4) return 0; if (bits <= 8) return 1; return 2; } int mT, mBit; union V { V() noexcept {} // NOLINT ~V() noexcept {} // NOLINT BlocksSparse4 b4; BlocksSparse8 b8; BlocksSparse16 b16; } mV; union P { P() noexcept {} // NOLINT ~P() noexcept {} // NOLINT BlockPalette4 p4; BlockPalette8 p8; BlockPalette16 p16; } mP; }; } ================================================ FILE: NEWorld.Game/Universe/World/OrderedArray.h ================================================ #pragma once #include #include #include #include #include template class Compare = std::less> class OrderedList { public: static_assert(std::is_trivial_v && std::is_trivial_v); OrderedList() noexcept : mEnd(mList.end()), mComp() {} using ArrayType = std::array, Size>; using Iterator = typename ArrayType::iterator; using ConstIterator = typename ArrayType::const_iterator; [[nodiscard]] Iterator begin() noexcept { return mList.begin(); } [[nodiscard]] ConstIterator begin() const noexcept { return mList.begin(); } [[nodiscard]] Iterator end() noexcept { return mEnd; } [[nodiscard]] ConstIterator end() const noexcept { return mEnd; } void Insert(Tk key, Td data) noexcept { const auto first = std::lower_bound( begin(), end(), key, [this](const auto& l, const auto& r) noexcept { return mComp(l.first, r); } ); if (first != mList.end()) { if (first != end()) { const auto last = std::min(mList.end() - 1, end()); std::copy_backward(first, last, last + 1); } *first = std::pair(key, data); if (end() != mList.end()) ++mEnd; } } void Clear() noexcept { mEnd = begin(); } [[nodiscard]] auto Count() const noexcept { return end() - begin(); } private: ArrayType mList {}; Iterator mEnd; Compare mComp; }; ================================================ FILE: NEWorld.Game/Universe/World/TerrainGen/Carve.cpp ================================================ namespace World::TerrainGen { /* void buildtree(Int3 pos) { auto [x, y, z] = pos.Data; //对生成条件进行更严格的检测 //一:正上方五格必须为空气 for (auto i = y + 1; i < y + 6; i++) { if (GetBlock({(x), (i), (z)}) != Blocks::ENV)return; } //二:周围五格不能有树 for (auto ix = x - 4; ix < x + 4; ix++) { for (auto iy = y - 4; iy < y + 4; iy++) { for (auto iz = z - 4; iz < z + 4; iz++) { if (GetBlock({(ix), (iy), (iz)}) == Blocks::WOOD || GetBlock({(ix), (iy), (iz)}) == Blocks::LEAF) return; } } } //终于可以开始生成了 //设置泥土 SetBlock({x, y, z}, Blocks::DIRT); //设置树干 auto h = 0;//高度 //测算泥土数量 auto Dirt = 0;//泥土数 for (auto ix = x - 4; ix < x + 4; ix++) { for (auto iy = y - 4; iy < y; iy++) { for (auto iz = z - 4; iz < z + 4; iz++) { if (GetBlock({(ix), (iy), (iz)}) == Blocks::DIRT)Dirt++; } } } //测算最高高度 for (auto i = y + 1; i < y + 16; i++) { if (GetBlock({(x), (i), (z)}) == Blocks::ENV) { h++; } else { break; }; } //取最小值 h = std::min(h, int(Dirt * 15 / 268 * std::max(rnd(), 0.8))); if (h < 7)return; //开始生成树干 for (auto i = y + 1; i < y + h + 1; i++) { SetBlock({(x), (i), (z)}, Blocks::WOOD); } //设置树叶及枝杈 //计算树叶起始生成高度 const auto leafh = int(double(h) * 0.618) + 1;//黄金分割比大法好!!! const auto distancen2 = int(double((h - leafh + 1) * (h - leafh + 1))) + 1; for (auto iy = y + leafh; iy < y + int(double(h) * 1.382) + 2; iy++) { for (auto ix = x - 6; ix < x + 6; ix++) { for (auto iz = z - 6; iz < z + 6; iz++) { const auto distancen = DistanceSquare(ix, iy, iz, x, y + leafh + 1, z); if ((GetBlock({(ix), (iy), (iz)}) == Blocks::ENV) && (distancen < distancen2)) { if ((distancen <= distancen2 / 9) && (rnd() > 0.3)) { SetBlock({(ix), (iy), (iz)}, Blocks::WOOD);//生成枝杈 } else { SetBlock({(ix), (iy), (iz)}, Blocks::LEAF); //生成树叶 } } } } } // TODO(move this function when terrain carving for terrain generation is possible) } */ } ================================================ FILE: NEWorld.Game/Universe/World/TerrainGen/Generate.h ================================================ #pragma once #include "Heights.h" #include "Universe/World/Chunk.h" namespace World::TerrainGen { class Generate { public: explicit Generate(int seed) : mHeights(seed) { Cursor(Int3{0}, Int3{15}, [this](auto p) noexcept { mSkyChunk.SetBlock(p, Blocks::ENV); mSkyChunk.SetBrightness(p, skylight); mLavaChunk.SetBlock(p, Blocks::LAVA); mLavaChunk.SetBrightness(p, skylight); }); } // TODO(remove when system is completed) static Generate &Get() { static Generate instance{3404}; return instance; } Chunk *Run(Int3 c) { auto result = Draft(c); // TODO(this is a hack, formalize it) if (result->RawUnsafe() == &mSkyChunk) result->Empty = true; if (!result->Empty) result->updated = true; return result; } private: Heights mHeights; ChunkData mSkyChunk{}, mLavaChunk{}; static ChunkData *NoiseTerrain(Int3 pos, Heights::Section &hm) { auto[cx, cy, cz] = pos.Data; auto result = std::make_unique(); int maxh; const auto sh = WaterLevel + 2 - (cy << 4); const auto wh = WaterLevel - (cy << 4); for (auto x = 0; x < 16; ++x) { for (auto z = 0; z < 16; ++z) { const auto h = hm.Get(x, z) - (cy << 4); if (h > sh && h > wh + 1) { //Grass layer if (h >= 0 && h < 16) result->SetBlock(Int3{x, h, z}, Blocks::GRASS); //Dirt layer maxh = std::min(std::max(0, h), 16); for (auto y = std::min(std::max(0, h - 5), 16); y < maxh; ++y) result->SetBlock(Int3{x, y, z}, Blocks::DIRT); } else { //Sand layer maxh = std::min(std::max(0, h + 1), 16); for (auto y = std::min(std::max(0, h - 5), 16); y < maxh; ++y) result->SetBlock(Int3{x, y, z}, Blocks::SAND); //Water layer const auto minh = std::min(std::max(0, h + 1), 16); maxh = std::min(std::max(0, wh + 1), 16); auto cur_br = BRIGHTNESSMAX - (WaterLevel - (maxh - 1 + (cy << 4))) * 2; if (cur_br < BRIGHTNESSMIN) cur_br = BRIGHTNESSMIN; for (auto y = maxh - 1; y >= minh; --y) { result->SetBlock(Int3{x, y, z}, Blocks::WATER); result->SetBrightness(Int3{x, y, z}, static_cast(cur_br)); cur_br -= 2; if (cur_br < BRIGHTNESSMIN) cur_br = BRIGHTNESSMIN; } } //Rock layer maxh = std::min(std::max(0, h - 5), 16); for (auto y = 0; y < maxh; ++y) result->SetBlock(Int3{x, y, z}, Blocks::ROCK); //Air layer for (auto y = std::min(std::max(0, std::max(h + 1, wh + 1)), 16); y < 16; ++y) { result->SetBlock(Int3{x, y, z}, Blocks::ENV); result->SetBrightness(Int3{x, y, z}, skylight); } } } return result.release(); } static Chunk* Make(Int3 pos, ChunkData *data, bool isShared, std::shared_ptr hm) { auto result = new Chunk(pos, data, isShared); result->Attach(std::move(hm)); return result; } Chunk * Draft(Int3 pos) { auto[cx, cy, cz] = pos.Data; //Fast generate parts //Part1 out of the terrain bound if (cy > 4) return new Chunk(pos, &mSkyChunk, true); if (cy < 0) return new Chunk(pos, &mLavaChunk, true); //Part2 out of geometry area auto cur = mHeights.Get(cx, cz); // TODO(attach this to chunk) if (cy > cur->High()) return Make(pos, &mSkyChunk, true, std::move(cur)); if (cy < cur->Low()) { auto result = std::make_unique(); Cursor(Int3{0}, Int3{15}, [&result](auto p) noexcept { result->SetBlock(p, Blocks::ROCK); result->SetBrightness(p, 0); }); return Make(pos, result.release(), false, std::move(cur)); } const auto terrain = NoiseTerrain(pos, *cur); return Make(pos, terrain, false, std::move(cur)); } }; } ================================================ FILE: NEWorld.Game/Universe/World/TerrainGen/Heights.h ================================================ #pragma once #include "Noise.h" #include #include #include #include namespace World::TerrainGen { class Heights { public: class Section : public kls::PmrBase { public: void Init(Noise &noise, int cx, int cz) { std::call_once(mLazy, [this, &noise, cx, cz]() { auto low = std::numeric_limits::max(), high = WaterLevel; const int bX = cx * 16, bZ = cz * 16; for (auto x = 0; x < 16; ++x) { for (auto z = 0; z < 16; ++z) { const auto height = mHeights[x][z] = noise.Get(x + bX, z + bZ); if (height < low) low = height; if (height > high) high = height; } } mCLow = static_cast(std::floor(double(low) / 16.0)); mCHigh = static_cast(std::ceil(double(high) / 16.0)); }); } [[nodiscard]] int Low() const noexcept { return mCLow; } [[nodiscard]] int High() const noexcept { return mCHigh; } [[nodiscard]] int Get(int x, int z) const noexcept { return mHeights[x][z]; } private: int mCLow{}, mCHigh{}; int mHeights[16][16]{}; std::once_flag mLazy{}; }; explicit Heights(int seed) : mNoise(seed) {} std::shared_ptr
Get(int cx, int cz) { auto result = GetEntry(cx, cz); result->Init(mNoise, cx, cz); return result; } private: Noise mNoise; std::mutex mMutex; // TODO(Add a cleanup tick) tsl::hopscotch_map> mSections{}; static uint64_t MakeKey(int32_t cx, int32_t cz) noexcept { const auto uCx = std::bit_cast(cx); const auto uCz = std::bit_cast(cz); return (static_cast(uCx) << 32ull) | static_cast(uCz); } std::shared_ptr
GetEntry(int cx, int cz) { std::lock_guard lk{mMutex}; const auto key = MakeKey(cx, cz); const auto find = mSections.find(key); if (find != mSections.end()) { auto current = find.value().lock(); if (current) return current; } auto result = std::make_shared
(); mSections[key] = result; return result; } }; } ================================================ FILE: NEWorld.Game/Universe/World/TerrainGen/Noise.h ================================================ #pragma once #include "Definitions.h" namespace World::TerrainGen { static constexpr int WaterLevel = 30; static constexpr double NoiseScaleX = 64; static constexpr double NoiseScaleZ = 64; // TODO(add seed support) class Noise { static constexpr double Base(int x, int y) { auto xx = x + y * 13258953287; xx = (xx >> 13) ^ xx; return ((xx * (xx * xx * 15731 + 789221) + 1376312589) & 0x7fffffff) / 16777216.0; } static constexpr double SmoothedNoise(int x, int y) { const auto corners = (Base(x - 1, y - 1) + Base(x + 1, y - 1) + Base(x - 1, y + 1) + Base(x + 1, y + 1)) / 8.0; const auto sides = (Base(x - 1, y) + Base(x + 1, y) + Base(x, y - 1) + Base(x, y + 1)) / 4.0; const auto center = Base(x, y); return corners + sides + center; } static constexpr double Interpolate(double a, double b, double x) { return a * (1.0 - x) + b * x; } static double InterpolatedNoise(double x, double y) { const auto int_X = static_cast(floor(x)); //不要问我为毛用floor,c++默认居然TM的是向零取整的 const auto fractional_X = x - int_X; const auto int_Y = static_cast(floor(y)); const auto fractional_Y = y - int_Y; const auto v1 = Base(int_X, int_Y); const auto v2 = Base(int_X + 1, int_Y); const auto v3 = Base(int_X, int_Y + 1); const auto v4 = Base(int_X + 1, int_Y + 1); const auto i1 = Interpolate(v1, v2, fractional_X); const auto i2 = Interpolate(v3, v4, fractional_X); return Interpolate(i1, i2, fractional_Y); } static double PerlinNoise2D(double x, double y) { double total = 0, frequency = 1, amplitude = 1; for (auto i = 0; i <= 4; i++) { total += InterpolatedNoise(x * frequency, y * frequency) * amplitude; frequency *= 2; amplitude /= 2.0; } return total; } public: explicit Noise(int mapSeed) : seed(mapSeed) { fastSrand(mapSeed); for (auto &i: perm) i = rnd() * 256.0; } int Get(int x, int y) { return static_cast(PerlinNoise2D(x / NoiseScaleX + 0.125, y / NoiseScaleZ + 0.125)) >> 2; } private: int seed; double perm[256]{}; }; } ================================================ FILE: NEWorld.Game/Universe/World/World.cpp ================================================ #include "World.h" #include "Particles.h" #include #include #include "System/FileSystem.h" #include "TerrainGen/Generate.h" #include "Renderer/World/WorldRenderer.h" namespace World { std::string worldname; Brightness skylight = 15; //Sky light level Brightness BRIGHTNESSMAX = 15; //Maximum brightness Brightness BRIGHTNESSMIN = 2; //Mimimum brightness Brightness BRIGHTNESSDEC = 1; //Brightness decrease std::vector> chunks{}; Chunk *cpCachePtr = nullptr; chunkid cpCacheID = 0; ChunkPtrArray cpArray; int cloud[128][128]; int rebuiltChunks, rebuiltChunksCount; int updatedChunks, updatedChunksCount; int unloadedChunks, unloadedChunksCount; OrderedList ChunkLoadList{}; OrderedList ChunkUnloadList{}; void Init() { std::stringstream ss; ss << "Worlds/" << worldname << "/"; NEWorld::filesystem::create_directories(ss.str()); ss.clear(); ss.str(""); ss << "Worlds/" << worldname << "/chunks"; NEWorld::filesystem::create_directories(ss.str()); cpCachePtr = nullptr; cpCacheID = 0; cpArray.Create((viewdistance + 2) * 2); } auto LowerChunkBound(chunkid cid) noexcept { return std::lower_bound(chunks.begin(), chunks.end(), cid, [](auto &left, auto right) noexcept { return left->GetId() < right; }); } Chunk *AddChunk(Int3 vec) { const auto cid = GetChunkId(vec); //Chunk ID const auto chunkIter = LowerChunkBound(cid); if (chunkIter != chunks.end()) { if ((*chunkIter)->GetId() == cid) { printf("[Console][Error]"); printf("Chunk(%d,%d,%d)has been loaded,when adding Chunk.\n", vec.X, vec.Y, vec.Z); return chunkIter->get(); } } // TODO(Actually try to load from disk) const auto newChunk = TerrainGen::Generate::Get().Run(vec); const auto& ptr = *chunks.insert(chunkIter, std::shared_ptr(newChunk)); WorldRenderer::ChunksRenderer::Default().Add(ptr); cpCacheID = cid; cpCachePtr = newChunk; cpArray.Add(newChunk, vec); return newChunk; } Chunk *GetChunk(Int3 vec) { const auto cid = GetChunkId(vec); if (cpCacheID == cid && cpCachePtr) return cpCachePtr; auto ret = cpArray.Get(vec); if (ret) { cpCacheID = cid; cpCachePtr = ret; return ret; } if (!chunks.empty()) { const auto iter = LowerChunkBound(cid); if (iter != chunks.end()) { const auto& chunk = *iter; if (chunk->GetId() == cid) { ret = chunk.get(); cpCacheID = cid; cpCachePtr = ret; cpArray.Add(ret, vec); return ret; } } } return nullptr; } void DeleteChunk(Int3 vec) { const auto id = GetChunkId(vec); //Chunk ID const auto chunkIter = LowerChunkBound(id); if (chunkIter != chunks.end()) { if ((*chunkIter)->GetId() == id) { const auto chunk = chunkIter->get(); if (cpCachePtr == chunk) { cpCacheID = 0; cpCachePtr = nullptr; } cpArray.Remove(vec); chunks.erase(chunkIter); } } } std::vector getHitboxes(const BoundingBox& box) { std::vector ret; for (auto a = int(box.min.values[0] + 0.5) - 1; a <= int(box.max.values[0] + 0.5) + 1; a++) { for (auto b = int(box.min.values[1] + 0.5) - 1; b <= int(box.max.values[1] + 0.5) + 1; b++) { for (auto c = int(box.min.values[2] + 0.5) - 1; c <= int(box.max.values[2] + 0.5) + 1; c++) { Int3 pos = { a, b, c }; if (BlockInfo(GetBlock(pos)).isSolid()) { auto blockBox = AABB::BoxForBlock(pos); if (AABB::Intersect(box, blockBox)) ret.push_back(blockBox); } } } } return ret; } bool inWater(const BoundingBox &box) { for (auto a = int(box.min.values[0] + 0.5) - 1; a <= int(box.max.values[0] + 0.5) + 1; a++) { for (auto b = int(box.min.values[1] + 0.5) - 1; b <= int(box.max.values[1] + 0.5) + 1; b++) { for (auto c = int(box.min.values[2] + 0.5) - 1; c <= int(box.max.values[2] + 0.5) + 1; c++) { Int3 pos = { a,b,c }; auto block = GetBlock(pos); if (block == Blocks::WATER || block == Blocks::LAVA) { if (AABB::Intersect(box, AABB::BoxForBlock(pos))) return true; } } } } return false; } void updateblock(int x, int y, int z, bool blockchanged, int depth) { //Blockupdate if (depth > 200) return; depth++; auto updated = blockchanged; const auto cx = GetChunkPos(x); const auto cy = GetChunkPos(y); const auto cz = GetChunkPos(z); if (ChunkOutOfBound({cx, cy, cz})) return; const auto b = GetBlockPos(Int3{x, y, z}); auto cptr = GetChunk({cx, cy, cz}); if (cptr != nullptr) { const auto oldbrightness = cptr->GetBrightness(b); auto skylighted = true; auto yi = y + 1; auto cyi = GetChunkPos(yi); if (y < 0) skylighted = false; else { while (!ChunkOutOfBound({(cx), (cyi + 1), (cz)}) && ChunkLoaded({(cx), (cyi + 1), (cz)}) && skylighted) { if (BlockInfo(GetBlock({x, yi, z})).isOpaque() || GetBlock({(x), (yi), (z)}) == Blocks::WATER) { skylighted = false; } yi++; cyi = GetChunkPos(yi); } } if (!BlockInfo(GetBlock({x, y, z})).isOpaque()) { Block blks[7] = {0, (GetBlock({(x), (y), (z + 1)})), //Front face (GetBlock({(x), (y), (z - 1)})), //Back face (GetBlock({(x + 1), (y), (z)})), //Right face (GetBlock({(x - 1), (y), (z)})), //Left face (GetBlock({(x), (y + 1), (z)})), //Top face (GetBlock({(x), (y - 1), (z)}))}; //Bottom face Brightness brts[7] = {0, getbrightness(x, y, z + 1), //Front face getbrightness(x, y, z - 1), //Back face getbrightness(x + 1, y, z), //Right face getbrightness(x - 1, y, z), //Left face getbrightness(x, y + 1, z), //Top face getbrightness(x, y - 1, z)}; //Bottom face auto maxbrightness = 1; for (auto i = 2; i <= 6; i++) { if (brts[maxbrightness] < brts[i]) maxbrightness = i; } auto br = brts[maxbrightness]; if (blks[maxbrightness] == Blocks::WATER) { if (br - 2 < BRIGHTNESSMIN) br = BRIGHTNESSMIN; else br -= 2; } else { if (br - 1 < BRIGHTNESSMIN) br = BRIGHTNESSMIN; else br--; } if (skylighted) { if (br < skylight) br = skylight; } if (br < BRIGHTNESSMIN) br = BRIGHTNESSMIN; //Set brightness cptr->SetBrightness(b, br); } else { //Opaque block cptr->SetBrightness(b, 0); if (GetBlock({x, y, z}) == Blocks::GLOWSTONE || GetBlock({x, y, z}) == Blocks::LAVA) { cptr->SetBrightness(b, BRIGHTNESSMAX); } } if (oldbrightness != cptr->GetBrightness(b)) updated = true; if (updated) { updateblock(x, y + 1, z, false, depth); updateblock(x, y - 1, z, false, depth); updateblock(x + 1, y, z, false, depth); updateblock(x - 1, y, z, false, depth); updateblock(x, y, z + 1, false, depth); updateblock(x, y, z - 1, false, depth); } setChunkUpdated(cx, cy, cz, true); if (b.X == 15 && cx < worldsize - 1) setChunkUpdated(cx + 1, cy, cz, true); if (b.X == 0 && cx > -worldsize) setChunkUpdated(cx - 1, cy, cz, true); if (b.Y == 15 && cy < worldheight - 1) setChunkUpdated(cx, cy + 1, cz, true); if (b.Y == 0 && cy > -worldheight) setChunkUpdated(cx, cy - 1, cz, true); if (b.Z == 15 && cz < worldsize - 1) setChunkUpdated(cx, cy, cz + 1, true); if (b.Z == 0 && cz > -worldsize) setChunkUpdated(cx, cy, cz - 1, true); } } bool ChunkHintMatch(const Int3 c, Chunk *const cptr) noexcept { return cptr && cptr->GetPosition() == c; } Block GetBlock(const Int3 v, Block mask, Chunk *const hint) { //获取方块 const auto c = GetChunkPos(v); if (ChunkOutOfBound(c)) return Blocks::ENV; const auto b = GetBlockPos(v); if (ChunkHintMatch(c, hint)) { return hint->GetBlock(b); } const auto ci = GetChunk(c); if (ci) { return ci->GetBlock(b); } return mask; } Brightness GetBrightness(Int3 v, Chunk *const hint) { //获取亮度 const auto c = GetChunkPos(v); if (ChunkOutOfBound(c)) return skylight; const auto b = GetBlockPos(v); if (ChunkHintMatch(c, hint)) { return hint->GetBrightness(b); } const auto ci = GetChunk(c); if (ci) { return ci->GetBrightness(b); } return skylight; } Chunk *GetChunkNoneLazy(const Int3 c) noexcept { return GetChunk(c); } void SetBlock(Int3 v, Block block, Chunk *hint) { //设置方块 const auto c = GetChunkPos(v); const auto b = GetBlockPos(v); if (ChunkHintMatch(c, hint)) { hint->SetBlock(b, block); updateblock(v.X, v.Y, v.Z, true); } else if (!ChunkOutOfBound(c)) { if (const auto i = GetChunkNoneLazy(c); i) { i->SetBlock(b, block); updateblock(v.X, v.Y, v.Z, true); } } } void SetBrightness(Int3 v, Brightness brightness, Chunk *hint) { //设置亮度 const auto c = GetChunkPos(v); const auto b = GetBlockPos(v); if (ChunkHintMatch(c, hint)) { hint->SetBrightness(b, brightness); } else if (!ChunkOutOfBound(c)) { if (const auto i = GetChunkNoneLazy(c); i) { i->SetBrightness(b, brightness); } } } bool chunkUpdated(const Int3 vec) { const auto i = GetChunk(vec); if (!i) return false; return i->updated; } void setChunkUpdated(int x, int y, int z, bool value) { if (const auto i = GetChunkNoneLazy({x, y, z}); i) { i->updated = value; } } static constexpr auto ccOffset = Int3(7); // offset to a chunk center void sortChunkLoadUnloadList(Int3 pos) { const auto cp = GetChunkPos(pos); ChunkUnloadList.Clear(); for (auto &chunk : chunks) { const auto c = chunk->GetPosition(); if (ChebyshevDistance(c, cp) > viewdistance) ChunkUnloadList.Insert(DistanceSquared(c * 16 + ccOffset, pos), chunk.get()); } ChunkLoadList.Clear(); const auto diff = Int3(viewdistance + 1); Cursor(cp - diff, cp + diff, [&](const auto &c) noexcept { if (ChunkOutOfBound(c)) return; if (!cpArray.Get(c)) ChunkLoadList.Insert(DistanceSquared(c * 16 + ccOffset, pos), c); }); } void saveAllChunks() { } void destroyAllChunks() { chunks.clear(); chunks.shrink_to_fit(); cpArray.Finalize(); rebuiltChunks = 0; rebuiltChunksCount = 0; updatedChunks = 0; updatedChunksCount = 0; unloadedChunks = 0; unloadedChunksCount = 0; ChunkLoadList.Clear(); ChunkUnloadList.Clear(); } void buildtree(Int3 pos) { auto [x, y, z] = pos.Data; //对生成条件进行更严格的检测 //一:正上方五格必须为空气 for (auto i = y + 1; i < y + 6; i++) { if (GetBlock({(x), (i), (z)}) != Blocks::ENV)return; } //二:周围五格不能有树 for (auto ix = x - 4; ix < x + 4; ix++) { for (auto iy = y - 4; iy < y + 4; iy++) { for (auto iz = z - 4; iz < z + 4; iz++) { if (GetBlock({(ix), (iy), (iz)}) == Blocks::WOOD || GetBlock({(ix), (iy), (iz)}) == Blocks::LEAF) return; } } } //终于可以开始生成了 //设置泥土 SetBlock({x, y, z}, Blocks::DIRT); //设置树干 auto h = 0;//高度 //测算泥土数量 auto Dirt = 0;//泥土数 for (auto ix = x - 4; ix < x + 4; ix++) { for (auto iy = y - 4; iy < y; iy++) { for (auto iz = z - 4; iz < z + 4; iz++) { if (GetBlock({(ix), (iy), (iz)}) == Blocks::DIRT)Dirt++; } } } //测算最高高度 for (auto i = y + 1; i < y + 16; i++) { if (GetBlock({(x), (i), (z)}) == Blocks::ENV) { h++; } else { break; }; } //取最小值 h = std::min(h, int(Dirt * 15 / 268 * std::max(rnd(), 0.8))); if (h < 7)return; //开始生成树干 for (auto i = y + 1; i < y + h + 1; i++) { SetBlock({(x), (i), (z)}, Blocks::WOOD); } //设置树叶及枝杈 //计算树叶起始生成高度 const auto leafh = int(double(h) * 0.618) + 1;//黄金分割比大法好!!! const auto distancen2 = int(double((h - leafh + 1) * (h - leafh + 1))) + 1; for (auto iy = y + leafh; iy < y + int(double(h) * 1.382) + 2; iy++) { for (auto ix = x - 6; ix < x + 6; ix++) { for (auto iz = z - 6; iz < z + 6; iz++) { const auto distancen = DistanceSquare(ix, iy, iz, x, y + leafh + 1, z); if ((GetBlock({(ix), (iy), (iz)}) == Blocks::ENV) && (distancen < distancen2)) { if ((distancen <= distancen2 / 9) && (rnd() > 0.3)) { SetBlock({(ix), (iy), (iz)}, Blocks::WOOD);//生成枝杈 } else { SetBlock({(ix), (iy), (iz)}, Blocks::LEAF); //生成树叶 } } } } } // TODO(move this function when terrain carving for terrain generation is possible) } void explode(int x, int y, int z, int r, Chunk *c) { const double maxdistsqr = r * r; for (auto fx = x - r - 1; fx < x + r + 1; fx++) { for (auto fy = y - r - 1; fy < y + r + 1; fy++) { for (auto fz = z - r - 1; fz < z + r + 1; fz++) { const auto distsqr = (fx - x) * (fx - x) + (fy - y) * (fy - y) + (fz - z) * (fz - z); if (distsqr <= maxdistsqr * 0.75 || distsqr <= maxdistsqr && rnd() > (distsqr - maxdistsqr * 0.6) / (maxdistsqr * 0.4)) { const auto e = GetBlock({(fx), (fy), (fz)}); if (e == Blocks::ENV) continue; for (auto j = 1; j <= 12; j++) { Particles::throwParticle(e, float(fx + rnd() - 0.5f), float(fy + rnd() - 0.2f), float(fz + rnd() - 0.5f), float(rnd() * 0.2f - 0.1f), float(rnd() * 0.2f - 0.1f), float(rnd() * 0.2f - 0.1f), float(rnd() * 0.02 + 0.03), int(rnd() * 60) + 30); } SetBlock({(fx), (fy), (fz)}, Blocks::ENV, c); } } } } } void PutBlock(const Int3 v, Block block) { auto &blockInfo = BlockInfo(block); if (blockInfo.BeforeBlockPlace(v, block)) { SetBlock(v, block); blockInfo.AfterBlockPlace(v, block); } } void PickBlock(const Int3 v) { const auto block = GetBlock(v); auto &type = BlockInfo(block); if (type.BeforeBlockDestroy(v, block)) { SetBlock(v, Blocks::ENV); type.AfterBlockDestroy(v, block); } } } ================================================ FILE: NEWorld.Game/Universe/World/World.h ================================================ #pragma once #include "Definitions.h" #include "ChunkPtrArray.h" #include "Chunk.h" #include "Blocks.h" #include "OrderedArray.h" #include "Universe/Entity/bvh.h" extern int viewdistance; namespace World { extern std::string worldname; const int worldsize = 134217728; const int worldheight = 128; extern Brightness skylight; //Sky light level extern Brightness BRIGHTNESSMAX; //Maximum brightness extern Brightness BRIGHTNESSMIN; //Mimimum brightness extern Brightness BRIGHTNESSDEC; //Brightness decree extern std::vector> chunks; extern ChunkPtrArray cpArray; extern int cloud[128][128]; extern int rebuiltChunks, rebuiltChunksCount; extern int updatedChunks, updatedChunksCount; extern int unloadedChunks, unloadedChunksCount; extern OrderedList ChunkLoadList; extern OrderedList ChunkUnloadList; template constexpr T GetChunkPos(const T n) noexcept { return n >> 4; } template constexpr T GetBlockPos(const T n) noexcept { return n & 15; } void Init(); Chunk *AddChunk(Int3 vec); void DeleteChunk(Int3 vec); Chunk *GetChunk(Int3 vec); constexpr bool ChunkOutOfBound(const Int3 v) noexcept { return v.Y < -worldheight || v.Y > worldheight - 1 || v.X < -134217728 || v.X > 134217727 || v.Z < -134217728 || v.Z > 134217727; } inline bool ChunkLoaded(const Int3 v) noexcept { if (ChunkOutOfBound(v)) return false; return GetChunk(v); } std::vector getHitboxes(const BoundingBox& box); bool inWater(const BoundingBox& box); void updateblock(int x, int y, int z, bool blockchanged, int depth = 0); Block GetBlock(Int3 v, Block mask = Blocks::ENV, Chunk *hint = nullptr); Brightness GetBrightness(Int3 v, Chunk *hint = nullptr); void SetBlock(Int3 v, Block block, Chunk *hint = nullptr); void SetBrightness(Int3 v, Brightness brightness, Chunk *hint = nullptr); inline Brightness getbrightness(int x, int y, int z, Chunk *cptr = nullptr) { return GetBrightness({x, y, z}, cptr); } void PutBlock(Int3 v, Block block); void PickBlock(Int3 v); bool chunkUpdated(Int3 vec); void setChunkUpdated(int x, int y, int z, bool value); void sortChunkLoadUnloadList(Int3 pos); void saveAllChunks(); void destroyAllChunks(); void buildtree(Int3 pos); void explode(int x, int y, int z, int r, Chunk *c = nullptr); } ================================================ FILE: NEWorld.Game/resource.h ================================================ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ 生成的包含文件。 // 供 resource.rc 使用 // // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 101 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1000 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif ================================================ FILE: NEWorld.Game/resource.rc ================================================ GLFW_ICON ICON "neworld.ico" ================================================ FILE: NEWorld.Game/stdinclude.h ================================================ #pragma once #include #include #define _USE_MATH_DEFINES #ifndef M_PI #define M_PI 3.14159265358979323846264338327950288 #endif #include #include #include #include #include #ifdef NEWORLD_GAME #define GLEW_NO_GLU #include #include #endif ================================================ FILE: NoesisGUI/CMake/FindNoesis.cmake ================================================ # modified from https://github.com/rschurade/Ingnomia find_path(NOESIS_INCLUDE_DIR NAMES NoesisPCH.h HINTS ${NOESIS_ROOT}/Include ) find_library(NOESIS_LIBRARY NAMES libNoesis Noesis HINTS ${NOESIS_ROOT}/Bin/linux_x86_64 ${NOESIS_ROOT}/Lib/windows_x86_64 ) if(WIN32) find_file(NOESIS_DLL NAMES Noesis.dll HINTS ${NOESIS_ROOT}/Bin/windows_x86_64 ) else() set(NOESIS_DLL ${NOESIS_LIBRARY}) endif() include(FindPackageHandleStandardArgs) # handle the QUIETLY and REQUIRED arguments and set NOESIS_FOUND to TRUE # if all listed variables are TRUE find_package_handle_standard_args(Noesis DEFAULT_MSG NOESIS_LIBRARY NOESIS_INCLUDE_DIR NOESIS_DLL) mark_as_advanced(NOESIS_INCLUDE_DIR NOESIS_LIBRARY NOESIS_DLL) if(NOESIS_FOUND AND NOT TARGET Noesis) add_library(Noesis SHARED IMPORTED) set_target_properties(Noesis PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${NOESIS_INCLUDE_DIR}" IMPORTED_LOCATION "${NOESIS_DLL}" IMPORTED_IMPLIB "${NOESIS_LIBRARY}" ) endif() ================================================ FILE: NoesisGUI/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) project(NEWorld) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/CMake) kls_add_library_module(NoesisGUI NW::Noesis) set(NOESIS_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/SDK" CACHE PATH "Root to the NoesisGui sdk") find_package(Noesis REQUIRED) file(GLOB NOESIS_APP_INCLUDE SDK/Src/Packages/*/*/Include SDK/Src/Packages/App/Theme/Data/Theme SDK/Src/Packages/App/Theme/Data/Theme/Fonts ) file(GLOB NOESIS_APP_SRCS SDK/Src/Packages/App/Theme/Src/*.cpp SDK/Src/Packages/App/Providers/Src/*.cpp SDK/Src/Packages/App/Interactivity/Src/*.cpp SDK/Src/Packages/App/MediaElement/Src/*.cpp SDK/Src/Packages/App/ApplicationLauncher/Src/NotifyPropertyChangedBase.cpp SDK/Src/Packages/Render/GLRenderDevice/Src/*.cpp ) target_sources(NoesisGUI PRIVATE ${NOESIS_APP_SRCS}) target_compile_definitions(NoesisGUI PUBLIC NS_APP_THEME_API= NS_APP_PROVIDERS_API= NS_APP_INTERACTIVITY_API= NS_APP_MEDIAELEMENT_API= NS_APP_APPLICATIONLAUNCHER_API= NS_RENDER_GLRENDERDEVICE_API= ) # For some reason we still need to put all these as public # TODO(Maybe we should put a wrapper library around it) target_include_directories(NoesisGUI PUBLIC ${NOESIS_APP_INCLUDE}) target_link_libraries(NoesisGUI PUBLIC Noesis) execute_process(COMMAND python bin2h.py WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) add_custom_command( TARGET NoesisGUI POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${NOESIS_DLL} ${KLS_OUT_PRODUCT_DIR} ) ================================================ FILE: NoesisGUI/bin2h.py ================================================ #!/usr/bin/env python from pathlib import Path def bin2h(filein, fileout): with open(filein, "rb") as fd: data = bytearray(fd.read()) with open(fileout, "w") as fo: fo.write("/* %s, %d bytes */\n" % (filein, len(data))) fo.write("const uint8_t %s[] =\n{\n" % filein.name.replace(".","_").replace(" ","_")) line = "" for i in data: num = "%d" % i + "," if len(line) + len(num) < 100: line += num else: fo.write(" " + line + "\n") line = num fo.write(" " + line[:-1] + "\n") fo.write("};\n") def main(): targets = list(Path(".").glob("SDK/Src/Packages/App/Theme/Data/Theme/*.xaml")) targets.extend(Path(".").glob("SDK/Src/Packages/App/Theme/Data/Theme/Fonts/*.otf")) for p in targets: tarp = str(p)+".bin.h" print(p, 'to' , tarp) bin2h(p, tarp) if __name__ == "__main__": main() ================================================ FILE: README.md ================================================ # NEWorld ![Screenshot](https://raw.githubusercontent.com/Infinideastudio/NEWorld/refactor/Docs/old.png) NEWorld is a 3D sandbox game inspired by Minecraft, licensed under [LGPL v3](http://www.gnu.org/licenses/lgpl.html). It features an engine specifically made for this game and hand-written with C++ for maximized optimization. We welcome all contributions. NEWorld 是一个类似于 Minecraft 的 3D 沙盒游戏,采用[LGPLv3许可证](http://www.gnu.org/licenses/lgpl.html)发布并受其保护。 我们使用 C++ 专为 NEWorld 其自制了游戏引擎,而没有使用任何现成的解决方案,以最大化性能,欢迎感兴趣的人加入到我们的开发中。 There were attempts to [refactor](https://github.com/Infinideastudio/NEWorld/tree/refactor) or [rewrite](https://github.com/Infinideastudio/NEWorld/tree/renew) the game since the codebase was old and somewhat messy. What you see here is the latest attempt to incrementally clean the codebase while keeping most of its features. ## Features 1. C++20 2. Optimized for performance 3. Should be cross-platform (although the development is currently focused in Windows) 4. Depends on OpenGL 4.5, glew, glfw, openal-soft, leveldb, Noesis GUI. 5. Re-invented wheels 6. Written for fun! ## Compilation 1. Clone this repo 2. Install vcpkg and install dependencies 3. Download [Noesis](https://www.noesisengine.com/), and put it into `External/Noesis` 4. Draft the license key into `External/Noesis/Include/NoesisLicense.h` 5. Compile and run!