diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000000000000000000000000000000000000..b68024fe2b70431a415b9215e1e292bacf070050 --- /dev/null +++ b/.clang-format @@ -0,0 +1,246 @@ +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AlignConsecutiveAssignments: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: true +AlignConsecutiveBitFields: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveDeclarations: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveMacros: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveShortCaseStatements: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCaseColons: false +AlignEscapedNewlines: Right +AlignOperands: Align +AlignTrailingComments: + Kind: Always + OverEmptyLines: 0 +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowBreakBeforeNoexceptSpecifier: Never +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortCompoundRequirementOnASingleLine: true +AllowShortEnumsOnASingleLine: true +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: true +BitFieldColonSpacing: Both +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterExternBlock: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakAdjacentStringLiterals: true +BreakAfterAttributes: Leave +BreakAfterJavaFieldAnnotations: false +BreakArrays: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: Always +BreakBeforeBraces: Attach +BreakBeforeInlineASMColon: OnlyMultiline +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeColon +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseBlocks: false +IndentCaseLabels: false +IndentExternBlock: AfterExternBlock +IndentGotoLabels: true +IndentPPDirectives: None +IndentRequiresClause: true +IndentWidth: 2 +IndentWrappedFunctionNames: false +InsertBraces: false +InsertNewlineAtEOF: false +InsertTrailingCommas: None +IntegerLiteralSeparator: + Binary: 0 + BinaryMinDigits: 0 + Decimal: 0 + DecimalMinDigits: 0 + Hex: 0 + HexMinDigits: 0 +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +KeepEmptyLinesAtEOF: false +LambdaBodyIndentation: Signature +LineEnding: DeriveLF +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PackConstructorInitializers: BinPack +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakScopeResolution: 500 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyIndentedWhitespace: 0 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +PPIndentWidth: -1 +QualifierAlignment: Leave +ReferenceAlignment: Pointer +ReflowComments: true +RemoveBracesLLVM: false +RemoveParentheses: Leave +RemoveSemicolon: false +RequiresClausePosition: OwnLine +RequiresExpressionIndentation: OuterScope +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SkipMacroDefinitionBody: false +SortIncludes: CaseSensitive +SortJavaStaticImport: Before +SortUsingDeclarations: LexicographicNumeric +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeJsonColon: false +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterOverloadedOperator: false + AfterPlacementOperator: true + AfterRequiresInClause: false + AfterRequiresInExpression: false + BeforeNonEmptyParentheses: false +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInContainerLiterals: true +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParens: Never +SpacesInParensOptions: + InCStyleCasts: false + InConditionalStatements: false + InEmptyParentheses: false + Other: false +SpacesInSquareBrackets: false +Standard: Latest +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseTab: Never +VerilogBreakBetweenInstancePorts: true +WhitespaceSensitiveMacros: + - BOOST_PP_STRINGIZE + - CF_SWIFT_NAME + - NS_SWIFT_NAME + - PP_STRINGIZE + - STRINGIZE +... + diff --git a/models/cube.obj b/models/cube.obj new file mode 100644 index 0000000000000000000000000000000000000000..909ab55982d94da97be8abe38d072ffea61a230f --- /dev/null +++ b/models/cube.obj @@ -0,0 +1,38 @@ +# Blender 4.2.3 LTS +# www.blender.org +o Cube +v -0.500000 -0.500000 0.500000 +v -0.500000 0.500000 0.500000 +v -0.500000 -0.500000 -0.500000 +v -0.500000 0.500000 -0.500000 +v 0.500000 -0.500000 0.500000 +v 0.500000 0.500000 0.500000 +v 0.500000 -0.500000 -0.500000 +v 0.500000 0.500000 -0.500000 +vn -1.0000 -0.0000 -0.0000 +vn -0.0000 -0.0000 -1.0000 +vn 1.0000 -0.0000 -0.0000 +vn -0.0000 -0.0000 1.0000 +vn -0.0000 -1.0000 -0.0000 +vn -0.0000 1.0000 -0.0000 +vt 0.375000 0.000000 +vt 0.625000 0.000000 +vt 0.625000 0.250000 +vt 0.375000 0.250000 +vt 0.625000 0.500000 +vt 0.375000 0.500000 +vt 0.625000 0.750000 +vt 0.375000 0.750000 +vt 0.625000 1.000000 +vt 0.375000 1.000000 +vt 0.125000 0.500000 +vt 0.125000 0.750000 +vt 0.875000 0.500000 +vt 0.875000 0.750000 +s 0 +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/4/2 4/3/2 8/5/2 7/6/2 +f 7/6/3 8/5/3 6/7/3 5/8/3 +f 5/8/4 6/7/4 2/9/4 1/10/4 +f 3/11/5 7/6/5 5/8/5 1/12/5 +f 8/5/6 4/13/6 2/14/6 6/7/6 diff --git a/models/wide_cube.obj b/models/wide_cube.obj new file mode 100644 index 0000000000000000000000000000000000000000..1c547958254aebe312fe44264f145e0c0625883d --- /dev/null +++ b/models/wide_cube.obj @@ -0,0 +1,38 @@ +# Blender 4.2.3 LTS +# www.blender.org +o Cube +v 3.089371 -0.034801 0.761726 +v 3.089371 1.644930 0.761726 +v 3.089371 -0.034801 -0.761726 +v 3.089371 1.644930 -0.761726 +v 3.699787 -0.034801 0.761726 +v 3.699787 1.644930 0.761726 +v 3.699787 -0.034801 -0.761726 +v 3.699787 1.644930 -0.761726 +vn -1.0000 -0.0000 -0.0000 +vn -0.0000 -0.0000 -1.0000 +vn 1.0000 -0.0000 -0.0000 +vn -0.0000 -0.0000 1.0000 +vn -0.0000 -1.0000 -0.0000 +vn -0.0000 1.0000 -0.0000 +vt 0.375000 0.000000 +vt 0.625000 0.000000 +vt 0.625000 0.250000 +vt 0.375000 0.250000 +vt 0.625000 0.500000 +vt 0.375000 0.500000 +vt 0.625000 0.750000 +vt 0.375000 0.750000 +vt 0.625000 1.000000 +vt 0.375000 1.000000 +vt 0.125000 0.500000 +vt 0.125000 0.750000 +vt 0.875000 0.500000 +vt 0.875000 0.750000 +s 0 +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/4/2 4/3/2 8/5/2 7/6/2 +f 7/6/3 8/5/3 6/7/3 5/8/3 +f 5/8/4 6/7/4 2/9/4 1/10/4 +f 3/11/5 7/6/5 5/8/5 1/12/5 +f 8/5/6 4/13/6 2/14/6 6/7/6 diff --git a/shaders/basic.vert b/shaders/basic.vert index a3adbe90a7a42e871e52704cbed2f275d47f6f1e..f7e6ed93a25b21dc54b66347b068d0d139447ee0 100644 --- a/shaders/basic.vert +++ b/shaders/basic.vert @@ -7,11 +7,17 @@ in vec2 in_TexCoord; uniform mat4 projectionMatrix; uniform mat4 modelToWorldToView; -out float shade; + +out vec3 position; +out vec3 normal; +out vec2 tex_coord; + void main(void) { - shade = (mat3(modelToWorldToView)*in_Normal).z; // Fake shading + position = in_Position; + normal = in_Normal; + tex_coord = in_TexCoord; + gl_Position=projectionMatrix*modelToWorldToView*vec4(in_Position, 1.0); } - diff --git a/shaders/ground.frag b/shaders/ground.frag index 7d92382799e0b4e4370ba9fc4c86ebcb5e8c3c01..cc59c06dbaf47026028eeac2e8239763cc5d8365 100644 --- a/shaders/ground.frag +++ b/shaders/ground.frag @@ -1,11 +1,30 @@ #version 150 -in float shade; +in vec3 position; +in vec3 normal; +in vec2 tex_coord; + +uniform sampler2D grass; +uniform sampler2D dirt; out vec4 out_Color; +const float tex_scale = 4.0; + +const vec3 sun = normalize(vec3(0, 1, 0)); + +float unlerp(float lo, float hi, float t) +{ + return clamp((t - lo) / (hi - lo), 0.0, 1.0); +} + void main(void) { - out_Color=vec4(shade,shade,shade,1.0); + vec3 nnormal = normalize(normal); + float light = max(0.0, dot(nnormal, sun)); + + vec3 albedo = mix(texture(dirt, tex_coord * tex_scale).rgb, texture(grass, tex_coord * tex_scale).rgb, unlerp(-0.2, 0.2, position.y)); + + out_Color = vec4(albedo * light, 1.0); } diff --git a/shaders/surface.frag b/shaders/surface.frag index 6a72d43822fbc5151b15ac581936eab112e872c8..e5bbed52d5c3d8572daca35dff3ca6baadc51cd7 100644 --- a/shaders/surface.frag +++ b/shaders/surface.frag @@ -2,15 +2,25 @@ in vec3 world_pos; in vec3 normal; +in vec2 tex_coord; -out vec4 out_Color; +in float test; uniform sampler2D sky; +uniform sampler2D ground; +uniform sampler2D ground_depth; uniform vec3 camera_pos; const float PI = 3.1415926535897f; const float R0 = 0.04; +const float MIN_T = 0; +const float MAX_T = 10.0; +const float DT = 0.1; + +out vec4 out_Color; + + vec2 sphere_uv(vec3 direction) { float u = 0.5 + atan(direction.z, direction.x) / (2 * PI); @@ -25,16 +35,56 @@ float fresnel(vec3 normal, vec3 view) { return mix(R0, 1.0, x * x * x * x * x); } +vec2 tex_at(vec3 pos) { + return pos.xz * vec2(0.1f) + vec2(0.5f); +} + +float depth_at(vec3 pos) { + return mix(4.5, -2.0, texture(ground_depth, tex_at(pos)).r); +} + +/** + * Adapted from Inigo Quilez article on raymarching terrain + * https://iquilezles.org/articles/terrainmarching/ + */ +vec3 refraction_ray(vec3 pos, vec3 dir) { + float lh = 0.0f; + float ly = 0.0f; + + for (float t = MIN_T; t < MAX_T; t += DT) { + vec3 cur_pos = pos + t * dir; + + float y_at = depth_at(cur_pos); + if (cur_pos.y < y_at) { + float res_t = t - DT + DT * (lh - ly) / (cur_pos.y - ly - y_at + lh); + + return texture(ground, tex_at(pos + res_t * dir)).rgb; + } + + lh = y_at; + ly = cur_pos.y; + } + + return vec3(1, 0, 1); +} + + void main(void) { vec3 view = normalize(world_pos - camera_pos); vec3 nnormal = normalize(normal); vec3 reflected = reflect(view, nnormal); float R = fresnel(nnormal, view); - vec3 water_color = vec3(0.0, 0.1, 0.4); + // vec3 water_color = vec3(0, 0.05, 0.1); vec3 sky_color = texture(sky, sphere_uv(reflected)).rgb; + vec3 refracted = refract(view, nnormal, 0.667); + vec3 water_color = refraction_ray(world_pos, refracted); + //out_Color = vec4(1,0,1,1); + // out_Color = vec4(refracted * vec3(0.5) + vec3(0.5), 1.0); out_Color = vec4(mix(water_color, sky_color, R), 1.0); + + // out_Color = texture(ground_depth, tex_coord); } diff --git a/shaders/surface.vert b/shaders/surface.vert index a7e5ff88a5a492a53bf64aab03e474ec9ad3c76e..69f69d92e4e14c7f0d9fd4a2d91965bd521e400b 100644 --- a/shaders/surface.vert +++ b/shaders/surface.vert @@ -12,6 +12,8 @@ uniform int in_WavesNum; out vec3 world_pos; out vec3 normal; +out vec2 tex_coord; +out float test; const float pi = 3.14159265358979323846; const float g = 9.82; @@ -82,6 +84,9 @@ void main(void) vec3 offset_pos = get_waves(in_Position, time); world_pos = offset_pos; normal = compute_normal(in_Position, time); + tex_coord = in_TexCoord; + + test = in_WavesNum; gl_Position = projectionMatrix * modelToWorldToView * vec4(offset_pos, 1.0); } diff --git a/shaders/waterfall.frag b/shaders/waterfall.frag index 7d92382799e0b4e4370ba9fc4c86ebcb5e8c3c01..3244e1710286b70dbf76c27ecbc9f4b66dd5f6d7 100644 --- a/shaders/waterfall.frag +++ b/shaders/waterfall.frag @@ -1,11 +1,91 @@ #version 150 -in float shade; +const float step_size = 0.1; +const int NUMBER_OF_STEPS = 32; +const float MINIMUM_HIT_DISTANCE = 0.0001; +const float MAXIMUM_TRACE_DISTANCE = 1000.0; +uniform int num_balls; +uniform float screenWidth; +uniform float screenHeight; + +in vec3 world_pos; +uniform vec3 camera_pos; out vec4 out_Color; +struct Ball { + vec4 pos; + float radius; +}; + +layout(std140) uniform BallBuffer { + Ball balls[30]; // TODO: Need to manually update size +}; + +float distance_from_sphere(vec3 pos, Ball ball) { + return length(pos - ball.pos.xyz) - ball.radius; +} + +// Function taken from: https://iquilezles.org/articles/distfunctions/ +float opSmoothUnion(float d1, float d2, float k) { + float h = clamp(0.5 + 0.5*(d2-d1)/k, 0.0, 1.0); + return mix(d2, d1, h) - k*h*(1.0-h); +} + +float SDF(vec3 position) { + float min_dist = distance_from_sphere(position, balls[0]); + + // TODO: iterate over uniform num_balls + for (int i = 1; i < 30; ++i) { + float dist = distance_from_sphere(position, balls[i]); + + min_dist = opSmoothUnion(min_dist, dist, 0.2); + } + + return min_dist; +} + +vec3 calculate_normal(vec3 position) { + const vec3 small_step = vec3(0.001, 0, 0); + return normalize( + vec3( + SDF(position + small_step.xyy) - SDF(position - small_step.xyy), + SDF(position + small_step.yxy) - SDF(position - small_step.yxy), + SDF(position + small_step.yyx) - SDF(position - small_step.yyx) + ) + ); +} + +vec3 ray_march(vec3 ro, vec3 rd) { + + float total_distance_traveled = 0.0; + + for (int i = 0; i < NUMBER_OF_STEPS; ++i) { + + vec3 current_position = ro + total_distance_traveled * rd; + + float min_dist = SDF(current_position); + + // hit + if (min_dist < MINIMUM_HIT_DISTANCE) { + const vec3 color = vec3(0.1, 0.2, 0.7); + vec3 normal = normalize(calculate_normal(current_position)); + return color; + } + + if (total_distance_traveled > MAXIMUM_TRACE_DISTANCE) { + discard; + } + + total_distance_traveled += min_dist; + } +} + void main(void) { - out_Color=vec4(shade,shade,shade,1.0); + vec3 dir = normalize(world_pos - camera_pos); + vec3 shaded_color = ray_march(world_pos, dir); + out_Color = vec4(shaded_color, 1.0); + //out_Color = vec4(gl_FragCoord.x, gl_FragCoord.y, gl_FragCoord.z, 1); + //out_Color = vec4(shade, shade, shade, 0.0); } - diff --git a/shaders/waterfall.vert b/shaders/waterfall.vert new file mode 100644 index 0000000000000000000000000000000000000000..655b91d54b661f3108c5595e0a7267a7ceb835f8 --- /dev/null +++ b/shaders/waterfall.vert @@ -0,0 +1,21 @@ +#version 150 + +in vec3 in_Position; +in vec3 in_Normal; +in vec2 in_TexCoord; + +uniform mat4 projectionMatrix; +uniform mat4 modelToWorldToView; +uniform mat4 invModelToWorld; +uniform int num_balls; +uniform float screenWidth; +uniform float screenHeight; +uniform vec3 camera_pos; + +out vec3 world_pos; + +void main(void) +{ + gl_Position = projectionMatrix * modelToWorldToView * vec4(in_Position, 1.0); + world_pos = in_Position; +} diff --git a/src/hdr.h b/src/hdr.h new file mode 100644 index 0000000000000000000000000000000000000000..a44c4acf114a97ae588b94cdc44f7636b70d3ba4 --- /dev/null +++ b/src/hdr.h @@ -0,0 +1,20 @@ +#pragma once + +#include <GL/gl.h> +#include <array> +#include <string> +#include <vector> + +struct HDRImage { + using Pixel = std::array<float, 3>; + + GLuint width; + GLuint height; + std::vector<Pixel> pixels; + GLuint texId; + + HDRImage(GLuint width, GLuint height, std::vector<Pixel> pixels); + + static HDRImage load_hdr(const std::string& path); +}; + diff --git a/src/main-waves.cpp b/src/main-waves.cpp deleted file mode 100644 index 4da357a0f987d799f0ef96070fa8005e626d4b93..0000000000000000000000000000000000000000 --- a/src/main-waves.cpp +++ /dev/null @@ -1,369 +0,0 @@ -// Revised 2019 with a bit better variable names. -// Experimental C++ version 2022. Almost no code changes. -#include <iostream> -#include <GL/gl.h> -#include <sys/types.h> -#define MAIN -#include "GL_utilities.h" -#include "LittleOBJLoader.h" -#include "LoadTGA.h" -#include "MicroGlut.h" -#include "VectorUtils4.h" -#include "waves.h" -#include <vector> -#include <random> -// uses framework OpenGL -// uses framework Cocoa - -struct Object { - Model* model; - GLuint program; - - Object() = default; - - void use() const - { - glUseProgram(program); - } - - void draw() const - { - DrawModel(model, program, "in_Position", "in_Normal", "in_TexCoord"); - } -}; - - -struct Surface : public Object { - static GLuint const BINDING_POINT {0}; - char const* UNIFORM_BLOCK = "WaveBuffer"; - static int const MAX_WAVES {16}; - static constexpr float const MEDIAN_WAVELENGTH {2.6}; - static constexpr float const MEDIAN_AMPLITUDE {0.006}; - static constexpr float const MEDIAN_SPEED {0.6}; - static constexpr float const MEDIAN_DIR {3.14/4.0}; - - struct Wave { - GLfloat L; // Wavelength (distance between waves in world space) - GLfloat A; // Amplitude (height from surface to wave crest) - GLfloat S; // Speed (distance the crest moves per second) - char padding1[4]; - vec2 D; // Direction (horizontal vector perpendicular to the wave front) - char padding2[8]; - }; - - GLuint ubo; - GLuint block_index; - - std::vector<Wave> waves {}; - // Direction of the wind [0-2π] - float wind_dir {0.0}; - - Surface() = default; - - Wave get_wave() { - std::random_device rd; - std::mt19937 gen(rd()); - - std::uniform_real_distribution<float> rd_wavelength( - 0.5 * MEDIAN_WAVELENGTH, 2 * MEDIAN_WAVELENGTH - ); - - std::uniform_real_distribution<float> rd_amplitude( - 0.5 * MEDIAN_AMPLITUDE, 2 * MEDIAN_AMPLITUDE - ); - - std::uniform_real_distribution<float> rd_speed( - 0.5 * MEDIAN_SPEED, 2 * MEDIAN_SPEED - ); - - std::uniform_real_distribution<float> rd_dir( - wind_dir - MEDIAN_DIR, wind_dir + MEDIAN_DIR - ); - - float const dir {rd_dir(rd)}; - return { - rd_wavelength(rd), - rd_amplitude(rd), - rd_speed(rd), - {}, - vec2(cos(dir), sin(dir)), - }; - } - - Surface(Model* model, GLuint program) - : Object{model, program} { - - use(); - - // Create a uniform buffer object, used to send waves. - glGenBuffers(1, &ubo); - glBindBuffer(GL_UNIFORM_BUFFER, ubo); - - // Allocate memory in the buffer. - glBufferData( - GL_UNIFORM_BUFFER, sizeof(Wave) * MAX_WAVES, nullptr, GL_STATIC_DRAW - ); - - block_index = glGetUniformBlockIndex(program, "WaveBuffer"); - - // Bind the buffer to a binding point. - glBindBufferBase(GL_UNIFORM_BUFFER, BINDING_POINT, ubo); - - // // Link the UBO to the shader’s uniform block - glUniformBlockBinding(program, block_index, BINDING_POINT); - - for (int i = 0; i < MAX_WAVES; i++) { - waves.push_back(get_wave()); - } - } - - void draw() const - { - if (waves.size() > MAX_WAVES) { - std::cerr << "Error: The max limit of waves has been exceeded." << std::endl; - return; - } - - // Bind the buffer before updating it. - glBindBuffer(GL_UNIFORM_BUFFER, ubo); - glBufferSubData(GL_UNIFORM_BUFFER, 0, waves.size() * sizeof(Wave), waves.data()); - - // Upload the number of waves. - glUniform1i(glGetUniformLocation(program, "in_WavesNum"), waves.size()); - - DrawModel(model, program, "in_Position", "in_Normal", "in_TexCoord"); - } -}; - -struct Scene { - Object ground; - Surface surface; - Object waterfall; - Object skybox; - - GLuint skybox_tex; - - mat4 proj_matrix = perspective(70.0, 1.0, 0.2, 20.0); - vec3 pos; - float yaw; - float pitch; - mat4 view_matrix = lookAtv(vec3(-5, 4, -5), vec3(0, 0, 0), vec3(0, 1, 0)); - - void init() - { - Model* ground_model = LoadModel("models/ground.obj"); - Model* surface_model = LoadModel("models/surface.obj"); - Model* waterfall_model = LoadModel("models/waterfall.obj"); - Model* skybox_model = LoadModel("models/skybox.obj"); - - // This is not a ground logic program - GLuint ground_program = loadShaders("shaders/basic.vert", "shaders/ground.frag"); - GLuint surface_program = loadShaders("shaders/surface.vert", "shaders/surface.frag"); - GLuint waterfall_program = loadShaders("shaders/basic.vert", "shaders/waterfall.frag"); - GLuint skybox_program = loadShaders("shaders/skybox.vert", "shaders/skybox.frag"); - printError("compiled shaders"); - - ground = Object { ground_model, ground_program }; - surface = Surface { surface_model, surface_program }; - waterfall = Object { waterfall_model, waterfall_program }; - skybox = Object { skybox_model, skybox_program }; - - LoadTGATextureSimple("textures/sky4k.tga", &skybox_tex); - glBindTexture(GL_TEXTURE_2D, skybox_tex); - printError("bind texture"); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - printError("tex parameteri"); - } - - void do_keyboard_input() - { - vec3 fwd = vec3(sin(yaw), 0, -cos(yaw)); - vec3 right = vec3(cos(yaw), 0, sin(yaw)); - vec3 up = vec3(0, 1, 0); - float speed = 0.15; - - if (glutKeyIsDown('w')) { - pos += speed * fwd; - } - if (glutKeyIsDown('s')) { - pos -= speed * fwd; - } - if (glutKeyIsDown('d')) { - pos += speed * right; - } - if (glutKeyIsDown('a')) { - pos -= speed * right; - } - if (glutKeyIsDown('r')) { - pos += speed * up; - } - if (glutKeyIsDown('f')) { - pos -= speed * up; - } - if (glutKeyIsDown('e')) { - pos += speed * up; - } - if (glutKeyIsDown('q')) { - pos -= speed * up; - } - } - - void update_view_matrix() - { - vec3 up { 0.0, 1.0, 0.0 }; - mat4 rot = Rx(pitch) * Ry(yaw); - mat4 translation = T(-pos.x, -pos.y, -pos.z); - view_matrix = rot * translation; - } - - void draw_skybox() - { - skybox.use(); - GLuint program = skybox.program; - glDisable(GL_DEPTH_TEST); - glDisable(GL_CULL_FACE); - - glUniformMatrix4fv(glGetUniformLocation(program, "projectionMatrix"), 1, GL_TRUE, proj_matrix.m); - glUniformMatrix4fv(glGetUniformLocation(program, "modelToWorldToView"), 1, GL_TRUE, view_matrix.m); - glUniform1i(glGetUniformLocation(program, "sky"), 0); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, skybox_tex); - - - skybox.draw(); - glEnable(GL_DEPTH_TEST); - glEnable(GL_CULL_FACE); - } - - void draw_surface() - { - surface.use(); - GLuint program = surface.program; - int elapsed_millis = glutGet(GLUT_ELAPSED_TIME); - float time = elapsed_millis * 0.001f; - - glUniformMatrix4fv(glGetUniformLocation(program, "projectionMatrix"), 1, GL_TRUE, proj_matrix.m); - glUniformMatrix4fv(glGetUniformLocation(program, "modelToWorldToView"), 1, GL_TRUE, view_matrix.m); - glUniform1i(glGetUniformLocation(program, "sky"), 0); - glUniform3f(glGetUniformLocation(program, "camera_pos"), pos.x, pos.y, pos.z); - glUniform1f(glGetUniformLocation(program, "time"), time); - - surface.draw(); - } - - - void draw_ground() - { - ground.use(); - GLuint program = ground.program; - glUniformMatrix4fv(glGetUniformLocation(program, "projectionMatrix"), 1, GL_TRUE, proj_matrix.m); - glUniformMatrix4fv(glGetUniformLocation(program, "modelToWorldToView"), 1, GL_TRUE, view_matrix.m); - - ground.draw(); - } - - void draw_waterfall() - { - waterfall.use(); - GLuint program = waterfall.program; - glUniformMatrix4fv(glGetUniformLocation(program, "projectionMatrix"), 1, GL_TRUE, proj_matrix.m); - glUniformMatrix4fv(glGetUniformLocation(program, "modelToWorldToView"), 1, GL_TRUE, view_matrix.m); - - waterfall.draw(); - } - - void draw() - { - do_keyboard_input(); - update_view_matrix(); - - draw_skybox(); - draw_surface(); - draw_ground(); - draw_waterfall(); - } -}; - -Scene scene {}; -int mouse_x = -1; -int mouse_y = -1; - -void init(void) -{ - dumpInfo(); - - // GL inits - glClearColor(0.2, 0.2, 0.5, 0); - glEnable(GL_DEPTH_TEST); - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); - printError("GL inits"); // This is merely a vague indication of where something might be wrong - // - scene.init(); - - // Load and compile shader - printError("init shader"); -} - -void display(void) -{ - printError("pre display"); - - // clear the screen - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - // activate the program, and set its variables - scene.draw(); - - printError("display"); - - glutSwapBuffers(); -} - -void on_mouse_move(int x, int y) -{ - const float sensitivity = 0.0025f; - if (mouse_y == -1 && mouse_x == -1) { - mouse_x = x; - mouse_y = y; - - return; - } - - int dx = x - mouse_x; - int dy = y - mouse_y; - mouse_x = x; - mouse_y = y; - - scene.pitch += dy * sensitivity; - scene.yaw += dx * sensitivity; - - scene.pitch = std::max(-1.5f, std::min(1.5f, scene.pitch)); -} - -void on_mouse_button(int button, int state, int x, int y) -{ - if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) { - mouse_x = x; - mouse_y = y; - } -} - -int main(int argc, char* argv[]) -{ - glutInit(&argc, argv); - glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE); - glutInitContextVersion(3, 2); - glutInitWindowSize(800, 800); - glutCreateWindow("TSBK03 Project"); - glutDisplayFunc(display); - glutRepeatingTimer(20); - init(); - glutMotionFunc(on_mouse_move); - glutMouseFunc(on_mouse_button); - - Waves::init(); - - glutMainLoop(); - exit(0); -} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0ad1f7837731942183fb50dd95b02a1ae811b03a --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,304 @@ +// Revised 2019 with a bit better variable names. +// Experimental C++ version 2022. Almost no code changes. + +#include "object.h" +#include "waterfall.h" +#include "surface.h" + +#include <GL/gl.h> +#include <cstdlib> +#include <sys/types.h> +#include <GL/glext.h> +#define MAIN +#include "GL_utilities.h" +#include "LittleOBJLoader.h" +#include "LoadTGA.h" +#include "MicroGlut.h" +#include "VectorUtils4.h" +// uses framework OpenGL +// uses framework Cocoa + + + +struct Scene { + Object ground; + Surface surface; + Waterfall waterfall; + Object skybox; + + GLuint skybox_tex; + FBOstruct *ground_fbo; + GLuint dirt_tex; + GLuint grass_tex; + + mat4 proj_matrix = perspective(70.0, 1.0, 0.2, 20.0); + mat4 orth_matrix = ortho(-5.0, 5.0, -5.0, 5.0, -2.0, 4.5); + + vec3 pos; + float yaw; + float pitch; + mat4 view_matrix = lookAtv(vec3(-5, 4, -5), vec3(0, 0, 0), vec3(0, 1, 0)); + mat4 top_view_matrix = lookAtv(vec3(0, 0, 0), vec3(0, -1, 0), vec3(0, 0, 1)); + + void init() { + Model *ground_model = LoadModel("models/ground.obj"); + Model *surface_model = LoadModel("models/surface.obj"); + Model *waterfall_model = LoadModel("models/wide_cube.obj"); + Model *skybox_model = LoadModel("models/skybox.obj"); + + // This is not a ground logic program + GLuint ground_program = + loadShaders("shaders/basic.vert", "shaders/ground.frag"); + GLuint surface_program = + loadShaders("shaders/surface.vert", "shaders/surface.frag"); + GLuint waterfall_program = + loadShaders("shaders/waterfall.vert", "shaders/waterfall.frag"); + GLuint skybox_program = + loadShaders("shaders/skybox.vert", "shaders/skybox.frag"); + printError("compiled shaders"); + ground = Object{ground_model, ground_program}; + surface = Surface{surface_model, surface_program}; + waterfall = Waterfall{waterfall_model, waterfall_program}; + skybox = Object{skybox_model, skybox_program}; + + ground_fbo = initFBO2(1024, 1024, GL_LINEAR, true); + + LoadTGATextureSimple("textures/sky4k.tga", &skybox_tex); + glBindTexture(GL_TEXTURE_2D, skybox_tex); + printError("bind texture"); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + printError("tex parameteri"); + + LoadTGATextureSimple("textures/grass.tga", &grass_tex); + LoadTGATextureSimple("textures/dirt.tga", &dirt_tex); + printError("tex parameteri"); + } + + void do_keyboard_input() { + vec3 fwd = vec3(sin(yaw), 0, -cos(yaw)); + vec3 right = vec3(cos(yaw), 0, sin(yaw)); + vec3 up = vec3(0, 1, 0); + float speed = 0.3; + + if (glutKeyIsDown('w')) { + pos += speed * fwd; + } + if (glutKeyIsDown('s')) { + pos -= speed * fwd; + } + if (glutKeyIsDown('d')) { + pos += speed * right; + } + if (glutKeyIsDown('a')) { + pos -= speed * right; + } + if (glutKeyIsDown('r')) { + pos += speed * up; + } + if (glutKeyIsDown('f')) { + pos -= speed * up; + } + } + + void draw_ground_fbo() + { + useFBO(ground_fbo, nullptr, nullptr); + ground.use(); + GLuint program = ground.program; + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glUniformMatrix4fv(glGetUniformLocation(program, "projectionMatrix"), 1, GL_TRUE, orth_matrix.m); + glUniformMatrix4fv(glGetUniformLocation(program, "modelToWorldToView"), 1, GL_TRUE, top_view_matrix.m); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, grass_tex); + + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, dirt_tex); + + glUniform1i(glGetUniformLocation(program, "grass"), 0); + glUniform1i(glGetUniformLocation(program, "dirt"), 1); + + ground.draw(); + + useFBO(nullptr, nullptr, nullptr); + } + + void draw_surface() + { + surface.use(); + GLuint program = surface.program; + int elapsed_millis = glutGet(GLUT_ELAPSED_TIME); + float time = elapsed_millis * 0.001f; + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, skybox_tex); + + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, ground_fbo->texid); + + glActiveTexture(GL_TEXTURE2); + glBindTexture(GL_TEXTURE_2D, ground_fbo->depth); + + glUniformMatrix4fv(glGetUniformLocation(program, "projectionMatrix"), 1, GL_TRUE, proj_matrix.m); + glUniformMatrix4fv(glGetUniformLocation(program, "modelToWorldToView"), 1, GL_TRUE, view_matrix.m); + glUniform1i(glGetUniformLocation(program, "sky"), 0); + glUniform1i(glGetUniformLocation(program, "ground"), 1); + glUniform1i(glGetUniformLocation(program, "ground_depth"), 2); + glUniform3f(glGetUniformLocation(program, "camera_pos"), pos.x, pos.y, pos.z); + glUniform1f(glGetUniformLocation(program, "time"), time); + + surface.draw(); + } + + void update_view_matrix() { + vec3 up{0.0, 1.0, 0.0}; + mat4 rot = Rx(pitch) * Ry(yaw); + mat4 translation = T(-pos.x, -pos.y, -pos.z); + view_matrix = rot * translation; + } + + void draw_skybox() { + skybox.use(); + GLuint program = skybox.program; + glDisable(GL_DEPTH_TEST); + glDisable(GL_CULL_FACE); + + glUniformMatrix4fv(glGetUniformLocation(program, "projectionMatrix"), 1, + GL_TRUE, proj_matrix.m); + glUniformMatrix4fv(glGetUniformLocation(program, "modelToWorldToView"), 1, + GL_TRUE, view_matrix.m); + glUniform1i(glGetUniformLocation(program, "sky"), 0); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, skybox_tex); + + skybox.draw(); + glEnable(GL_DEPTH_TEST); + glEnable(GL_CULL_FACE); + } + + void draw_waterfall() { + waterfall.use(); + GLuint program = waterfall.program; + glUniformMatrix4fv(glGetUniformLocation(program, "projectionMatrix"), 1, + GL_TRUE, proj_matrix.m); + glUniformMatrix4fv(glGetUniformLocation(program, "worldToView"), 1, GL_TRUE, + view_matrix.m); + glUniformMatrix4fv(glGetUniformLocation(program, "modelToWorldToView"), 1, + GL_TRUE, view_matrix.m); + glUniform3f(glGetUniformLocation(program, "camera_pos"), pos.x, pos.y, + pos.z); + + waterfall.draw(); + waterfall.move_waterfall_balls(); + } + void draw_ground() + { + ground.use(); + GLuint program = ground.program; + glUniformMatrix4fv(glGetUniformLocation(program, "projectionMatrix"), 1, GL_TRUE, proj_matrix.m); + glUniformMatrix4fv(glGetUniformLocation(program, "modelToWorldToView"), 1, GL_TRUE, view_matrix.m); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, grass_tex); + + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, dirt_tex); + + glUniform1i(glGetUniformLocation(program, "grass"), 0); + glUniform1i(glGetUniformLocation(program, "dirt"), 1); + + ground.draw(); + } + + + void draw() + { + do_keyboard_input(); + update_view_matrix(); + + draw_ground_fbo(); + + draw_skybox(); + draw_surface(); + draw_ground(); + draw_waterfall(); + } +}; + +Scene scene{}; +int mouse_x = -1; +int mouse_y = -1; + +void init(void) { + dumpInfo(); + + // GL inits + glClearColor(0.2, 0.2, 0.5, 0); + glEnable(GL_DEPTH_TEST); + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + printError("GL inits"); // This is merely a vague indication of where + // something might be wrong + // + scene.init(); + + // Load and compile shader + printError("init shader"); +} + +void display(void) { + printError("pre display"); + + // clear the screen + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // activate the program, and set its variables + scene.draw(); + + printError("display"); + + glutSwapBuffers(); +} + +void on_mouse_move(int x, int y) +{ + const float sensitivity = 0.0025f; + if (mouse_y == -1 && mouse_x == -1) { + mouse_x = x; + mouse_y = y; + return; + } + + int dx = x - mouse_x; + int dy = y - mouse_y; + mouse_x = x; + mouse_y = y; + + scene.pitch += dy * sensitivity; + scene.yaw += dx * sensitivity; + scene.pitch = std::max(-1.5f, std::min(1.5f, scene.pitch)); +} + +void on_mouse_button(int button, int state, int x, int y) { + if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) { + mouse_x = x; + mouse_y = y; + } +} + +int main(int argc, char *argv[]) { + glutInit(&argc, argv); + glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE); + glutInitContextVersion(3, 2); + glutInitWindowSize(800, 800); + glutCreateWindow("TSBK03 Project"); + glutDisplayFunc(display); + glutRepeatingTimer(20); + init(); + glutMotionFunc(on_mouse_move); + glutMouseFunc(on_mouse_button); + + glutMainLoop(); + exit(0); +} diff --git a/src/object.h b/src/object.h new file mode 100644 index 0000000000000000000000000000000000000000..48c805c674bcec12801cef3be1fbfdfdece26a25 --- /dev/null +++ b/src/object.h @@ -0,0 +1,18 @@ +#pragma once + +#include "LittleOBJLoader.h" +#include <GL/gl.h> + +struct Object { + Model *model; + GLuint program; + + Object() = default; + + void use() const { glUseProgram(program); } + + void draw() const { + DrawModel(model, program, "in_Position", "in_Normal", "in_TexCoord"); + // DrawModel(model, program, "in_Position", "in_Normal", NULL); + } +}; diff --git a/src/surface.cpp b/src/surface.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5592da146889d09ce8bd925958482866b5f9855d --- /dev/null +++ b/src/surface.cpp @@ -0,0 +1,79 @@ +#include "surface.h" +#include <iostream> +#include <ostream> +#include <random> + + +Surface::Wave Surface::get_wave() const { + std::random_device rd; + std::mt19937 gen(rd()); + + std::uniform_real_distribution<float> rd_wavelength( + 0.5 * MEDIAN_WAVELENGTH, 2 * MEDIAN_WAVELENGTH + ); + + std::uniform_real_distribution<float> rd_amplitude( + 0.5 * MEDIAN_AMPLITUDE, 2 * MEDIAN_AMPLITUDE + ); + + std::uniform_real_distribution<float> rd_speed( + 0.5 * MEDIAN_SPEED, 2 * MEDIAN_SPEED + ); + + std::uniform_real_distribution<float> rd_dir( + wind_dir - MEDIAN_DIR, wind_dir + MEDIAN_DIR + ); + + float const dir {rd_dir(rd)}; + return { + rd_wavelength(rd), + rd_amplitude(rd), + rd_speed(rd), + {}, + vec2(cos(dir), sin(dir)), + }; +} + +Surface::Surface(Model* model, GLuint program) + : Object{model, program} { + use(); + + // Create a uniform buffer object, used to send waves. + glGenBuffers(1, &ubo); + glBindBuffer(GL_UNIFORM_BUFFER, ubo); + + // Allocate memory in the buffer. + glBufferData( + GL_UNIFORM_BUFFER, sizeof(Wave) * MAX_WAVES, nullptr, GL_STATIC_DRAW + ); + + block_index = glGetUniformBlockIndex(program, "WaveBuffer"); + + // Bind the buffer to a binding point. + glBindBufferBase(GL_UNIFORM_BUFFER, BINDING_POINT, ubo); + + // // Link the UBO to the shader’s uniform block + glUniformBlockBinding(program, block_index, BINDING_POINT); + + for (int i = 0; i < MAX_WAVES; i++) { + waves.push_back(get_wave()); + } +} + +void Surface::draw() const +{ + if (waves.size() > MAX_WAVES) { + std::cerr << "Error: The max limit of waves has been exceeded." << std::endl; + return; + } + + // Bind the buffer before updating it. + glBindBuffer(GL_UNIFORM_BUFFER, ubo); + glBufferSubData(GL_UNIFORM_BUFFER, 0, waves.size() * sizeof(Wave), waves.data()); + + // Upload the number of waves. + glUniform1i(glGetUniformLocation(program, "in_WavesNum"), waves.size()); + + DrawModel(model, program, "in_Position", "in_Normal", "in_TexCoord"); +} + diff --git a/src/surface.h b/src/surface.h new file mode 100644 index 0000000000000000000000000000000000000000..9a50510124612da3cf6cccce99eebc4bf3f85c16 --- /dev/null +++ b/src/surface.h @@ -0,0 +1,39 @@ +#pragma once + +#include "object.h" +#include <vector> + + +class Surface : public Object { +public: + + Surface() = default; + Surface(Model* model, GLuint program); + void draw() const; + +private: + static GLuint const BINDING_POINT {1}; + char const* UNIFORM_BLOCK = "WaveBuffer"; + static int const MAX_WAVES {16}; + static constexpr float const MEDIAN_WAVELENGTH {2.6}; + static constexpr float const MEDIAN_AMPLITUDE {0.006}; + static constexpr float const MEDIAN_SPEED {0.6}; + static constexpr float const MEDIAN_DIR {3.14/4.0}; + + struct Wave { + GLfloat L; // Wavelength (distance between waves in world space) + GLfloat A; // Amplitude (height from surface to wave crest) + GLfloat S; // Speed (distance the crest moves per second) + char padding1[4]; + vec2 D; // Direction (horizontal vector perpendicular to the wave front) + char padding2[8]; + }; + + GLuint ubo; + GLuint block_index; + std::vector<Wave> waves {}; + float wind_dir {0.0}; // Direction of the wind [0-2π] + + Wave get_wave() const; +}; + diff --git a/src/waterfall.cpp b/src/waterfall.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b1cfa99a8793c11e7556e0671e172db40ff8b9e3 --- /dev/null +++ b/src/waterfall.cpp @@ -0,0 +1,107 @@ +#include "waterfall.h" +#include "GL_utilities.h" +#include "object.h" +#include <cmath> +#include <iostream> +#include <ostream> + +Waterfall::Waterfall(Model *model, GLuint program) : Object{model, program} { + + float min_x = std::numeric_limits<float>::max(); + float max_x = std::numeric_limits<float>::min(); + float min_y = std::numeric_limits<float>::max(); + float max_y = std::numeric_limits<float>::min(); + float min_z = std::numeric_limits<float>::max(); + float max_z = std::numeric_limits<float>::min(); + + for (int i = 0; i < model->numVertices; ++i) { + min_x = std::min(min_x, model->vertexArray[i].x); + max_x = std::max(max_x, model->vertexArray[i].x); + min_y = std::min(min_y, model->vertexArray[i].y); + max_y = std::max(max_y, model->vertexArray[i].y); + min_z = std::min(min_z, model->vertexArray[i].z); + max_z = std::max(max_z, model->vertexArray[i].z); + } + + x = {min_x, max_x}; + y = {min_y, max_y}; + z = {min_z, max_z}; + + gen_balls(); + use(); + + // Create a uniform buffer object, used to send waves. + glGenBuffers(1, &ubo); + glBindBuffer(GL_UNIFORM_BUFFER, ubo); + + // Allocate memory in the buffer. + glBufferData(GL_UNIFORM_BUFFER, sizeof(balls), nullptr, GL_DYNAMIC_DRAW); + + block_index = glGetUniformBlockIndex(program, UNIFORM_BLOCK); + + if (block_index == GL_INVALID_INDEX) { + std::cout << "Waterfall: Block name could not be found." << std::endl; + } + + // Bind the buffer to a binding point. + glBindBufferBase(GL_UNIFORM_BUFFER, BINDING_POINT, ubo); + + // Link the UBO to the shader’s uniform block + glUniformBlockBinding(program, block_index, BINDING_POINT); +} + +void Waterfall::draw() const { + + // Bind the buffer before updating it. + glBindBuffer(GL_UNIFORM_BUFFER, ubo); + // Assign values to the `balls` array and update the UBO data + glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(balls), balls); + + // Upload the number of balls. + glUniform1i(glGetUniformLocation(program, "num_balls"), NUM_BALLS); + + DrawModel(model, program, "in_Position", "in_Normal", "in_TexCoord"); + printError("Waterfall draw"); +} + +void Waterfall::move_waterfall_balls() { + // Moves the balls one distance in their velocity. If they fall + // below 0 (end of the waterfall) their height is reset of waterfall + // height. + + for (int i{0}; i < NUM_BALLS; ++i) { + balls[i].pos += ball_velocities[i]; + + // TODO: check if balls are outside in x and z plane aswell + // maybe just generate a new ball if it falls outside + if (balls[i].pos.y < 0) { + balls[i].pos.y = y.second; + } + } +} + +float lerp(float a, float b, float t) { return a + t * (b - a); } + +void Waterfall::gen_balls() { + for (int i{0}; i < NUM_BALLS; ++i) { + + // pseudo random value between 0.01 and 0.1 + float radius = ((rand() / (float)RAND_MAX) * 0.9 + 0.1) / 10.0; + + // pseudo random value between min-radius and max-radius + float pos_x = lerp(x.first, x.second, (rand() / (float)RAND_MAX)); + float pos_y = lerp(y.first, y.second, (rand() / (float)RAND_MAX)); + float pos_z = lerp(z.first, z.second, (rand() / (float)RAND_MAX)); + vec4 pos = vec4(pos_x, pos_y, pos_z, 0); + + // pseudo random value between -0.001 and -0.01 + float velocity_y = -((rand() / (float)RAND_MAX) * 0.9 + 0.1) / 50.0; + vec4 velocity = vec4(0, velocity_y, 0, 0); + + balls[i] = Waterfall::Ball(pos, radius); + ball_velocities[i] = velocity; + } + // balls[0].pos = vec4(3.2, 1.0, 0, 0); + // balls[0].radius = 0.55; + // ball_velocities[0] = vec4(0); +} diff --git a/src/waterfall.h b/src/waterfall.h new file mode 100644 index 0000000000000000000000000000000000000000..76f849ac1fadf9b9c2d39f7d4b38ec3d89ecab30 --- /dev/null +++ b/src/waterfall.h @@ -0,0 +1,41 @@ +#pragma once + +#include "VectorUtils4.h" +#include "object.h" + +struct Waterfall : Object { + + Waterfall() = default; + Waterfall(Model *model, GLuint program); + void move_waterfall_balls(); + void gen_balls(); + void draw() const; + + static GLuint const BINDING_POINT{0}; + char const *UNIFORM_BLOCK = "BallBuffer"; + static int const NUM_BALLS{30}; + + // Dimensions of the waterfall + // on the form <min, max> + std::pair<float, float> x; + std::pair<float, float> y; + std::pair<float, float> z; + + // Properties of each ball + struct Ball { + vec4 pos; + float radius; + + // Glsls padding to make struct a multiple of vec4 (cringe) + vec3 padding; + + Ball(vec3 pos, float radius) : pos{pos}, radius{radius} {} + Ball() : pos{0, 0, 0, 0}, radius{0} {} + }; + + Ball balls[NUM_BALLS]; + vec4 ball_velocities[NUM_BALLS]; + + GLuint ubo; + GLuint block_index; +}; diff --git a/textures/autumn_field_puresky_4k.hdr b/textures/autumn_field_puresky_4k.hdr new file mode 100644 index 0000000000000000000000000000000000000000..494be719db2d0523f390fa20d9c2575938455cb0 Binary files /dev/null and b/textures/autumn_field_puresky_4k.hdr differ diff --git a/textures/dirt.tga b/textures/dirt.tga new file mode 100644 index 0000000000000000000000000000000000000000..0b87bfe1c249e6b235d4ef3afc6380db70e2f260 Binary files /dev/null and b/textures/dirt.tga differ diff --git a/textures/grass.tga b/textures/grass.tga new file mode 100644 index 0000000000000000000000000000000000000000..841a4ecb5fa5ce8a3aa616491637eb655f5251b6 Binary files /dev/null and b/textures/grass.tga differ