first commit
This commit is contained in:
127
addons/zylann.hterrain/native/.clang-format
Executable file
127
addons/zylann.hterrain/native/.clang-format
Executable file
@@ -0,0 +1,127 @@
|
||||
# Commented out parameters are those with the same value as base LLVM style
|
||||
# We can uncomment them if we want to change their value, or enforce the
|
||||
# chosen value in case the base style changes (last sync: Clang 6.0.1).
|
||||
---
|
||||
### General config, applies to all languages ###
|
||||
BasedOnStyle: LLVM
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: DontAlign
|
||||
# AlignConsecutiveAssignments: false
|
||||
# AlignConsecutiveDeclarations: false
|
||||
# AlignEscapedNewlines: Right
|
||||
# AlignOperands: true
|
||||
AlignTrailingComments: false
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
# AllowShortBlocksOnASingleLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: true
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
AllowShortIfStatementsOnASingleLine: true
|
||||
# AllowShortLoopsOnASingleLine: false
|
||||
# AlwaysBreakAfterDefinitionReturnType: None
|
||||
# AlwaysBreakAfterReturnType: None
|
||||
# AlwaysBreakBeforeMultilineStrings: false
|
||||
# AlwaysBreakTemplateDeclarations: false
|
||||
# BinPackArguments: true
|
||||
# BinPackParameters: true
|
||||
# BraceWrapping:
|
||||
# AfterClass: false
|
||||
# AfterControlStatement: false
|
||||
# AfterEnum: false
|
||||
# AfterFunction: false
|
||||
# AfterNamespace: false
|
||||
# AfterObjCDeclaration: false
|
||||
# AfterStruct: false
|
||||
# AfterUnion: false
|
||||
# AfterExternBlock: false
|
||||
# BeforeCatch: false
|
||||
# BeforeElse: false
|
||||
# IndentBraces: false
|
||||
# SplitEmptyFunction: true
|
||||
# SplitEmptyRecord: true
|
||||
# SplitEmptyNamespace: true
|
||||
# BreakBeforeBinaryOperators: None
|
||||
# BreakBeforeBraces: Attach
|
||||
# BreakBeforeInheritanceComma: false
|
||||
BreakBeforeTernaryOperators: false
|
||||
# BreakConstructorInitializersBeforeComma: false
|
||||
BreakConstructorInitializers: AfterColon
|
||||
# BreakStringLiterals: true
|
||||
ColumnLimit: 0
|
||||
# CommentPragmas: '^ IWYU pragma:'
|
||||
# CompactNamespaces: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
ConstructorInitializerIndentWidth: 8
|
||||
ContinuationIndentWidth: 8
|
||||
Cpp11BracedListStyle: false
|
||||
# DerivePointerAlignment: false
|
||||
# DisableFormat: false
|
||||
# ExperimentalAutoDetectBinPacking: false
|
||||
# FixNamespaceComments: true
|
||||
# ForEachMacros:
|
||||
# - foreach
|
||||
# - Q_FOREACH
|
||||
# - BOOST_FOREACH
|
||||
# IncludeBlocks: Preserve
|
||||
IncludeCategories:
|
||||
- Regex: '".*"'
|
||||
Priority: 1
|
||||
- Regex: '^<.*\.h>'
|
||||
Priority: 2
|
||||
- Regex: '^<.*'
|
||||
Priority: 3
|
||||
# IncludeIsMainRegex: '(Test)?$'
|
||||
IndentCaseLabels: true
|
||||
# IndentPPDirectives: None
|
||||
IndentWidth: 4
|
||||
# IndentWrappedFunctionNames: false
|
||||
# JavaScriptQuotes: Leave
|
||||
# JavaScriptWrapImports: true
|
||||
# KeepEmptyLinesAtTheStartOfBlocks: true
|
||||
# MacroBlockBegin: ''
|
||||
# MacroBlockEnd: ''
|
||||
# MaxEmptyLinesToKeep: 1
|
||||
# NamespaceIndentation: None
|
||||
# PenaltyBreakAssignment: 2
|
||||
# PenaltyBreakBeforeFirstCallParameter: 19
|
||||
# PenaltyBreakComment: 300
|
||||
# PenaltyBreakFirstLessLess: 120
|
||||
# PenaltyBreakString: 1000
|
||||
# PenaltyExcessCharacter: 1000000
|
||||
# PenaltyReturnTypeOnItsOwnLine: 60
|
||||
# PointerAlignment: Right
|
||||
# RawStringFormats:
|
||||
# - Delimiter: pb
|
||||
# Language: TextProto
|
||||
# BasedOnStyle: google
|
||||
# ReflowComments: true
|
||||
# SortIncludes: true
|
||||
# SortUsingDeclarations: true
|
||||
# SpaceAfterCStyleCast: false
|
||||
# SpaceAfterTemplateKeyword: true
|
||||
# SpaceBeforeAssignmentOperators: true
|
||||
# SpaceBeforeParens: ControlStatements
|
||||
# SpaceInEmptyParentheses: false
|
||||
# SpacesBeforeTrailingComments: 1
|
||||
# SpacesInAngles: false
|
||||
# SpacesInContainerLiterals: true
|
||||
# SpacesInCStyleCastParentheses: false
|
||||
# SpacesInParentheses: false
|
||||
# SpacesInSquareBrackets: false
|
||||
TabWidth: 4
|
||||
UseTab: Always
|
||||
---
|
||||
### C++ specific config ###
|
||||
Language: Cpp
|
||||
Standard: Cpp03
|
||||
---
|
||||
### ObjC specific config ###
|
||||
Language: ObjC
|
||||
Standard: Cpp03
|
||||
ObjCBlockIndentWidth: 4
|
||||
# ObjCSpaceAfterProperty: false
|
||||
# ObjCSpaceBeforeProtocolList: true
|
||||
---
|
||||
### Java specific config ###
|
||||
Language: Java
|
||||
# BreakAfterJavaFieldAnnotations: false
|
||||
...
|
||||
4
addons/zylann.hterrain/native/.gitignore
vendored
Executable file
4
addons/zylann.hterrain/native/.gitignore
vendored
Executable file
@@ -0,0 +1,4 @@
|
||||
# Build
|
||||
# Ignored locally because there are other folders in which we want to version OBJ files
|
||||
*.obj
|
||||
|
||||
119
addons/zylann.hterrain/native/SConstruct
Executable file
119
addons/zylann.hterrain/native/SConstruct
Executable file
@@ -0,0 +1,119 @@
|
||||
#!python
|
||||
import os
|
||||
|
||||
opts = Variables([], ARGUMENTS)
|
||||
|
||||
# Gets the standard flags CC, CCX, etc.
|
||||
env = Environment(ENV = os.environ)
|
||||
|
||||
# Define our options
|
||||
opts.Add(EnumVariable('target', "Compilation target", 'debug', ['debug', 'release']))
|
||||
opts.Add(EnumVariable('platform', "Compilation platform", '', ['', 'windows', 'linux', 'osx']))
|
||||
opts.Add(BoolVariable('use_llvm', "Use the LLVM / Clang compiler", 'no'))
|
||||
opts.Add(EnumVariable('macos_arch', "Target macOS architecture", 'universal', ['universal', 'x86_64', 'arm64']))
|
||||
|
||||
# Hardcoded ones
|
||||
target_path = "bin/"
|
||||
TARGET_NAME = "hterrain_native"
|
||||
|
||||
# Local dependency paths
|
||||
godot_headers_path = "godot-cpp/godot-headers/"
|
||||
cpp_bindings_path = "godot-cpp/"
|
||||
cpp_bindings_library = "libgodot-cpp"
|
||||
|
||||
# only support 64 at this time
|
||||
bits = 64
|
||||
|
||||
# Updates the environment with the option variables.
|
||||
opts.Update(env)
|
||||
|
||||
# Process some arguments
|
||||
if env['use_llvm']:
|
||||
env['CC'] = 'clang'
|
||||
env['CXX'] = 'clang++'
|
||||
|
||||
if env['platform'] == '':
|
||||
print("No valid target platform selected.")
|
||||
quit()
|
||||
|
||||
# For the reference:
|
||||
# - CCFLAGS are compilation flags shared between C and C++
|
||||
# - CFLAGS are for C-specific compilation flags
|
||||
# - CXXFLAGS are for C++-specific compilation flags
|
||||
# - CPPFLAGS are for pre-processor flags
|
||||
# - CPPDEFINES are for pre-processor defines
|
||||
# - LINKFLAGS are for linking flags
|
||||
|
||||
# Check our platform specifics
|
||||
if env['platform'] == "osx":
|
||||
target_path += 'osx/'
|
||||
cpp_bindings_library += '.osx'
|
||||
if env['target'] == 'debug':
|
||||
env.Append(CCFLAGS = ['-g', '-O2', '-arch', 'x86_64'])
|
||||
env.Append(CXXFLAGS = ['-std=c++17'])
|
||||
env.Append(LINKFLAGS = ['-arch', 'x86_64'])
|
||||
else:
|
||||
env.Append(CCFLAGS = ['-g', '-O3', '-arch', 'x86_64'])
|
||||
env.Append(CXXFLAGS = ['-std=c++17'])
|
||||
env.Append(LINKFLAGS = ['-arch', 'x86_64'])
|
||||
|
||||
elif env['platform'] == "linux":
|
||||
target_path += 'linux/'
|
||||
cpp_bindings_library += '.linux'
|
||||
if env['target'] == 'debug':
|
||||
# -g3 means we want plenty of debug info, more than default
|
||||
env.Append(CCFLAGS = ['-fPIC', '-g3', '-Og'])
|
||||
env.Append(CXXFLAGS = ['-std=c++17'])
|
||||
else:
|
||||
env.Append(CCFLAGS = ['-fPIC', '-O3'])
|
||||
env.Append(CXXFLAGS = ['-std=c++17'])
|
||||
env.Append(LINKFLAGS = ['-s'])
|
||||
|
||||
elif env['platform'] == "windows":
|
||||
target_path += 'win64/'
|
||||
cpp_bindings_library += '.windows'
|
||||
# This makes sure to keep the session environment variables on windows,
|
||||
# that way you can run scons in a vs 2017 prompt and it will find all the required tools
|
||||
#env.Append(ENV = os.environ)
|
||||
|
||||
env.Append(CPPDEFINES = ['WIN32', '_WIN32', '_WINDOWS', '_CRT_SECURE_NO_WARNINGS'])
|
||||
env.Append(CCFLAGS = ['-W3', '-GR'])
|
||||
if env['target'] == 'debug':
|
||||
env.Append(CPPDEFINES = ['_DEBUG'])
|
||||
env.Append(CCFLAGS = ['-EHsc', '-MDd', '-ZI'])
|
||||
env.Append(LINKFLAGS = ['-DEBUG'])
|
||||
else:
|
||||
env.Append(CPPDEFINES = ['NDEBUG'])
|
||||
env.Append(CCFLAGS = ['-O2', '-EHsc', '-MD'])
|
||||
|
||||
if env['target'] == 'debug':
|
||||
cpp_bindings_library += '.debug'
|
||||
else:
|
||||
cpp_bindings_library += '.release'
|
||||
|
||||
if env['macos_arch'] == 'universal':
|
||||
cpp_bindings_library += '.' + str(bits)
|
||||
else:
|
||||
cpp_bindings_library += '.' + env['macos_arch']
|
||||
|
||||
# make sure our binding library is properly included
|
||||
env.Append(CPPPATH = [
|
||||
'.',
|
||||
godot_headers_path,
|
||||
cpp_bindings_path + 'include/',
|
||||
cpp_bindings_path + 'include/core/',
|
||||
cpp_bindings_path + 'include/gen/'
|
||||
])
|
||||
env.Append(LIBPATH = [cpp_bindings_path + 'bin/'])
|
||||
env.Append(LIBS = [cpp_bindings_library])
|
||||
|
||||
# Add source files of our library
|
||||
env.Append(CPPPATH = ['src/'])
|
||||
sources = Glob('src/*.cpp')
|
||||
|
||||
library = env.SharedLibrary(target = target_path + TARGET_NAME , source = sources)
|
||||
|
||||
Default(library)
|
||||
|
||||
# Generates help for the -h scons option.
|
||||
Help(opts.GenerateHelpText(env))
|
||||
BIN
addons/zylann.hterrain/native/bin/linux/libhterrain_native.so
Executable file
BIN
addons/zylann.hterrain/native/bin/linux/libhterrain_native.so
Executable file
Binary file not shown.
BIN
addons/zylann.hterrain/native/bin/osx/libhterrain_native.dylib
Executable file
BIN
addons/zylann.hterrain/native/bin/osx/libhterrain_native.dylib
Executable file
Binary file not shown.
BIN
addons/zylann.hterrain/native/bin/win64/hterrain_native.dll
Executable file
BIN
addons/zylann.hterrain/native/bin/win64/hterrain_native.dll
Executable file
Binary file not shown.
55
addons/zylann.hterrain/native/factory.gd
Executable file
55
addons/zylann.hterrain/native/factory.gd
Executable file
@@ -0,0 +1,55 @@
|
||||
@tool
|
||||
|
||||
const NATIVE_PATH = "res://addons/zylann.hterrain/native/"
|
||||
|
||||
const HT_ImageUtilsGeneric = preload("./image_utils_generic.gd")
|
||||
const HT_QuadTreeLodGeneric = preload("./quad_tree_lod_generic.gd")
|
||||
|
||||
# No native code was ported when moving to Godot 4.
|
||||
# It may be changed too using GDExtension.
|
||||
|
||||
# See https://docs.godotengine.org/en/stable/classes/class_os.html#class-os-method-get-name
|
||||
const _supported_os = {
|
||||
# "Windows": true,
|
||||
# "X11": true,
|
||||
# "OSX": true
|
||||
}
|
||||
# See https://docs.godotengine.org/en/stable/tutorials/export/feature_tags.html
|
||||
const _supported_archs = ["x86_64"]
|
||||
|
||||
|
||||
static func _supports_current_arch() -> bool:
|
||||
for arch in _supported_archs:
|
||||
# This is misleading, we are querying features of the ENGINE, not the OS
|
||||
if OS.has_feature(arch):
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
static func is_native_available() -> bool:
|
||||
if not _supports_current_arch():
|
||||
return false
|
||||
var os = OS.get_name()
|
||||
if not _supported_os.has(os):
|
||||
return false
|
||||
# API changes can cause binary incompatibility
|
||||
var v = Engine.get_version_info()
|
||||
return v.major == 4 and v.minor == 0
|
||||
|
||||
|
||||
static func get_image_utils():
|
||||
if is_native_available():
|
||||
var HT_ImageUtilsNative = load(NATIVE_PATH + "image_utils.gdns")
|
||||
# TODO Godot doesn't always return `null` when it fails so that `if` doesn't always help...
|
||||
# See https://github.com/Zylann/godot_heightmap_plugin/issues/331
|
||||
if HT_ImageUtilsNative != null:
|
||||
return HT_ImageUtilsNative.new()
|
||||
return HT_ImageUtilsGeneric.new()
|
||||
|
||||
|
||||
static func get_quad_tree_lod():
|
||||
if is_native_available():
|
||||
var HT_QuadTreeLod = load(NATIVE_PATH + "quad_tree_lod.gdns")
|
||||
if HT_QuadTreeLod != null:
|
||||
return HT_QuadTreeLod.new()
|
||||
return HT_QuadTreeLodGeneric.new()
|
||||
1
addons/zylann.hterrain/native/factory.gd.uid
Normal file
1
addons/zylann.hterrain/native/factory.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cupyw3v6cqa4n
|
||||
316
addons/zylann.hterrain/native/image_utils_generic.gd
Executable file
316
addons/zylann.hterrain/native/image_utils_generic.gd
Executable file
@@ -0,0 +1,316 @@
|
||||
@tool
|
||||
|
||||
# These functions are the same as the ones found in the GDNative library.
|
||||
# They are used if the user's platform is not supported.
|
||||
|
||||
const HT_Util = preload("../util/util.gd")
|
||||
|
||||
var _blur_buffer : Image
|
||||
|
||||
|
||||
func get_red_range(im: Image, rect: Rect2) -> Vector2:
|
||||
rect = rect.intersection(Rect2(0, 0, im.get_width(), im.get_height()))
|
||||
var min_x := int(rect.position.x)
|
||||
var min_y := int(rect.position.y)
|
||||
var max_x := min_x + int(rect.size.x)
|
||||
var max_y := min_y + int(rect.size.y)
|
||||
|
||||
var min_height := im.get_pixel(min_x, min_y).r
|
||||
var max_height := min_height
|
||||
|
||||
for y in range(min_y, max_y):
|
||||
for x in range(min_x, max_x):
|
||||
var h = im.get_pixel(x, y).r
|
||||
if h < min_height:
|
||||
min_height = h
|
||||
elif h > max_height:
|
||||
max_height = h
|
||||
|
||||
return Vector2(min_height, max_height)
|
||||
|
||||
|
||||
func get_red_sum(im: Image, rect: Rect2) -> float:
|
||||
rect = rect.intersection(Rect2(0, 0, im.get_width(), im.get_height()))
|
||||
var min_x := int(rect.position.x)
|
||||
var min_y := int(rect.position.y)
|
||||
var max_x := min_x + int(rect.size.x)
|
||||
var max_y := min_y + int(rect.size.y)
|
||||
|
||||
var sum := 0.0
|
||||
|
||||
for y in range(min_y, max_y):
|
||||
for x in range(min_x, max_x):
|
||||
sum += im.get_pixel(x, y).r
|
||||
|
||||
return sum
|
||||
|
||||
|
||||
func get_red_sum_weighted(im: Image, brush: Image, pos: Vector2, factor: float) -> float:
|
||||
var min_x = int(pos.x)
|
||||
var min_y = int(pos.y)
|
||||
var max_x = min_x + brush.get_width()
|
||||
var max_y = min_y + brush.get_height()
|
||||
var min_noclamp_x = min_x
|
||||
var min_noclamp_y = min_y
|
||||
|
||||
min_x = clampi(min_x, 0, im.get_width())
|
||||
min_y = clampi(min_y, 0, im.get_height())
|
||||
max_x = clampi(max_x, 0, im.get_width())
|
||||
max_y = clampi(max_y, 0, im.get_height())
|
||||
|
||||
var sum = 0.0
|
||||
|
||||
for y in range(min_y, max_y):
|
||||
var by = y - min_noclamp_y
|
||||
|
||||
for x in range(min_x, max_x):
|
||||
var bx = x - min_noclamp_x
|
||||
|
||||
var shape_value = brush.get_pixel(bx, by).r
|
||||
sum += im.get_pixel(x, y).r * shape_value * factor
|
||||
|
||||
return sum
|
||||
|
||||
|
||||
func add_red_brush(im: Image, brush: Image, pos: Vector2, factor: float):
|
||||
var min_x = int(pos.x)
|
||||
var min_y = int(pos.y)
|
||||
var max_x = min_x + brush.get_width()
|
||||
var max_y = min_y + brush.get_height()
|
||||
var min_noclamp_x = min_x
|
||||
var min_noclamp_y = min_y
|
||||
|
||||
min_x = clampi(min_x, 0, im.get_width())
|
||||
min_y = clampi(min_y, 0, im.get_height())
|
||||
max_x = clampi(max_x, 0, im.get_width())
|
||||
max_y = clampi(max_y, 0, im.get_height())
|
||||
|
||||
for y in range(min_y, max_y):
|
||||
var by = y - min_noclamp_y
|
||||
|
||||
for x in range(min_x, max_x):
|
||||
var bx = x - min_noclamp_x
|
||||
|
||||
var shape_value = brush.get_pixel(bx, by).r
|
||||
var r = im.get_pixel(x, y).r + shape_value * factor
|
||||
im.set_pixel(x, y, Color(r, r, r))
|
||||
|
||||
|
||||
func lerp_channel_brush(im: Image, brush: Image, pos: Vector2,
|
||||
factor: float, target_value: float, channel: int):
|
||||
|
||||
var min_x = int(pos.x)
|
||||
var min_y = int(pos.y)
|
||||
var max_x = min_x + brush.get_width()
|
||||
var max_y = min_y + brush.get_height()
|
||||
var min_noclamp_x = min_x
|
||||
var min_noclamp_y = min_y
|
||||
|
||||
min_x = clampi(min_x, 0, im.get_width())
|
||||
min_y = clampi(min_y, 0, im.get_height())
|
||||
max_x = clampi(max_x, 0, im.get_width())
|
||||
max_y = clampi(max_y, 0, im.get_height())
|
||||
|
||||
for y in range(min_y, max_y):
|
||||
var by = y - min_noclamp_y
|
||||
|
||||
for x in range(min_x, max_x):
|
||||
var bx = x - min_noclamp_x
|
||||
|
||||
var shape_value = brush.get_pixel(bx, by).r
|
||||
var c = im.get_pixel(x, y)
|
||||
c[channel] = lerp(c[channel], target_value, shape_value * factor)
|
||||
im.set_pixel(x, y, c)
|
||||
|
||||
|
||||
func lerp_color_brush(im: Image, brush: Image, pos: Vector2,
|
||||
factor: float, target_value: Color):
|
||||
|
||||
var min_x = int(pos.x)
|
||||
var min_y = int(pos.y)
|
||||
var max_x = min_x + brush.get_width()
|
||||
var max_y = min_y + brush.get_height()
|
||||
var min_noclamp_x = min_x
|
||||
var min_noclamp_y = min_y
|
||||
|
||||
min_x = clampi(min_x, 0, im.get_width())
|
||||
min_y = clampi(min_y, 0, im.get_height())
|
||||
max_x = clampi(max_x, 0, im.get_width())
|
||||
max_y = clampi(max_y, 0, im.get_height())
|
||||
|
||||
for y in range(min_y, max_y):
|
||||
var by = y - min_noclamp_y
|
||||
|
||||
for x in range(min_x, max_x):
|
||||
var bx = x - min_noclamp_x
|
||||
|
||||
var shape_value = brush.get_pixel(bx, by).r
|
||||
var c = im.get_pixel(x, y).lerp(target_value, factor * shape_value)
|
||||
im.set_pixel(x, y, c)
|
||||
|
||||
|
||||
func generate_gaussian_brush(im: Image) -> float:
|
||||
var sum := 0.0
|
||||
var center := Vector2(im.get_width() / 2, im.get_height() / 2)
|
||||
var radius := minf(im.get_width(), im.get_height()) / 2.0
|
||||
|
||||
for y in im.get_height():
|
||||
for x in im.get_width():
|
||||
var d := Vector2(x, y).distance_to(center) / radius
|
||||
var v := clampf(1.0 - d * d * d, 0.0, 1.0)
|
||||
im.set_pixel(x, y, Color(v, v, v))
|
||||
sum += v;
|
||||
|
||||
return sum
|
||||
|
||||
|
||||
func blur_red_brush(im: Image, brush: Image, pos: Vector2, factor: float):
|
||||
factor = clampf(factor, 0.0, 1.0)
|
||||
|
||||
if _blur_buffer == null:
|
||||
_blur_buffer = Image.new()
|
||||
var buffer := _blur_buffer
|
||||
|
||||
var buffer_width := brush.get_width() + 2
|
||||
var buffer_height := brush.get_height() + 2
|
||||
|
||||
if buffer_width != buffer.get_width() or buffer_height != buffer.get_height():
|
||||
buffer.create(buffer_width, buffer_height, false, Image.FORMAT_RF)
|
||||
|
||||
var min_x := int(pos.x) - 1
|
||||
var min_y := int(pos.y) - 1
|
||||
var max_x := min_x + buffer.get_width()
|
||||
var max_y := min_y + buffer.get_height()
|
||||
|
||||
var im_clamp_w = im.get_width() - 1
|
||||
var im_clamp_h = im.get_height() - 1
|
||||
|
||||
# Copy pixels to temporary buffer
|
||||
for y in range(min_y, max_y):
|
||||
for x in range(min_x, max_x):
|
||||
var ix := clampi(x, 0, im_clamp_w)
|
||||
var iy := clampi(y, 0, im_clamp_h)
|
||||
var c = im.get_pixel(ix, iy)
|
||||
buffer.set_pixel(x - min_x, y - min_y, c)
|
||||
|
||||
min_x = int(pos.x)
|
||||
min_y = int(pos.y)
|
||||
max_x = min_x + brush.get_width()
|
||||
max_y = min_y + brush.get_height()
|
||||
var min_noclamp_x := min_x
|
||||
var min_noclamp_y := min_y
|
||||
|
||||
min_x = clampi(min_x, 0, im.get_width())
|
||||
min_y = clampi(min_y, 0, im.get_height())
|
||||
max_x = clampi(max_x, 0, im.get_width())
|
||||
max_y = clampi(max_y, 0, im.get_height())
|
||||
|
||||
# Apply blur
|
||||
for y in range(min_y, max_y):
|
||||
var by := y - min_noclamp_y
|
||||
|
||||
for x in range(min_x, max_x):
|
||||
var bx := x - min_noclamp_x
|
||||
|
||||
var shape_value := brush.get_pixel(bx, by).r * factor
|
||||
|
||||
var p10 = buffer.get_pixel(bx + 1, by ).r
|
||||
var p01 = buffer.get_pixel(bx, by + 1).r
|
||||
var p11 = buffer.get_pixel(bx + 1, by + 1).r
|
||||
var p21 = buffer.get_pixel(bx + 2, by + 1).r
|
||||
var p12 = buffer.get_pixel(bx + 1, by + 2).r
|
||||
|
||||
var m = (p10 + p01 + p11 + p21 + p12) * 0.2
|
||||
var p = lerpf(p11, m, shape_value * factor)
|
||||
|
||||
im.set_pixel(x, y, Color(p, p, p))
|
||||
|
||||
|
||||
func paint_indexed_splat(index_map: Image, weight_map: Image, brush: Image, pos: Vector2, \
|
||||
texture_index: int, factor: float):
|
||||
|
||||
var min_x := pos.x
|
||||
var min_y := pos.y
|
||||
var max_x := min_x + brush.get_width()
|
||||
var max_y := min_y + brush.get_height()
|
||||
var min_noclamp_x := min_x
|
||||
var min_noclamp_y := min_y
|
||||
|
||||
min_x = clampi(min_x, 0, index_map.get_width())
|
||||
min_y = clampi(min_y, 0, index_map.get_height())
|
||||
max_x = clampi(max_x, 0, index_map.get_width())
|
||||
max_y = clampi(max_y, 0, index_map.get_height())
|
||||
|
||||
var texture_index_f := float(texture_index) / 255.0
|
||||
var all_texture_index_f := Color(texture_index_f, texture_index_f, texture_index_f)
|
||||
var ci := texture_index % 3
|
||||
var cm := Color(-1, -1, -1)
|
||||
cm[ci] = 1
|
||||
|
||||
for y in range(min_y, max_y):
|
||||
var by := y - min_noclamp_y
|
||||
|
||||
for x in range(min_x, max_x):
|
||||
var bx := x - min_noclamp_x
|
||||
|
||||
var shape_value := brush.get_pixel(bx, by).r * factor
|
||||
if shape_value == 0.0:
|
||||
continue
|
||||
|
||||
var i := index_map.get_pixel(x, y)
|
||||
var w := weight_map.get_pixel(x, y)
|
||||
|
||||
# Decompress third weight to make computations easier
|
||||
w[2] = 1.0 - w[0] - w[1]
|
||||
|
||||
# The index map tells which textures to blend.
|
||||
# The weight map tells their blending amounts.
|
||||
# This brings the limitation that up to 3 textures can blend at a time in a given pixel.
|
||||
# Painting this in real time can be a challenge.
|
||||
|
||||
# The approach here is a compromise for simplicity.
|
||||
# Each texture is associated a fixed component of the index map (R, G or B),
|
||||
# so two neighbor pixels having the same component won't be guaranteed to blend.
|
||||
# In other words, texture T will not be able to blend with T + N * k,
|
||||
# where k is an integer, and N is the number of components in the index map (up to 4).
|
||||
# It might still be able to blend due to a special case when an area is uniform,
|
||||
# but not otherwise.
|
||||
|
||||
# Dynamic component assignment sounds like the alternative, however I wasn't able
|
||||
# to find a painting algorithm that wasn't confusing, at least the current one is
|
||||
# predictable.
|
||||
|
||||
# Need to use approximation because Color is float but GDScript uses doubles...
|
||||
if abs(i[ci] - texture_index_f) > 0.001:
|
||||
# Pixel does not have our texture index,
|
||||
# transfer its weight to other components first
|
||||
if w[ci] > shape_value:
|
||||
w -= cm * shape_value
|
||||
|
||||
elif w[ci] >= 0.0:
|
||||
w[ci] = 0.0
|
||||
i[ci] = texture_index_f
|
||||
|
||||
else:
|
||||
# Pixel has our texture index, increase its weight
|
||||
if w[ci] + shape_value < 1.0:
|
||||
w += cm * shape_value
|
||||
|
||||
else:
|
||||
# Pixel weight is full, we can set all components to the same index.
|
||||
# Need to nullify other weights because they would otherwise never reach
|
||||
# zero due to normalization
|
||||
w = Color(0, 0, 0)
|
||||
w[ci] = 1.0
|
||||
i = all_texture_index_f
|
||||
|
||||
# No `saturate` function in Color??
|
||||
w[0] = clampf(w[0], 0.0, 1.0)
|
||||
w[1] = clampf(w[1], 0.0, 1.0)
|
||||
w[2] = clampf(w[2], 0.0, 1.0)
|
||||
|
||||
# Renormalize
|
||||
w /= w[0] + w[1] + w[2]
|
||||
|
||||
index_map.set_pixel(x, y, i)
|
||||
weight_map.set_pixel(x, y, w)
|
||||
1
addons/zylann.hterrain/native/image_utils_generic.gd.uid
Normal file
1
addons/zylann.hterrain/native/image_utils_generic.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://2f87sp1jmfe1
|
||||
188
addons/zylann.hterrain/native/quad_tree_lod_generic.gd
Executable file
188
addons/zylann.hterrain/native/quad_tree_lod_generic.gd
Executable file
@@ -0,0 +1,188 @@
|
||||
@tool
|
||||
# Independent quad tree designed to handle LOD
|
||||
|
||||
class HT_QTLQuad:
|
||||
# Optional array of 4 HT_QTLQuad
|
||||
var children = null
|
||||
|
||||
# TODO Use Vector2i
|
||||
var origin_x : int = 0
|
||||
var origin_y : int = 0
|
||||
|
||||
var data = null
|
||||
|
||||
func _init():
|
||||
pass
|
||||
|
||||
func clear():
|
||||
clear_children()
|
||||
data = null
|
||||
|
||||
func clear_children():
|
||||
children = null
|
||||
|
||||
func has_children() -> bool:
|
||||
return children != null
|
||||
|
||||
|
||||
var _tree := HT_QTLQuad.new()
|
||||
var _max_depth : int = 0
|
||||
var _base_size : int = 16
|
||||
var _split_scale : float = 2.0
|
||||
|
||||
var _make_func : Callable
|
||||
var _recycle_func : Callable
|
||||
var _vertical_bounds_func : Callable
|
||||
|
||||
|
||||
func set_callbacks(make_cb: Callable, recycle_cb: Callable, vbounds_cb: Callable):
|
||||
_make_func = make_cb
|
||||
_recycle_func = recycle_cb
|
||||
_vertical_bounds_func = vbounds_cb
|
||||
|
||||
|
||||
func clear():
|
||||
_join_all_recursively(_tree, _max_depth)
|
||||
_max_depth = 0
|
||||
_base_size = 0
|
||||
|
||||
|
||||
static func compute_lod_count(base_size: int, full_size: int) -> int:
|
||||
var po : int = 0
|
||||
while full_size > base_size:
|
||||
full_size = full_size >> 1
|
||||
po += 1
|
||||
return po
|
||||
|
||||
|
||||
func create_from_sizes(base_size: int, full_size: int):
|
||||
clear()
|
||||
_base_size = base_size
|
||||
_max_depth = compute_lod_count(base_size, full_size)
|
||||
|
||||
|
||||
func get_lod_count() -> int:
|
||||
# TODO _max_depth is a maximum, not a count. Would be better for it to be a count (+1)
|
||||
return _max_depth + 1
|
||||
|
||||
|
||||
# The higher, the longer LODs will spread and higher the quality.
|
||||
# The lower, the shorter LODs will spread and lower the quality.
|
||||
func set_split_scale(p_split_scale: float):
|
||||
var MIN := 2.0
|
||||
var MAX := 5.0
|
||||
|
||||
# Split scale must be greater than a threshold,
|
||||
# otherwise lods will decimate too fast and it will look messy
|
||||
_split_scale = clampf(p_split_scale, MIN, MAX)
|
||||
|
||||
|
||||
func get_split_scale() -> float:
|
||||
return _split_scale
|
||||
|
||||
|
||||
func update(view_pos: Vector3):
|
||||
_update(_tree, _max_depth, view_pos)
|
||||
|
||||
# This makes sure we keep seeing the lowest LOD,
|
||||
# if the tree is cleared while we are far away
|
||||
if not _tree.has_children() and _tree.data == null:
|
||||
_tree.data = _make_chunk(_max_depth, 0, 0)
|
||||
|
||||
|
||||
func get_lod_factor(lod: int) -> int:
|
||||
return 1 << lod
|
||||
|
||||
|
||||
func _update(quad: HT_QTLQuad, lod: int, view_pos: Vector3):
|
||||
# This function should be called regularly over frames.
|
||||
|
||||
var lod_factor : int = get_lod_factor(lod)
|
||||
var chunk_size : int = _base_size * lod_factor
|
||||
var world_center := \
|
||||
chunk_size * (Vector3(quad.origin_x, 0, quad.origin_y) + Vector3(0.5, 0, 0.5))
|
||||
|
||||
if _vertical_bounds_func.is_valid():
|
||||
var vbounds : Vector2 = _vertical_bounds_func.call(quad.origin_x, quad.origin_y, lod)
|
||||
world_center.y = (vbounds.x + vbounds.y) / 2.0
|
||||
|
||||
var split_distance := _base_size * lod_factor * _split_scale
|
||||
|
||||
if not quad.has_children():
|
||||
if lod > 0 and world_center.distance_to(view_pos) < split_distance:
|
||||
# Split
|
||||
quad.children = [null, null, null, null]
|
||||
|
||||
for i in 4:
|
||||
var child := HT_QTLQuad.new()
|
||||
child.origin_x = quad.origin_x * 2 + (i & 1)
|
||||
child.origin_y = quad.origin_y * 2 + ((i & 2) >> 1)
|
||||
quad.children[i] = child
|
||||
child.data = _make_chunk(lod - 1, child.origin_x, child.origin_y)
|
||||
# If the quad needs to split more, we'll ask more recycling...
|
||||
|
||||
if quad.data != null:
|
||||
_recycle_chunk(quad.data, quad.origin_x, quad.origin_y, lod)
|
||||
quad.data = null
|
||||
|
||||
else:
|
||||
var no_split_child := true
|
||||
|
||||
for child in quad.children:
|
||||
_update(child, lod - 1, view_pos)
|
||||
if child.has_children():
|
||||
no_split_child = false
|
||||
|
||||
if no_split_child and world_center.distance_to(view_pos) > split_distance:
|
||||
# Join
|
||||
for i in 4:
|
||||
var child : HT_QTLQuad = quad.children[i]
|
||||
_recycle_chunk(child.data, child.origin_x, child.origin_y, lod - 1)
|
||||
quad.clear_children()
|
||||
quad.data = _make_chunk(lod, quad.origin_x, quad.origin_y)
|
||||
|
||||
|
||||
func _join_all_recursively(quad: HT_QTLQuad, lod: int):
|
||||
if quad.has_children():
|
||||
for i in 4:
|
||||
_join_all_recursively(quad.children[i], lod - 1)
|
||||
|
||||
quad.clear_children()
|
||||
|
||||
elif quad.data != null:
|
||||
_recycle_chunk(quad.data, quad.origin_x, quad.origin_y, lod)
|
||||
quad.data = null
|
||||
|
||||
|
||||
func _make_chunk(lod: int, origin_x: int, origin_y: int):
|
||||
var chunk = null
|
||||
if _make_func.is_valid():
|
||||
chunk = _make_func.call(origin_x, origin_y, lod)
|
||||
return chunk
|
||||
|
||||
|
||||
func _recycle_chunk(chunk, origin_x: int, origin_y: int, lod: int):
|
||||
if _recycle_func.is_valid():
|
||||
_recycle_func.call(chunk, origin_x, origin_y, lod)
|
||||
|
||||
|
||||
func debug_draw_tree(ci: CanvasItem):
|
||||
var quad := _tree
|
||||
_debug_draw_tree_recursive(ci, quad, _max_depth, 0)
|
||||
|
||||
|
||||
func _debug_draw_tree_recursive(ci: CanvasItem, quad: HT_QTLQuad, lod_index: int, child_index: int):
|
||||
if quad.has_children():
|
||||
for i in 4:
|
||||
_debug_draw_tree_recursive(ci, quad.children[i], lod_index - 1, i)
|
||||
else:
|
||||
var size : int = get_lod_factor(lod_index)
|
||||
var checker : int = 0
|
||||
if child_index == 1 or child_index == 2:
|
||||
checker = 1
|
||||
var chunk_indicator : int = 0
|
||||
if quad.data != null:
|
||||
chunk_indicator = 1
|
||||
var r := Rect2(Vector2(quad.origin_x, quad.origin_y) * size, Vector2(size, size))
|
||||
ci.draw_rect(r, Color(1.0 - lod_index * 0.2, 0.2 * checker, chunk_indicator, 1))
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://ci5njrvvrqucr
|
||||
0
addons/zylann.hterrain/native/src/.gdignore
Executable file
0
addons/zylann.hterrain/native/src/.gdignore
Executable file
30
addons/zylann.hterrain/native/src/gd_library.cpp
Executable file
30
addons/zylann.hterrain/native/src/gd_library.cpp
Executable file
@@ -0,0 +1,30 @@
|
||||
#include "image_utils.h"
|
||||
#include "quad_tree_lod.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
void GDN_EXPORT godot_gdnative_init(godot_gdnative_init_options *o) {
|
||||
#ifdef _DEBUG
|
||||
printf("godot_gdnative_init hterrain_native\n");
|
||||
#endif
|
||||
godot::Godot::gdnative_init(o);
|
||||
}
|
||||
|
||||
void GDN_EXPORT godot_gdnative_terminate(godot_gdnative_terminate_options *o) {
|
||||
#ifdef _DEBUG
|
||||
printf("godot_gdnative_terminate hterrain_native\n");
|
||||
#endif
|
||||
godot::Godot::gdnative_terminate(o);
|
||||
}
|
||||
|
||||
void GDN_EXPORT godot_nativescript_init(void *handle) {
|
||||
#ifdef _DEBUG
|
||||
printf("godot_nativescript_init hterrain_native\n");
|
||||
#endif
|
||||
godot::Godot::nativescript_init(handle);
|
||||
|
||||
godot::register_tool_class<godot::ImageUtils>();
|
||||
godot::register_tool_class<godot::QuadTreeLod>();
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
364
addons/zylann.hterrain/native/src/image_utils.cpp
Executable file
364
addons/zylann.hterrain/native/src/image_utils.cpp
Executable file
@@ -0,0 +1,364 @@
|
||||
#include "image_utils.h"
|
||||
#include "int_range_2d.h"
|
||||
#include "math_funcs.h"
|
||||
|
||||
namespace godot {
|
||||
|
||||
template <typename F>
|
||||
inline void generic_brush_op(Image &image, Image &brush, Vector2 p_pos, float factor, F op) {
|
||||
IntRange2D range = IntRange2D::from_min_max(p_pos, brush.get_size());
|
||||
int min_x_noclamp = range.min_x;
|
||||
int min_y_noclamp = range.min_y;
|
||||
range.clip(Vector2i(image.get_size()));
|
||||
|
||||
image.lock();
|
||||
brush.lock();
|
||||
|
||||
for (int y = range.min_y; y < range.max_y; ++y) {
|
||||
int by = y - min_y_noclamp;
|
||||
|
||||
for (int x = range.min_x; x < range.max_x; ++x) {
|
||||
int bx = x - min_x_noclamp;
|
||||
|
||||
float b = brush.get_pixel(bx, by).r * factor;
|
||||
op(image, x, y, b);
|
||||
}
|
||||
}
|
||||
|
||||
image.unlock();
|
||||
brush.unlock();
|
||||
}
|
||||
|
||||
ImageUtils::ImageUtils() {
|
||||
#ifdef _DEBUG
|
||||
Godot::print("Constructing ImageUtils");
|
||||
#endif
|
||||
}
|
||||
|
||||
ImageUtils::~ImageUtils() {
|
||||
#ifdef _DEBUG
|
||||
// TODO Cannot print shit here, see https://github.com/godotengine/godot/issues/37417
|
||||
// Means only the console will print this
|
||||
//Godot::print("Destructing ImageUtils");
|
||||
printf("Destructing ImageUtils\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
void ImageUtils::_init() {
|
||||
}
|
||||
|
||||
Vector2 ImageUtils::get_red_range(Ref<Image> image_ref, Rect2 rect) const {
|
||||
ERR_FAIL_COND_V(image_ref.is_null(), Vector2());
|
||||
Image &image = **image_ref;
|
||||
|
||||
IntRange2D range(rect);
|
||||
range.clip(Vector2i(image.get_size()));
|
||||
|
||||
image.lock();
|
||||
|
||||
float min_value = image.get_pixel(range.min_x, range.min_y).r;
|
||||
float max_value = min_value;
|
||||
|
||||
for (int y = range.min_y; y < range.max_y; ++y) {
|
||||
for (int x = range.min_x; x < range.max_x; ++x) {
|
||||
float v = image.get_pixel(x, y).r;
|
||||
|
||||
if (v > max_value) {
|
||||
max_value = v;
|
||||
} else if (v < min_value) {
|
||||
min_value = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
image.unlock();
|
||||
|
||||
return Vector2(min_value, max_value);
|
||||
}
|
||||
|
||||
float ImageUtils::get_red_sum(Ref<Image> image_ref, Rect2 rect) const {
|
||||
ERR_FAIL_COND_V(image_ref.is_null(), 0.f);
|
||||
Image &image = **image_ref;
|
||||
|
||||
IntRange2D range(rect);
|
||||
range.clip(Vector2i(image.get_size()));
|
||||
|
||||
image.lock();
|
||||
|
||||
float sum = 0.f;
|
||||
|
||||
for (int y = range.min_y; y < range.max_y; ++y) {
|
||||
for (int x = range.min_x; x < range.max_x; ++x) {
|
||||
sum += image.get_pixel(x, y).r;
|
||||
}
|
||||
}
|
||||
|
||||
image.unlock();
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
float ImageUtils::get_red_sum_weighted(Ref<Image> image_ref, Ref<Image> brush_ref, Vector2 p_pos, float factor) const {
|
||||
ERR_FAIL_COND_V(image_ref.is_null(), 0.f);
|
||||
ERR_FAIL_COND_V(brush_ref.is_null(), 0.f);
|
||||
Image &image = **image_ref;
|
||||
Image &brush = **brush_ref;
|
||||
|
||||
float sum = 0.f;
|
||||
generic_brush_op(image, brush, p_pos, factor, [&sum](Image &image, int x, int y, float b) {
|
||||
sum += image.get_pixel(x, y).r * b;
|
||||
});
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
void ImageUtils::add_red_brush(Ref<Image> image_ref, Ref<Image> brush_ref, Vector2 p_pos, float factor) const {
|
||||
ERR_FAIL_COND(image_ref.is_null());
|
||||
ERR_FAIL_COND(brush_ref.is_null());
|
||||
Image &image = **image_ref;
|
||||
Image &brush = **brush_ref;
|
||||
|
||||
generic_brush_op(image, brush, p_pos, factor, [](Image &image, int x, int y, float b) {
|
||||
float r = image.get_pixel(x, y).r + b;
|
||||
image.set_pixel(x, y, Color(r, r, r));
|
||||
});
|
||||
}
|
||||
|
||||
void ImageUtils::lerp_channel_brush(Ref<Image> image_ref, Ref<Image> brush_ref, Vector2 p_pos, float factor, float target_value, int channel) const {
|
||||
ERR_FAIL_COND(image_ref.is_null());
|
||||
ERR_FAIL_COND(brush_ref.is_null());
|
||||
Image &image = **image_ref;
|
||||
Image &brush = **brush_ref;
|
||||
|
||||
generic_brush_op(image, brush, p_pos, factor, [target_value, channel](Image &image, int x, int y, float b) {
|
||||
Color c = image.get_pixel(x, y);
|
||||
c[channel] = Math::lerp(c[channel], target_value, b);
|
||||
image.set_pixel(x, y, c);
|
||||
});
|
||||
}
|
||||
|
||||
void ImageUtils::lerp_color_brush(Ref<Image> image_ref, Ref<Image> brush_ref, Vector2 p_pos, float factor, Color target_value) const {
|
||||
ERR_FAIL_COND(image_ref.is_null());
|
||||
ERR_FAIL_COND(brush_ref.is_null());
|
||||
Image &image = **image_ref;
|
||||
Image &brush = **brush_ref;
|
||||
|
||||
generic_brush_op(image, brush, p_pos, factor, [target_value](Image &image, int x, int y, float b) {
|
||||
const Color c = image.get_pixel(x, y).linear_interpolate(target_value, b);
|
||||
image.set_pixel(x, y, c);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO Smooth (each pixel being box-filtered, contrary to the existing smooth)
|
||||
|
||||
float ImageUtils::generate_gaussian_brush(Ref<Image> image_ref) const {
|
||||
ERR_FAIL_COND_V(image_ref.is_null(), 0.f);
|
||||
Image &image = **image_ref;
|
||||
|
||||
int w = static_cast<int>(image.get_width());
|
||||
int h = static_cast<int>(image.get_height());
|
||||
Vector2 center(w / 2, h / 2);
|
||||
float radius = Math::min(w, h) / 2;
|
||||
|
||||
ERR_FAIL_COND_V(radius <= 0.1f, 0.f);
|
||||
|
||||
float sum = 0.f;
|
||||
image.lock();
|
||||
|
||||
for (int y = 0; y < h; ++y) {
|
||||
for (int x = 0; x < w; ++x) {
|
||||
float d = Vector2(x, y).distance_to(center) / radius;
|
||||
float v = Math::clamp(1.f - d * d * d, 0.f, 1.f);
|
||||
image.set_pixel(x, y, Color(v, v, v));
|
||||
sum += v;
|
||||
}
|
||||
}
|
||||
|
||||
image.unlock();
|
||||
return sum;
|
||||
}
|
||||
|
||||
void ImageUtils::blur_red_brush(Ref<Image> image_ref, Ref<Image> brush_ref, Vector2 p_pos, float factor) {
|
||||
ERR_FAIL_COND(image_ref.is_null());
|
||||
ERR_FAIL_COND(brush_ref.is_null());
|
||||
Image &image = **image_ref;
|
||||
Image &brush = **brush_ref;
|
||||
|
||||
factor = Math::clamp(factor, 0.f, 1.f);
|
||||
|
||||
// Relative to the image
|
||||
IntRange2D buffer_range = IntRange2D::from_pos_size(p_pos, brush.get_size());
|
||||
buffer_range.pad(1);
|
||||
|
||||
const int image_width = static_cast<int>(image.get_width());
|
||||
const int image_height = static_cast<int>(image.get_height());
|
||||
|
||||
const int buffer_width = static_cast<int>(buffer_range.get_width());
|
||||
const int buffer_height = static_cast<int>(buffer_range.get_height());
|
||||
_blur_buffer.resize(buffer_width * buffer_height);
|
||||
|
||||
image.lock();
|
||||
|
||||
// Cache pixels, because they will be queried more than once and written to later
|
||||
int buffer_i = 0;
|
||||
for (int y = buffer_range.min_y; y < buffer_range.max_y; ++y) {
|
||||
for (int x = buffer_range.min_x; x < buffer_range.max_x; ++x) {
|
||||
const int ix = Math::clamp(x, 0, image_width - 1);
|
||||
const int iy = Math::clamp(y, 0, image_height - 1);
|
||||
_blur_buffer[buffer_i] = image.get_pixel(ix, iy).r;
|
||||
++buffer_i;
|
||||
}
|
||||
}
|
||||
|
||||
IntRange2D range = IntRange2D::from_min_max(p_pos, brush.get_size());
|
||||
const int min_x_noclamp = range.min_x;
|
||||
const int min_y_noclamp = range.min_y;
|
||||
range.clip(Vector2i(image.get_size()));
|
||||
|
||||
const int buffer_offset_left = -1;
|
||||
const int buffer_offset_right = 1;
|
||||
const int buffer_offset_top = -buffer_width;
|
||||
const int buffer_offset_bottom = buffer_width;
|
||||
|
||||
brush.lock();
|
||||
|
||||
// Apply blur
|
||||
for (int y = range.min_y; y < range.max_y; ++y) {
|
||||
const int brush_y = y - min_y_noclamp;
|
||||
|
||||
for (int x = range.min_x; x < range.max_x; ++x) {
|
||||
const int brush_x = x - min_x_noclamp;
|
||||
|
||||
const float brush_value = brush.get_pixel(brush_x, brush_y).r * factor;
|
||||
|
||||
buffer_i = (brush_x + 1) + (brush_y + 1) * buffer_width;
|
||||
|
||||
const float p10 = _blur_buffer[buffer_i + buffer_offset_top];
|
||||
const float p01 = _blur_buffer[buffer_i + buffer_offset_left];
|
||||
const float p11 = _blur_buffer[buffer_i];
|
||||
const float p21 = _blur_buffer[buffer_i + buffer_offset_right];
|
||||
const float p12 = _blur_buffer[buffer_i + buffer_offset_bottom];
|
||||
|
||||
// Average
|
||||
float m = (p10 + p01 + p11 + p21 + p12) * 0.2f;
|
||||
float p = Math::lerp(p11, m, brush_value);
|
||||
|
||||
image.set_pixel(x, y, Color(p, p, p));
|
||||
}
|
||||
}
|
||||
|
||||
image.unlock();
|
||||
brush.unlock();
|
||||
}
|
||||
|
||||
void ImageUtils::paint_indexed_splat(Ref<Image> index_map_ref, Ref<Image> weight_map_ref,
|
||||
Ref<Image> brush_ref, Vector2 p_pos, int texture_index, float factor) {
|
||||
|
||||
ERR_FAIL_COND(index_map_ref.is_null());
|
||||
ERR_FAIL_COND(weight_map_ref.is_null());
|
||||
ERR_FAIL_COND(brush_ref.is_null());
|
||||
Image &index_map = **index_map_ref;
|
||||
Image &weight_map = **weight_map_ref;
|
||||
Image &brush = **brush_ref;
|
||||
|
||||
ERR_FAIL_COND(index_map.get_size() != weight_map.get_size());
|
||||
|
||||
factor = Math::clamp(factor, 0.f, 1.f);
|
||||
|
||||
IntRange2D range = IntRange2D::from_min_max(p_pos, brush.get_size());
|
||||
const int min_x_noclamp = range.min_x;
|
||||
const int min_y_noclamp = range.min_y;
|
||||
range.clip(Vector2i(index_map.get_size()));
|
||||
|
||||
const float texture_index_f = float(texture_index) / 255.f;
|
||||
const Color all_texture_index_f(texture_index_f, texture_index_f, texture_index_f);
|
||||
const int ci = texture_index % 3;
|
||||
Color cm(-1, -1, -1);
|
||||
cm[ci] = 1;
|
||||
|
||||
brush.lock();
|
||||
index_map.lock();
|
||||
weight_map.lock();
|
||||
|
||||
for (int y = range.min_y; y < range.max_y; ++y) {
|
||||
const int brush_y = y - min_y_noclamp;
|
||||
|
||||
for (int x = range.min_x; x < range.max_x; ++x) {
|
||||
const int brush_x = x - min_x_noclamp;
|
||||
|
||||
const float brush_value = brush.get_pixel(brush_x, brush_y).r * factor;
|
||||
|
||||
if (brush_value == 0.f) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Color i = index_map.get_pixel(x, y);
|
||||
Color w = weight_map.get_pixel(x, y);
|
||||
|
||||
// Decompress third weight to make computations easier
|
||||
w[2] = 1.f - w[0] - w[1];
|
||||
|
||||
if (std::abs(i[ci] - texture_index_f) > 0.001f) {
|
||||
// Pixel does not have our texture index,
|
||||
// transfer its weight to other components first
|
||||
if (w[ci] > brush_value) {
|
||||
w[0] -= cm[0] * brush_value;
|
||||
w[1] -= cm[1] * brush_value;
|
||||
w[2] -= cm[2] * brush_value;
|
||||
|
||||
} else if (w[ci] >= 0.f) {
|
||||
w[ci] = 0.f;
|
||||
i[ci] = texture_index_f;
|
||||
}
|
||||
|
||||
} else {
|
||||
// Pixel has our texture index, increase its weight
|
||||
if (w[ci] + brush_value < 1.f) {
|
||||
w[0] += cm[0] * brush_value;
|
||||
w[1] += cm[1] * brush_value;
|
||||
w[2] += cm[2] * brush_value;
|
||||
|
||||
} else {
|
||||
// Pixel weight is full, we can set all components to the same index.
|
||||
// Need to nullify other weights because they would otherwise never reach
|
||||
// zero due to normalization
|
||||
w = Color(0, 0, 0);
|
||||
w[ci] = 1.0;
|
||||
i = all_texture_index_f;
|
||||
}
|
||||
}
|
||||
|
||||
// No `saturate` function in Color??
|
||||
w[0] = Math::clamp(w[0], 0.f, 1.f);
|
||||
w[1] = Math::clamp(w[1], 0.f, 1.f);
|
||||
w[2] = Math::clamp(w[2], 0.f, 1.f);
|
||||
|
||||
// Renormalize
|
||||
const float sum = w[0] + w[1] + w[2];
|
||||
w[0] /= sum;
|
||||
w[1] /= sum;
|
||||
w[2] /= sum;
|
||||
|
||||
index_map.set_pixel(x, y, i);
|
||||
weight_map.set_pixel(x, y, w);
|
||||
}
|
||||
}
|
||||
|
||||
brush.lock();
|
||||
index_map.unlock();
|
||||
weight_map.unlock();
|
||||
}
|
||||
|
||||
void ImageUtils::_register_methods() {
|
||||
register_method("get_red_range", &ImageUtils::get_red_range);
|
||||
register_method("get_red_sum", &ImageUtils::get_red_sum);
|
||||
register_method("get_red_sum_weighted", &ImageUtils::get_red_sum_weighted);
|
||||
register_method("add_red_brush", &ImageUtils::add_red_brush);
|
||||
register_method("lerp_channel_brush", &ImageUtils::lerp_channel_brush);
|
||||
register_method("lerp_color_brush", &ImageUtils::lerp_color_brush);
|
||||
register_method("generate_gaussian_brush", &ImageUtils::generate_gaussian_brush);
|
||||
register_method("blur_red_brush", &ImageUtils::blur_red_brush);
|
||||
register_method("paint_indexed_splat", &ImageUtils::paint_indexed_splat);
|
||||
}
|
||||
|
||||
} // namespace godot
|
||||
38
addons/zylann.hterrain/native/src/image_utils.h
Executable file
38
addons/zylann.hterrain/native/src/image_utils.h
Executable file
@@ -0,0 +1,38 @@
|
||||
#ifndef IMAGE_UTILS_H
|
||||
#define IMAGE_UTILS_H
|
||||
|
||||
#include <core/Godot.hpp>
|
||||
#include <gen/Image.hpp>
|
||||
#include <gen/Reference.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace godot {
|
||||
|
||||
class ImageUtils : public Reference {
|
||||
GODOT_CLASS(ImageUtils, Reference)
|
||||
public:
|
||||
static void _register_methods();
|
||||
|
||||
ImageUtils();
|
||||
~ImageUtils();
|
||||
|
||||
void _init();
|
||||
|
||||
Vector2 get_red_range(Ref<Image> image_ref, Rect2 rect) const;
|
||||
float get_red_sum(Ref<Image> image_ref, Rect2 rect) const;
|
||||
float get_red_sum_weighted(Ref<Image> image_ref, Ref<Image> brush_ref, Vector2 p_pos, float factor) const;
|
||||
void add_red_brush(Ref<Image> image_ref, Ref<Image> brush_ref, Vector2 p_pos, float factor) const;
|
||||
void lerp_channel_brush(Ref<Image> image_ref, Ref<Image> brush_ref, Vector2 p_pos, float factor, float target_value, int channel) const;
|
||||
void lerp_color_brush(Ref<Image> image_ref, Ref<Image> brush_ref, Vector2 p_pos, float factor, Color target_value) const;
|
||||
float generate_gaussian_brush(Ref<Image> image_ref) const;
|
||||
void blur_red_brush(Ref<Image> image_ref, Ref<Image> brush_ref, Vector2 p_pos, float factor);
|
||||
void paint_indexed_splat(Ref<Image> index_map_ref, Ref<Image> weight_map_ref, Ref<Image> brush_ref, Vector2 p_pos, int texture_index, float factor);
|
||||
//void erode_red_brush(Ref<Image> image_ref, Ref<Image> brush_ref, Vector2 p_pos, float factor);
|
||||
|
||||
private:
|
||||
std::vector<float> _blur_buffer;
|
||||
};
|
||||
|
||||
} // namespace godot
|
||||
|
||||
#endif // IMAGE_UTILS_H
|
||||
59
addons/zylann.hterrain/native/src/int_range_2d.h
Executable file
59
addons/zylann.hterrain/native/src/int_range_2d.h
Executable file
@@ -0,0 +1,59 @@
|
||||
#ifndef INT_RANGE_2D_H
|
||||
#define INT_RANGE_2D_H
|
||||
|
||||
#include "math_funcs.h"
|
||||
#include "vector2i.h"
|
||||
#include <core/Rect2.hpp>
|
||||
|
||||
struct IntRange2D {
|
||||
int min_x;
|
||||
int min_y;
|
||||
int max_x;
|
||||
int max_y;
|
||||
|
||||
static inline IntRange2D from_min_max(godot::Vector2 min_pos, godot::Vector2 max_pos) {
|
||||
return IntRange2D(godot::Rect2(min_pos, max_pos));
|
||||
}
|
||||
|
||||
static inline IntRange2D from_pos_size(godot::Vector2 min_pos, godot::Vector2 size) {
|
||||
return IntRange2D(godot::Rect2(min_pos, size));
|
||||
}
|
||||
|
||||
IntRange2D(godot::Rect2 rect) {
|
||||
min_x = static_cast<int>(rect.position.x);
|
||||
min_y = static_cast<int>(rect.position.y);
|
||||
max_x = static_cast<int>(rect.position.x + rect.size.x);
|
||||
max_y = static_cast<int>(rect.position.y + rect.size.y);
|
||||
}
|
||||
|
||||
inline bool is_inside(Vector2i size) const {
|
||||
return min_x >= size.x &&
|
||||
min_y >= size.y &&
|
||||
max_x <= size.x &&
|
||||
max_y <= size.y;
|
||||
}
|
||||
|
||||
inline void clip(Vector2i size) {
|
||||
min_x = Math::clamp(min_x, 0, size.x);
|
||||
min_y = Math::clamp(min_y, 0, size.y);
|
||||
max_x = Math::clamp(max_x, 0, size.x);
|
||||
max_y = Math::clamp(max_y, 0, size.y);
|
||||
}
|
||||
|
||||
inline void pad(int p) {
|
||||
min_x -= p;
|
||||
min_y -= p;
|
||||
max_x += p;
|
||||
max_y += p;
|
||||
}
|
||||
|
||||
inline int get_width() const {
|
||||
return max_x - min_x;
|
||||
}
|
||||
|
||||
inline int get_height() const {
|
||||
return max_y - min_y;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // INT_RANGE_2D_H
|
||||
28
addons/zylann.hterrain/native/src/math_funcs.h
Executable file
28
addons/zylann.hterrain/native/src/math_funcs.h
Executable file
@@ -0,0 +1,28 @@
|
||||
#ifndef MATH_FUNCS_H
|
||||
#define MATH_FUNCS_H
|
||||
|
||||
namespace Math {
|
||||
|
||||
inline float lerp(float minv, float maxv, float t) {
|
||||
return minv + t * (maxv - minv);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T clamp(T x, T minv, T maxv) {
|
||||
if (x < minv) {
|
||||
return minv;
|
||||
}
|
||||
if (x > maxv) {
|
||||
return maxv;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T min(T a, T b) {
|
||||
return a < b ? a : b;
|
||||
}
|
||||
|
||||
} // namespace Math
|
||||
|
||||
#endif // MATH_FUNCS_H
|
||||
242
addons/zylann.hterrain/native/src/quad_tree_lod.cpp
Executable file
242
addons/zylann.hterrain/native/src/quad_tree_lod.cpp
Executable file
@@ -0,0 +1,242 @@
|
||||
#include "quad_tree_lod.h"
|
||||
|
||||
namespace godot {
|
||||
|
||||
void QuadTreeLod::set_callbacks(Ref<FuncRef> make_cb, Ref<FuncRef> recycle_cb, Ref<FuncRef> vbounds_cb) {
|
||||
_make_func = make_cb;
|
||||
_recycle_func = recycle_cb;
|
||||
_vertical_bounds_func = vbounds_cb;
|
||||
}
|
||||
|
||||
int QuadTreeLod::get_lod_count() {
|
||||
// TODO make this a count, not max
|
||||
return _max_depth + 1;
|
||||
}
|
||||
|
||||
int QuadTreeLod::get_lod_factor(int lod) {
|
||||
return 1 << lod;
|
||||
}
|
||||
|
||||
int QuadTreeLod::compute_lod_count(int base_size, int full_size) {
|
||||
int po = 0;
|
||||
while (full_size > base_size) {
|
||||
full_size = full_size >> 1;
|
||||
po += 1;
|
||||
}
|
||||
return po;
|
||||
}
|
||||
|
||||
// The higher, the longer LODs will spread and higher the quality.
|
||||
// The lower, the shorter LODs will spread and lower the quality.
|
||||
void QuadTreeLod::set_split_scale(real_t p_split_scale) {
|
||||
real_t MIN = 2.0f;
|
||||
real_t MAX = 5.0f;
|
||||
|
||||
// Split scale must be greater than a threshold,
|
||||
// otherwise lods will decimate too fast and it will look messy
|
||||
if (p_split_scale < MIN)
|
||||
p_split_scale = MIN;
|
||||
if (p_split_scale > MAX)
|
||||
p_split_scale = MAX;
|
||||
|
||||
_split_scale = p_split_scale;
|
||||
}
|
||||
|
||||
real_t QuadTreeLod::get_split_scale() {
|
||||
return _split_scale;
|
||||
}
|
||||
|
||||
void QuadTreeLod::clear() {
|
||||
_join_all_recursively(ROOT, _max_depth);
|
||||
_max_depth = 0;
|
||||
_base_size = 0;
|
||||
}
|
||||
|
||||
void QuadTreeLod::create_from_sizes(int base_size, int full_size) {
|
||||
clear();
|
||||
_base_size = base_size;
|
||||
_max_depth = compute_lod_count(base_size, full_size);
|
||||
|
||||
// Total qty of nodes is (N^L - 1) / (N - 1). -1 for root, where N=num children, L=levels including the root
|
||||
int node_count = ((static_cast<int>(pow(4, _max_depth+1)) - 1) / (4 - 1)) - 1;
|
||||
_node_pool.resize(node_count); // e.g. ((4^6 -1) / 3 ) - 1 = 1364 excluding root
|
||||
|
||||
_free_indices.resize((node_count / 4)); // 1364 / 4 = 341
|
||||
for (int i = 0; i < _free_indices.size(); i++) // i = 0 to 340, *4 = 0 to 1360
|
||||
_free_indices[i] = 4 * i; // _node_pool[4*0 + i0] is first child, [4*340 + i3] is last
|
||||
}
|
||||
|
||||
void QuadTreeLod::update(Vector3 view_pos) {
|
||||
_update(ROOT, _max_depth, view_pos);
|
||||
|
||||
// This makes sure we keep seeing the lowest LOD,
|
||||
// if the tree is cleared while we are far away
|
||||
Quad *root = _get_root();
|
||||
if (!root->has_children() && root->is_null())
|
||||
root->set_data(_make_chunk(_max_depth, 0, 0));
|
||||
}
|
||||
|
||||
void QuadTreeLod::debug_draw_tree(CanvasItem *ci) {
|
||||
if (ci != nullptr)
|
||||
_debug_draw_tree_recursive(ci, ROOT, _max_depth, 0);
|
||||
}
|
||||
|
||||
// Intention is to only clear references to children
|
||||
void QuadTreeLod::_clear_children(unsigned int index) {
|
||||
Quad *quad = _get_node(index);
|
||||
if (quad->has_children()) {
|
||||
_recycle_children(quad->first_child);
|
||||
quad->first_child = NO_CHILDREN;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the index of the first_child. Allocates from _free_indices.
|
||||
unsigned int QuadTreeLod::_allocate_children() {
|
||||
if (_free_indices.size() == 0) {
|
||||
return NO_CHILDREN;
|
||||
}
|
||||
|
||||
unsigned int i0 = _free_indices[_free_indices.size() - 1];
|
||||
_free_indices.pop_back();
|
||||
return i0;
|
||||
}
|
||||
|
||||
// Pass the first_child index, not the parent index. Stores back in _free_indices.
|
||||
void QuadTreeLod::_recycle_children(unsigned int i0) {
|
||||
// Debug check, there is no use case in recycling a node which is not a first child
|
||||
CRASH_COND(i0 % 4 != 0);
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
_node_pool[i0 + i].init();
|
||||
}
|
||||
|
||||
_free_indices.push_back(i0);
|
||||
}
|
||||
|
||||
Variant QuadTreeLod::_make_chunk(int lod, int origin_x, int origin_y) {
|
||||
if (_make_func.is_valid()) {
|
||||
return _make_func->call_func(origin_x, origin_y, lod);
|
||||
} else {
|
||||
return Variant();
|
||||
}
|
||||
}
|
||||
|
||||
void QuadTreeLod::_recycle_chunk(unsigned int quad_index, int lod) {
|
||||
Quad *quad = _get_node(quad_index);
|
||||
if (_recycle_func.is_valid()) {
|
||||
_recycle_func->call_func(quad->get_data(), quad->origin_x, quad->origin_y, lod);
|
||||
}
|
||||
}
|
||||
|
||||
void QuadTreeLod::_join_all_recursively(unsigned int quad_index, int lod) {
|
||||
Quad *quad = _get_node(quad_index);
|
||||
|
||||
if (quad->has_children()) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
_join_all_recursively(quad->first_child + i, lod - 1);
|
||||
}
|
||||
_clear_children(quad_index);
|
||||
|
||||
} else if (quad->is_valid()) {
|
||||
_recycle_chunk(quad_index, lod);
|
||||
quad->clear_data();
|
||||
}
|
||||
}
|
||||
|
||||
void QuadTreeLod::_update(unsigned int quad_index, int lod, Vector3 view_pos) {
|
||||
// This function should be called regularly over frames.
|
||||
Quad *quad = _get_node(quad_index);
|
||||
int lod_factor = get_lod_factor(lod);
|
||||
int chunk_size = _base_size * lod_factor;
|
||||
Vector3 world_center = static_cast<real_t>(chunk_size) * (Vector3(static_cast<real_t>(quad->origin_x), 0.f, static_cast<real_t>(quad->origin_y)) + Vector3(0.5f, 0.f, 0.5f));
|
||||
|
||||
if (_vertical_bounds_func.is_valid()) {
|
||||
Variant result = _vertical_bounds_func->call_func(quad->origin_x, quad->origin_y, lod);
|
||||
ERR_FAIL_COND(result.get_type() != Variant::VECTOR2);
|
||||
Vector2 vbounds = static_cast<Vector2>(result);
|
||||
world_center.y = (vbounds.x + vbounds.y) / 2.0f;
|
||||
}
|
||||
|
||||
int split_distance = _base_size * lod_factor * static_cast<int>(_split_scale);
|
||||
|
||||
if (!quad->has_children()) {
|
||||
if (lod > 0 && world_center.distance_to(view_pos) < split_distance) {
|
||||
// Split
|
||||
unsigned int new_idx = _allocate_children();
|
||||
ERR_FAIL_COND(new_idx == NO_CHILDREN);
|
||||
quad->first_child = new_idx;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
Quad *child = _get_node(quad->first_child + i);
|
||||
child->origin_x = quad->origin_x * 2 + (i & 1);
|
||||
child->origin_y = quad->origin_y * 2 + ((i & 2) >> 1);
|
||||
child->set_data(_make_chunk(lod - 1, child->origin_x, child->origin_y));
|
||||
// If the quad needs to split more, we'll ask more recycling...
|
||||
}
|
||||
|
||||
if (quad->is_valid()) {
|
||||
_recycle_chunk(quad_index, lod);
|
||||
quad->clear_data();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bool no_split_child = true;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
_update(quad->first_child + i, lod - 1, view_pos);
|
||||
|
||||
if (_get_node(quad->first_child + i)->has_children())
|
||||
no_split_child = false;
|
||||
}
|
||||
|
||||
if (no_split_child && world_center.distance_to(view_pos) > split_distance) {
|
||||
// Join
|
||||
for (int i = 0; i < 4; i++) {
|
||||
_recycle_chunk(quad->first_child + i, lod - 1);
|
||||
}
|
||||
_clear_children(quad_index);
|
||||
quad->set_data(_make_chunk(lod, quad->origin_x, quad->origin_y));
|
||||
}
|
||||
}
|
||||
} // _update
|
||||
|
||||
void QuadTreeLod::_debug_draw_tree_recursive(CanvasItem *ci, unsigned int quad_index, int lod_index, int child_index) {
|
||||
Quad *quad = _get_node(quad_index);
|
||||
|
||||
if (quad->has_children()) {
|
||||
int ch_index = quad->first_child;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
_debug_draw_tree_recursive(ci, ch_index + i, lod_index - 1, i);
|
||||
}
|
||||
|
||||
} else {
|
||||
real_t size = static_cast<real_t>(get_lod_factor(lod_index));
|
||||
int checker = 0;
|
||||
if (child_index == 1 || child_index == 2)
|
||||
checker = 1;
|
||||
|
||||
int chunk_indicator = 0;
|
||||
if (quad->is_valid())
|
||||
chunk_indicator = 1;
|
||||
|
||||
Rect2 rect2(Vector2(static_cast<real_t>(quad->origin_x), static_cast<real_t>(quad->origin_y)) * size,
|
||||
Vector2(size, size));
|
||||
Color color(1.0f - static_cast<real_t>(lod_index) * 0.2f, 0.2f * static_cast<real_t>(checker), static_cast<real_t>(chunk_indicator), 1.0f);
|
||||
ci->draw_rect(rect2, color);
|
||||
}
|
||||
}
|
||||
|
||||
void QuadTreeLod::_register_methods() {
|
||||
register_method("set_callbacks", &QuadTreeLod::set_callbacks);
|
||||
register_method("get_lod_count", &QuadTreeLod::get_lod_count);
|
||||
register_method("get_lod_factor", &QuadTreeLod::get_lod_factor);
|
||||
register_method("compute_lod_count", &QuadTreeLod::compute_lod_count);
|
||||
register_method("set_split_scale", &QuadTreeLod::set_split_scale);
|
||||
register_method("get_split_scale", &QuadTreeLod::get_split_scale);
|
||||
register_method("clear", &QuadTreeLod::clear);
|
||||
register_method("create_from_sizes", &QuadTreeLod::create_from_sizes);
|
||||
register_method("update", &QuadTreeLod::update);
|
||||
register_method("debug_draw_tree", &QuadTreeLod::debug_draw_tree);
|
||||
}
|
||||
|
||||
} // namespace godot
|
||||
121
addons/zylann.hterrain/native/src/quad_tree_lod.h
Executable file
121
addons/zylann.hterrain/native/src/quad_tree_lod.h
Executable file
@@ -0,0 +1,121 @@
|
||||
#ifndef QUAD_TREE_LOD_H
|
||||
#define QUAD_TREE_LOD_H
|
||||
|
||||
#include <CanvasItem.hpp>
|
||||
#include <FuncRef.hpp>
|
||||
#include <Godot.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace godot {
|
||||
|
||||
class QuadTreeLod : public Reference {
|
||||
GODOT_CLASS(QuadTreeLod, Reference)
|
||||
public:
|
||||
static void _register_methods();
|
||||
|
||||
QuadTreeLod() {}
|
||||
~QuadTreeLod() {}
|
||||
|
||||
void _init() {}
|
||||
|
||||
void set_callbacks(Ref<FuncRef> make_cb, Ref<FuncRef> recycle_cb, Ref<FuncRef> vbounds_cb);
|
||||
int get_lod_count();
|
||||
int get_lod_factor(int lod);
|
||||
int compute_lod_count(int base_size, int full_size);
|
||||
void set_split_scale(real_t p_split_scale);
|
||||
real_t get_split_scale();
|
||||
void clear();
|
||||
void create_from_sizes(int base_size, int full_size);
|
||||
void update(Vector3 view_pos);
|
||||
void debug_draw_tree(CanvasItem *ci);
|
||||
|
||||
private:
|
||||
static const unsigned int NO_CHILDREN = -1;
|
||||
static const unsigned int ROOT = -1;
|
||||
|
||||
class Quad {
|
||||
public:
|
||||
unsigned int first_child = NO_CHILDREN;
|
||||
int origin_x = 0;
|
||||
int origin_y = 0;
|
||||
|
||||
Quad() {
|
||||
init();
|
||||
}
|
||||
|
||||
~Quad() {
|
||||
}
|
||||
|
||||
inline void init() {
|
||||
first_child = NO_CHILDREN;
|
||||
origin_x = 0;
|
||||
origin_y = 0;
|
||||
clear_data();
|
||||
}
|
||||
|
||||
inline void clear_data() {
|
||||
_data = Variant();
|
||||
}
|
||||
|
||||
inline bool has_children() {
|
||||
return first_child != NO_CHILDREN;
|
||||
}
|
||||
|
||||
inline bool is_null() {
|
||||
return _data.get_type() == Variant::NIL;
|
||||
}
|
||||
|
||||
inline bool is_valid() {
|
||||
return _data.get_type() != Variant::NIL;
|
||||
}
|
||||
|
||||
inline Variant get_data() {
|
||||
return _data;
|
||||
}
|
||||
|
||||
inline void set_data(Variant p_data) {
|
||||
_data = p_data;
|
||||
}
|
||||
|
||||
private:
|
||||
Variant _data; // Type is HTerrainChunk.gd : Object
|
||||
};
|
||||
|
||||
Quad _root;
|
||||
std::vector<Quad> _node_pool;
|
||||
std::vector<unsigned int> _free_indices;
|
||||
|
||||
int _max_depth = 0;
|
||||
int _base_size = 16;
|
||||
real_t _split_scale = 2.0f;
|
||||
|
||||
Ref<FuncRef> _make_func;
|
||||
Ref<FuncRef> _recycle_func;
|
||||
Ref<FuncRef> _vertical_bounds_func;
|
||||
|
||||
inline Quad *_get_root() {
|
||||
return &_root;
|
||||
}
|
||||
|
||||
inline Quad *_get_node(unsigned int index) {
|
||||
if (index == ROOT) {
|
||||
return &_root;
|
||||
} else {
|
||||
return &_node_pool[index];
|
||||
}
|
||||
}
|
||||
|
||||
void _clear_children(unsigned int index);
|
||||
unsigned int _allocate_children();
|
||||
void _recycle_children(unsigned int i0);
|
||||
Variant _make_chunk(int lod, int origin_x, int origin_y);
|
||||
void _recycle_chunk(unsigned int quad_index, int lod);
|
||||
void _join_all_recursively(unsigned int quad_index, int lod);
|
||||
void _update(unsigned int quad_index, int lod, Vector3 view_pos);
|
||||
void _debug_draw_tree_recursive(CanvasItem *ci, unsigned int quad_index, int lod_index, int child_index);
|
||||
}; // class QuadTreeLod
|
||||
|
||||
} // namespace godot
|
||||
|
||||
#endif // QUAD_TREE_LOD_H
|
||||
19
addons/zylann.hterrain/native/src/vector2i.h
Executable file
19
addons/zylann.hterrain/native/src/vector2i.h
Executable file
@@ -0,0 +1,19 @@
|
||||
#ifndef VECTOR2I_H
|
||||
#define VECTOR2I_H
|
||||
|
||||
#include <core/Vector2.hpp>
|
||||
|
||||
struct Vector2i {
|
||||
int x;
|
||||
int y;
|
||||
|
||||
Vector2i(godot::Vector2 v) :
|
||||
x(static_cast<int>(v.x)),
|
||||
y(static_cast<int>(v.y)) {}
|
||||
|
||||
bool any_zero() const {
|
||||
return x == 0 || y == 0;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // VECTOR2I_H
|
||||
Reference in New Issue
Block a user