diff --git a/src/basics/Disk.cpp b/src/basics/Disk.cpp index 3b1cbba..e90146e 100644 --- a/src/basics/Disk.cpp +++ b/src/basics/Disk.cpp @@ -2,10 +2,6 @@ #include "PackStream.h" -Disk::Disk() -{ -} - Disk::Disk(const Vector3 &point, const Vector3 &normal, double radius): InfinitePlane(point, normal), radius(radius) { diff --git a/src/basics/Disk.h b/src/basics/Disk.h index 5493314..738c2bf 100644 --- a/src/basics/Disk.h +++ b/src/basics/Disk.h @@ -14,7 +14,7 @@ namespace basics { class BASICSSHARED_EXPORT Disk: public InfinitePlane { public: - Disk(); + Disk() = default; Disk(const Vector3 &point, const Vector3 &normal, double radius); inline double getRadius() const {return radius;} diff --git a/src/basics/SpaceSegment.cpp b/src/basics/SpaceSegment.cpp index bfec17e..783a515 100644 --- a/src/basics/SpaceSegment.cpp +++ b/src/basics/SpaceSegment.cpp @@ -2,6 +2,7 @@ #include #include "SpaceGridIterator.h" +using namespace std; SpaceSegment::SpaceSegment(const Vector3& start, const Vector3& end): start(start), end(end) @@ -58,6 +59,44 @@ bool SpaceSegment::intersectYInterval(double ymin, double ymax) return true; } +bool SpaceSegment::intersectBoundingBox(const SpaceSegment &bbox) const +{ + Vector3 dir = getDirection(); + // r.dir is unit direction vector of ray + double dfx = 1.0 / dir.x; + double dfy = 1.0 / dir.y; + double dfz = 1.0 / dir.z; + // lb is the corner of AABB with minimal coordinates - left bottom, rt is maximal corner + // r.org is origin of ray + double t1 = (bbox.start.x - start.x) * dfx; + double t2 = (bbox.end.x - start.x) * dfx; + double t3 = (bbox.start.y - start.y) * dfy; + double t4 = (bbox.end.y - start.y) * dfy; + double t5 = (bbox.start.z - start.z) * dfz; + double t6 = (bbox.end.z - start.z) * dfz; + + double tmin = max(max(min(t1, t2), min(t3, t4)), min(t5, t6)); + double tmax = min(min(max(t1, t2), max(t3, t4)), max(t5, t6)); + + // if tmax < 0, ray (line) is intersecting AABB, but whole AABB is behing us + double t; + if (tmax < 0.0) + { + t = tmax; + return false; + } + + // if tmin > tmax, ray doesn't intersect AABB + if (tmin > tmax) + { + t = tmax; + return false; + } + + t = tmin; + return true; +} + SpaceSegment SpaceSegment::projectedOnXPlane(double x) const { return SpaceSegment(Vector3(x, start.y, start.z), Vector3(x, end.y, end.z)); diff --git a/src/basics/SpaceSegment.h b/src/basics/SpaceSegment.h index e6e0f4a..53aea88 100644 --- a/src/basics/SpaceSegment.h +++ b/src/basics/SpaceSegment.h @@ -33,6 +33,11 @@ public: */ bool intersectYInterval(double ymin, double ymax); + /** + * Return true if the segment intersects a bounding box, represented by another segment (crossing diagonal). + */ + bool intersectBoundingBox(const SpaceSegment &bbox) const; + /** * Return a version of this segment, projected on a X plane. */ diff --git a/src/definition/VegetationModelDefinition.cpp b/src/definition/VegetationModelDefinition.cpp index 81a32fe..f30baa4 100644 --- a/src/definition/VegetationModelDefinition.cpp +++ b/src/definition/VegetationModelDefinition.cpp @@ -119,7 +119,6 @@ static void addBranchRecurse(std::vector &branches, const Vector if (length > 0.1) { int split_count = 3; - Vector3 new_base = base.add(direction.scale(length)); Matrix4 pivot1 = Matrix4::newRotateAxis(randomizeValue(1.0 - 0.6 * length, 0.9, 1.1), VECTOR_EAST); Vector3 new_direction = pivot1.multPoint(direction); for (int i = 0; i < split_count; i++) @@ -127,7 +126,8 @@ static void addBranchRecurse(std::vector &branches, const Vector Matrix4 pivot2 = Matrix4::newRotateAxis(randomizeValue(M_PI * 2.0 / (double)split_count, 0.9, 1.1), direction); new_direction = pivot2.multPoint(new_direction); - addBranchRecurse(branches, new_base, new_direction, randomizeValue(radius, 0.65, 0.75), randomizeValue(length, 0.55, 0.65)); + Vector3 new_base = base.add(direction.scale(randomizeValue(length, 0.4, 1.0))); + addBranchRecurse(branches, new_base, new_direction, randomizeValue(radius, 0.45, 0.6), randomizeValue(length, 0.55, 0.85)); } } } @@ -140,7 +140,7 @@ void VegetationModelDefinition::randomize() foliage_items.clear(); // Add trunk and branches - addBranchRecurse(solid_volumes, VECTOR_ZERO, VECTOR_UP, 0.05, 0.5); + addBranchRecurse(solid_volumes, VECTOR_ZERO, VECTOR_UP, 0.04, 0.5); // Add foliage groups for (const auto &branch: solid_volumes) @@ -150,7 +150,7 @@ void VegetationModelDefinition::randomize() { double radius = length * 0.5; Vector3 center = branch.getAxis().getOrigin().add(branch.getAxis().getDirection().scale(radius)); - foliage_groups.push_back(Sphere(center, radius * 5.0)); + foliage_groups.push_back(Sphere(center, radius * 3.0)); } } @@ -159,7 +159,7 @@ void VegetationModelDefinition::randomize() { double radius = 0.15; Vector3 dir = Vector3::randomInSphere(1.0 - radius); - Vector3 normal = dir.add(Vector3::randomInSphere(0.4)).add(Vector3(0.0, 0.6, 0.0)).normalize(); + Vector3 normal = dir.add(Vector3::randomInSphere(0.4)).add(Vector3(0.0, 0.3, 0.0)).normalize(); Disk leaf(dir, normal, randomizeValue(radius, 0.8, 1.0)); foliage_items.push_back(leaf); } diff --git a/src/interface/commandline/tests.cpp b/src/interface/commandline/tests.cpp index eff1c13..10d04cd 100644 --- a/src/interface/commandline/tests.cpp +++ b/src/interface/commandline/tests.cpp @@ -17,6 +17,12 @@ #include "LightFilter.h" #include "GodRaysSampler.h" #include "Rasterizer.h" +#include "SpaceSegment.h" +#include "OverlayRasterizer.h" +#include "VegetationModelDefinition.h" +#include "VegetationInstance.h" +#include "VegetationRenderer.h" +#include "RayCastingResult.h" #include @@ -251,6 +257,50 @@ static void testSunNearHorizon() } } +static void testVegetationModels() +{ + class TestRasterizer: public OverlayRasterizer + { + public: + TestRasterizer(SoftwareCanvasRenderer *renderer, const VegetationModelDefinition &model): + OverlayRasterizer(renderer, renderer->getProgressHelper()), + instance(model, VECTOR_ZERO), + vegetation(renderer->getVegetationRenderer()) + { + } + + virtual Color processPixel(int, int, double relx, double rely) const override + { + relx *= 0.75; + rely *= 0.75; + SpaceSegment segment(Vector3(relx, rely + 0.5, -5.0), Vector3(relx, rely + 0.5, 5.0)); + RayCastingResult result = vegetation->renderInstance(segment, instance, false, true); + return result.hit ? result.hit_color : Color(0.6, 0.7, 0.9); + } + + VegetationInstance instance; + VegetationRenderer *vegetation; + }; + + Scenery scenery; + scenery.autoPreset(1); + scenery.getClouds()->clear(); + scenery.getTerrain()->propWaterHeight()->setValue(1.0); + scenery.getCamera()->setTarget(VECTOR_ZERO); + scenery.getCamera()->setLocation(Vector3(0.0, 0.0, -5.0)); + int width = 800; + int height = 800; + + SoftwareCanvasRenderer renderer(&scenery); + renderer.setSize(width, height); + renderer.setQuality(0.5); + + VegetationModelDefinition model(NULL); + renderer.setSoloRasterizer(new TestRasterizer(&renderer, model)); + + startTestRender(&renderer, "vegetation_model_basic"); +} + void runTestSuite() { testGroundShadowQuality(); @@ -260,4 +310,5 @@ void runTestSuite() testNearFrustum(); testCloudsNearGround(); testSunNearHorizon(); + testVegetationModels(); } diff --git a/src/render/software/OverlayRasterizer.cpp b/src/render/software/OverlayRasterizer.cpp new file mode 100644 index 0000000..5fcf056 --- /dev/null +++ b/src/render/software/OverlayRasterizer.cpp @@ -0,0 +1,52 @@ +#include "OverlayRasterizer.h" + +#include "Color.h" +#include "SoftwareRenderer.h" +#include "CameraDefinition.h" +#include "CanvasFragment.h" + +OverlayRasterizer::OverlayRasterizer(SoftwareRenderer *renderer, RenderProgress *progress): + Rasterizer(renderer, progress, 0, COLOR_WHITE) +{ +} + +int OverlayRasterizer::prepareRasterization() +{ + return 1; +} + +void OverlayRasterizer::rasterizeToCanvas(CanvasPortion *canvas) +{ + double width = (double)renderer->render_camera->getWidth(); + double height = (double)renderer->render_camera->getHeight(); + Vector3 topleft = renderer->unprojectPoint(Vector3(height, 0.0, 1.0)); + Vector3 bottomleft = renderer->unprojectPoint(Vector3(0.0, 0.0, 1.0)); + Vector3 topright = renderer->unprojectPoint(Vector3(height, width, 1.0)); + Vector3 bottomright = renderer->unprojectPoint(Vector3(0.0, width, 1.0)); + + pushQuad(canvas, topleft, bottomleft, bottomright, topright); +} + +Color OverlayRasterizer::shadeFragment(const CanvasFragment &fragment, const CanvasFragment *) const +{ + double width = (double)renderer->render_camera->getWidth() - 1.0; + double height = (double)renderer->render_camera->getHeight() - 1.0; + double relx; + double rely; + double x = floor(fragment.getPixel().x); + double y = floor(fragment.getPixel().y); + + if (width > height) + { + relx = 2.0 * ((x - (width - height) * 0.5) / height - 0.5); + rely = 2.0 * (y / height - 0.5); + } + else + { + relx = 2.0 * (x / height - 0.5); + rely = 2.0 * ((y - (height - width) * 0.5) / height - 0.5); + } + + return processPixel((int)x, (int)y, relx, rely); +} + diff --git a/src/render/software/OverlayRasterizer.h b/src/render/software/OverlayRasterizer.h new file mode 100644 index 0000000..65b0d64 --- /dev/null +++ b/src/render/software/OverlayRasterizer.h @@ -0,0 +1,35 @@ +#ifndef OVERLAYRASTERIZER_H +#define OVERLAYRASTERIZER_H + +#include "software_global.h" + +#include "Rasterizer.h" + +namespace paysages { +namespace software { + +/** + * Base class for overlay rasterizer. + * + * It's a rasterizer that puts a single quad in front of camera, in order to apply a shader on each pixel. + */ +class SOFTWARESHARED_EXPORT OverlayRasterizer: public Rasterizer +{ +public: + OverlayRasterizer(SoftwareRenderer *renderer, RenderProgress *progress); + + /** + * Abstract method to implement to shade each pixel. + */ + virtual Color processPixel(int x, int y, double relx, double rely) const = 0; + +private: + virtual int prepareRasterization(); + virtual void rasterizeToCanvas(CanvasPortion *canvas); + virtual Color shadeFragment(const CanvasFragment &fragment, const CanvasFragment *previous) const; +}; + +} +} + +#endif // OVERLAYRASTERIZER_H diff --git a/src/render/software/TerrainRenderer.cpp b/src/render/software/TerrainRenderer.cpp index 033a5c0..3553c85 100644 --- a/src/render/software/TerrainRenderer.cpp +++ b/src/render/software/TerrainRenderer.cpp @@ -220,3 +220,18 @@ bool TerrainRenderer::applyLightFilter(LightComponent &light, const Vector3 &at) return true; } } + +void TerrainRenderer::estimateMinMaxHeight(double x1, double z1, double x2, double z2, double *ymin, double *ymax) +{ + double y1 = getHeight(x1, z1, true); + double y2 = getHeight(x2, z2, true); + + // TODO Add quality factor + // TODO Use all 4 corners + // TODO Apply max slope + // TODO Estimate displacement + + std::pairminmax = std::minmax(y1, y2); + *ymin = minmax.first; + *ymax = minmax.second; +} diff --git a/src/render/software/TerrainRenderer.h b/src/render/software/TerrainRenderer.h index ec462ba..d901724 100644 --- a/src/render/software/TerrainRenderer.h +++ b/src/render/software/TerrainRenderer.h @@ -34,6 +34,11 @@ public: virtual Color getFinalColor(const Vector3 &location, double precision); virtual bool applyLightFilter(LightComponent &light, const Vector3 &at) override; + /** + * Estimate a probable range of altitudes, given a rectangle area. + */ + void estimateMinMaxHeight(double x1, double z1, double x2, double z2, double *ymin, double *ymax); + private: SoftwareRenderer *parent; TerrainRayWalker *walker_ray; diff --git a/src/render/software/VegetationModelRenderer.cpp b/src/render/software/VegetationModelRenderer.cpp index 1446112..1fe6eb2 100644 --- a/src/render/software/VegetationModelRenderer.cpp +++ b/src/render/software/VegetationModelRenderer.cpp @@ -113,7 +113,6 @@ VegetationResult VegetationModelRenderer::getResult(const SpaceSegment &segment, material.shininess = 3.0; material.hardness = 0.3; material.validate(); - // FIXME Can't use reference to temporary material return VegetationResult(location, normal, material); } else diff --git a/src/render/software/VegetationRenderer.cpp b/src/render/software/VegetationRenderer.cpp index d974600..89ff530 100644 --- a/src/render/software/VegetationRenderer.cpp +++ b/src/render/software/VegetationRenderer.cpp @@ -21,8 +21,8 @@ class VegetationGridIterator: public SpaceGridIterator { public: - VegetationGridIterator(const SpaceSegment &segment, VegetationRenderer *renderer, VegetationLayerDefinition *layer, bool only_hit): - segment(segment), renderer(renderer), layer(layer), only_hit(only_hit) + VegetationGridIterator(const SpaceSegment &segment, VegetationRenderer *renderer, bool only_hit): + segment(segment), renderer(renderer), only_hit(only_hit) { } @@ -30,24 +30,12 @@ public: virtual bool onCell(int x, int, int z) override { - std::vector instances; - - layer->getPresence()->collectInstances(&instances, *layer->getModel(), x - 0.5, z - 0.5, x + 0.5, z + 0.5); - - for (auto &instance: instances) - { - result = renderer->renderInstance(segment, instance, only_hit); - if (result.hit) - { - return false; - } - } - return true; + result = renderer->getBoundResult(segment, (double)x, (double)z, only_hit); + return not result.hit; } private: const SpaceSegment &segment; VegetationRenderer *renderer; - VegetationLayerDefinition *layer; RayCastingResult result; bool only_hit; }; @@ -97,17 +85,11 @@ RayCastingResult VegetationRenderer::getResult(const SpaceSegment &segment, bool { if (enabled) { - VegetationDefinition *vegetation = parent->getScenery()->getVegetation(); - int n = vegetation->count(); - // TODO Don't stop at first layer, find the nearest hit - for (int i = 0; i < n; i++) + // Find instances potentially crossing the segment + VegetationGridIterator it(segment, this, only_hit); + if (not segment.projectedOnYPlane().iterateOnGrid(it)) { - // Find instances potentially crossing the segment - VegetationGridIterator it(segment, this, vegetation->getVegetationLayer(i), only_hit); - if (not segment.projectedOnYPlane().iterateOnGrid(it)) - { - return it.getResult(); - } + return it.getResult(); } return RayCastingResult(); } @@ -117,6 +99,42 @@ RayCastingResult VegetationRenderer::getResult(const SpaceSegment &segment, bool } } +RayCastingResult VegetationRenderer::getBoundResult(const SpaceSegment &segment, double x, double z, bool only_hit, double xsize, double zsize) +{ + // Early check if we may cross any vegetation + double ymin, ymax; + double vegetation_max_height = 0.0; // TODO + parent->getTerrainRenderer()->estimateMinMaxHeight(x, z, x + xsize, z + zsize, &ymin, &ymax); + ymax += vegetation_max_height; + SpaceSegment bbox(Vector3(x, ymin, z), Vector3(x + xsize, ymax, z + zsize)); + if (not segment.intersectBoundingBox(bbox)) { + return RayCastingResult(); + } + + // Iterate all layers and instances + VegetationDefinition *vegetation = parent->getScenery()->getVegetation(); + int n = vegetation->count(); + for (int i = 0; i < n; i++) + { + VegetationLayerDefinition *layer = vegetation->getVegetationLayer(i); + + std::vector instances; + layer->getPresence()->collectInstances(&instances, *layer->getModel(), x, z, x + xsize, z + zsize); + + for (auto &instance: instances) + { + RayCastingResult result = renderInstance(segment, instance, only_hit); + if (result.hit) + { + // TODO Don't stop at first hit, find the nearest one + return result; + } + } + } + + return RayCastingResult(); +} + bool VegetationRenderer::applyLightFilter(LightComponent &light, const Vector3 &at) { if (enabled) diff --git a/src/render/software/VegetationRenderer.h b/src/render/software/VegetationRenderer.h index c7fd1b4..ffb3fcb 100644 --- a/src/render/software/VegetationRenderer.h +++ b/src/render/software/VegetationRenderer.h @@ -34,6 +34,11 @@ public: */ RayCastingResult getResult(const SpaceSegment &segment, bool only_hit=false); + /** + * Perform ray casting on a squared region. + */ + RayCastingResult getBoundResult(const SpaceSegment &segment, double x, double z, bool only_hit=false, double xsize=1.0, double zsize=1.0); + virtual bool applyLightFilter(LightComponent &light, const Vector3 &at) override; private: diff --git a/src/render/software/software_global.h b/src/render/software/software_global.h index e0f5855..b1ca866 100644 --- a/src/render/software/software_global.h +++ b/src/render/software/software_global.h @@ -36,6 +36,7 @@ namespace software { class WaterRenderer; class Rasterizer; + class OverlayRasterizer; class SkyRasterizer; class TerrainRasterizer; diff --git a/src/tests/OverlayRasterizer_Test.cpp b/src/tests/OverlayRasterizer_Test.cpp new file mode 100644 index 0000000..6bb6acc --- /dev/null +++ b/src/tests/OverlayRasterizer_Test.cpp @@ -0,0 +1,65 @@ +#include "BaseTestCase.h" +#include "OverlayRasterizer.h" + +#include +#include "Scenery.h" +#include "SoftwareCanvasRenderer.h" +#include "Color.h" + +typedef struct { + int x; + int y; + double relx; + double rely; +} PixelCall; + +std::vector calls; + +class MockOverlayRasterizer: public OverlayRasterizer +{ +public: + MockOverlayRasterizer(SoftwareCanvasRenderer *renderer): + OverlayRasterizer(renderer, renderer->getProgressHelper()) + { + } + + virtual Color processPixel(int x, int y, double relx, double rely) const override + { + PixelCall call = {x, y, relx, rely}; + calls.push_back(call); + return COLOR_BLUE; + } +}; + +void checkCall(const PixelCall &call, int x, int y, double relx, double rely) +{ + EXPECT_EQ(x, call.x); + EXPECT_EQ(y, call.y); + EXPECT_DOUBLE_EQ(relx, call.relx); + EXPECT_DOUBLE_EQ(rely, call.rely); +} + +TEST(OverlayRasterizer, pixelProcessing) +{ + calls.clear(); + + Scenery scenery; + SoftwareCanvasRenderer renderer(&scenery); + renderer.setSize(4, 3); + renderer.setSoloRasterizer(new MockOverlayRasterizer(&renderer)); + renderer.render(); + + ASSERT_EQ(12, (int)calls.size()); + checkCall(calls[0], 0, 0, -1.5, -1.0); + checkCall(calls[1], 0, 2, -1.5, 1.0); + checkCall(calls[2], 2, 0, 0.5, -1.0); + checkCall(calls[3], 2, 2, 0.5, 1.0); + checkCall(calls[4], 0, 1, -1.5, 0.0); + checkCall(calls[5], 1, 0, -0.5, -1.0); + checkCall(calls[6], 1, 1, -0.5, 0.0); + checkCall(calls[7], 1, 2, -0.5, 1.0); + checkCall(calls[8], 2, 1, 0.5, 0.0); + checkCall(calls[9], 3, 0, 1.5, -1.0); + checkCall(calls[10], 3, 1, 1.5, 0.0); + checkCall(calls[11], 3, 2, 1.5, 1.0); +} diff --git a/src/tests/SpaceSegment_Test.cpp b/src/tests/SpaceSegment_Test.cpp index c7c219f..90876fe 100644 --- a/src/tests/SpaceSegment_Test.cpp +++ b/src/tests/SpaceSegment_Test.cpp @@ -59,3 +59,18 @@ TEST(SpaceSegment, iterateOnGrid_Negative) ASSERT_EQ(1, (int)it.locations.size()); EXPECT_VECTOR3_COORDS(it.locations[0], -9.0, -9.0, -9.0); } + +TEST(SpaceSegment, intersectBoundingBox) +{ + SpaceSegment bbox(Vector3(-1.0, -1.0, -1.0), Vector3(1.0, 4.0, 1.0)); + + EXPECT_TRUE(SpaceSegment(Vector3(0.0, 0.0, 0.0), Vector3(1.0, 1.0, 1.0)).intersectBoundingBox(bbox)); + + EXPECT_TRUE(SpaceSegment(Vector3(-5.0, 0.0, 0.0), Vector3(5.0, 0.0, 0.0)).intersectBoundingBox(bbox)); + EXPECT_FALSE(SpaceSegment(Vector3(-5.0, -2.0, 0.0), Vector3(5.0, -2.0, 0.0)).intersectBoundingBox(bbox)); + + EXPECT_TRUE(SpaceSegment(Vector3(-5.0, 0.0, -5.0), Vector3(5.0, 0.0, 5.0)).intersectBoundingBox(bbox)); + EXPECT_FALSE(SpaceSegment(Vector3(-5.0, -2.0, 0.0), Vector3(5.0, -2.0, 5.0)).intersectBoundingBox(bbox)); + + EXPECT_FALSE(SpaceSegment(Vector3(-10.0, 0.0, 0.0), Vector3(10.0, -6.0, 0.0)).intersectBoundingBox(bbox)); +}