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/InventorySlot.xaml
================================================
================================================
FILE: Assets/GUI/MainMenu.xaml
================================================
Smooth lighting
Nice Grass
VSync
Shadows
================================================
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
M0,0L6,6 12,0
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