From b820c89bd0967acf688413d0254e0c3a52a6df64 Mon Sep 17 00:00:00 2001 From: yggdrasil75 Date: Sun, 18 Jan 2026 20:44:30 -0500 Subject: [PATCH] pushing some additional features --- util/grid/grid3.hpp | 2 +- util/grid/shapegens.hpp | 530 ++++++++++++++++++++++++++++++++++++++ util/noise/pnoise2.hpp | 202 +++++++++++++-- util/vectorlogic/vec2.hpp | 5 + util/vectorlogic/vec3.hpp | 23 ++ 5 files changed, 735 insertions(+), 27 deletions(-) create mode 100644 util/grid/shapegens.hpp diff --git a/util/grid/grid3.hpp b/util/grid/grid3.hpp index 42a811a..06677a3 100644 --- a/util/grid/grid3.hpp +++ b/util/grid/grid3.hpp @@ -200,7 +200,6 @@ public: class VoxelGrid { private: - double binSize = 1; Vec3i gridSize; //int width, height, depth; std::vector voxels; @@ -212,6 +211,7 @@ private: } public: + double binSize = 1; VoxelGrid() : gridSize(0,0,0) { std::cout << "creating empty grid." << std::endl; } diff --git a/util/grid/shapegens.hpp b/util/grid/shapegens.hpp new file mode 100644 index 0000000..70263fc --- /dev/null +++ b/util/grid/shapegens.hpp @@ -0,0 +1,530 @@ +#ifndef VOXEL_GENERATORS_HPP +#define VOXEL_GENERATORS_HPP + +#include "grid3.hpp" +#include +#include +#include +#include "../noise/pnoise2.hpp" +#include "../vectorlogic/vec3.hpp" +#include + +class VoxelGenerators { +public: + // Basic Primitive Generators + static void createSphere(VoxelGrid& grid, const Vec3f& center, float radius, + const Vec3ui8& color = Vec3ui8(255, 255, 255), + bool filled = true) { + TIME_FUNCTION; + + Vec3i gridCenter = (center / grid.binSize).floorToI(); + Vec3i radiusVoxels = Vec3i(static_cast(radius / grid.binSize)); + + Vec3i minBounds = gridCenter - radiusVoxels; + Vec3i maxBounds = gridCenter + radiusVoxels; + + // Ensure bounds are within grid + minBounds = minBounds.max(Vec3i(0, 0, 0)); + maxBounds = maxBounds.min(Vec3i(grid.getWidth() - 1, grid.getHeight() - 1, grid.getDepth() - 1)); + + float radiusSq = radius * radius; + + for (int z = minBounds.z; z <= maxBounds.z; ++z) { + for (int y = minBounds.y; y <= maxBounds.y; ++y) { + for (int x = minBounds.x; x <= maxBounds.x; ++x) { + Vec3f voxelCenter(x * grid.binSize, y * grid.binSize, z * grid.binSize); + Vec3f delta = voxelCenter - center; + float distanceSq = delta.lengthSquared(); + + if (filled) { + // Solid sphere + if (distanceSq <= radiusSq) { + grid.set(Vec3i(x, y, z), true, color); + } + } else { + // Hollow sphere (shell) + float shellThickness = grid.binSize; + if (distanceSq <= radiusSq && distanceSq >= (radius - shellThickness) * (radius - shellThickness)) { + grid.set(Vec3i(x, y, z), true, color); + } + } + } + } + } + + grid.clearMeshCache(); + } + + static void createCube(VoxelGrid& grid, const Vec3f& center, const Vec3f& size, + const Vec3ui8& color = Vec3ui8(255, 255, 255), + bool filled = true) { + TIME_FUNCTION; + + Vec3f halfSize = size * 0.5f; + Vec3f minPos = center - halfSize; + Vec3f maxPos = center + halfSize; + + Vec3i minVoxel = (minPos / grid.binSize).floorToI(); + Vec3i maxVoxel = (maxPos / grid.binSize).floorToI(); + + // Clamp to grid bounds + minVoxel = minVoxel.max(Vec3i(0, 0, 0)); + maxVoxel = maxVoxel.min(Vec3i(grid.getWidth() - 1, grid.getHeight() - 1, grid.getDepth() - 1)); + + if (filled) { + // Solid cube + for (int z = minVoxel.z; z <= maxVoxel.z; ++z) { + for (int y = minVoxel.y; y <= maxVoxel.y; ++y) { + for (int x = minVoxel.x; x <= maxVoxel.x; ++x) { + grid.set(Vec3i(x, y, z), true, color); + } + } + } + } else { + // Hollow cube (just the faces) + for (int z = minVoxel.z; z <= maxVoxel.z; ++z) { + for (int y = minVoxel.y; y <= maxVoxel.y; ++y) { + for (int x = minVoxel.x; x <= maxVoxel.x; ++x) { + // Check if on any face + bool onFace = (x == minVoxel.x || x == maxVoxel.x || + y == minVoxel.y || y == maxVoxel.y || + z == minVoxel.z || z == maxVoxel.z); + + if (onFace) { + grid.set(Vec3i(x, y, z), true, color); + } + } + } + } + } + + grid.clearMeshCache(); + } + + static void createCylinder(VoxelGrid& grid, const Vec3f& center, float radius, float height, + const Vec3ui8& color = Vec3ui8(255, 255, 255), + bool filled = true, int axis = 1) { // 0=X, 1=Y, 2=Z + TIME_FUNCTION; + + Vec3f halfHeight = Vec3f(0, 0, 0); + halfHeight[axis] = height * 0.5f; + + Vec3f minPos = center - halfHeight; + Vec3f maxPos = center + halfHeight; + + Vec3i minVoxel = (minPos / grid.binSize).floorToI(); + Vec3i maxVoxel = (maxPos / grid.binSize).floorToI(); + + minVoxel = minVoxel.max(Vec3i(0, 0, 0)); + maxVoxel = maxVoxel.min(Vec3i(grid.getWidth() - 1, grid.getHeight() - 1, grid.getDepth() - 1)); + + float radiusSq = radius * radius; + + for (int k = minVoxel[axis]; k <= maxVoxel[axis]; ++k) { + // Iterate through the other two dimensions + for (int j = minVoxel[(axis + 1) % 3]; j <= maxVoxel[(axis + 1) % 3]; ++j) { + for (int i = minVoxel[(axis + 2) % 3]; i <= maxVoxel[(axis + 2) % 3]; ++i) { + Vec3i pos; + pos[axis] = k; + pos[(axis + 1) % 3] = j; + pos[(axis + 2) % 3] = i; + + Vec3f voxelCenter = pos.toFloat() * grid.binSize; + + // Calculate distance from axis + float dx = voxelCenter.x - center.x; + float dy = voxelCenter.y - center.y; + float dz = voxelCenter.z - center.z; + + // Zero out the axis component + if (axis == 0) dx = 0; + else if (axis == 1) dy = 0; + else dz = 0; + + float distanceSq = dx*dx + dy*dy + dz*dz; + + if (filled) { + if (distanceSq <= radiusSq) { + grid.set(pos, true, color); + } + } else { + float shellThickness = grid.binSize; + if (distanceSq <= radiusSq && + distanceSq >= (radius - shellThickness) * (radius - shellThickness)) { + grid.set(pos, true, color); + } + } + } + } + } + + grid.clearMeshCache(); + } + + static void createCone(VoxelGrid& grid, const Vec3f& baseCenter, float radius, float height, + const Vec3ui8& color = Vec3ui8(255, 255, 255), + bool filled = true, int axis = 1) { // 0=X, 1=Y, 2=Z + TIME_FUNCTION; + + Vec3f tip = baseCenter; + tip[axis] += height; + + Vec3f minPos = baseCenter.min(tip); + Vec3f maxPos = baseCenter.max(tip); + + // Expand by radius in other dimensions + for (int i = 0; i < 3; ++i) { + if (i != axis) { + minPos[i] -= radius; + maxPos[i] += radius; + } + } + + Vec3i minVoxel = (minPos / grid.binSize).floorToI(); + Vec3i maxVoxel = (maxPos / grid.binSize).floorToI(); + + minVoxel = minVoxel.max(Vec3i(0, 0, 0)); + maxVoxel = maxVoxel.min(Vec3i(grid.getWidth() - 1, grid.getHeight() - 1, grid.getDepth() - 1)); + + for (int k = minVoxel[axis]; k <= maxVoxel[axis]; ++k) { + // Current height from base + float h = (k * grid.binSize) - baseCenter[axis]; + if (h < 0 || h > height) continue; + + // Current radius at this height + float currentRadius = radius * (1.0f - h / height); + + for (int j = minVoxel[(axis + 1) % 3]; j <= maxVoxel[(axis + 1) % 3]; ++j) { + for (int i = minVoxel[(axis + 2) % 3]; i <= maxVoxel[(axis + 2) % 3]; ++i) { + Vec3i pos; + pos[axis] = k; + pos[(axis + 1) % 3] = j; + pos[(axis + 2) % 3] = i; + + Vec3f voxelCenter = pos.toFloat() * grid.binSize; + + // Calculate distance from axis + float dx = voxelCenter.x - baseCenter.x; + float dy = voxelCenter.y - baseCenter.y; + float dz = voxelCenter.z - baseCenter.z; + + // Zero out the axis component + if (axis == 0) dx = 0; + else if (axis == 1) dy = 0; + else dz = 0; + + float distanceSq = dx*dx + dy*dy + dz*dz; + + if (filled) { + if (distanceSq <= currentRadius * currentRadius) { + grid.set(pos, true, color); + } + } else { + float shellThickness = grid.binSize; + if (distanceSq <= currentRadius * currentRadius && + distanceSq >= (currentRadius - shellThickness) * (currentRadius - shellThickness)) { + grid.set(pos, true, color); + } + } + } + } + } + + grid.clearMeshCache(); + } + + static void createTorus(VoxelGrid& grid, const Vec3f& center, float majorRadius, float minorRadius, + const Vec3ui8& color = Vec3ui8(255, 255, 255)) { + TIME_FUNCTION; + + float outerRadius = majorRadius + minorRadius; + Vec3f minPos = center - Vec3f(outerRadius, outerRadius, minorRadius); + Vec3f maxPos = center + Vec3f(outerRadius, outerRadius, minorRadius); + + Vec3i minVoxel = (minPos / grid.binSize).floorToI(); + Vec3i maxVoxel = (maxPos / grid.binSize).floorToI(); + + minVoxel = minVoxel.max(Vec3i(0, 0, 0)); + maxVoxel = maxVoxel.min(Vec3i(grid.getWidth() - 1, grid.getHeight() - 1, grid.getDepth() - 1)); + + for (int z = minVoxel.z; z <= maxVoxel.z; ++z) { + for (int y = minVoxel.y; y <= maxVoxel.y; ++y) { + for (int x = minVoxel.x; x <= maxVoxel.x; ++x) { + Vec3f pos(x * grid.binSize, y * grid.binSize, z * grid.binSize); + Vec3f delta = pos - center; + + // Torus equation: (sqrt(x² + y²) - R)² + z² = r² + float xyDist = std::sqrt(delta.x * delta.x + delta.y * delta.y); + float distToCircle = xyDist - majorRadius; + float distanceSq = distToCircle * distToCircle + delta.z * delta.z; + + if (distanceSq <= minorRadius * minorRadius) { + grid.set(Vec3i(x, y, z), true, color); + } + } + } + } + + grid.clearMeshCache(); + } + + // Procedural Generators + static void createPerlinNoiseTerrain(VoxelGrid& grid, float frequency = 0.1f, float amplitude = 10.0f, + int octaves = 4, float persistence = 0.5f, + const Vec3ui8& baseColor = Vec3ui8(34, 139, 34)) { + TIME_FUNCTION; + + if (grid.getHeight() < 1) return; + + PNoise2 noise; + + for (int z = 0; z < grid.getDepth(); ++z) { + for (int x = 0; x < grid.getWidth(); ++x) { + // Generate height value using Perlin noise + float heightValue = 0.0f; + float freq = frequency; + float amp = amplitude; + + for (int octave = 0; octave < octaves; ++octave) { + float nx = x * freq / 100.0f; + float nz = z * freq / 100.0f; + heightValue += noise.permute(Vec2f(nx, nz)) * amp; + + freq *= 2.0f; + amp *= persistence; + } + + // Normalize and scale to grid height + int terrainHeight = static_cast((heightValue + amplitude) / (2.0f * amplitude) * grid.getHeight()); + terrainHeight = std::max(0, std::min(grid.getHeight() - 1, terrainHeight)); + + // Create column of voxels + for (int y = 0; y <= terrainHeight; ++y) { + // Color gradient based on height + float t = static_cast(y) / grid.getHeight(); + Vec3ui8 color = baseColor; + + // Add some color variation + if (t < 0.3f) { + // Water level + color = Vec3ui8(30, 144, 255); + } else if (t < 0.5f) { + // Sand + color = Vec3ui8(238, 214, 175); + } else if (t < 0.8f) { + // Grass + color = baseColor; + } else { + // Snow + color = Vec3ui8(255, 250, 250); + } + + grid.set(Vec3i(x, y, z), true, color); + } + } + } + + grid.clearMeshCache(); + } + + static void createMengerSponge(VoxelGrid& grid, int iterations = 3, + const Vec3ui8& color = Vec3ui8(255, 255, 255)) { + TIME_FUNCTION; + + // Start with a solid cube + createCube(grid, + Vec3f(grid.getWidth() * grid.binSize * 0.5f, + grid.getHeight() * grid.binSize * 0.5f, + grid.getDepth() * grid.binSize * 0.5f), + Vec3f(grid.getWidth() * grid.binSize, + grid.getHeight() * grid.binSize, + grid.getDepth() * grid.binSize), + color, true); + + // Apply Menger sponge iteration + for (int iter = 0; iter < iterations; ++iter) { + int divisor = static_cast(std::pow(3, iter + 1)); + + // Calculate the pattern + for (int z = 0; z < grid.getDepth(); ++z) { + for (int y = 0; y < grid.getHeight(); ++y) { + for (int x = 0; x < grid.getWidth(); ++x) { + // Check if this voxel should be removed in this iteration + int modX = x % divisor; + int modY = y % divisor; + int modZ = z % divisor; + + int third = divisor / 3; + + // Remove center cubes + if ((modX >= third && modX < 2 * third) && + (modY >= third && modY < 2 * third)) { + grid.set(Vec3i(x, y, z), false, color); + } + if ((modX >= third && modX < 2 * third) && + (modZ >= third && modZ < 2 * third)) { + grid.set(Vec3i(x, y, z), false, color); + } + if ((modY >= third && modY < 2 * third) && + (modZ >= third && modZ < 2 * third)) { + grid.set(Vec3i(x, y, z), false, color); + } + } + } + } + } + + grid.clearMeshCache(); + } + + // Helper function to check if a point is inside a polygon (for 2D shapes) + static bool pointInPolygon(const Vec2f& point, const std::vector& polygon) { + bool inside = false; + size_t n = polygon.size(); + + for (size_t i = 0, j = n - 1; i < n; j = i++) { + if (((polygon[i].y > point.y) != (polygon[j].y > point.y)) && + (point.x < (polygon[j].x - polygon[i].x) * (point.y - polygon[i].y) / + (polygon[j].y - polygon[i].y) + polygon[i].x)) { + inside = !inside; + } + } + + return inside; + } + + // Utah Teapot (simplified voxel approximation) + static void createTeapot(VoxelGrid& grid, const Vec3f& position, float scale = 1.0f, + const Vec3ui8& color = Vec3ui8(200, 200, 200)) { + TIME_FUNCTION; + + // Simplified teapot using multiple primitive components + Vec3f center = position; + + // Body (ellipsoid) + createSphere(grid, center, 3.0f * scale, color, false); + + // Spout (rotated cylinder) + Vec3f spoutStart = center + Vec3f(2.0f * scale, 0, 0); + Vec3f spoutEnd = center + Vec3f(4.0f * scale, 1.5f * scale, 0); + createCylinderBetween(grid, spoutStart, spoutEnd, 0.5f * scale, color, true); + + // Handle (semi-circle) + Vec3f handleStart = center + Vec3f(-2.0f * scale, 0, 0); + Vec3f handleEnd = center + Vec3f(-3.0f * scale, 2.0f * scale, 0); + createCylinderBetween(grid, handleStart, handleEnd, 0.4f * scale, color, true); + + // Lid (small cylinder on top) + Vec3f lidCenter = center + Vec3f(0, 3.0f * scale, 0); + createCylinder(grid, lidCenter, 1.0f * scale, 0.5f * scale, color, true, 1); + + grid.clearMeshCache(); + } + + static void createCylinderBetween(VoxelGrid& grid, const Vec3f& start, const Vec3f& end, float radius, + const Vec3ui8& color, bool filled = true) { + TIME_FUNCTION; + + Vec3f direction = (end - start).normalized(); + float length = (end - start).length(); + + // Create local coordinate system + Vec3f up(0, 1, 0); + if (std::abs(direction.dot(up)) > 0.99f) { + up = Vec3f(1, 0, 0); + } + + Vec3f right = direction.cross(up).normalized(); + Vec3f localUp = right.cross(direction).normalized(); + + Vec3f minPos = start.min(end) - Vec3f(radius, radius, radius); + Vec3f maxPos = start.max(end) + Vec3f(radius, radius, radius); + + Vec3i minVoxel = (minPos / grid.binSize).floorToI(); + Vec3i maxVoxel = (maxPos / grid.binSize).floorToI(); + + minVoxel = minVoxel.max(Vec3i(0, 0, 0)); + maxVoxel = maxVoxel.min(Vec3i(grid.getWidth() - 1, grid.getHeight() - 1, grid.getDepth() - 1)); + + float radiusSq = radius * radius; + + for (int z = minVoxel.z; z <= maxVoxel.z; ++z) { + for (int y = minVoxel.y; y <= maxVoxel.y; ++y) { + for (int x = minVoxel.x; x <= maxVoxel.x; ++x) { + Vec3f voxelPos(x * grid.binSize, y * grid.binSize, z * grid.binSize); + + // Project point onto cylinder axis + Vec3f toPoint = voxelPos - start; + float t = toPoint.dot(direction); + + // Check if within cylinder length + if (t < 0 || t > length) continue; + + Vec3f projected = start + direction * t; + Vec3f delta = voxelPos - projected; + float distanceSq = delta.lengthSquared(); + + if (filled) { + if (distanceSq <= radiusSq) { + grid.set(Vec3i(x, y, z), true, color); + } + } else { + float shellThickness = grid.binSize; + if (distanceSq <= radiusSq && + distanceSq >= (radius - shellThickness) * (radius - shellThickness)) { + grid.set(Vec3i(x, y, z), true, color); + } + } + } + } + } + } + + // Generate from mathematical function + template + static void createFromFunction(VoxelGrid& grid, Func func, + const Vec3f& minBounds, const Vec3f& maxBounds, + float threshold = 0.5f, + const Vec3ui8& color = Vec3ui8(255, 255, 255)) { + TIME_FUNCTION; + + Vec3i minVoxel = (minBounds / grid.binSize).floorToI(); + Vec3i maxVoxel = (maxBounds / grid.binSize).floorToI(); + + minVoxel = minVoxel.max(Vec3i(0, 0, 0)); + maxVoxel = maxVoxel.min(Vec3i(grid.getWidth() - 1, grid.getHeight() - 1, grid.getDepth() - 1)); + + for (int z = minVoxel.z; z <= maxVoxel.z; ++z) { + for (int y = minVoxel.y; y <= maxVoxel.y; ++y) { + for (int x = minVoxel.x; x <= maxVoxel.x; ++x) { + Vec3f pos(x * grid.binSize, y * grid.binSize, z * grid.binSize); + + float value = func(pos.x, pos.y, pos.z); + + if (value > threshold) { + grid.set(Vec3i(x, y, z), true, color); + } + } + } + } + + grid.clearMeshCache(); + } + + // Example mathematical functions + static float sphereFunction(float x, float y, float z) { + return 1.0f - (x*x + y*y + z*z); + } + + static float torusFunction(float x, float y, float z, float R = 2.0f, float r = 1.0f) { + float d = std::sqrt(x*x + y*y) - R; + return r*r - d*d - z*z; + } + + static float gyroidFunction(float x, float y, float z, float scale = 0.5f) { + x *= scale; y *= scale; z *= scale; + return std::sin(x) * std::cos(y) + std::sin(y) * std::cos(z) + std::sin(z) * std::cos(x); + } +}; + +#endif \ No newline at end of file diff --git a/util/noise/pnoise2.hpp b/util/noise/pnoise2.hpp index 27f1fe9..68d50db 100644 --- a/util/noise/pnoise2.hpp +++ b/util/noise/pnoise2.hpp @@ -52,14 +52,21 @@ private: /// @note Vector selection affects 3D noise patterns Vec3ui8 GetConstantVector3(int v) { int h = v & 7; - if (h == 0) return Vec3ui8(1,1,1); - else if (h == 1) return Vec3ui8(-1,1, 1); - else if (h == 2) return Vec3ui8(-1,-1, 1); - else if (h == 3) return Vec3ui8(-1,-1, 1); - else if (h == 4) return Vec3ui8(-1,-1,-1); - else if (h == 5) return Vec3ui8(-1,-1, -1); - else if (h == 6) return Vec3ui8(-1,-1, -1); - else return Vec3ui8(1,-1, -1); + switch(h) { + case 0: return Vec3ui8( 1, 1, 0); + case 1: return Vec3ui8(-1, 1, 0); + case 2: return Vec3ui8( 1,-1, 0); + case 3: return Vec3ui8(-1,-1, 0); + case 4: return Vec3ui8( 1, 0, 1); + case 5: return Vec3ui8(-1, 0, 1); + case 6: return Vec3ui8( 1, 0,-1); + case 7: return Vec3ui8(-1, 0,-1); + case 8: return Vec3ui8( 0, 1, 1); + case 9: return Vec3ui8( 0,-1, 1); + case 10: return Vec3ui8( 0, 1,-1); + case 11: return Vec3ui8( 0,-1,-1); + default: return Vec3ui8(0,0,0); + } } /// @brief Gradient function for 2D/3D Perlin noise @@ -98,6 +105,15 @@ private: return (permute(point) + 1.0f) * 0.5f; } + /// @brief Normalize 3D noise value from [-1,1] to [0,1] + /// @tparam T Coordinate type + /// @param point Input coordinate + /// @return Normalized noise value in [0,1] range + template + float normalizedNoise(const Vec3& point) { + return (permute(point) + 1.0f) * 0.5f; + } + /// @brief Map value from one range to another /// @param value Input value /// @param inMin Original range minimum @@ -147,6 +163,16 @@ private: return (permutation[(x + permutation[y & 255]) & 255] / 255.0f) * 2.0f - 1.0f; } + /// @brief Hash function for 3D coordinates + /// @param x X coordinate integer + /// @param y Y coordinate integer + /// @param z Z coordinate integer + /// @return Hash value in [-1,1] range + /// @note 3D version of hash function + float hash(int x, int y, int z) { + return (permutation[(z + permutation[(y + permutation[x & 255]) & 255]) & 255] / 255.0f) * 2.0f - 1.0f; + } + public: /// @brief Default constructor with random seed /// @note Uses random_device for seed; different runs produce different noise @@ -178,20 +204,20 @@ public: float xf = point.x - X; float yf = point.y - Y; - Vec2 BL = Vec2(xf-0, yf-0); - Vec2 BR = Vec2(xf-1, yf-0); - Vec2 TL = Vec2(xf-0, yf-1); - Vec2 TR = Vec2(xf-1, yf-1); + Vec2f BL = Vec2f(xf-0, yf-0); + Vec2f BR = Vec2f(xf-1, yf-0); + Vec2f TL = Vec2f(xf-0, yf-1); + Vec2f TR = Vec2f(xf-1, yf-1); int vBL = permutation[permutation[xmod+0]+ymod+0]; int vBR = permutation[permutation[xmod+1]+ymod+0]; int vTL = permutation[permutation[xmod+0]+ymod+1]; int vTR = permutation[permutation[xmod+1]+ymod+1]; - float dBL = BL.dot(GetConstantVector(vBL)); - float dBR = BR.dot(GetConstantVector(vBR)); - float dTL = TL.dot(GetConstantVector(vTL)); - float dTR = TR.dot(GetConstantVector(vTR)); + // float dBL = BL.dot(GetConstantVector(vBL)); + // float dBR = BR.dot(GetConstantVector(vBR)); + // float dTL = TL.dot(GetConstantVector(vTL)); + // float dTR = TR.dot(GetConstantVector(vTR)); float u = fade(xf); float v = fade(yf); @@ -238,15 +264,15 @@ public: int vRTL = permutation[permutation[permutation[Z+1]+X+0]+Y+1]; int vRTR = permutation[permutation[permutation[Z+1]+X+1]+Y+1]; - float dFBL = FBL.dot(GetConstantVector3(vFBL)); - float dFBR = FBR.dot(GetConstantVector3(vFBR)); - float dFTL = FTL.dot(GetConstantVector3(vFTL)); - float dFTR = FTR.dot(GetConstantVector3(vFTR)); + // float dFBL = FBL.dot(GetConstantVector3(vFBL)); + // float dFBR = FBR.dot(GetConstantVector3(vFBR)); + // float dFTL = FTL.dot(GetConstantVector3(vFTL)); + // float dFTR = FTR.dot(GetConstantVector3(vFTR)); - float dRBL = RBL.dot(GetConstantVector3(vRBL)); - float dRBR = RBR.dot(GetConstantVector3(vRBR)); - float dRTL = RTL.dot(GetConstantVector3(vRTL)); - float dRTR = RTR.dot(GetConstantVector3(vRTR)); + // float dRBL = RBL.dot(GetConstantVector3(vRBL)); + // float dRBR = RBR.dot(GetConstantVector3(vRBR)); + // float dRTL = RTL.dot(GetConstantVector3(vRTL)); + // float dRTR = RTR.dot(GetConstantVector3(vRTR)); float u = fade(xf); float v = fade(yf); @@ -298,6 +324,51 @@ public: return lerp(nx0, nx1, sy); } + /// @brief Generate 3D value noise + /// @param point 3D coordinate + /// @return Noise value in [-1,1] range + float valueNoise(const Vec3& point) { + int xi = (int)std::floor(point.x); + int yi = (int)std::floor(point.y); + int zi = (int)std::floor(point.z); + + float tx = point.x - xi; + float ty = point.y - yi; + float tz = point.z - zi; + + int rx0 = xi & 255; + int rx1 = (xi + 1) & 255; + int ry0 = yi & 255; + int ry1 = (yi + 1) & 255; + int rz0 = zi & 255; + int rz1 = (zi + 1) & 255; + + // Random values at corners + float c000 = hash(rx0, ry0, rz0); + float c100 = hash(rx1, ry0, rz0); + float c010 = hash(rx0, ry1, rz0); + float c110 = hash(rx1, ry1, rz0); + float c001 = hash(rx0, ry0, rz1); + float c101 = hash(rx1, ry0, rz1); + float c011 = hash(rx0, ry1, rz1); + float c111 = hash(rx1, ry1, rz1); + + // Interpolation + float sx = fade(tx); + float sy = fade(ty); + float sz = fade(tz); + + float nx00 = lerp(c000, c100, sx); + float nx10 = lerp(c010, c110, sx); + float nx01 = lerp(c001, c101, sx); + float nx11 = lerp(c011, c111, sx); + + float ny0 = lerp(nx00, nx10, sy); + float ny1 = lerp(nx01, nx11, sy); + + return lerp(ny0, ny1, sz); + } + /// @brief Generate RGBA color from 3D noise with offset channels /// @param point 3D coordinate /// @return Vec4ui8 containing RGBA noise values @@ -348,6 +419,29 @@ public: return total / maxV; } + /// @brief Generate 3D fractal (octave) noise + /// @tparam T Coordinate type + /// @param point Input coordinate + /// @param octaves Number of noise layers + /// @param persistence Amplitude multiplier per octave + /// @param lacunarity Frequency multiplier per octave + /// @return Combined noise value + template + float fractalNoise(const Vec3& point, int octaves, float persistence, float lacunarity) { + float total = 0.0f; + float frequency = 1.f; + float amplitude = 1.f; + float maxV = 0.f; + for (int i = 0; i < octaves; i++) { + total += permute(point*frequency) * amplitude; + maxV += amplitude; + amplitude *= persistence; + frequency *= lacunarity; + } + + return total / maxV; + } + /// @brief Generate turbulence noise (absolute value of octaves) /// @tparam T Coordinate type /// @param point Input coordinate @@ -357,9 +451,25 @@ public: template float turbulence(const Vec2& point, int octaves) { float value = 0.0f; - Vec2 tempPoint = point; + Vec2f tempPoint = point.toFloat(); for (int i = 0; i < octaves; i++) { - value += std::abs(permute(tempPoint)); + value += std::abs(permute(tempPoint)) / (1 << i); + tempPoint *= 2.f; + } + return value; + } + + /// @brief Generate 3D turbulence noise + /// @tparam T Coordinate type + /// @param point Input coordinate + /// @param octaves Number of noise layers + /// @return Turbulence noise value + template + float turbulence(const Vec3& point, int octaves) { + float value = 0.0f; + Vec3f tempPoint = point.toFloat(); + for (int i = 0; i < octaves; i++) { + value += std::abs(permute(tempPoint)) / (1 << i); tempPoint *= 2.f; } return value; @@ -388,6 +498,28 @@ public: return result; } + /// @brief Generate 3D ridged noise + /// @param point Input coordinate + /// @param octaves Number of noise layers + /// @param offset Weighting offset for ridge formation + /// @return Ridged noise value + float ridgedNoise(const Vec3& point, int octaves, float offset = 1.0f) { + float result = 0.f; + float weight = 1.f; + Vec3 p = point; + + for (int i = 0; i < octaves; i++) { + float signal = 1.f - std::abs(permute(p)); + signal *= signal; + signal *= weight; + weight = signal * offset; + result += signal; + p *= 2.f; + } + + return result; + } + /// @brief Generate billow (cloud-like) noise /// @param point Input coordinate /// @param octaves Number of noise layers @@ -406,6 +538,24 @@ public: return value; } + + /// @brief Generate 3D billow noise + /// @param point Input coordinate + /// @param octaves Number of noise layers + /// @return Billow noise value + float billowNoise(const Vec3& point, int octaves) { + float value = 0.0f; + float amplitude = 1.0f; + float frequency = 1.0f; + + for (int i = 0; i < octaves; i++) { + value += std::abs(permute(point * frequency)) * amplitude; + amplitude *= 0.5f; + frequency *= 2.0f; + } + + return value; + } }; #endif \ No newline at end of file diff --git a/util/vectorlogic/vec2.hpp b/util/vectorlogic/vec2.hpp index fe95223..1785958 100644 --- a/util/vectorlogic/vec2.hpp +++ b/util/vectorlogic/vec2.hpp @@ -312,6 +312,11 @@ public: float aspect() { return static_cast(x) / static_cast(y); } + + Vec2 toFloat() { + return Vec2(static_cast(x), static_cast(y)); + } + }; template diff --git a/util/vectorlogic/vec3.hpp b/util/vectorlogic/vec3.hpp index b05f293..c31e95d 100644 --- a/util/vectorlogic/vec3.hpp +++ b/util/vectorlogic/vec3.hpp @@ -454,6 +454,29 @@ public: return std::hash()(v.x) ^ (std::hash()(v.y) << 1) ^ (std::hash()(v.z) << 2); } }; + + Vec2 toLatLon() const { + float r = length(); + if (r == 0) return Vec2(0, 0); + float θ = std::acos(z / r); + float lat = static_cast(M_PI/2.0 - θ); + + float lon = static_cast(std::atan2(y, x)); + return Vec2(lat, lon); + } + + Vec2 toLatLon(const Vec3& center) const { + Vec3 relative = *this - center; + return relative.toLatLon(); + } + + T toElevation() const { + return length(); + } + + T toElevation(const Vec3& center) const { + return distance(center); + } }; using Vec3f = Vec3;