├── .gitignore ├── renders ├── impact.png ├── tide.png └── splash1.png ├── .gitmodules ├── include └── fluid │ ├── math │ ├── constants.h │ ├── warping.h │ ├── intersection.h │ ├── vec_simd.h │ └── mat.h │ ├── renderer │ ├── path_tracer.h │ ├── bidirectional_path_tracer.h │ ├── fresnel.h │ ├── camera.h │ ├── material.h │ ├── scene.h │ ├── spectrum.h │ ├── aabb_tree.h │ ├── common.h │ ├── bsdf.h │ ├── rendering.h │ └── primitive.h │ ├── data_structures │ ├── obstacle.h │ ├── source.h │ ├── point_cloud.h │ ├── mesh.h │ ├── space_hashing.h │ ├── aab.h │ ├── short_vec.h │ └── grid.h │ ├── mesher.h │ ├── misc.h │ ├── mac_grid.h │ ├── voxelizer.h │ └── pressure_solver.h ├── src ├── data_structures │ ├── point_cloud.cpp │ └── obstacle.cpp ├── renderer │ ├── fresnel.cpp │ ├── camera.cpp │ ├── path_tracer.cpp │ ├── material.cpp │ ├── scene.cpp │ ├── bsdf.cpp │ └── primitive.cpp ├── math │ ├── warping.cpp │ └── intersection.cpp ├── mac_grid.cpp └── voxelizer.cpp ├── plugins └── maya │ ├── nodes │ ├── point_cloud_loader_node.h │ ├── mesher_node.h │ ├── grid_manipulator_node.h │ ├── voxelizer_node.h │ ├── grid_node.h │ ├── point_cloud_loader_node.cpp │ ├── grid_manipulator_node.cpp │ └── mesher_node.cpp │ ├── commands │ ├── add_obstacle.h │ ├── add_fluid_source.h │ ├── create_simulation_grid.h │ ├── create_simulation_grid.cpp │ ├── add_fluid_source.cpp │ └── add_obstacle.cpp │ ├── misc.h │ └── main.cpp ├── testbed ├── test_scenes.h └── test_scenes.cpp ├── README.md ├── CMakeLists.txt └── LICENSE.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | build/ 3 | -------------------------------------------------------------------------------- /renders/impact.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukedan/libfluid/HEAD/renders/impact.png -------------------------------------------------------------------------------- /renders/tide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukedan/libfluid/HEAD/renders/tide.png -------------------------------------------------------------------------------- /renders/splash1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukedan/libfluid/HEAD/renders/splash1.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "3rdparty/pcg-cpp"] 2 | path = 3rdparty/pcg-cpp 3 | url = https://github.com/imneme/pcg-cpp 4 | -------------------------------------------------------------------------------- /include/fluid/math/constants.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Numerical constants. 5 | 6 | namespace fluid::constants { 7 | constexpr static double 8 | pi = 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679; 9 | } 10 | -------------------------------------------------------------------------------- /src/data_structures/point_cloud.cpp: -------------------------------------------------------------------------------- 1 | #include "fluid/data_structures/point_cloud.h" 2 | 3 | /// \file 4 | /// Implementation of certain point cloud related functions. 5 | 6 | namespace fluid { 7 | namespace point_cloud { 8 | std::vector load_from_naive(std::istream &in, std::size_t count) { 9 | std::vector result; 10 | load_from_naive( 11 | in, 12 | [&result](vec3d v) { 13 | result.emplace_back(v); 14 | }, 15 | count 16 | ); 17 | return result; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /include/fluid/renderer/path_tracer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Basic path tracer. 5 | 6 | #include 7 | 8 | #include "../math/mat.h" 9 | #include "common.h" 10 | #include "bsdf.h" 11 | #include "scene.h" 12 | #include "camera.h" 13 | 14 | namespace fluid::renderer { 15 | /// Basic path tracer. 16 | class path_tracer { 17 | public: 18 | /// Computes the incoming light along the inverse direction of the given ray. 19 | spectrum incoming_light(const scene&, const ray&, pcg32&) const; 20 | 21 | std::size_t max_bounces = 5; ///< The maximum number of ray bounces. 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /include/fluid/data_structures/obstacle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Definition of static obstacles. 5 | 6 | #include "mesh.h" 7 | 8 | namespace fluid { 9 | /// A static obstacle. 10 | class obstacle { 11 | public: 12 | using mesh_t = mesh; ///< The mesh type. 13 | 14 | /// Default constructor. 15 | obstacle() = default; 16 | /// Constructs this obstacle by voxelizing the given mesh. 17 | obstacle(mesh_t, double cell_size, vec3d ref_grid_offset, vec3s ref_grid_size); 18 | 19 | mesh_t obstacle_mesh; ///< The mesh of this obstacle. 20 | std::vector cells; ///< Cells that are entirely occupied by this obstacle. 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /include/fluid/data_structures/source.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Definition of fluid sources. 5 | 6 | #include 7 | 8 | #include "../math/vec.h" 9 | 10 | namespace fluid { 11 | /// A fluid source. 12 | class source { 13 | public: 14 | std::vector cells; ///< The cells where fluid particles will be spawned. 15 | vec3d velocity; ///< The velocity of spawned particles. 16 | std::size_t target_density_cubic_root = 2; ///< Cubic root of target seeding density. 17 | bool 18 | active = true, ///< Whether or not this source is active. 19 | /// If \p true, will cause the simulator to override the velocities of all particles in \ref cells with 20 | /// \ref velocity. 21 | coerce_velocity = false; 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /include/fluid/renderer/bidirectional_path_tracer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Definition of the bidirectional path tracer. 5 | 6 | #include 7 | 8 | #include "common.h" 9 | #include "scene.h" 10 | 11 | namespace fluid::renderer { 12 | /// The bidirectional path tracer. 13 | class bidirectional_path_tracer { 14 | public: 15 | /// Computes the incoming light along the inverse direction of the given ray. 16 | spectrum incoming_light(const scene&, const ray&, pcg32&) const; 17 | 18 | std::size_t 19 | max_camera_bounces = 15, ///< Maximum bounces of rays from the camera. 20 | max_light_bounces = 15; ///< Maximum bounces of rays from light sources. 21 | double ray_offset = 1e-6; ///< The offset of rays used for raycasting, visibility testing, etc. 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /plugins/maya/nodes/point_cloud_loader_node.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Declaration of the point cloud loader node. 5 | 6 | #include 7 | 8 | namespace fluid::maya { 9 | /// The point cloud loader node. 10 | class point_cloud_loader_node : public MPxNode { 11 | public: 12 | static MObject 13 | attr_file_name, ///< The file name attribute. 14 | attr_output_points; ///< The output point cloud attribute. 15 | const static MTypeId id; ///< The ID of this node. 16 | 17 | /// Reloads the point cloud. 18 | MStatus compute(const MPlug&, MDataBlock&) override; 19 | 20 | /// Creation function passed to \p MFnPlugin::registerNode(). 21 | static void *creator(); 22 | /// Initialization function passed to \p MFnPlugin::registerNode(). 23 | static MStatus initialize(); 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /testbed/test_scenes.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | fluid::renderer::scene::mesh_t create_plane(); 7 | fluid::renderer::scene::mesh_t create_box(); 8 | 9 | std::pair red_green_box(double asp_ratio); 10 | std::pair cornell_box_base(double asp_ratio); 11 | std::pair cornell_box_one_light(double asp_ratio); 12 | std::pair cornell_box_two_lights(double asp_ratio); 13 | std::pair glass_ball_box(double asp_ratio); 14 | 15 | std::pair fluid_box( 16 | fluid::vec3d min, fluid::vec3d max, double fovy, double asp_ratio 17 | ); -------------------------------------------------------------------------------- /include/fluid/renderer/fresnel.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Declaration of Fresnel functions. 5 | 6 | /// Different Fresnel functions. 7 | namespace fluid::renderer::fresnel { 8 | /// Fresnel function for dielectric materials. 9 | /// 10 | /// \param cos_in The cosine of the incoming ray angle, i.e., the Y coordinate in tangent space. This parameter 11 | /// must be positive; if the ray comes from the other direction, simply swap \p eta_in and 12 | /// \p eta_out. 13 | /// \param eta_in The index of refraction of the material that the incoming ray is in. 14 | /// \param eta_out The index of refraction of the material that the refracted ray is in. 15 | double dielectric(double cos_in, double eta_in_over_out, double eta_out); 16 | /// \override 17 | double dielectric(double cos_in, double cos_out, double eta_in, double eta_out); 18 | } 19 | -------------------------------------------------------------------------------- /plugins/maya/commands/add_obstacle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Definition of the add_obstacle command. 5 | 6 | #include 7 | #include 8 | 9 | namespace fluid::maya { 10 | /// The command used for adding an obstacle. 11 | class add_obstacle_command : public MPxCommand { 12 | public: 13 | static const MString name; ///< The name of this command. 14 | 15 | /// Creates an instance of this command. 16 | static void *creator(); 17 | 18 | /// Creates the nodes. 19 | MStatus doIt(const MArgList&) override; 20 | /// Undoes the operation. 21 | MStatus undoIt() override; 22 | /// Redoes the operation. 23 | MStatus redoIt() override; 24 | 25 | /// This command can be undone. 26 | bool isUndoable() const override { 27 | return true; 28 | } 29 | private: 30 | MDGModifier _graph_modifier; ///< Used to create nodes and links. 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /plugins/maya/commands/add_fluid_source.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Declaration of the addFluidSource node. 5 | 6 | #include 7 | #include 8 | 9 | namespace fluid::maya { 10 | /// A command that adds a fluid source to the simulation. 11 | class add_fluid_source_command : public MPxCommand { 12 | public: 13 | static const MString name; ///< The name of this command. 14 | 15 | /// Creates an instance of this command. 16 | static void *creator(); 17 | 18 | /// Creates the nodes. 19 | MStatus doIt(const MArgList&) override; 20 | /// Undoes the operation. 21 | MStatus undoIt() override; 22 | /// Redoes the operation. 23 | MStatus redoIt() override; 24 | 25 | /// This command can be undone. 26 | bool isUndoable() const override { 27 | return true; 28 | } 29 | private: 30 | MDGModifier _graph_modifier; ///< Used to create nodes and links. 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /plugins/maya/commands/create_simulation_grid.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Declaration of the \p createSimulationGrid command. 5 | 6 | #include 7 | #include 8 | 9 | namespace fluid::maya { 10 | /// A command that creates a simulation grid node and a mesher node. 11 | class create_simulation_grid_command : public MPxCommand { 12 | public: 13 | static const MString name; ///< The name of this command. 14 | 15 | /// Creates an instance of this command. 16 | static void *creator(); 17 | 18 | /// Creates the nodes. 19 | MStatus doIt(const MArgList&) override; 20 | /// Undoes the operation. 21 | MStatus undoIt() override; 22 | /// Redoes the operation. 23 | MStatus redoIt() override; 24 | 25 | /// This command can be undone. 26 | bool isUndoable() const override { 27 | return true; 28 | } 29 | private: 30 | MDGModifier _graph_modifier; ///< Used to create nodes and links. 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /include/fluid/renderer/camera.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Implementation of the basic camera. 5 | 6 | #include "../math/vec.h" 7 | #include "common.h" 8 | 9 | namespace fluid::renderer { 10 | /// A basic pinhole camera. 11 | struct camera { 12 | vec3d 13 | position, ///< The position of the camera. 14 | norm_forward, ///< Normalized forward direction. This corresponds to the center of the screen. 15 | half_horizontal, ///< Half of the screen's width. Use with \ref norm_forward. 16 | half_vertical; ///< Half of the screen's height, pointing downwards. Use with \ref norm_forward. 17 | 18 | /// Creates a camera from the given parameters. 19 | static camera from_parameters(vec3d pos, vec3d ref, vec3d up, double fovy_radians, double width_over_height); 20 | 21 | /// Returns the ray that corresponds to the given screen position. The direction of the ray is *not* 22 | /// normalized. 23 | ray get_ray(vec2d screen_pos) const; 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /src/data_structures/obstacle.cpp: -------------------------------------------------------------------------------- 1 | #include "fluid/data_structures/obstacle.h" 2 | 3 | /// \file 4 | /// Implementation of obstacles. 5 | 6 | #include "fluid/voxelizer.h" 7 | 8 | namespace fluid { 9 | obstacle::obstacle(mesh_t m, double cell_size, vec3d ref_grid_offset, vec3s ref_grid_size) : 10 | obstacle_mesh(std::move(m)) { 11 | 12 | auto [rmin, rmax] = voxelizer::get_bounding_box( 13 | obstacle_mesh.positions.begin(), obstacle_mesh.positions.end() 14 | ); 15 | voxelizer vox; 16 | vec3i offset = vox.resize_reposition_grid_constrained(rmin, rmax, cell_size, ref_grid_offset); 17 | vox.voxelize_mesh_surface(obstacle_mesh); 18 | vox.mark_exterior(); 19 | 20 | auto [min_coord, max_coord] = vox.get_overlapping_cell_range(offset, ref_grid_size); 21 | vox.voxels.for_each_in_range_unchecked( 22 | [this, offset](vec3s pos, voxelizer::cell_type type) { 23 | if (type == voxelizer::cell_type::interior) { 24 | cells.emplace_back(vec3s(vec3i(pos) + offset)); 25 | } 26 | }, 27 | min_coord, max_coord 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/renderer/fresnel.cpp: -------------------------------------------------------------------------------- 1 | #include "fluid/renderer/fresnel.h" 2 | 3 | /// \file 4 | /// Implementations of different Fresnel functions. 5 | 6 | #include 7 | #include 8 | 9 | namespace fluid::renderer::fresnel { 10 | double dielectric(double cos_in, double eta_in, double eta_out) { 11 | double eta = eta_in / eta_out; 12 | double sin2_out = (1.0 - cos_in * cos_in) * eta * eta; 13 | double one_minus_squared_sin = 1.0 - sin2_out; 14 | if (one_minus_squared_sin <= 0.0) { // total internal reflection 15 | return 1.0; 16 | } 17 | double cos_out = std::sqrt(std::max(0.0, one_minus_squared_sin)); 18 | return dielectric(cos_in, cos_out, eta_in, eta_out); 19 | } 20 | 21 | double dielectric(double cos_in, double cos_out, double eta_in, double eta_out) { 22 | double r_parallel = 23 | (eta_out * cos_in - eta_in * cos_out) / 24 | (eta_out * cos_in + eta_in * cos_out); 25 | double r_perpendicular = 26 | (eta_in * cos_in - eta_out * cos_out) / 27 | (eta_in * cos_in + eta_out * cos_out); 28 | return 0.5 * (r_parallel * r_parallel + r_perpendicular * r_perpendicular); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /plugins/maya/nodes/mesher_node.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Declaration of the grid node. 5 | 6 | #include 7 | 8 | namespace fluid::maya { 9 | /// The mesher node. 10 | class mesher_node : public MPxNode { 11 | public: 12 | static MObject 13 | attr_cell_size, ///< The cell size node attribute. 14 | attr_grid_size, ///< The grid size node attribute. 15 | attr_grid_offset, ///< The particle offset attribute. 16 | attr_particles, ///< The particles attribute. 17 | attr_particle_size, ///< The particle size attribute. 18 | attr_particle_extents, ///< The particle extents attribute. 19 | attr_particle_check_radius, ///< The particle check radius attribute. 20 | attr_output_mesh; ///< The output mesh node attribute. 21 | const static MTypeId id; ///< The ID of this node. 22 | 23 | /// Updates the mesh. 24 | MStatus compute(const MPlug&, MDataBlock&) override; 25 | 26 | /// Creation function passed to \p MFnPlugin::registerNode(). 27 | static void *creator(); 28 | /// Initialization function passed to \p MFnPlugin::registerNode(). 29 | static MStatus initialize(); 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /src/renderer/camera.cpp: -------------------------------------------------------------------------------- 1 | #include "fluid/renderer/camera.h" 2 | 3 | /// \file 4 | /// Implementation of the camera. 5 | 6 | #include "fluid/renderer/common.h" 7 | 8 | namespace fluid::renderer { 9 | camera camera::from_parameters(vec3d pos, vec3d ref, vec3d up, double fovy_radians, double width_over_height) { 10 | camera result; 11 | result.position = pos; 12 | result.norm_forward = (ref - pos).normalized_unchecked(); 13 | 14 | double tan_half = std::tan(0.5 * fovy_radians); 15 | result.half_horizontal = vec_ops::cross(result.norm_forward, up) 16 | .normalized_checked() 17 | .value_or(get_cross_product_axis(result.norm_forward)); 18 | result.half_vertical = vec_ops::cross(result.norm_forward, result.half_horizontal); 19 | 20 | result.half_horizontal *= tan_half * width_over_height; 21 | result.half_vertical *= tan_half; 22 | 23 | return result; 24 | } 25 | 26 | ray camera::get_ray(vec2d screen_pos) const { 27 | screen_pos = screen_pos * 2.0 - vec2d(1.0, 1.0); 28 | ray result; 29 | result.origin = position; 30 | result.direction = norm_forward + screen_pos.x * half_horizontal + screen_pos.y * half_vertical; 31 | return result; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /include/fluid/data_structures/point_cloud.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Declaration of point cloud related functions. 5 | 6 | #include 7 | #include 8 | 9 | #include "../math/vec.h" 10 | 11 | namespace fluid { 12 | /// Point cloud related functions. 13 | namespace point_cloud { 14 | /// Saves a point cloud to the given output stream. 15 | template inline void save_to_naive(std::ostream &out, It begin, It end) { 16 | for (auto c = begin; c != end; ++c) { 17 | out << c->x << " " << c->y << " " << c->z << "\n"; 18 | } 19 | } 20 | /// Loads a point cloud from the given input stream. This function keeps reading until reading fails or the 21 | /// desired number of points is reached. 22 | template inline void load_from_naive( 23 | std::istream &in, Callback &&cb, std::size_t count = std::numeric_limits::max() 24 | ) { 25 | for (std::size_t i = 0; i < count; ++i) { 26 | vec3d v; 27 | if (in >> v.x >> v.y >> v.z) { 28 | cb(v); 29 | } else { 30 | break; 31 | } 32 | } 33 | } 34 | /// \overload 35 | [[nodiscard]] std::vector load_from_naive( 36 | std::istream&, std::size_t = std::numeric_limits::max() 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /plugins/maya/nodes/grid_manipulator_node.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Nodes used for the visualization of particles. 5 | 6 | #include 7 | 8 | #include 9 | 10 | namespace fluid::maya { 11 | /// Draw override for \ref grid_node. 12 | class grid_manipulator_node : public MPxManipContainer { 13 | public: 14 | const static MTypeId id; ///< The ID of this node. 15 | 16 | /// Connects this manipulator to the given dependency node. 17 | MStatus connectToDependNode(const MObject&) override; 18 | 19 | /// Saves data for \ref drawUI(). 20 | void preDrawUI(const M3dView&) override; 21 | /// Renders the grid. 22 | void drawUI(MUIDrawManager&, const MFrameContext&) const override; 23 | 24 | /// Creation function passed to \p MFnPlugin::registerNode(). 25 | static void *creator(); 26 | /// Initialization function passed to \p MFnPlugin::registerNode(). 27 | static MStatus initialize(); 28 | private: 29 | MObject _grid_node; ///< The grid node. 30 | 31 | std::vector _particles; ///< All particles. 32 | vec3d 33 | _grid_min, ///< The minimum corner of the grid. 34 | _grid_max; ///< The maximum corner of the grid. 35 | 36 | /// Retrieves and returns all grid attributes. 37 | std::tuple _get_grid_attributes() const; 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /plugins/maya/misc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Miscellaneous functions and definitions. 5 | 6 | #include 7 | 8 | #define FLUID_MAYA_STR(X) #X 9 | #define FLUID_MAYA_STR_EXPAND(X) FLUID_MAYA_STR(X) 10 | 11 | /// Adds debug information to a message. 12 | #define FLUID_DEBUG_MESSAGE(MSG) (__FILE__ ":" FLUID_MAYA_STR_EXPAND(__LINE__) " - " MSG) 13 | /// Checks the given \p MStatus. The given object must be a variable name. 14 | #define FLUID_MAYA_CHECK(MSTAT, MSG) \ 15 | if (!::fluid::maya::maya_check(MSTAT, FLUID_DEBUG_MESSAGE(MSG))) { \ 16 | return MSTAT; \ 17 | } 18 | /// Checks the return value of the given function call which must be a \p MStatus. 19 | #define FLUID_MAYA_CHECK_RETURN(FUNC_CALL, MSG) \ 20 | { \ 21 | ::MStatus _temp = (FUNC_CALL); \ 22 | FLUID_MAYA_CHECK(_temp, MSG); \ 23 | } 24 | 25 | namespace fluid::maya { 26 | /// Checks the validity of the given \p MStatus. 27 | inline bool maya_check(const MStatus &stat, const char *text) { 28 | if (!stat) { 29 | stat.perror(text); 30 | return false; 31 | } 32 | return true; 33 | } 34 | /// \overload 35 | inline bool maya_check(const MStatus &stat) { 36 | return maya_check(stat, "undescribed maya error"); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/renderer/path_tracer.cpp: -------------------------------------------------------------------------------- 1 | #include "fluid/renderer/path_tracer.h" 2 | 3 | /// \file 4 | /// Implementation of the basic path tracer. 5 | 6 | #include 7 | 8 | namespace fluid::renderer { 9 | const spectrum spectrum::identity(vec3d(1.0, 1.0, 1.0)); 10 | 11 | 12 | spectrum path_tracer::incoming_light(const scene &scene, const ray &r, pcg32 &random) const { 13 | spectrum result; 14 | ray cur_ray = r; 15 | cur_ray.direction = cur_ray.direction.normalized_unchecked(); 16 | spectrum attenuation = spectrum::identity; 17 | std::uniform_real_distribution dist(0.0, 1.0); 18 | for (std::size_t i = 0; i < max_bounces; ++i) { 19 | auto [prim, res, isect] = scene.ray_cast(cur_ray); 20 | if (prim == nullptr) { 21 | break; 22 | } 23 | 24 | // direct lighting 25 | result += modulate(attenuation, isect.surface_bsdf.emission); 26 | 27 | // sample outgoing ray 28 | vec3d incoming_direction = isect.tangent * -cur_ray.direction; 29 | bsdfs::outgoing_ray_sample sample = isect.surface_bsdf.sample_f( 30 | incoming_direction, vec2d(dist(random), dist(random)), transport_mode::radiance 31 | ); 32 | spectrum isect_atten = sample.reflectance * (std::abs(sample.norm_out_direction_tangent.y) / sample.pdf); 33 | 34 | cur_ray = isect.spawn_ray(sample.norm_out_direction_tangent); 35 | 36 | attenuation = modulate(attenuation, isect_atten); 37 | } 38 | return result; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/renderer/material.cpp: -------------------------------------------------------------------------------- 1 | #include "fluid/renderer/material.h" 2 | 3 | /// \file 4 | /// Implementation of materials. 5 | 6 | #include "fluid/math/constants.h" 7 | #include "fluid/math/warping.h" 8 | 9 | namespace fluid::renderer { 10 | namespace materials { 11 | bsdf lambertian_reflection::get_bsdf(vec2d uv) const { 12 | bsdf result; 13 | auto &lambert = result.value.emplace(); 14 | lambert.reflectance = reflectance.get_value(uv); 15 | return result; 16 | } 17 | 18 | 19 | bsdf specular_reflection::get_bsdf(vec2d uv) const { 20 | bsdf result; 21 | auto &specular = result.value.emplace(); 22 | specular.reflectance = reflectance.get_value(uv); 23 | return result; 24 | } 25 | 26 | 27 | bsdf specular_transmission::get_bsdf(vec2d uv) const { 28 | bsdf result; 29 | auto &specular = result.value.emplace(); 30 | specular.skin = skin.get_value(uv); 31 | specular.index_of_refraction = index_of_refraction; 32 | return result; 33 | } 34 | } 35 | 36 | 37 | bsdf material::get_bsdf(vec2d uv_unit) const { 38 | return std::visit( 39 | [&](const auto &mat) { 40 | bsdf result = mat.get_bsdf(uv_unit); 41 | result.emission = emission.get_value(uv_unit); 42 | return result; 43 | }, 44 | value 45 | ); 46 | } 47 | 48 | bool material::has_emission() const { 49 | return !emission.modulation.near_zero(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /include/fluid/math/warping.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Functions used to warp samples from one domain to another. 5 | 6 | #include "vec.h" 7 | 8 | namespace fluid::warping { 9 | /// Maps a unit square uniformly to a unit disk. 10 | [[nodiscard]] vec2d unit_disk_from_unit_square(vec2d); 11 | /// Maps a unit square uniformly to a unit disk in a manner that is visually more uniform. 12 | [[nodiscard]] vec2d unit_disk_from_unit_square_concentric(vec2d); 13 | /// The probability density function of \ref unit_disk_from_unit_square_concentric(). 14 | [[nodiscard]] double pdf_unit_disk_from_unit_square(); 15 | 16 | /// Maps a unit square uniformly to a unit radius sphere. 17 | [[nodiscard]] vec3d unit_sphere_from_unit_square(vec2d); 18 | /// The probability density function of \ref unit_sphere_from_unit_square(). 19 | [[nodiscard]] double pdf_unit_sphere_from_unit_square(); 20 | 21 | /// Maps a unit square uniformly to a unit radius hemisphere. 22 | [[nodiscard]] vec3d unit_hemisphere_from_unit_square(vec2d); 23 | /// The probability density function of \ref unit_hemisphere_from_unit_square(). 24 | [[nodiscard]] double pdf_unit_hemisphere_from_unit_square(); 25 | 26 | /// Maps a unit square to a unit radius hemisphere. The mapping is weighed by the cosine of the angle between the 27 | /// output and the Y axis. 28 | [[nodiscard]] vec3d unit_hemisphere_from_unit_square_cosine(vec2d); 29 | /// The probability density function of \ref unit_hemisphere_from_unit_square_cosine(). 30 | [[nodiscard]] double pdf_unit_hemisphere_from_unit_square_cosine(vec3d); 31 | } 32 | -------------------------------------------------------------------------------- /plugins/maya/nodes/voxelizer_node.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Definition of source nodes. 5 | 6 | #include 7 | 8 | namespace fluid::maya { 9 | /// A fluid source. 10 | class voxelizer_node : public MPxNode { 11 | public: 12 | static MObject 13 | attr_input_mesh, ///< The input mesh. 14 | attr_cell_size, ///< The velocity of the particles. 15 | attr_ref_grid_offset, ///< The offset of the reference grid. 16 | attr_ref_grid_size, ///< The number of cells on each axis of the reference grid. 17 | attr_include_interior, ///< Indicates whether to include interior cells. 18 | attr_include_surface, ///< Indicates whether to include surface cells. 19 | 20 | attr_output_grid_offset, ///< The offset of the voxelization grid. 21 | attr_output_grid_size, ///< The size of the voxelization grid. 22 | /// Cells occupied by the mesh. This will be stored as a int array and the indices will be relative to 23 | /// the voxelization grid. 24 | attr_output_cells, 25 | /// Similar to \ref attr_output_cells, but with only cells in the reference grid. The indices will also 26 | /// be relative to the reference grid. 27 | attr_output_cells_ref; 28 | const static MTypeId id; ///< The ID of this node. 29 | 30 | /// Forwards the attributes to the output attribute. 31 | MStatus compute(const MPlug&, MDataBlock&) override; 32 | 33 | /// Creation function passed to \p MFnPlugin::registerNode(). 34 | static void *creator(); 35 | /// Initialization function passed to \p MFnPlugin::registerNode(). 36 | static MStatus initialize(); 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /include/fluid/mesher.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Generation of triangular mesh from fluid. 5 | 6 | #include 7 | 8 | #include "math/vec.h" 9 | #include "data_structures/grid.h" 10 | #include "data_structures/space_hashing.h" 11 | #include "data_structures/mesh.h" 12 | 13 | namespace fluid { 14 | /// Used to generate triangular surface meshes from fluid particles. 15 | class mesher { 16 | public: 17 | /// The mesh type. 18 | using mesh_t = mesh; 19 | 20 | /// Resizes the sampling grid. This is counted in the number of cells, so \ref _surface_function will have 21 | /// one more coordinate on each dimension. 22 | void resize(vec3s); 23 | 24 | /// Generates a mesh for the given set of particles. The generated mesh will have only positions and indices. 25 | [[nodiscard]] mesh_t generate_mesh(const std::vector&, double r); 26 | 27 | vec3d grid_offset; ///< The offset of the sampling grid in world space. 28 | double 29 | cell_size = 0.0, ///< The size of a cell. 30 | particle_extent = 0.5; ///< The extent of each particle. 31 | std::size_t cell_radius = 2; ///< The radius of cells to look at when sampling the fluid to a grid. 32 | private: 33 | grid3 _surface_function; ///< Sampled values of the implicit surface function. 34 | space_hashing<3, const vec3d*> _hash; ///< Space hashing. 35 | 36 | /// The kernel function for weighing particles. 37 | [[nodiscard]] FLUID_FORCEINLINE double _kernel(double sqr_dist) const; 38 | /// Hashes the particles and samples the particles into the grid. 39 | void _sample_surface_function(const std::vector&, double); 40 | /// Generate the mesh using the marching cubes algorithm. 41 | [[nodiscard]] mesh_t _marching_cubes() const; 42 | /// Adds a point to the vector, returning its index. 43 | [[nodiscard]] FLUID_FORCEINLINE std::size_t _add_point( 44 | std::vector &v, vec3s cell, double *surf_func, std::size_t edge_table_index 45 | ) const; 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /include/fluid/misc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Miscellaneous definitions. 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #ifdef _MSC_VER 11 | # define FLUID_FORCEINLINE __forceinline 12 | #elif defined(__GNUC__) 13 | # define FLUID_FORCEINLINE [[gnu::always_inline]] inline 14 | #else 15 | # define FLUID_FORCEINLINE inline 16 | #endif 17 | 18 | namespace fluid { 19 | /// Linear interpolation. 20 | template [[nodiscard]] FLUID_FORCEINLINE T lerp(const T &a, const T &b, double t) { 21 | return a * (1.0 - t) + b * t; 22 | } 23 | /// Bilinear interpolation. 24 | template [[nodiscard]] FLUID_FORCEINLINE T bilerp( 25 | const T &v00, const T &v01, const T &v10, const T &v11, double t1, double t2 26 | ) { 27 | return lerp(lerp(v00, v01, t2), lerp(v10, v11, t2), t1); 28 | } 29 | /// Trilinear interpolation. 30 | template [[nodiscard]] FLUID_FORCEINLINE T trilerp( 31 | const T &v000, const T &v001, const T &v010, const T &v011, 32 | const T &v100, const T &v101, const T &v110, const T &v111, 33 | double t1, double t2, double t3 34 | ) { 35 | return lerp(bilerp(v000, v001, v010, v011, t2, t3), bilerp(v100, v101, v110, v111, t2, t3), t1); 36 | } 37 | 38 | /// A semaphore. Copied from 39 | /// https://stackoverflow.com/questions/4792449/c0x-has-no-semaphores-how-to-synchronize-threads. 40 | class semaphore { 41 | public: 42 | void notify() { 43 | std::lock_guard lock(_mutex); 44 | ++_count; 45 | _cond.notify_one(); 46 | } 47 | 48 | void wait() { 49 | std::unique_lock lock(_mutex); 50 | while (_count == 0) { // handle spurious wake-ups 51 | _cond.wait(lock); 52 | } 53 | --_count; 54 | } 55 | bool try_wait() { 56 | std::lock_guard lock(_mutex); 57 | if (_count > 0) { 58 | --_count; 59 | return true; 60 | } 61 | return false; 62 | } 63 | private: 64 | std::mutex _mutex; 65 | std::condition_variable _cond; 66 | unsigned _count = 0; 67 | 68 | }; 69 | } 70 | -------------------------------------------------------------------------------- /src/math/warping.cpp: -------------------------------------------------------------------------------- 1 | #include "fluid/math/warping.h" 2 | 3 | /// \file 4 | /// Implementation of warping functions. 5 | 6 | #include 7 | 8 | #include "fluid/math/constants.h" 9 | 10 | namespace fluid::warping { 11 | vec2d unit_disk_from_unit_square(vec2d square) { 12 | double r = std::sqrt(square.x), angle = square.y * 2.0 * constants::pi; 13 | return vec2d(r * std::cos(angle), r * std::sin(angle)); 14 | } 15 | 16 | vec2d unit_disk_from_unit_square_concentric(vec2d square) { 17 | vec2d p = 2.0 * square - vec2d(1.0, 1.0); 18 | double radius, phi; 19 | if (p.x > -p.y) { 20 | if (p.x > p.y) { 21 | radius = p.x; 22 | phi = p.y / p.x; 23 | } else { 24 | radius = p.y; 25 | phi = 2.0 - p.x / p.y; 26 | } 27 | } else { 28 | if (p.x < p.y) { 29 | radius = -p.x; 30 | phi = 4.0 + p.y / p.x; 31 | } else { 32 | radius = -p.y; 33 | phi = 6.0 - p.x / p.y; 34 | } 35 | } 36 | phi = phi * constants::pi / 4.0; 37 | return vec2d(radius * std::cos(phi), radius * std::sin(phi)); 38 | } 39 | 40 | double pdf_unit_disk_from_unit_square() { 41 | return 1.0 / constants::pi; 42 | } 43 | 44 | 45 | vec3d unit_sphere_from_unit_square(vec2d square) { 46 | double cos_phi = square.x * 2.0 - 1.0, sin_phi = std::sqrt(1.0 - cos_phi * cos_phi); 47 | double theta = square.y * 2.0f * constants::pi; 48 | return vec3d(sin_phi * std::cos(theta), sin_phi * std::sin(theta), cos_phi); 49 | } 50 | 51 | double pdf_unit_sphere_from_unit_square() { 52 | return 1.0 / (4.0 * constants::pi); 53 | } 54 | 55 | 56 | vec3d unit_hemisphere_from_unit_square(vec2d square) { 57 | double cosphi = square.x, sinphi = std::sqrt(1.0 - cosphi * cosphi); 58 | double theta = square.y * 2.0 * constants::pi; 59 | return vec3d(sinphi * std::cos(theta), cosphi, sinphi * std::sin(theta)); 60 | } 61 | 62 | double pdf_unit_hemisphere_from_unit_square() { 63 | return 2.0 / (4.0 * constants::pi); 64 | } 65 | 66 | 67 | vec3d unit_hemisphere_from_unit_square_cosine(vec2d square) { 68 | vec2d result = unit_disk_from_unit_square_concentric(square); 69 | double y = std::sqrt(std::max(0.0, 1.0 - result.squared_length())); 70 | return vec3d(result.x, y, result.y); 71 | } 72 | 73 | double pdf_unit_hemisphere_from_unit_square_cosine(vec3d sample) { 74 | return sample.y / constants::pi; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /plugins/maya/nodes/grid_node.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Declaration of the node that represents the simulation grid. 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | namespace fluid::maya { 17 | /// A node that represents the simulation grid. 18 | class grid_node : public MPxNode { 19 | public: 20 | static MObject 21 | attr_time, ///< The time attribute. 22 | attr_cell_size, ///< The cell size attribute. 23 | attr_grid_size, ///< The grid size attribute. 24 | attr_grid_offset, ///< The grid offset attribute. 25 | attr_gravity, ///< The gravity attribute. 26 | attr_transfer_method, ///< The transfer method attribute. 27 | // fluid source attributes 28 | attr_source_cells, ///< The cells occupied by each fluid source. 29 | attr_source_velocity, ///< The velocities of each fluid source. 30 | attr_source_enabled, ///< Whether each fluid source is enabled. 31 | /// Whether or not to force all particles in each fluid source to have the desired velocity. 32 | attr_source_coerce_velocity, 33 | attr_source_seeding_density, ///< The desired density inside the source region. 34 | attr_sources, ///< Compound attribute that includes data of all fluid sources. 35 | // obstacle attributes 36 | attr_obstacle_cells, ///< The cells that belong to a obstacle. 37 | attr_obstacles, ///< Compound attribute that includes all obstacles. 38 | // output 39 | attr_output_particle_positions; ///< The particle positions attribute. 40 | static MTypeId id; ///< The ID of this node. 41 | 42 | /// Updates the simulation. 43 | MStatus compute(const MPlug&, MDataBlock&) override; 44 | 45 | /// When input attributes other than time is changed, clear all cached data. 46 | MStatus setDependentsDirty(const MPlug&, MPlugArray&) override; 47 | 48 | /// Creation function passed to \p MFnPlugin::registerNode(). 49 | static void *creator(); 50 | /// Initialization function passed to \p MFnPlugin::registerNode(). 51 | static MStatus initialize(); 52 | private: 53 | // deque to reduce move operations 54 | std::deque _particle_cache; ///< Cached particle positions for each frame. 55 | std::vector _last_frame_particles; ///< Saved particles from the last cached frame. 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /include/fluid/math/intersection.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Declaration of various intersection tests. 5 | 6 | #include 7 | 8 | #include "vec.h" 9 | 10 | namespace fluid { 11 | /// Tests if the given axis-aligned box overlaps the given triangle. 12 | [[nodiscard]] bool aab_triangle_overlap( 13 | vec3d box_center, vec3d half_extent, vec3d p1, vec3d p2, vec3d p3 14 | ); 15 | /// Tests if the given axis-aligned box overlaps the given triangle, assuming that the box overlaps with the 16 | /// triangle's AABB. 17 | [[nodiscard]] bool aab_triangle_overlap_bounded( 18 | vec3d box_center, vec3d half_extent, vec3d p1, vec3d p2, vec3d p3 19 | ); 20 | /// Tests if the given axis-aligned box overlaps the given triangle, assuming that the box's center is at 21 | /// the origin and that the box overlaps with the triangle's AABB. 22 | [[nodiscard]] bool aab_triangle_overlap_bounded_center(vec3d half_extent, vec3d p1, vec3d p2, vec3d p3); 23 | 24 | /// Tests for intersection between a ray and a triangle. 25 | /// 26 | /// \return If there's an intersection at \p p, the returned vector (x, y, z) will satisfy: 27 | /// p = origin + x * direction = p1 + y * (p2 - p1) + z * (p3 - p1). Otherwise, the \p x 28 | /// component will be \p nan. 29 | [[nodiscard]] vec3d ray_triangle_intersection( 30 | vec3d origin, vec3d direction, vec3d p1, vec3d p2, vec3d p3, double parallel_epsilon = 1e-6 31 | ); 32 | /// Tests for intersection between a ray and a triangle. The triangle is represented by the first vertex and the 33 | /// offsets of the other two vertices from the first one. 34 | [[nodiscard]] vec3d ray_triangle_intersection_edges( 35 | vec3d origin, vec3d direction, vec3d p1, vec3d e12, vec3d e13, double parallel_epsilon = 1e-6 36 | ); 37 | 38 | /// Returns whether the ray overlaps the given axis-aligned box. 39 | /// 40 | /// \return \p t values of the entry and exit points. If the ray does not intersect the box, then its \p x 41 | /// component will be \p nan. It is possible for the ray to start inside the box, in which case the \p x 42 | /// component will be less than zero. 43 | [[nodiscard]] vec2d aab_ray_intersection(vec3d min, vec3d max, vec3d vo, vec3d vd); 44 | 45 | /// Computes the intersection between the given ray and a sphere of radius 1 at the origin. 46 | /// 47 | /// \return The return value is similar to that of \ref aab_ray_intersection(). 48 | [[nodiscard]] vec2d unit_radius_sphere_ray_intersection(vec3d vo, vec3d vd); 49 | } 50 | -------------------------------------------------------------------------------- /include/fluid/renderer/material.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Definition of materials. 5 | 6 | #include 7 | 8 | #include "common.h" 9 | #include "bsdf.h" 10 | 11 | namespace fluid::renderer { 12 | /// Definition of different materials. 13 | namespace materials { 14 | /// A *channel* stores a shared pointer to a texture and a value used to modulate the texture. If the texture 15 | /// is \p null, then it's assumed to be pure white. 16 | /// 17 | /// \todo Wrapping modes. 18 | template struct channel { 19 | std::shared_ptr> texture; ///< The texture. 20 | T modulation; ///< The spectrum that the texture is multiplied by. 21 | 22 | /// Returns the value of this channel at the given texture coordinates. 23 | spectrum get_value(vec2d uv) const { 24 | if (texture == nullptr) { 25 | return modulation; 26 | } 27 | return modulate(texture->sample(uv), modulation); 28 | } 29 | }; 30 | 31 | /// Lambertian reflection material. 32 | struct lambertian_reflection { 33 | channel reflectance; ///< Reflectance. 34 | 35 | /// Returns a \ref bsdfs::lambertian_reflection_brdf. 36 | bsdf get_bsdf(vec2d) const; 37 | }; 38 | 39 | /// Specular reflection material. 40 | struct specular_reflection { 41 | channel reflectance; ///< Reflectance. 42 | 43 | /// Returns a \ref bsdfs::specular_reflection_brdf. 44 | bsdf get_bsdf(vec2d) const; 45 | }; 46 | 47 | /// Specular transmission material. 48 | struct specular_transmission { 49 | /// The color used for both reflection and transmission. 50 | channel skin; 51 | double index_of_refraction = 1.0; ///< The index of refraction of this material. 52 | 53 | bsdf get_bsdf(vec2d) const; 54 | }; 55 | } 56 | 57 | /// A generic material. 58 | struct material { 59 | /// The \p std::variant used to store the material. 60 | using union_t = std::variant< 61 | materials::lambertian_reflection, 62 | materials::specular_reflection, 63 | materials::specular_transmission 64 | >; 65 | 66 | /// Returns a BSDF that corresponds to the given UV coordinates. \ref bsdf::emission is set by this function, 67 | /// while \ref bsdf::value is set by the underlying material type. 68 | bsdf get_bsdf(vec2d) const; 69 | 70 | /// Returns whether this material is emissive, i.e., whether it acts as a light source. 71 | bool has_emission() const; 72 | 73 | union_t value; ///< The value of this material. 74 | materials::channel emission; ///< The emission of this material. 75 | }; 76 | } 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libfluid 2 | 3 | ![](renders/tide.png) 4 | 5 | PIC/FLIP/APIC fluid simulation library with a built-in bidirectional path tracer and a Maya plugin. 6 | 7 | 8 | ## Buliding libfluid 9 | 10 | libfluid uses CMake and requires a C++17-compliant compiler. Building has been tested on Visual Studio 2019 and g++ 8.1.0. The library itself does not require any extra dependencies. libfluid also includes a bidirectional path tracer that is built if `FLUID_BUILD_RENDERER` is `ON`. 11 | 12 | ### The testbed 13 | 14 | The repository also includes a testbed, which is built depending on the value of `FLUID_BUILD_TESTBED` (`ON` by default). The testbed requires `GLU` and `GLFW`. 15 | 16 | ### Maya plugin 17 | 18 | The building of the Maya plugin is dependent on the variable `FLUID_BUILD_MAYA_PLUGIN`. The variable `FLUID_MAYA_DEVKIT_PATH` should be set to point to the Maya devkit directory that contains the `include` and `lib` directories. 19 | 20 | 21 | ## Testbed controls 22 | 23 | Note: these controls may or may not be up-to-date. See `key_callback()` in [`main.cpp`](testbed/main.cpp) for controls actually implemented in the testbed. 24 | 25 | ### Simulation Controls 26 | 27 | | Key | Description | 28 | |-|-| 29 | | Enter | Start / pause fluid simulation | 30 | | R | Reset simulation | 31 | | Spacebar | Step simulation | 32 | | P | Toggle particle visualization | 33 | | C | Toggle cell occupation visualization | 34 | | F | Toggle face velocity visualization | 35 | | M | Toggle mesh visibility | 36 | | A | Toggle APIC debug visualization | 37 | | F1 | Cycle particle visualization mode | 38 | | F2 | Cycle mesh visualization mode | 39 | | F3 | Save fluid mesh to `mesh.obj` | 40 | | F4 | Save particles to `points.txt` | 41 | | 5 | Set simulation setup 4 | 42 | | 6 | Set simulation setup 3 | 43 | | 7 | Set simulation setup 2 | 44 | | 8 | Set simulation setup 1 | 45 | | 9 | Set simulation setup 0 | 46 | 47 | ### Render Controls 48 | 49 | | Key | Description | 50 | |-|-| 51 | | V | Toggle render preview visibility | 52 | | S | Start / pause render preview sampling | 53 | | 0 | Load current water simulation for rendering | 54 | | 1 | Load Cornell box scene for rendering | 55 | | 2 | Load glass sphere scene for rendering | 56 | | F5 | Render high-resolution high-sample-count image to `test.ppm` | 57 | | F6 | Save render preview to `test.ppm` | 58 | 59 | 60 | ## Acknowledgements 61 | 62 | - [`rlguy/GridFluidSim3D`](https://github.com/rlguy/GridFluidSim3D) helped a lot for the solver implementation. 63 | - [`nepluno/apic2d`](https://github.com/nepluno/apic2d) is a good reference of the general algorithm. 64 | -------------------------------------------------------------------------------- /include/fluid/renderer/scene.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Definition of the scene. 5 | 6 | #include 7 | 8 | #include "../math/mat.h" 9 | #include "../data_structures/mesh.h" 10 | #include "material.h" 11 | #include "aabb_tree.h" 12 | 13 | namespace fluid::renderer { 14 | /// Information about an entity. 15 | struct entity_info { 16 | material mat; ///< The material of this entity. 17 | }; 18 | 19 | /// Stores information about an intersection. 20 | struct intersection_info { 21 | bsdf surface_bsdf; ///< The BSDF function of this surface. 22 | rmat3d tangent; ///< The matrix used to convert directions from world space into tangent space. 23 | vec3d 24 | intersection, ///< The exact point of intersection. 25 | geometric_normal; ///< The geometric normal of the intersection point. 26 | vec2d uv; ///< The UV at the intersection point. 27 | 28 | /// Spawns a new ray given its direction in tangent space. The resulting ray's direction has the same length 29 | /// as the input direction. 30 | ray spawn_ray(vec3d tangent_dir, double offset = 1e-6) const; 31 | 32 | /// Creates a \ref intersection_info from the given raycast result. 33 | static intersection_info from_intersection(const ray&, const primitive*, ray_cast_result); 34 | }; 35 | 36 | /// Manages scene entities. 37 | class scene { 38 | public: 39 | using mesh_t = mesh; ///< Mesh type. 40 | 41 | /// Adds a transformed mesh entity to the scene. 42 | void add_mesh_entity(const mesh_t&, const rmat3x4d&, entity_info); 43 | /// Adds a primitive to the scene. 44 | void add_primitive_entity(const primitive::union_t&, entity_info); 45 | /// Finishes building the scene. 46 | void finish(); 47 | 48 | /// Returns the list of all primitives that emit light. 49 | const std::vector &get_lights() const { 50 | return _lights; 51 | } 52 | 53 | /// Performs ray casting. 54 | std::tuple ray_cast(const ray&) const; 55 | /// Tests the visibility between two points. 56 | bool test_visibility(vec3d, vec3d, double eps = 1e-6) const; 57 | 58 | /// Spawns a ray given the position, direction in tangent space, and normal. It is assumed that the direction 59 | /// is in the same hemisphere as the normal. 60 | static ray spawn_ray_from(vec3d pos, vec3d tangent_dir, vec3d normal, double offset = 1e-6); 61 | private: 62 | aabb_tree _tree; ///< The AABB tree. 63 | std::deque _entities; ///< Information about all entities. 64 | std::vector _lights; ///< The list of light sources. 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /plugins/maya/nodes/point_cloud_loader_node.cpp: -------------------------------------------------------------------------------- 1 | #include "point_cloud_loader_node.h" 2 | 3 | /// \file 4 | /// Implementation of the point cloud loader node. 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #include "../misc.h" 15 | 16 | namespace fluid::maya { 17 | MObject 18 | point_cloud_loader_node::attr_file_name, 19 | point_cloud_loader_node::attr_output_points; 20 | 21 | void *point_cloud_loader_node::creator() { 22 | return new point_cloud_loader_node(); 23 | } 24 | 25 | MStatus point_cloud_loader_node::initialize() { 26 | MStatus stat; 27 | 28 | MFnTypedAttribute file_name; 29 | attr_file_name = file_name.create("fileName", "file", MFnData::kString, MObject::kNullObj, &stat); 30 | FLUID_MAYA_CHECK(stat, "parameter creation"); 31 | 32 | MFnTypedAttribute output_points; 33 | attr_output_points = output_points.create( 34 | "outputPoints", "out", MFnData::kPointArray, MObject::kNullObj, &stat 35 | ); 36 | FLUID_MAYA_CHECK(stat, "parameter creation"); 37 | FLUID_MAYA_CHECK_RETURN(output_points.setWritable(false), "parameter creation"); 38 | FLUID_MAYA_CHECK_RETURN(output_points.setStorable(false), "parameter creation"); 39 | 40 | FLUID_MAYA_CHECK_RETURN(addAttribute(attr_file_name), "parameter registration"); 41 | FLUID_MAYA_CHECK_RETURN(addAttribute(attr_output_points), "parameter registration"); 42 | 43 | FLUID_MAYA_CHECK_RETURN(attributeAffects(attr_file_name, attr_output_points), "parameter registration"); 44 | 45 | return MStatus::kSuccess; 46 | } 47 | 48 | MStatus point_cloud_loader_node::compute(const MPlug &plug, MDataBlock &data_block) { 49 | if (plug != attr_output_points) { 50 | return MStatus::kUnknownParameter; 51 | } 52 | 53 | MStatus stat; 54 | 55 | MDataHandle file_name_data = data_block.inputValue(attr_file_name, &stat); 56 | FLUID_MAYA_CHECK(stat, "retrieve attribute"); 57 | MDataHandle output_points_data = data_block.outputValue(attr_output_points, &stat); 58 | FLUID_MAYA_CHECK(stat, "retrieve attribute"); 59 | 60 | MPointArray points; 61 | { 62 | std::ifstream fin(file_name_data.asString().asUTF8()); 63 | point_cloud::load_from_naive( 64 | fin, 65 | [&points](vec3d p) { 66 | MStatus stat = points.append(MPoint(p.x, p.y, p.z)); 67 | if (!stat) { 68 | stat.perror("MPointArray::append() failed"); 69 | } 70 | } 71 | ); 72 | } 73 | 74 | MFnPointArrayData points_array; 75 | MObject points_array_data = points_array.create(points, &stat); 76 | FLUID_MAYA_CHECK(stat, "finalize compute"); 77 | FLUID_MAYA_CHECK_RETURN(output_points_data.set(points_array_data), "finalize compute"); 78 | output_points_data.setClean(); 79 | return MStatus::kSuccess; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /include/fluid/mac_grid.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// A MAC grid that stores fluid information including density and velocity. 5 | 6 | #include "data_structures/grid.h" 7 | 8 | namespace fluid { 9 | class pressure_solver; 10 | 11 | /// Stores a marker-and-cell (MAC) grid. 12 | class mac_grid { 13 | public: 14 | /// A cell in the simulation grid. 15 | struct cell { 16 | /// The type of this cell's contents. 17 | enum class type : unsigned char { 18 | air = 0x1, ///< The cell contains air. 19 | fluid = 0x2, ///< The cell contains fluid. 20 | solid = 0x4, ///< The cell contains solid. 21 | }; 22 | 23 | /// The velocities sampled at the centers of faces in the positive direction. Or, if this cell is part of 24 | /// a fluid source, then this is the velocity of that source. 25 | vec3d velocities_posface; 26 | type cell_type = type::air; ///< The contents of this cell. 27 | }; 28 | /// Stores velocity samples on faces near a particle. The coordinates of each member does not necessarily 29 | /// correspond to that of the same cell. 30 | struct face_samples { 31 | vec3d 32 | v000, ///< Velocity at (0, 0, 0). 33 | v001, ///< Velocity at (1, 0, 0). 34 | v010, ///< Velocity at (0, 1, 0). 35 | v011, ///< Velocity at (1, 1, 0). 36 | v100, ///< Velocity at (0, 0, 1). 37 | v101, ///< Velocity at (1, 0, 1). 38 | v110, ///< Velocity at (0, 1, 1). 39 | v111; ///< Velocity at (1, 1, 1). 40 | }; 41 | 42 | /// Default constructor. 43 | mac_grid() = default; 44 | /// Initializes the grid. 45 | explicit mac_grid(vec3s grid_count); 46 | 47 | /// Returns the velocities around the given position. Velocities of cells that are out of the grid have their 48 | /// corresponding coordinates clamped to zero. 49 | [[nodiscard]] std::pair get_face_samples(vec3s grid_index, vec3d offset) const; 50 | 51 | /// Returns the cell at the given index, or \p nullptr if the cell is out of bounds. Note that since unsigned 52 | /// overflow is well defined, "negative" coordinates works fine. 53 | [[nodiscard]] mac_grid::cell *get_cell(vec3s); 54 | /// \overload 55 | [[nodiscard]] const mac_grid::cell *get_cell(vec3s) const; 56 | 57 | /// Returns the cell and its type at the given index. For cells that are outside of the simulation grid, this 58 | /// function returns \ref fluid_grid::cell::type::solid. 59 | [[nodiscard]] std::pair get_cell_and_type(vec3s); 60 | /// \overload 61 | [[nodiscard]] std::pair get_cell_and_type(vec3s) const; 62 | 63 | /// Returns the simulation grid. 64 | [[nodiscard]] grid3 &grid() { 65 | return _grid; 66 | } 67 | /// Returns the simulation grid. 68 | [[nodiscard]] const grid3 &grid() const { 69 | return _grid; 70 | } 71 | protected: 72 | grid3 _grid; ///< The simulation grid. 73 | }; 74 | } 75 | -------------------------------------------------------------------------------- /include/fluid/renderer/spectrum.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Definition of spectrum types. 5 | 6 | #include 7 | 8 | #include "fluid/math/vec.h" 9 | #include "fluid/renderer/common.h" 10 | 11 | namespace fluid::renderer { 12 | /// Used to represent a spectrum. 13 | struct rgb_spectrum { 14 | /// Default constructor. 15 | rgb_spectrum() = default; 16 | 17 | const static rgb_spectrum identity; /// A spectrum with value one on all wavelengths. 18 | 19 | /// Converts this spectrum to RGB values. 20 | vec3d to_rgb() const { 21 | return _rgb; 22 | } 23 | /// Creates a new spectrum from the given RGB values. 24 | inline static rgb_spectrum from_rgb(vec3d rgb) { 25 | return rgb_spectrum(rgb); 26 | } 27 | 28 | /// Debug output. 29 | friend std::ostream &operator<<(std::ostream &out, const rgb_spectrum &rgb) { 30 | return out << "rgb(" << rgb._rgb.x << ", " << rgb._rgb.y << ", " << rgb._rgb.z << ")"; 31 | } 32 | 33 | /// In-place addition. 34 | rgb_spectrum &operator+=(const rgb_spectrum &rhs) { 35 | _rgb += rhs._rgb; 36 | return *this; 37 | } 38 | /// Addition. 39 | friend rgb_spectrum operator+(const rgb_spectrum &lhs, const rgb_spectrum &rhs) { 40 | rgb_spectrum result = lhs; 41 | return result += rhs; 42 | } 43 | 44 | /// In-place subtraction. 45 | rgb_spectrum &operator-=(const rgb_spectrum &rhs) { 46 | _rgb -= rhs._rgb; 47 | return *this; 48 | } 49 | /// Subtraction. 50 | friend rgb_spectrum operator-(const rgb_spectrum &lhs, const rgb_spectrum &rhs) { 51 | rgb_spectrum result = lhs; 52 | return result -= rhs; 53 | } 54 | 55 | /// In-place scalar multiplication. 56 | rgb_spectrum &operator*=(double rhs) { 57 | _rgb *= rhs; 58 | return *this; 59 | } 60 | /// Scalar multiplication. 61 | friend rgb_spectrum operator*(rgb_spectrum lhs, double rhs) { 62 | return lhs *= rhs; 63 | } 64 | /// Scalar multiplication. 65 | friend rgb_spectrum operator*(double lhs, rgb_spectrum rhs) { 66 | return rhs *= lhs; 67 | } 68 | 69 | /// In-place scalar division. 70 | rgb_spectrum &operator/=(double rhs) { 71 | _rgb /= rhs; 72 | return *this; 73 | } 74 | /// Scalar division. 75 | friend rgb_spectrum operator/(rgb_spectrum lhs, double rhs) { 76 | return lhs /= rhs; 77 | } 78 | 79 | /// Tests if this specturm is almost black. 80 | bool near_zero(double threshold = 1e-6) const { 81 | return _rgb.x < threshold && _rgb.y < threshold && _rgb.z < threshold; 82 | } 83 | 84 | /// Modulate spectrum by memberwise multiplication. 85 | friend inline rgb_spectrum modulate(const rgb_spectrum &lhs, const rgb_spectrum &rhs) { 86 | return rgb_spectrum(vec_ops::memberwise::mul(lhs._rgb, rhs._rgb)); 87 | } 88 | private: 89 | /// Directly initializes this spectrum. 90 | explicit rgb_spectrum(vec3d value) : _rgb(value) { 91 | } 92 | 93 | vec3d _rgb; ///< The RGB values. 94 | }; 95 | 96 | using spectrum = rgb_spectrum; ///< The default spectrum type. 97 | } 98 | -------------------------------------------------------------------------------- /include/fluid/voxelizer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Declaration of the voxelizer. 5 | 6 | #include 7 | 8 | #include "math/vec.h" 9 | #include "data_structures/grid.h" 10 | #include "data_structures/mesh.h" 11 | 12 | namespace fluid { 13 | /// Used to voxelize meshes. 14 | class voxelizer { 15 | public: 16 | /// The type of a cell. 17 | enum class cell_type : unsigned char { 18 | interior, ///< The cell lies completely inside the mesh. 19 | exterior, ///< The cell lies outside of the mesh. 20 | surface ///< The cell intersects with the surface of the mesh. 21 | }; 22 | 23 | /// Returns the bounding box of the given list of vertices. 24 | template inline static std::pair get_bounding_box(It beg, It end) { 25 | if (beg == end) { 26 | return { vec3d(), vec3d() }; 27 | } 28 | vec3d min(*beg), max(*beg); 29 | auto it = beg; 30 | for (++it; it != end; ++it) { 31 | _update_bounding_box(vec3d(*it), min, max); 32 | } 33 | return { min, max }; 34 | } 35 | 36 | /// Sets \ref grid_offset and the size of \ref voxels based on the given bounding box. This function clears 37 | /// the contents of \ref voxels. 38 | void resize_reposition_grid(vec3d min, vec3d max); 39 | /// Sets \ref grid_offset, \ref cell_size, and the size of \ref voxels based on the given bounding box, 40 | /// making sure that the grid aligns with the specified grid. This function clears the contents of 41 | /// \ref voxels. 42 | /// 43 | /// \return The offset of \ref voxels in the given grid. 44 | vec3i resize_reposition_grid_constrained(vec3d min, vec3d max, double ref_cell_size, vec3d ref_grid_offset); 45 | 46 | /// Returns the range of cells that overlap a reference grid. The input is the result of 47 | /// \ref resize_reposition_grid_constrained() and the size of the reference grid. 48 | std::pair get_overlapping_cell_range(vec3i, vec3s) const; 49 | 50 | /// Marks cells that overlap the given triangle as surface cells. The triangle must be entirely contained by 51 | /// \ref voxels. 52 | void voxelize_triangle(vec3d, vec3d, vec3d); 53 | /// Marks surface cells in \ref voxels. The mesh must be entirely contained by \ref voxels, which can be made 54 | /// sure by calling \ref resize_reposition_grid() first. 55 | template void voxelize_mesh_surface(const Mesh &mesh) { 56 | for (std::size_t i = 0; i + 2 < mesh.indices.size(); i += 3) { 57 | voxelize_triangle( 58 | vec3d(mesh.positions[mesh.indices[i]]), 59 | vec3d(mesh.positions[mesh.indices[i + 1]]), 60 | vec3d(mesh.positions[mesh.indices[i + 2]]) 61 | ); 62 | } 63 | } 64 | 65 | /// Marks the exterior cells. 66 | void mark_exterior(); 67 | 68 | double cell_size = 1.0; ///< The size of each cell. 69 | grid3 voxels; ///< The voxels. 70 | vec3d grid_offset; ///< The offset of \ref voxels. 71 | private: 72 | /// Updates the bounding box. 73 | static void _update_bounding_box(vec3d pos, vec3d &min, vec3d &max); 74 | }; 75 | } 76 | -------------------------------------------------------------------------------- /include/fluid/renderer/aabb_tree.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Definition of the AABB tree. 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include "common.h" 11 | #include "primitive.h" 12 | 13 | namespace fluid::renderer { 14 | /// A bounding volume hierarchy that uses axis aligned bounding boxes. 15 | class aabb_tree { 16 | public: 17 | /// A node in the tree. 18 | struct node { 19 | /// Default constructor. 20 | node() { 21 | } 22 | /// Destructor. 23 | ~node(); 24 | 25 | union { 26 | /// Children bounding boxes. The lower 64 bits stores the values for \ref child1. 27 | aab3<__m128d> children_bb; 28 | aab3d leaf_bb; ///< Bounding box of this node, only valid for leaf nodes. 29 | }; 30 | /// The first child. This is \p nullptr if this node is a leaf node, and non-null otherwise. Therefore 31 | /// this member determines which member of the next union is active. 32 | node *child1 = nullptr; 33 | union { 34 | node *child2; ///< The second child. 35 | const primitive *prim; ///< The primitive associated with a leaf node. 36 | }; 37 | 38 | /// Returns whether this node is a leaf node. 39 | bool is_leaf() const { 40 | return child1 == nullptr; 41 | } 42 | 43 | /// Sets the children bounding boxes of this node. 44 | void set_children_bounding_boxes(aab3d c1, aab3d c2); 45 | /// Sets the children bounding boxes of this node, assuming that the child nodes are leaves (i.e., that 46 | /// \ref leaf_bb is active for the children). 47 | void set_children_bounding_boxes_leaf(); 48 | }; 49 | 50 | /// Default constructor. 51 | aabb_tree() = default; 52 | /// No copy construction. 53 | aabb_tree(const aabb_tree&) = delete; 54 | /// Move constructor. 55 | aabb_tree(aabb_tree&&) noexcept; 56 | /// No copy assignment. 57 | aabb_tree &operator=(const aabb_tree&) = delete; 58 | /// Move assignment. 59 | aabb_tree &operator=(aabb_tree&&) noexcept; 60 | 61 | /// Adds a primitive to the tree. This function clears \ref _node_pool and \ref _root. 62 | void add_primitive(primitive); 63 | 64 | /// Builds the tree. If \ref add_primitive() is called after this, then this function needs to be called 65 | /// again before \ref ray_cast() is called. 66 | void build(); 67 | 68 | /// Performs ray casting. The additional parameter is used to limit the range of the ray cast. 69 | std::pair ray_cast( 70 | const ray&, double max_t = std::numeric_limits::max() 71 | ) const; 72 | 73 | /// Returns the list of all primitives. 74 | const std::vector &get_primitives() const { 75 | return _primitive_pool; 76 | } 77 | 78 | /// Evaluates the given bounding box to decide whether or not to merge two subtrees. The smaller the 79 | /// heuristic is, the better. The default heuristic is based on the surface area of the bounding box. 80 | [[nodiscard]] static double evaluate_heuristic(aab3d); 81 | private: 82 | std::vector _node_pool; ///< Storage for all nodes. 83 | std::vector _primitive_pool; ///< Storage for all primitives. 84 | node *_root = nullptr; ///< The root node. 85 | }; 86 | } 87 | -------------------------------------------------------------------------------- /include/fluid/data_structures/mesh.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Mesh related data structures. 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "../math/vec.h" 11 | 12 | namespace fluid { 13 | /// A mesh. 14 | template struct mesh { 15 | std::vector> positions; ///< Vertex positions. 16 | std::vector> normals; ///< Vertex normals. 17 | std::vector indices; ///< Triangle indices. 18 | std::vector colors; ///< Vertex colors. 19 | std::vector> uvs; ///< The UVs. 20 | 21 | /// Clears all data in this mesh. 22 | void clear() { 23 | positions.clear(); 24 | normals.clear(); 25 | indices.clear(); 26 | colors.clear(); 27 | uvs.clear(); 28 | } 29 | 30 | /// Reverses all face directions by changing vertex indices. 31 | void reverse_face_directions() { 32 | for (std::size_t i = 0; i + 2 < indices.size(); i += 3) { 33 | std::swap(indices[i + 1], indices[i + 2]); 34 | } 35 | } 36 | 37 | /// Generates normals for all vertices. 38 | void generate_normals() { 39 | if (!normals.empty()) { 40 | std::fill(normals.begin(), normals.end(), vec3()); 41 | } 42 | normals.resize(positions.size()); 43 | for (std::size_t i = 0; i + 2 < indices.size(); i += 3) { 44 | IndexT i1 = indices[i], i2 = indices[i + 1], i3 = indices[i + 2]; 45 | vec3 norm(vec_ops::cross(positions[i2] - positions[i1], positions[i3] - positions[i1])); 46 | normals[i1] += norm; 47 | normals[i2] += norm; 48 | normals[i3] += norm; 49 | } 50 | for (vec3 &norm : normals) { 51 | norm = norm.normalized_checked().value_or(vec3(1, 0, 0)); 52 | } 53 | } 54 | 55 | /// Saves this mesh to an .OBJ file. 56 | void save_obj(std::ostream &out) const { 57 | for (auto &p : positions) { 58 | out << "v " << p.x << " " << p.y << " " << p.z << "\n"; 59 | } 60 | if (!normals.empty()) { 61 | for (auto &n : normals) { 62 | out << "vn " << n.x << " " << n.y << " " << n.z << "\n"; 63 | } 64 | } 65 | if (!uvs.empty()) { 66 | for (auto &uv : uvs) { 67 | out << "vt " << uv.x << " " << uv.y << "\n"; 68 | } 69 | } 70 | 71 | // faces 72 | if (normals.empty() && uvs.empty()) { 73 | for (std::size_t i = 0; i < indices.size() - 2; i += 3) { 74 | out << "f " << indices[i] + 1 << " " << indices[i + 1] + 1 << " " << indices[i + 2] + 1 << "\n"; 75 | } 76 | } else if (normals.empty()) { 77 | for (std::size_t i = 0; i < indices.size() - 2; ) { 78 | out << "f"; 79 | for (std::size_t j = 0; j < 3; ++i, ++j) { 80 | IndexT id = indices[i] + 1; 81 | out << " " << id << "/" << id; 82 | } 83 | out << "\n"; 84 | } 85 | } else { 86 | for (std::size_t i = 0; i < indices.size() - 2; ) { 87 | out << "f"; 88 | for (std::size_t j = 0; j < 3; ++i, ++j) { 89 | IndexT id = indices[i] + 1; 90 | out << " " << id << "/"; 91 | if (!uvs.empty()) { 92 | out << id; 93 | } 94 | out << "/" << id; 95 | } 96 | out << "\n"; 97 | } 98 | } 99 | } 100 | }; 101 | } 102 | -------------------------------------------------------------------------------- /src/renderer/scene.cpp: -------------------------------------------------------------------------------- 1 | #include "fluid/renderer/scene.h" 2 | 3 | /// \file 4 | /// Implementation of the scene. 5 | 6 | namespace fluid::renderer { 7 | ray intersection_info::spawn_ray(vec3d tangent_dir, double offset) const { 8 | ray result; 9 | result.origin = intersection; 10 | result.origin += geometric_normal * (tangent_dir.y > 0.0 ? offset : -offset); 11 | result.direction = tangent.transposed() * tangent_dir; 12 | return result; 13 | } 14 | 15 | intersection_info intersection_info::from_intersection(const ray &ray, const primitive *prim, ray_cast_result hit) { 16 | intersection_info result; 17 | result.uv = prim->get_uv(hit); 18 | result.surface_bsdf = prim->entity->mat.get_bsdf(result.uv); 19 | result.geometric_normal = prim->get_geometric_normal(hit); 20 | result.tangent = compute_arbitrary_tangent_space(result.geometric_normal); 21 | result.intersection = ray.origin + ray.direction * hit.t; 22 | return result; 23 | } 24 | 25 | 26 | void scene::add_mesh_entity(const mesh_t &m, const rmat3x4d &trans, entity_info info) { 27 | entity_info &ent = _entities.emplace_back(std::move(info)); 28 | std::vector trans_pos(m.positions.size()); 29 | for (std::size_t i = 0; i < m.positions.size(); ++i) { 30 | trans_pos[i] = trans * vec4d(m.positions[i], 1.0); 31 | } 32 | for (std::size_t i = 0; i + 2 < m.indices.size(); i += 3) { 33 | primitive prim; 34 | prim.entity = &ent; 35 | auto &tri = prim.value.emplace(); 36 | std::size_t i1 = m.indices[i], i2 = m.indices[i + 1], i3 = m.indices[i + 2]; 37 | tri.point1 = trans_pos[i1]; 38 | tri.edge12 = trans_pos[i2] - tri.point1; 39 | tri.edge13 = trans_pos[i3] - tri.point1; 40 | if (!m.uvs.empty()) { 41 | tri.uv_p1 = m.uvs[i1]; 42 | tri.uv_e12 = m.uvs[i2] - tri.uv_p1; 43 | tri.uv_e13 = m.uvs[i3] - tri.uv_p1; 44 | } 45 | tri.compute_attributes(); 46 | _tree.add_primitive(std::move(prim)); 47 | } 48 | } 49 | 50 | void scene::add_primitive_entity(const primitive::union_t &geom, entity_info i) { 51 | entity_info &ent = _entities.emplace_back(std::move(i)); 52 | primitive prim; 53 | prim.value = geom; 54 | prim.entity = &ent; 55 | _tree.add_primitive(prim); 56 | } 57 | 58 | void scene::finish() { 59 | _tree.build(); 60 | // collect light sources 61 | _lights.clear(); 62 | for (const primitive &prim : _tree.get_primitives()) { 63 | if (!prim.entity->mat.emission.modulation.near_zero()) { 64 | _lights.emplace_back(&prim); 65 | } 66 | } 67 | } 68 | 69 | std::tuple scene::ray_cast(const ray &r) const { 70 | auto [prim, res] = _tree.ray_cast(r); 71 | if (prim) { 72 | return { prim, res, intersection_info::from_intersection(r, prim, res) }; 73 | } 74 | return { nullptr, ray_cast_result(), intersection_info() }; 75 | } 76 | 77 | bool scene::test_visibility(vec3d p1, vec3d p2, double eps) const { 78 | vec3d diff = p2 - p1; 79 | vec3d offset = diff.normalized_unchecked() * eps; 80 | ray r; 81 | r.direction = diff - 2.0 * offset; 82 | r.origin = p1 + offset; 83 | auto [prim, res] = _tree.ray_cast(r, 1.0); 84 | return prim == nullptr; 85 | } 86 | 87 | ray scene::spawn_ray_from(vec3d pos, vec3d dir, vec3d norm, double offset) { 88 | ray result; 89 | result.origin = pos + norm * offset; 90 | result.direction = compute_arbitrary_tangent_space(norm).transposed() * dir; 91 | return result; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /plugins/maya/commands/create_simulation_grid.cpp: -------------------------------------------------------------------------------- 1 | #include "create_simulation_grid.h" 2 | 3 | /// \file 4 | /// Implementation of the \p createSimulationGrid command. 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "../misc.h" 14 | #include "../nodes/grid_node.h" 15 | #include "../nodes/grid_manipulator_node.h" 16 | #include "../nodes/mesher_node.h" 17 | 18 | namespace fluid::maya { 19 | void *create_simulation_grid_command::creator() { 20 | return new create_simulation_grid_command(); 21 | } 22 | 23 | MStatus create_simulation_grid_command::doIt(const MArgList &args) { 24 | MStatus stat; 25 | 26 | MObject grid = _graph_modifier.createNode(grid_node::id, &stat); 27 | FLUID_MAYA_CHECK(stat, "node creation"); 28 | 29 | MObject mesher = _graph_modifier.createNode(mesher_node::id, &stat); 30 | FLUID_MAYA_CHECK(stat, "node creation"); 31 | MFnDependencyNode fn_mesher(mesher, &stat); 32 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 33 | 34 | // time -> grid 35 | MItDependencyNodes time_iter(MFn::kTime, &stat); 36 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 37 | bool has_time = !time_iter.isDone(&stat); 38 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 39 | if (has_time) { 40 | MObject time = time_iter.thisNode(&stat); 41 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 42 | MFnDependencyNode fn_time(time, &stat); 43 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 44 | MPlug time_plug = fn_time.findPlug("outTime", false, &stat); 45 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 46 | 47 | MFnDependencyNode fn_grid(grid, &stat); 48 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 49 | MPlug grid_time_plug = fn_grid.findPlug(grid_node::attr_time, false, &stat); 50 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 51 | 52 | FLUID_MAYA_CHECK_RETURN(_graph_modifier.connect(time_plug, grid_time_plug), "attribute connection"); 53 | } else { 54 | // TODO print warning 55 | } 56 | 57 | // grid -> mesher 58 | FLUID_MAYA_CHECK_RETURN( 59 | _graph_modifier.connect( 60 | grid, grid_node::attr_output_particle_positions, 61 | mesher, mesher_node::attr_particles 62 | ), 63 | "attribute connection" 64 | ); 65 | 66 | // mesher -> mesh 67 | MPlug mesher_output = fn_mesher.findPlug(mesher_node::attr_output_mesh, false, &stat); 68 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 69 | 70 | MFnMesh mesh_creator; 71 | MPointArray points; 72 | MIntArray counts, connects; 73 | MObject transform = mesh_creator.create(0, 0, points, counts, connects, MObject::kNullObj, &stat); 74 | FLUID_MAYA_CHECK(stat, "mesh creation"); 75 | MFnDagNode fn_transform(transform, &stat); 76 | FLUID_MAYA_CHECK(stat, "mesh creation"); 77 | MObject shape = fn_transform.child(0, &stat); 78 | FLUID_MAYA_CHECK(stat, "mesh creation"); 79 | MFnDagNode fn_shape(shape, &stat); 80 | FLUID_MAYA_CHECK(stat, "mesh creation"); 81 | MPlug mesh_input = fn_shape.findPlug("inMesh", false, &stat); 82 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 83 | FLUID_MAYA_CHECK_RETURN(_graph_modifier.connect(mesher_output, mesh_input), "attribute connection"); 84 | 85 | FLUID_MAYA_CHECK_RETURN(_graph_modifier.doIt(), "execute queued operations"); 86 | 87 | return MStatus::kSuccess; 88 | } 89 | 90 | MStatus create_simulation_grid_command::undoIt() { 91 | return _graph_modifier.undoIt(); 92 | } 93 | 94 | MStatus create_simulation_grid_command::redoIt() { 95 | return _graph_modifier.doIt(); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.9) 2 | project(libfluid) 3 | 4 | set(FLUID_USE_OPENMP ON CACHE BOOL "Whether or not to use OpenMP.") 5 | set(FLUID_BUILD_RENDERER ON CACHE BOOL "Whether or not to build the renderer.") 6 | set(FLUID_BUILD_TESTBED ON CACHE BOOL "Whether or not to build the testbed.") 7 | set(FLUID_BUILD_MAYA_PLUGIN ON CACHE BOOL "Whether or not to build the Maya plugin.") 8 | set(FLUID_MAYA_DEVKIT_PATH "" CACHE PATH "Path to the Maya devkit.") 9 | 10 | 11 | include(CheckIPOSupported) 12 | check_ipo_supported(RESULT FLUID_IPO_SUPPORTED OUTPUT FLUID_IPO_MESSAGE) 13 | if(NOT FLUID_IPO_SUPPORTED) 14 | message(WARNING "IPO is not supported: ${FLUID_IPO_MESSAGE}") 15 | endif() 16 | 17 | 18 | add_library(fluid) 19 | 20 | target_compile_features(fluid 21 | PUBLIC cxx_std_17) 22 | target_include_directories(fluid 23 | PUBLIC 24 | 3rdparty/pcg-cpp/include/ 25 | include/) 26 | target_sources(fluid 27 | PRIVATE 28 | "src/data_structures/point_cloud.cpp" 29 | "src/data_structures/obstacle.cpp" 30 | "src/math/intersection.cpp" 31 | "src/math/warping.cpp" 32 | "src/mac_grid.cpp" 33 | "src/mesher.cpp" 34 | "src/pressure_solver.cpp" 35 | "src/simulation.cpp" 36 | "src/voxelizer.cpp") 37 | if(FLUID_BUILD_RENDERER) 38 | target_sources(fluid 39 | PRIVATE 40 | "src/renderer/aabb_tree.cpp" 41 | "src/renderer/bidirectional_path_tracer.cpp" 42 | "src/renderer/bsdf.cpp" 43 | "src/renderer/camera.cpp" 44 | "src/renderer/fresnel.cpp" 45 | "src/renderer/material.cpp" 46 | "src/renderer/path_tracer.cpp" 47 | "src/renderer/primitive.cpp" 48 | "src/renderer/scene.cpp") 49 | endif() 50 | if(MSVC) 51 | target_compile_options(fluid 52 | PUBLIC /arch:AVX2 53 | PRIVATE /W4) 54 | elseif(CMAKE_COMPILER_IS_GNUCXX) 55 | target_link_libraries(fluid 56 | PUBLIC stdc++fs) 57 | endif() 58 | if(FLUID_IPO_SUPPORTED) 59 | set_property(TARGET fluid PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) 60 | endif() 61 | 62 | if(FLUID_USE_OPENMP) 63 | find_package(OpenMP) 64 | if(OpenMP_CXX_FOUND) 65 | target_link_libraries(fluid 66 | PUBLIC OpenMP::OpenMP_CXX) 67 | endif() 68 | endif() 69 | 70 | 71 | if(FLUID_BUILD_TESTBED) 72 | add_executable(testbed) 73 | 74 | target_compile_features(testbed 75 | PRIVATE cxx_std_17) 76 | target_sources(testbed 77 | PRIVATE 78 | "testbed/test_scenes.cpp" 79 | "testbed/main.cpp") 80 | 81 | find_package(OpenGL REQUIRED) 82 | find_package(glfw3 CONFIG REQUIRED) 83 | target_link_libraries(testbed 84 | PRIVATE fluid glfw OpenGL::GLU OpenGL::GL) 85 | if(FLUID_IPO_SUPPORTED) 86 | set_property(TARGET testbed PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) 87 | endif() 88 | endif() 89 | 90 | 91 | if(FLUID_BUILD_MAYA_PLUGIN) 92 | add_library(fluid_maya SHARED) 93 | set_target_properties(fluid_maya PROPERTIES SUFFIX .mll) 94 | 95 | target_compile_features(fluid_maya 96 | PRIVATE cxx_std_17) 97 | target_sources(fluid_maya 98 | PRIVATE 99 | "plugins/maya/commands/add_fluid_source.cpp" 100 | "plugins/maya/commands/add_obstacle.cpp" 101 | "plugins/maya/commands/create_simulation_grid.cpp" 102 | "plugins/maya/nodes/grid_manipulator_node.cpp" 103 | "plugins/maya/nodes/grid_node.cpp" 104 | "plugins/maya/nodes/mesher_node.cpp" 105 | "plugins/maya/nodes/point_cloud_loader_node.cpp" 106 | "plugins/maya/nodes/voxelizer_node.cpp" 107 | "plugins/maya/main.cpp") 108 | 109 | target_include_directories(fluid_maya 110 | PRIVATE 111 | "${FLUID_MAYA_DEVKIT_PATH}/include") 112 | target_link_directories(fluid_maya 113 | PRIVATE 114 | "${FLUID_MAYA_DEVKIT_PATH}/lib") 115 | target_link_libraries(fluid_maya 116 | PRIVATE 117 | fluid 118 | Foundation OpenMaya OpenMayaUI OpenMayaAnim OpenMayaFX OpenMayaRender Image) 119 | if(FLUID_IPO_SUPPORTED) 120 | set_property(TARGET fluid_maya PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) 121 | endif() 122 | endif() 123 | -------------------------------------------------------------------------------- /include/fluid/renderer/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Common functions and data structures. 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "../math/vec.h" 12 | #include "../math/mat.h" 13 | #include "../data_structures/grid.h" 14 | 15 | namespace fluid::renderer { 16 | /// A ray. 17 | struct ray { 18 | vec3d 19 | origin, ///< The origin of the ray. 20 | direction; ///< The direction of the ray. This is not necessarily normalized. 21 | }; 22 | 23 | 24 | /// Modulation for basic scalar types by multiplication. 25 | template inline T modulate(const T &lhs, const T &rhs) { 26 | return lhs * rhs; 27 | } 28 | 29 | 30 | /// Stores an image. 31 | template struct image { 32 | /// Default constructor. 33 | image() = default; 34 | /// Initializes this image with the given size. 35 | explicit image(vec2s size) : pixels(size) { 36 | } 37 | 38 | /// Samples this image given the UV coordinates. 39 | Pixel sample(vec2d uv) const { 40 | // TODO wrapping modes 41 | return sample_unit(vec2d(uv.x - std::floor(uv.x), uv.y - std::floor(uv.y))); 42 | } 43 | /// Samples this image given the UV coordinates. The coordinates are assumed to be within [0, 1]; 44 | Pixel sample_unit(vec2d uv) const { 45 | uv = vec_ops::memberwise::mul(uv, vec2d(pixels.get_size())) + vec2d(0.5, 0.5); 46 | vec2d fract; 47 | vec2s pos_tl, pos_br; 48 | vec_ops::for_each( 49 | [](double uv, double &f, std::size_t &ps, std::size_t &pg, std::size_t size) { 50 | double ipart; 51 | f = std::modf(uv, &ipart); 52 | pg = static_cast(ipart); 53 | ps = std::max(ps, 1) - 1; 54 | pg = std::min(size - 1, pg); 55 | }, 56 | uv, fract, pos_tl, pos_br, pixels.get_size() 57 | ); 58 | Pixel 59 | pix_tl = pixels(pos_tl.x, pos_tl.y), 60 | pix_tr = pixels(pos_br.x, pos_tl.y), 61 | pix_bl = pixels(pos_tl.x, pos_br.y), 62 | pix_br = pixels(pos_br.x, pos_br.y); 63 | return bilerp(pix_tl, pix_tr, pix_bl, pix_br, fract.y, fract.x); 64 | } 65 | 66 | /// Saves this image. 67 | template void save_ppm(const std::filesystem::path &p, ToRGB &&torgb) const { 68 | std::ofstream fout(p); 69 | fout << "P3\n" << pixels.get_size().x << " " << pixels.get_size().y << "\n255\n"; 70 | for (std::size_t y = 0; y < pixels.get_size().y; ++y) { 71 | for (std::size_t x = 0; x < pixels.get_size().x; ++x) { 72 | vec3 rgb = torgb(pixels(x, y)); 73 | fout << 74 | static_cast(rgb.x) << " " << 75 | static_cast(rgb.y) << " " << 76 | static_cast(rgb.z) << "\n"; 77 | } 78 | } 79 | } 80 | 81 | /// Returns the aspect ration of this image, i.e., width over height. 82 | double aspect_ratio() const { 83 | return 84 | static_cast(pixels.get_size().x) / 85 | static_cast(pixels.get_size().y); 86 | } 87 | 88 | grid2 pixels; ///< The pixels of this image. 89 | }; 90 | 91 | 92 | /// Returns the axis that produces the largest cross product with the input vector. 93 | inline vec3d get_cross_product_axis(vec3d vec) { 94 | vec3d abs(std::abs(vec.x), std::abs(vec.y), std::abs(vec.z)); 95 | // select the axis with the smallest absolute value 96 | if (abs.y > abs.x) { 97 | if (abs.z > abs.x) { 98 | return vec3d(1.0, 0.0, 0.0); 99 | } else { 100 | return vec3d(0.0, 0.0, 1.0); 101 | } 102 | } else { 103 | if (abs.z > abs.y) { 104 | return vec3d(0.0, 1.0, 0.0); 105 | } else { 106 | return vec3d(0.0, 0.0, 1.0); 107 | } 108 | } 109 | } 110 | /// Computes an orthonormal matrix used to convert positions from world space to tangent space for the given 111 | /// normal vector, with the normal mapped to the Y axis. The normal vector is assumed to be normalized. 112 | inline rmat3d compute_arbitrary_tangent_space(vec3d normal) { 113 | vec3d x = vec_ops::cross(normal, get_cross_product_axis(normal)).normalized_unchecked(); 114 | vec3d z = vec_ops::cross(x, normal); 115 | return rmat3d::from_rows(x, normal, z); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /include/fluid/data_structures/space_hashing.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Definition of the space hashing data structure. 5 | 6 | #include 7 | #include 8 | 9 | #include "grid.h" 10 | 11 | namespace fluid { 12 | /// A space-hashing data structure. Each grid stores particles in that grid. 13 | template struct space_hashing { 14 | public: 15 | using size_type = vec; ///< Used to store sizes and indices. 16 | 17 | /// Default constructor. 18 | space_hashing() = default; 19 | /// Initializes the table to the given size. 20 | explicit space_hashing(size_type size) : _table(size) { 21 | } 22 | 23 | /// Resizes the grid, clearing all data in the process. 24 | void resize(size_type size) { 25 | _table = _grid_type(size); 26 | _chain.clear(); 27 | } 28 | 29 | /// Adds an object to the grid. The object must not be moved in memory after it has been added until 30 | /// \ref clear() has been called. The object will not be added if it falls out of the grid. 31 | /// 32 | /// \return Whether the object is in the grid and has been added. 33 | bool add_object_at(size_type index, T obj) { 34 | bool in_bounds = true; 35 | vec_ops::for_each( 36 | [&in_bounds](std::size_t coord, std::size_t max) { 37 | if (coord >= max) { 38 | in_bounds = false; 39 | } 40 | }, 41 | index, _table.get_size() 42 | ); 43 | if (in_bounds) { 44 | add_object_at_unchecked(index, std::move(obj)); 45 | return true; 46 | } 47 | return false; 48 | } 49 | 50 | /// Adds an object to the grid without checking its coordinates. Use with caution. 51 | void add_object_at_unchecked(size_type index, T obj) { 52 | add_object_at_raw(_table.index_to_raw(index), std::move(obj)); 53 | } 54 | /// \ref add_object_at() that takes a raw index. 55 | void add_object_at_raw(std::size_t index_raw, T obj) { 56 | _cell &cell = _table[index_raw]; 57 | std::size_t new_head = _chain.size(); 58 | _chain.emplace_back(std::move(obj), cell.head); 59 | cell.head = new_head; 60 | ++cell.count; 61 | } 62 | 63 | /// Returns the number of objects in the given cell. 64 | std::size_t get_num_objects_at(size_type index) const { 65 | return _table(index).count; 66 | } 67 | 68 | /// Clears all particles. 69 | void clear() { 70 | _table.fill(_cell()); 71 | _chain.clear(); 72 | } 73 | 74 | /// Calls the given callback for all objects in the given cell. 75 | template void for_all_objects_in(vec3s cell, Callback &&cb) { 76 | _for_all_objects_in_cell(_table(cell), std::forward(cb)); 77 | } 78 | /// Calls the given callback function for all objects near the given cell. 79 | template void for_all_nearby_objects( 80 | vec3s cell, vec3s min_offset, vec3s max_offset, Callback &&cb 81 | ) { 82 | _table.for_each_in_range_checked( 83 | [this, &cb](vec3s, _cell &cell) { 84 | _for_all_objects_in_cell(cell, std::forward(cb)); 85 | }, 86 | cell, min_offset, max_offset 87 | ); 88 | } 89 | private: 90 | /// The contents of a grid cell. 91 | struct _cell { 92 | std::size_t 93 | head = 0, ///< Index of the first element. 94 | count = 0; ///< The number of objects in this cell. 95 | }; 96 | /// A node in the linked list. 97 | struct _chain_elem { 98 | /// Default constructor. 99 | _chain_elem() = default; 100 | /// Initializes all fields of this object. 101 | _chain_elem(T &&obj, std::size_t n) : object(std::move(obj)), next(n) { 102 | } 103 | 104 | T object; ///< The object. 105 | std::size_t next = 0; ///< Index of the next node. 106 | }; 107 | using _grid_type = grid; ///< The type of the grid. 108 | 109 | _grid_type _table; ///< The hash table. 110 | std::vector<_chain_elem> _chain; ///< The linked list. 111 | 112 | /// Calls the given callback for all objects in the given cell. 113 | template void _for_all_objects_in_cell(const _cell &cell, Callback &&cb) { 114 | for (std::size_t i = 0, id = cell.head; i < cell.count; ++i) { 115 | _chain_elem &elem = _chain[id]; 116 | cb(elem.object); 117 | id = elem.next; 118 | } 119 | } 120 | }; 121 | } 122 | -------------------------------------------------------------------------------- /include/fluid/renderer/bsdf.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Definition of a series of BSDF (bidirectional scattering distribution function). 5 | 6 | #include 7 | 8 | #include "common.h" 9 | #include "spectrum.h" 10 | 11 | namespace fluid::renderer { 12 | /// Indicates what is being transported along rays. 13 | enum class transport_mode : std::uint8_t { 14 | radiance, ///< Radiance. 15 | importance ///< Importance. (?) 16 | }; 17 | 18 | namespace bsdfs { 19 | /// An outgoing ray sample produced by a material. 20 | struct outgoing_ray_sample { 21 | spectrum reflectance; ///< Reflectance. 22 | vec3d norm_out_direction_tangent; ///< Normalized out ray direction in tangent space. 23 | double pdf = 0.0; ///< The probability density function. 24 | }; 25 | 26 | /// BRDF of Lambertian reflection. 27 | struct lambertian_reflection_brdf { 28 | constexpr static bool double_sided = true; ///< Controls whether or not this material is double-sided. 29 | 30 | /// For lambertian materials, the \p f term is constant. 31 | spectrum f(vec3d, vec3d, transport_mode) const; 32 | /// Returns the result of \ref fluid::warping::pdf_unit_hemisphere_from_unit_square_cosine(); 33 | double pdf(vec3d, vec3d) const; 34 | /// The Lambertian material samples rays using 35 | /// \ref fluid::warping::unit_hemisphere_from_unit_square_cosine(); 36 | outgoing_ray_sample sample_f(vec3d, vec2d, transport_mode) const; 37 | 38 | /// Returns \p false. 39 | bool is_delta() const; 40 | 41 | spectrum reflectance; ///< The reflectance of this material. 42 | }; 43 | 44 | /// BRDF of specular reflection. 45 | struct specular_reflection_brdf { 46 | /// The \p f term is zero unless the two rays are exactly mirrored, but we don't check for that. 47 | spectrum f(vec3d, vec3d, transport_mode) const; 48 | /// The probability density function is a delta function and is not modeled here. 49 | double pdf(vec3d, vec3d) const; 50 | /// Simply mirrors the input ray and cancels out the Lambertian term. 51 | outgoing_ray_sample sample_f(vec3d, vec2d, transport_mode) const; 52 | 53 | /// Returns \p true. 54 | bool is_delta() const; 55 | 56 | spectrum reflectance; ///< The reflectance of this material. 57 | }; 58 | 59 | /// BSDF of specular transmission. 60 | struct specular_transmission_bsdf { 61 | /// The \p f term is zero unless the two rays match exactly, but we don't check for that. 62 | spectrum f(vec3d, vec3d, transport_mode) const; 63 | /// The probability density function is a delta function and is not modeled here. 64 | double pdf(vec3d, vec3d) const; 65 | /// Based on the Fresnel reflectance, reflects or refracts the incoming ray. 66 | outgoing_ray_sample sample_f(vec3d, vec2d, transport_mode) const; 67 | 68 | /// Returns \p true. 69 | bool is_delta() const; 70 | 71 | /// The color used for both reflection and transmission. 72 | spectrum skin; 73 | double index_of_refraction = 1.0; ///< The IOR of this material. 74 | }; 75 | } 76 | 77 | /// A generic BSDF. 78 | struct bsdf { 79 | /// Storage for different material types. 80 | using union_t = std::variant< 81 | bsdfs::lambertian_reflection_brdf, 82 | bsdfs::specular_reflection_brdf, 83 | bsdfs::specular_transmission_bsdf 84 | >; 85 | 86 | /// Returns the value of the \p f term in the light transport equation. The input vectors are assumed to be 87 | /// normalized and in tangent space. 88 | spectrum f(vec3d norm_in, vec3d norm_out, transport_mode) const; 89 | /// Returns the probability of the given ray being sampled by the material. The input vectors are assumed to 90 | /// be normalized and in tangent space. 91 | double pdf(vec3d norm_in, vec3d norm_out) const; 92 | /// Samples an outgoing ray given a incoming ray and a random sample inside a unit square. 93 | bsdfs::outgoing_ray_sample sample_f(vec3d norm_in, vec2d random, transport_mode) const; 94 | 95 | /// Returns whether this BSDF contains a delta distribution. 96 | bool is_delta() const; 97 | 98 | union_t value; ///< The value of this BSDF. 99 | /// The amount of light emitted from this material. This will be ignored if the \p x component is less than 100 | /// zero. 101 | spectrum emission; 102 | }; 103 | } 104 | -------------------------------------------------------------------------------- /include/fluid/data_structures/aab.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Axis-aligned boxes. 5 | 6 | #include 7 | 8 | namespace fluid { 9 | /// Axis-aligned boxes. 10 | template struct aab { 11 | using vec_type = vec; ///< The vector type. 12 | 13 | /// Default constructor. 14 | aab() = default; 15 | /// Initializes all fields of this struct. 16 | aab(vec_type minp, vec_type maxp) : min(minp), max(maxp) { 17 | } 18 | 19 | /// Returns the \ref aab that contains all given points. 20 | template [[nodiscard]] inline static aab containing( 21 | const vec_type &first, const Args &...args 22 | ) { 23 | aab result(first, first); 24 | (result.make_contain(args), ...); 25 | return result; 26 | } 27 | /// Returns the \ref aab that contains all given points. 28 | template [[nodiscard]] inline static aab containing_dynamic(It &&begin, It &&end) { 29 | if (begin == end) { 30 | return aab(); 31 | } 32 | aab result; 33 | result.min = result.max = *begin; 34 | std::decay_t it = begin; 35 | for (++it; it != end; ++it) { 36 | result.make_contain(*it); 37 | } 38 | return result; 39 | } 40 | 41 | /// Adjusts \ref min and \ref max to make this \ref aab contain the given point. 42 | void make_contain(const vec_type &vec) { 43 | vec_ops::for_each( 44 | [](T &minv, T &maxv, const T &cur) { 45 | minv = std::min(minv, cur); 46 | maxv = std::max(maxv, cur); 47 | }, 48 | min, max, vec 49 | ); 50 | } 51 | 52 | /// Returns whether the two bounding boxes intersect. If the bounding boxes use integer coordinates, then 53 | /// them simply "touching" will count as intersecting. 54 | [[nodiscard]] inline static bool intersects(const aab &lhs, const aab &rhs) { 55 | bool result = true; 56 | vec_ops::for_each( 57 | [&result](const T &min1, const T &max1, const T &min2, const T &max2) { 58 | if (max1 < min2 || min1 > max2) { 59 | result = false; 60 | } 61 | }, 62 | lhs.min, lhs.max, rhs.min, rhs.max 63 | ); 64 | return result; 65 | } 66 | /// Returns the intersection of the given two bounding boxes. If the bounding boxes do not intersect, a 67 | /// bounding box with negative volume will be returned. 68 | [[nodiscard]] inline static aab intersection(const aab &lhs, const aab &rhs) { 69 | aab result; 70 | result.min = vec_ops::apply( 71 | static_cast(std::max), lhs.min, rhs.min 72 | ); 73 | result.max = vec_ops::apply( 74 | static_cast(std::min), lhs.max, rhs.max 75 | ); 76 | return result; 77 | } 78 | /// Returns the \ref aab that bounds the given two bounding boxes. 79 | [[nodiscard]] inline static aab bounding(const aab &lhs, const aab &rhs) { 80 | aab result; 81 | result.min = vec_ops::apply( 82 | static_cast(std::min), lhs.min, rhs.min 83 | ); 84 | result.max = vec_ops::apply( 85 | static_cast(std::max), lhs.max, rhs.max 86 | ); 87 | return result; 88 | } 89 | 90 | /// Computes the size of this box. 91 | [[nodiscard]] vec_type get_size() const { 92 | return max - min; 93 | } 94 | /// Computes the area (in two dimensions) or volume (in three or more dimensions) of this box. 95 | [[nodiscard]] T get_volume() const { 96 | vec_type size = get_size(); 97 | T result = static_cast(1); 98 | vec_ops::for_each( 99 | [&result](const T &sz) { 100 | result *= sz; 101 | }, 102 | size 103 | ); 104 | return result; 105 | } 106 | 107 | /// Returns the center of this box. 108 | [[nodiscard]] vec_type get_center() const { 109 | return (min + max) / static_cast(2); 110 | } 111 | 112 | vec_type 113 | min, ///< The minimum corner. 114 | max; ///< The maximum corner. 115 | }; 116 | 117 | template using aab2 = aab<2, T>; ///< 2D axis-aligned bounding boxes. 118 | using aab2d = aab2; ///< 2D double axis-aligned bounding boxes. 119 | 120 | template using aab3 = aab<3, T>; ///< 3D axis-aligned bounding boxes. 121 | using aab3d = aab3; ///< 3D double axis-aligned bounding boxes. 122 | } 123 | -------------------------------------------------------------------------------- /include/fluid/pressure_solver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// The pressure solver. 4 | 5 | #include 6 | #include 7 | 8 | #include "math/vec.h" 9 | #include "mac_grid.h" 10 | #include "simulation.h" 11 | 12 | namespace fluid { 13 | /// A pressure solver. 14 | class pressure_solver { 15 | public: 16 | /// Additional data for each cell. 17 | struct cell_data { 18 | /// Initializes all members to zero. 19 | cell_data(); 20 | 21 | std::size_t 22 | nonsolid_neighbors : 3, ///< The number of neighboring solid cells. This is the diagonal term. 23 | fluid_xpos : 1, ///< Indicates whether the next cell in the X direction is a fluid cell. 24 | fluid_ypos : 1, ///< Indicates whether the next cell in the Y direction is a fluid cell. 25 | fluid_zpos : 1; ///< Indicates whether the next cell in the Z direction is a fluid cell. 26 | }; 27 | 28 | /// Initializes this solver given the \ref fluid_grid. 29 | explicit pressure_solver(simulation&, const std::vector&); 30 | 31 | /// Solves for the pressure. 32 | /// 33 | /// \return The pressure vector, the residual, and the number of iterations. 34 | [[nodiscard]] std::tuple, double, std::size_t> solve(double dt); 35 | /// Applies the pressure by updating the velocity field. 36 | void apply_pressure(double dt, const std::vector&) const; 37 | 38 | double 39 | tau = 0.97, ///< The tau value. 40 | sigma = 0.25, ///< The sigma value. 41 | tolerance = 1e-6; ///< The tolerance for terminating the algorithm. 42 | std::size_t max_iterations = 200; ///< The maximum number of iterations. 43 | protected: 44 | /// Indicates that a cell is not a fluid cell and does not have an index in \ref _fluid_cells. 45 | constexpr static std::size_t _not_a_fluid_cell = std::numeric_limits::max(); 46 | 47 | /// The sparse matrix A in the book. Each element corresponds to one element in 48 | /// \ref fluid_grid::_fluid_cell_indices in \ref _fluid. Note that to obtain the actrual A matrix an 49 | /// additional coefficient needs to be multiplied. 50 | std::vector _a; 51 | /// The index of each cell in \ref _fluid_cells. Non-fluid cells have the value \ref _not_a_fluid_cell. 52 | grid3 _fluid_cell_indices; 53 | double _a_scale = 0.0; ///< The coefficient that \ref _a should be scaled by. 54 | /// The complete list of cells that contain fluid, sorted in the order they're stored in the grid. 55 | const std::vector &_fluid_cells; 56 | simulation &_sim; ///< The simulation. 57 | 58 | /// Returns the fluid cell index of a neighboring cell in the negative x-, y-, or z-direction. 59 | template [[nodiscard]] std::size_t _get_neg_neighbor_index(vec3s v) const { 60 | if (v[Dim] > 0) { 61 | return _fluid_cell_indices(v - vec3s::axis()); 62 | } 63 | return _not_a_fluid_cell; 64 | } 65 | /// Returns the fluid cell index of a neighboring cell in the positive x-, y-, or z-direction. 66 | template [[nodiscard]] std::size_t _get_pos_neighbor_index(vec3s v) const { 67 | if (v[Dim] + 1 < _sim.grid().grid().get_size()[Dim]) { 68 | return _fluid_cell_indices(v + vec3s::axis()); 69 | } 70 | return _not_a_fluid_cell; 71 | } 72 | 73 | /// Fills \ref _fluid_cell_indices. 74 | void _compute_fluid_cell_indices(); 75 | 76 | /// Computes the matrix A. 77 | void _compute_a_matrix(); 78 | /// Computes the vector b. 79 | [[nodiscard]] std::vector _compute_b_vector() const; 80 | 81 | /// Computes the preconditioner vector. The input vector must have enough space (i.e., must be as long as 82 | /// \ref _a). 83 | [[nodiscard]] std::vector _compute_preconditioner() const; 84 | /// Multiplies the given vector by the preconditioner matrix. The input vectors must have enough space. 85 | void _apply_preconditioner( 86 | std::vector &z, std::vector &q_scratch, 87 | const std::vector &precon, const std::vector &r 88 | ) const; 89 | 90 | /// Multiplies the given vector by the A matrix. The out vector must have enough space. 91 | void _apply_a(std::vector&, const std::vector&) const; 92 | 93 | /// Calculates a + s * b. The three vectors must have the same length. 94 | static void _muladd( 95 | std::vector&, const std::vector &a, const std::vector &b, double s 96 | ); 97 | }; 98 | } 99 | -------------------------------------------------------------------------------- /src/mac_grid.cpp: -------------------------------------------------------------------------------- 1 | #include "fluid/mac_grid.h" 2 | 3 | /// \file 4 | /// Implementation of the fluid grid. 5 | 6 | namespace fluid { 7 | mac_grid::mac_grid(vec3s grid_count) : _grid(grid_count) { 8 | } 9 | 10 | mac_grid::cell *mac_grid::get_cell(vec3s i) { 11 | vec3s grid_size = grid().get_size(); 12 | if (i.x >= grid_size.x || i.y >= grid_size.y || i.z >= grid_size.z) { 13 | return nullptr; 14 | } 15 | return &grid()(i); 16 | } 17 | 18 | const mac_grid::cell *mac_grid::get_cell(vec3s i) const { 19 | vec3s grid_size = grid().get_size(); 20 | if (i.x >= grid_size.x || i.y >= grid_size.y || i.z >= grid_size.z) { 21 | return nullptr; 22 | } 23 | return &grid()(i); 24 | } 25 | 26 | std::pair mac_grid::get_cell_and_type(vec3s i) { 27 | if (cell *cell = get_cell(i)) { 28 | return { cell, cell->cell_type }; 29 | } 30 | return { nullptr, cell::type::solid }; 31 | } 32 | 33 | std::pair mac_grid::get_cell_and_type(vec3s i) const { 34 | if (const cell *c = get_cell(i)) { 35 | return { c, c->cell_type }; 36 | } 37 | return { nullptr, cell::type::solid }; 38 | } 39 | 40 | /// Clamps the input value, returning both the result and whether the value has been modified. Note that this 41 | /// function returns clamped for the max value, so that velocities at the max borders are ignored. 42 | std::pair _clamp(std::size_t val, std::size_t min, std::size_t max) { 43 | if (val < min) { 44 | return { min, true }; 45 | } 46 | if (val >= max) { 47 | return { max, true }; 48 | } 49 | return { val, false }; 50 | } 51 | std::pair mac_grid::get_face_samples( 52 | vec3s grid_index, vec3d offset 53 | ) const { 54 | // z y x 55 | vec3d vels[3][3][3]; 56 | for (std::size_t dz = 0; dz < 3; ++dz) { 57 | auto [cz, zclamp] = _clamp(grid_index.z + dz, 1, grid().get_size().z); 58 | --cz; 59 | for (std::size_t dy = 0; dy < 3; ++dy) { 60 | auto [cy, yclamp] = _clamp(grid_index.y + dy, 1, grid().get_size().y); 61 | --cy; 62 | for (std::size_t dx = 0; dx < 3; ++dx) { 63 | auto [cx, xclamp] = _clamp(grid_index.x + dx, 1, grid().get_size().x); 64 | --cx; 65 | 66 | vec3d vel = grid()(cx, cy, cz).velocities_posface; 67 | if (xclamp) { 68 | vel.x = 0.0; 69 | } 70 | if (yclamp) { 71 | vel.y = 0.0; 72 | } 73 | if (zclamp) { 74 | vel.z = 0.0; 75 | } 76 | vels[dz][dy][dx] = vel; 77 | } 78 | } 79 | } 80 | std::size_t dx = 1, dy = 1, dz = 1; 81 | vec3d tmid = offset - vec3d(0.5, 0.5, 0.5); 82 | if (tmid.x < 0.0) { 83 | dx = 0; 84 | tmid.x += 1.0; 85 | } 86 | if (tmid.y < 0.0) { 87 | dy = 0; 88 | tmid.y += 1.0; 89 | } 90 | if (tmid.z < 0.0) { 91 | dz = 0; 92 | tmid.z += 1.0; 93 | } 94 | face_samples result; 95 | // v000(vels[dz ][dy ][0].x, vels[dz ][0][dx ].y, vels[0][dy ][dx ].z) 96 | // v001(vels[dz ][dy ][1].x, vels[dz ][0][dx + 1].y, vels[0][dy ][dx + 1].z) 97 | // v010(vels[dz ][dy + 1][0].x, vels[dz ][1][dx ].y, vels[0][dy + 1][dx ].z) 98 | // v011(vels[dz ][dy + 1][1].x, vels[dz ][1][dx + 1].y, vels[0][dy + 1][dx + 1].z) 99 | // v100(vels[dz + 1][dy ][0].x, vels[dz + 1][0][dx ].y, vels[1][dy ][dx ].z) 100 | // v101(vels[dz + 1][dy ][1].x, vels[dz + 1][0][dx + 1].y, vels[1][dy ][dx + 1].z) 101 | // v110(vels[dz + 1][dy + 1][0].x, vels[dz + 1][1][dx ].y, vels[1][dy + 1][dx ].z) 102 | // v111(vels[dz + 1][dy + 1][1].x, vels[dz + 1][1][dx + 1].y, vels[1][dy + 1][dx + 1].z) 103 | result.v000 = vec3d(vels[dz][dy][0].x, vels[dz][0][dx].y, vels[0][dy][dx].z); 104 | result.v001 = vec3d(vels[dz][dy][1].x, vels[dz][0][dx + 1].y, vels[0][dy][dx + 1].z); 105 | result.v010 = vec3d(vels[dz][dy + 1][0].x, vels[dz][1][dx].y, vels[0][dy + 1][dx].z); 106 | result.v011 = vec3d(vels[dz][dy + 1][1].x, vels[dz][1][dx + 1].y, vels[0][dy + 1][dx + 1].z); 107 | result.v100 = vec3d(vels[dz + 1][dy][0].x, vels[dz + 1][0][dx].y, vels[1][dy][dx].z); 108 | result.v101 = vec3d(vels[dz + 1][dy][1].x, vels[dz + 1][0][dx + 1].y, vels[1][dy][dx + 1].z); 109 | result.v110 = vec3d(vels[dz + 1][dy + 1][0].x, vels[dz + 1][1][dx].y, vels[1][dy + 1][dx].z); 110 | result.v111 = vec3d(vels[dz + 1][dy + 1][1].x, vels[dz + 1][1][dx + 1].y, vels[1][dy + 1][dx + 1].z); 111 | return { result, tmid }; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /plugins/maya/nodes/grid_manipulator_node.cpp: -------------------------------------------------------------------------------- 1 | #include "grid_manipulator_node.h" 2 | 3 | /// \file 4 | /// Implementation of the grid manipulator node. 5 | 6 | #include 7 | #include 8 | 9 | #include "../misc.h" 10 | #include "grid_node.h" 11 | 12 | namespace fluid::maya { 13 | void *grid_manipulator_node::creator() { 14 | return new grid_manipulator_node(); 15 | } 16 | 17 | MStatus grid_manipulator_node::initialize() { 18 | return MStatus::kSuccess; 19 | } 20 | 21 | MStatus grid_manipulator_node::connectToDependNode(const MObject &node) { 22 | _grid_node = node; 23 | FLUID_MAYA_CHECK_RETURN(finishAddingManips(), "connect to grid node"); 24 | return MPxManipContainer::connectToDependNode(node); 25 | } 26 | 27 | void grid_manipulator_node::preDrawUI(const M3dView&) { 28 | MStatus stat; 29 | 30 | auto [grid_offset, cell_size, grid_size] = _get_grid_attributes(); 31 | _grid_min = grid_offset; 32 | _grid_max = grid_offset + cell_size * vec3d(grid_size); 33 | 34 | MPlug particles_plug(_grid_node, grid_node::attr_output_particle_positions); 35 | MObject particles_obj; 36 | maya_check(particles_plug.getValue(particles_obj), FLUID_DEBUG_MESSAGE("prepare for draw")); 37 | MFnPointArrayData particles_data(particles_obj, &stat); 38 | maya_check(stat, FLUID_DEBUG_MESSAGE("prepare for draw")); 39 | unsigned int num_particles = particles_data.length(&stat); 40 | maya_check(stat, FLUID_DEBUG_MESSAGE("prepare for draw")); 41 | _particles.resize(static_cast(num_particles)); 42 | for (unsigned int i = 0; i < num_particles; ++i) { 43 | const MPoint &pt = particles_data[i]; 44 | vec3d &target = _particles[static_cast(i)]; 45 | target.x = pt.x; 46 | target.y = pt.y; 47 | target.z = pt.z; 48 | } 49 | } 50 | 51 | void grid_manipulator_node::drawUI(MUIDrawManager &ui, const MFrameContext&) const { 52 | vec3d min = _grid_min, max = _grid_max; 53 | ui.beginDrawable(); 54 | /*ui.setColor();*/ 55 | 56 | ui.line(MPoint(min.x, min.y, min.z), MPoint(max.x, min.y, min.z)); 57 | ui.line(MPoint(max.x, min.y, min.z), MPoint(max.x, max.y, min.z)); 58 | ui.line(MPoint(max.x, max.y, min.z), MPoint(min.x, max.y, min.z)); 59 | ui.line(MPoint(min.x, max.y, min.z), MPoint(min.x, min.y, min.z)); 60 | 61 | ui.line(MPoint(min.x, min.y, min.z), MPoint(min.x, min.y, max.z)); 62 | ui.line(MPoint(max.x, min.y, min.z), MPoint(max.x, min.y, max.z)); 63 | ui.line(MPoint(max.x, max.y, min.z), MPoint(max.x, max.y, max.z)); 64 | ui.line(MPoint(min.x, max.y, min.z), MPoint(min.x, max.y, max.z)); 65 | 66 | ui.line(MPoint(min.x, min.y, max.z), MPoint(max.x, min.y, max.z)); 67 | ui.line(MPoint(max.x, min.y, max.z), MPoint(max.x, max.y, max.z)); 68 | ui.line(MPoint(max.x, max.y, max.z), MPoint(min.x, max.y, max.z)); 69 | ui.line(MPoint(min.x, max.y, max.z), MPoint(min.x, min.y, max.z)); 70 | 71 | ui.endDrawable(); 72 | 73 | ui.beginDrawable(); 74 | for (vec3d pt : _particles) { 75 | ui.point(MPoint(pt.x, pt.y, pt.z)); 76 | } 77 | ui.endDrawable(); 78 | } 79 | 80 | std::tuple grid_manipulator_node::_get_grid_attributes() const { 81 | MStatus stat; 82 | 83 | vec3d grid_offset; 84 | double cell_size = 0.0; 85 | vec3i grid_size; 86 | 87 | MPlug grid_offset_plug(_grid_node, grid_node::attr_grid_offset); 88 | MObject grid_offset_obj; 89 | maya_check(grid_offset_plug.getValue(grid_offset_obj), FLUID_DEBUG_MESSAGE("bounding box calculation")); 90 | MFnNumericData grid_offset_data(grid_offset_obj, &stat); 91 | maya_check(stat, FLUID_DEBUG_MESSAGE("bounding box calculation")); 92 | maya_check( 93 | grid_offset_data.getData3Double(grid_offset.x, grid_offset.y, grid_offset.z), 94 | FLUID_DEBUG_MESSAGE("bounding box calculation") 95 | ); 96 | 97 | MPlug cell_size_plug(_grid_node, grid_node::attr_cell_size); 98 | maya_check(cell_size_plug.getValue(cell_size), FLUID_DEBUG_MESSAGE("bounding box calculation")); 99 | 100 | MPlug grid_size_plug(_grid_node, grid_node::attr_grid_size); 101 | MObject grid_size_obj; 102 | maya_check(grid_size_plug.getValue(grid_size_obj), FLUID_DEBUG_MESSAGE("bounding box calculation")); 103 | MFnNumericData grid_size_data(grid_size_obj, &stat); 104 | maya_check(stat, FLUID_DEBUG_MESSAGE("bounding box calculation")); 105 | maya_check( 106 | grid_size_data.getData3Int(grid_size.x, grid_size.y, grid_size.z), 107 | FLUID_DEBUG_MESSAGE("bounding box calculation") 108 | ); 109 | 110 | return { grid_offset, cell_size, grid_size }; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/voxelizer.cpp: -------------------------------------------------------------------------------- 1 | #include "fluid/voxelizer.h" 2 | 3 | /// \file 4 | /// Implementation of the voxelizer. 5 | 6 | #include 7 | #include 8 | 9 | #include "fluid/math/intersection.h" 10 | 11 | namespace fluid { 12 | void voxelizer::resize_reposition_grid(vec3d min, vec3d max) { 13 | vec3d 14 | size = max - min, 15 | grid_size = vec_ops::apply(static_cast(std::ceil), size / cell_size); 16 | grid_offset = min - 0.5 * (grid_size * cell_size - size) - vec3d(cell_size, cell_size, cell_size); 17 | voxels = grid3(vec3s(grid_size) + vec3s(2, 2, 2), cell_type::interior); 18 | } 19 | 20 | vec3i voxelizer::resize_reposition_grid_constrained( 21 | vec3d min, vec3d max, double ref_cell_size, vec3d ref_grid_offset 22 | ) { 23 | cell_size = ref_cell_size; 24 | vec3i 25 | grid_min(vec_ops::apply( 26 | static_cast(std::floor), (min - ref_grid_offset) / cell_size) 27 | ), 28 | grid_max(vec_ops::apply( 29 | static_cast(std::ceil), (max - ref_grid_offset) / cell_size) 30 | ); 31 | grid_min -= vec3i(1, 1, 1); 32 | grid_max += vec3i(1, 1, 1); 33 | grid_offset = ref_grid_offset + vec3d(grid_min) * cell_size; 34 | voxels = grid3(vec3s(grid_max - grid_min), cell_type::interior); 35 | return grid_min; 36 | } 37 | 38 | std::pair voxelizer::get_overlapping_cell_range(vec3i offset, vec3s ref_grid_size) const { 39 | vec3s min_coord = vec_ops::apply( 40 | [](int coord) { 41 | return coord < 0 ? static_cast(-coord) : 0; 42 | }, 43 | offset 44 | ); 45 | vec3s max_coord = vec_ops::apply( 46 | [](int coord, std::size_t max) { 47 | return std::min(static_cast(std::max(0, coord)), max); 48 | }, 49 | offset + vec3i(voxels.get_size()), ref_grid_size 50 | ); 51 | return { min_coord, max_coord }; 52 | } 53 | 54 | void voxelizer::voxelize_triangle(vec3d p1, vec3d p2, vec3d p3) { 55 | vec3d min = p1, max = p1; 56 | _update_bounding_box(p2, min, max); 57 | _update_bounding_box(p3, min, max); 58 | 59 | double half_cell_size = 0.5 * cell_size; 60 | vec3d half_extents = vec3d(half_cell_size, half_cell_size, half_cell_size); 61 | 62 | // here we assume the indices are in range 63 | // otherwise converting a negative float to unsigned is undefined behavior 64 | vec3s min_id((min - grid_offset) / cell_size), max_id((max - grid_offset) / cell_size); 65 | vec3d min_center = grid_offset + vec3d(min_id) * cell_size + half_extents, center = min_center; 66 | for (std::size_t z = min_id.z; z <= max_id.z; ++z, center.z += cell_size) { 67 | center.y = min_center.y; 68 | for (std::size_t y = min_id.y; y <= max_id.y; ++y, center.y += cell_size) { 69 | center.x = min_center.x; 70 | for (std::size_t x = min_id.x; x <= max_id.x; ++x, center.x += cell_size) { 71 | cell_type &type = voxels(x, y, z); 72 | if (type != cell_type::surface) { 73 | if (aab_triangle_overlap_bounded(center, half_extents, p1, p2, p3)) { 74 | type = cell_type::surface; 75 | } 76 | } 77 | } 78 | } 79 | } 80 | } 81 | 82 | void voxelizer::mark_exterior() { 83 | if (voxels.get_array_size(voxels.get_size()) == 0) { 84 | return; 85 | } 86 | 87 | if (voxels(0, 0, 0) == cell_type::surface) { 88 | return; 89 | } 90 | voxels(0, 0, 0) = cell_type::exterior; 91 | std::stack stack; 92 | stack.emplace(vec3s(0, 0, 0)); 93 | 94 | auto check_push = [this, &stack](vec3s p) { 95 | cell_type &ct = voxels(p); 96 | if (ct == cell_type::interior) { 97 | ct = cell_type::exterior; 98 | stack.emplace(p); 99 | } 100 | }; 101 | 102 | while (!stack.empty()) { 103 | vec3s cur = stack.top(); 104 | stack.pop(); 105 | 106 | if (cur.z > 0) { 107 | check_push(vec3s(cur.x, cur.y, cur.z - 1)); 108 | } 109 | if (cur.y > 0) { 110 | check_push(vec3s(cur.x, cur.y - 1, cur.z)); 111 | } 112 | if (cur.x > 0) { 113 | check_push(vec3s(cur.x - 1, cur.y, cur.z)); 114 | } 115 | 116 | if (cur.z + 1 < voxels.get_size().z) { 117 | check_push(vec3s(cur.x, cur.y, cur.z + 1)); 118 | } 119 | if (cur.y + 1 < voxels.get_size().y) { 120 | check_push(vec3s(cur.x, cur.y + 1, cur.z)); 121 | } 122 | if (cur.x + 1 < voxels.get_size().x) { 123 | check_push(vec3s(cur.x + 1, cur.y, cur.z)); 124 | } 125 | } 126 | } 127 | 128 | void voxelizer::_update_bounding_box(vec3d pos, vec3d &min, vec3d &max) { 129 | vec_ops::for_each( 130 | [](double v, double &minv, double &maxv) { 131 | minv = std::min(minv, v); 132 | maxv = std::max(maxv, v); 133 | }, 134 | pos, min, max 135 | ); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /include/fluid/renderer/rendering.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Utilities for rendering full images. 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "../math/vec.h" 11 | #include "common.h" 12 | #include "camera.h" 13 | 14 | #define FLUID_RENDERER_PARALLEL 15 | 16 | namespace fluid::renderer { 17 | /// Renders the scene to an image using a naive method for parallelization. 18 | template image render_naive( 19 | Incoming &&li, const camera &cam, vec2s size, std::size_t spp, pcg32 &random 20 | ) { 21 | using namespace std::chrono_literals; 22 | 23 | image result(size); 24 | std::uniform_real_distribution dist(0.0, 1.0); 25 | vec2d screen_div = vec_ops::memberwise::div(vec2d(1.0, 1.0), vec2d(size)); 26 | [[maybe_unused]] std::atomic finished = 0; 27 | [[maybe_unused]] std::thread monitor_thread; 28 | if constexpr (Monitor) { 29 | monitor_thread = std::thread( 30 | [&finished](std::size_t total) { 31 | while (true) { 32 | std::size_t fin = finished; 33 | std::cout << fin << " / " << total << " (" << (100.0 * fin / static_cast(total)) << "%)" << std::endl; 34 | if (fin == total) { 35 | break; 36 | } 37 | std::this_thread::sleep_for(100ms); 38 | } 39 | }, 40 | size.x * size.y 41 | ); 42 | } 43 | #ifdef FLUID_RENDERER_PARALLEL 44 | # pragma omp parallel 45 | #endif 46 | { 47 | pcg32 thread_rnd(random()); 48 | #ifdef FLUID_RENDERER_PARALLEL 49 | # pragma omp for 50 | #endif 51 | for (int y = 0; y < size.y; ++y) { 52 | for (std::size_t x = 0; x < size.x; ++x) { 53 | spectrum res; 54 | for (std::size_t i = 0; i < spp; ++i) { 55 | vec2d pos = vec_ops::memberwise::mul( 56 | vec2d(vec2s(x, y)) + vec2d(dist(thread_rnd), dist(thread_rnd)), screen_div 57 | ); 58 | res += li(cam.get_ray(pos), thread_rnd); 59 | } 60 | result.pixels(x, y) = res / static_cast(spp); 61 | if constexpr (Monitor) { 62 | ++finished; 63 | } 64 | } 65 | } 66 | } 67 | if constexpr (Monitor) { 68 | monitor_thread.join(); 69 | } 70 | return result; 71 | } 72 | 73 | /// Accumulates incoming light to the given buffer using a naive method for parallelization. 74 | template void accumulate_naive( 75 | Incoming &&li, image &buf, const camera &cam, std::size_t spp, pcg32 &random 76 | ) { 77 | using namespace std::chrono_literals; 78 | 79 | std::uniform_real_distribution dist(0.0, 1.0); 80 | vec2d screen_div = vec_ops::memberwise::div(vec2d(1.0, 1.0), vec2d(buf.pixels.get_size())); 81 | [[maybe_unused]] std::atomic finished = 0; 82 | [[maybe_unused]] std::thread monitor_thread; 83 | if constexpr (Monitor) { 84 | monitor_thread = std::thread( 85 | [&finished](std::size_t total) { 86 | while (true) { 87 | std::size_t fin = finished; 88 | std::cout << fin << " / " << total << " (" << (100.0 * fin / static_cast(total)) << "%)" << std::endl; 89 | if (fin == total) { 90 | break; 91 | } 92 | std::this_thread::sleep_for(100ms); 93 | } 94 | }, 95 | buf.pixels.get_size().x * buf.pixels.get_size().y 96 | ); 97 | } 98 | #ifdef FLUID_RENDERER_PARALLEL 99 | # pragma omp parallel 100 | #endif 101 | { 102 | pcg32 thread_rnd(random()); 103 | #ifdef FLUID_RENDERER_PARALLEL 104 | # pragma omp for 105 | #endif 106 | for (int y = 0; y < buf.pixels.get_size().y; ++y) { 107 | for (std::size_t x = 0; x < buf.pixels.get_size().x; ++x) { 108 | spectrum res; 109 | for (std::size_t i = 0; i < spp; ++i) { 110 | vec2d pos = vec_ops::memberwise::mul( 111 | vec2d(vec2s(x, y)) + vec2d(dist(thread_rnd), dist(thread_rnd)), screen_div 112 | ); 113 | res += li(cam.get_ray(pos), thread_rnd); 114 | } 115 | buf.pixels(x, y) += res; 116 | if constexpr (Monitor) { 117 | ++finished; 118 | } 119 | } 120 | } 121 | } 122 | if constexpr (Monitor) { 123 | monitor_thread.join(); 124 | } 125 | } 126 | 127 | /// Renders the scene to an image, dividing it into blocks. 128 | template image render_block( 129 | Incoming &&li, const camera &cam, vec2s size, std::size_t spp, 130 | pcg32 &random, vec2s block_size, std::size_t nthreads 131 | ) { 132 | image result(size); 133 | vec2s num_blocks_dir = vec_ops::memberwise::div(size + block_size - vec2s(1, 1), block_size); 134 | std::size_t num_blocks = num_blocks_dir.x * num_blocks_dir.y; 135 | std::size_t blocks_per_thread = num_blocks / nthreads; 136 | // TODO 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/renderer/bsdf.cpp: -------------------------------------------------------------------------------- 1 | #include "fluid/renderer/bsdf.h" 2 | 3 | /// \file 4 | /// Implementation of different BSDFs. 5 | 6 | #include "fluid/math/constants.h" 7 | #include "fluid/math/warping.h" 8 | #include "fluid/renderer/fresnel.h" 9 | 10 | namespace fluid::renderer { 11 | namespace bsdfs { 12 | spectrum lambertian_reflection_brdf::f(vec3d in, vec3d out, transport_mode) const { 13 | return in.y * out.y > 0.0 ? reflectance / constants::pi : spectrum(); 14 | } 15 | 16 | outgoing_ray_sample lambertian_reflection_brdf::sample_f( 17 | vec3d norm_in, vec2d random, transport_mode mode 18 | ) const { 19 | outgoing_ray_sample result; 20 | result.norm_out_direction_tangent = warping::unit_hemisphere_from_unit_square_cosine(random); 21 | result.pdf = warping::pdf_unit_hemisphere_from_unit_square_cosine(result.norm_out_direction_tangent); 22 | if constexpr (double_sided) { 23 | if (norm_in.y < 0.0) { 24 | result.norm_out_direction_tangent.y = -result.norm_out_direction_tangent.y; 25 | } 26 | } 27 | result.reflectance = f(norm_in, result.norm_out_direction_tangent, mode); 28 | return result; 29 | } 30 | 31 | double lambertian_reflection_brdf::pdf(vec3d norm_in, vec3d norm_out) const { 32 | if constexpr (double_sided) { 33 | if ((norm_in.y > 0) == (norm_out.y > 0)) { 34 | norm_out.y = std::abs(norm_out.y); 35 | return warping::pdf_unit_hemisphere_from_unit_square_cosine(norm_out); 36 | } 37 | return 0.0; 38 | } else { 39 | return warping::pdf_unit_hemisphere_from_unit_square_cosine(norm_out);; 40 | } 41 | } 42 | 43 | bool lambertian_reflection_brdf::is_delta() const { 44 | return false; 45 | } 46 | 47 | 48 | spectrum specular_reflection_brdf::f(vec3d, vec3d, transport_mode) const { 49 | return spectrum(); 50 | } 51 | 52 | outgoing_ray_sample specular_reflection_brdf::sample_f(vec3d norm_in, vec2d, transport_mode) const { 53 | outgoing_ray_sample result; 54 | result.norm_out_direction_tangent.x = -norm_in.x; 55 | result.norm_out_direction_tangent.y = norm_in.y; 56 | result.norm_out_direction_tangent.z = -norm_in.z; 57 | result.pdf = 1.0; 58 | result.reflectance = reflectance / std::abs(norm_in.y); // cancel out Lambertian term 59 | return result; 60 | } 61 | 62 | double specular_reflection_brdf::pdf(vec3d, vec3d) const { 63 | return 0.0; 64 | } 65 | 66 | bool specular_reflection_brdf::is_delta() const { 67 | return true; 68 | } 69 | 70 | 71 | spectrum specular_transmission_bsdf::f(vec3d, vec3d, transport_mode) const { 72 | return spectrum(); 73 | } 74 | 75 | outgoing_ray_sample specular_transmission_bsdf::sample_f( 76 | vec3d norm_in, vec2d random, transport_mode mode 77 | ) const { 78 | outgoing_ray_sample result; 79 | double eta_in = 1.0, eta_out = index_of_refraction, cos_in = norm_in.y, sign = 1.0; 80 | if (cos_in < 0.0) { 81 | std::swap(eta_in, eta_out); 82 | cos_in = -cos_in; 83 | sign = -1.0; 84 | } 85 | double eta = eta_in / eta_out; 86 | double sin2_out = (1.0 - cos_in * cos_in) * eta * eta; 87 | if (sin2_out >= 1.0) { // total internal reflection 88 | result.norm_out_direction_tangent = vec3d(-norm_in.x, norm_in.y, -norm_in.z); 89 | result.pdf = 1.0; 90 | result.reflectance = skin / cos_in; 91 | return result; 92 | } 93 | double cos_out = std::sqrt(1.0 - sin2_out); 94 | double fres = fresnel::dielectric(cos_in, cos_out, eta_in, eta_out); 95 | if (random.x > fres) { // refraction 96 | result.norm_out_direction_tangent = -eta * norm_in; 97 | result.norm_out_direction_tangent.y += (eta * cos_in - cos_out) * sign; 98 | result.pdf = 1.0 - fres; 99 | result.reflectance = (1.0 - fres) * skin / cos_out; 100 | if (mode == transport_mode::radiance) { 101 | result.reflectance *= eta * eta; 102 | } 103 | } else { // reflection 104 | result.norm_out_direction_tangent = vec3d(-norm_in.x, norm_in.y, -norm_in.z); 105 | result.pdf = fres; 106 | result.reflectance = fres * skin / cos_in; 107 | } 108 | return result; 109 | } 110 | 111 | double specular_transmission_bsdf::pdf(vec3d, vec3d) const { 112 | return 0.0; 113 | } 114 | 115 | bool specular_transmission_bsdf::is_delta() const { 116 | return true; 117 | } 118 | } 119 | 120 | 121 | spectrum bsdf::f(vec3d norm_in, vec3d norm_out, transport_mode mode) const { 122 | return std::visit( 123 | [&](const auto &v) { 124 | return v.f(norm_in, norm_out, mode); 125 | }, 126 | value 127 | ); 128 | } 129 | 130 | /// Returns the probability of the given ray being sampled by the material. The input vectors are assumed to 131 | /// be normalized and in tangent space. 132 | double bsdf::pdf(vec3d norm_in, vec3d norm_out) const { 133 | return std::visit( 134 | [&](const auto &v) { 135 | return v.pdf(norm_in, norm_out); 136 | }, 137 | value 138 | ); 139 | } 140 | 141 | /// Samples an outgoing ray given a incoming ray and a random sample inside a unit square. 142 | bsdfs::outgoing_ray_sample bsdf::sample_f(vec3d norm_in, vec2d random, transport_mode mode) const { 143 | return std::visit( 144 | [&](const auto &v) { 145 | return v.sample_f(norm_in, random, mode); 146 | }, 147 | value 148 | ); 149 | } 150 | 151 | bool bsdf::is_delta() const { 152 | return std::visit( 153 | [](const auto &v) { 154 | return v.is_delta(); 155 | }, 156 | value 157 | ); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /include/fluid/renderer/primitive.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Declaration of primitives. 5 | 6 | #include 7 | 8 | #include "fluid/math/vec.h" 9 | #include "fluid/math/mat.h" 10 | #include "fluid/math/intersection.h" 11 | #include "fluid/data_structures/aab.h" 12 | #include "fluid/renderer/common.h" 13 | 14 | namespace fluid::renderer { 15 | struct entity_info; 16 | 17 | /// Stores the resutls of a ray cast operation. 18 | struct ray_cast_result { 19 | double t = 0.0; ///< The \p t value of the ray. If the ray cast didn't hit, then this value will be \p nan. 20 | double custom[3]{}; ///< Custom data used by each primitive type. 21 | 22 | /// Returns whether this ray cast is a hit. 23 | bool is_hit() const { 24 | return !std::isnan(t); 25 | } 26 | }; 27 | 28 | /// Definition of different types of primitives. 29 | namespace primitives { 30 | /// Stores the result of sampling the surface of a primitive. 31 | struct surface_sample { 32 | vec3d 33 | position, ///< Position on the surface in world coordinates. 34 | geometric_normal; ///< The geometric normal. 35 | vec2d uv; ///< The UV at the sampled position. 36 | double pdf = 0.0; ///< The probability density function of this sample's position. 37 | }; 38 | 39 | /// A triangle primitive. 40 | struct triangle_primitive { 41 | vec3d 42 | point1, ///< The first vertex of this triangle. 43 | edge12, ///< Edge from the first vertex to the second vertex. 44 | edge13, ///< Edge from the first vertex to the third vertex. 45 | geometric_normal, ///< The geometric normal of this triangle. 46 | normal_p1, ///< Shading normal at \ref point1. 47 | normal_e12, ///< Shading normal difference between point 1 and point 2. 48 | normal_e13; ///< Shading normal difference between point 1 and point 3. 49 | vec2d 50 | uv_p1, ///< The UV of \ref point1. 51 | uv_e12, ///< The UV difference of \ref edge12. 52 | uv_e13; ///< The UV difference of \ref edge13. 53 | double double_surface_area = 0.0; ///< The surface area of this primitive. This includes both sides. 54 | 55 | /// Returns the bounding box. 56 | [[nodiscard]] aab3d get_bounding_box() const; 57 | /// Returns the result of \ref ray_triangle_intersection_edges(). 58 | [[nodiscard]] ray_cast_result ray_cast(const ray&) const; 59 | /// Returns \ref geometric_normal. 60 | [[nodiscard]] vec3d get_geometric_normal(ray_cast_result) const; 61 | /// Returns the UV at the given intersection. 62 | [[nodiscard]] vec2d get_uv(ray_cast_result) const; 63 | 64 | /// Samples the surface of this triangle. 65 | [[nodiscard]] surface_sample sample_surface(vec2d) const; 66 | /// Returns \ref double_surface_area. 67 | [[nodiscard]] double surface_area() const; 68 | 69 | /// Computes the normal and surface area of this triangle. 70 | void compute_attributes(); 71 | }; 72 | /// A sphere primitive. The primitive is a sphere at the origin with radius 1 transformed by the given 73 | /// transformation matrix. 74 | struct sphere_primitive { 75 | rmat3d 76 | world_to_local, ///< Transforms directions from world coordinates to local coordinates. 77 | local_to_world; ///< Transforms directions from world coordinates to local coordinates. 78 | vec3d 79 | world_to_local_offset, ///< The offset used when transforming from world to local coordinates. 80 | local_to_world_offset; ///< The offset used when transforming from local to world coordinates. 81 | 82 | /// Returns the bounding box. 83 | [[nodiscard]] aab3d get_bounding_box() const; 84 | /// Returns the ray cast result. 85 | [[nodiscard]] ray_cast_result ray_cast(const ray&) const; 86 | /// Computes the normal at the given intersection. 87 | [[nodiscard]] vec3d get_geometric_normal(ray_cast_result) const; 88 | /// Computes the UV at the given intersection. 89 | [[nodiscard]] vec2d get_uv(ray_cast_result) const; 90 | 91 | /// Samples the surface of this sphere. This function is generally inaccurate and should be avoided. 92 | [[nodiscard]] surface_sample sample_surface(vec2d) const; 93 | /// Not implemented. 94 | [[nodiscard]] double surface_area() const; 95 | 96 | /// Sets the transformation of this sphere. 97 | void set_transformation(rmat3x4d); 98 | }; 99 | } 100 | 101 | /// A generic primitive. 102 | struct primitive { 103 | /// The union used store the primitive. 104 | using union_t = std::variant< 105 | primitives::triangle_primitive, 106 | primitives::sphere_primitive 107 | >; 108 | 109 | /// Forwards the call to underlying primitive types. 110 | [[nodiscard]] aab3d get_bounding_box() const; 111 | /// Performs ray casting. 112 | [[nodiscard]] ray_cast_result ray_cast(const ray&) const; 113 | /// Returns an orthonormal matrix that converts from world space directions to tangent space directions. 114 | [[nodiscard]] vec3d get_geometric_normal(ray_cast_result) const; 115 | /// Returns the UV at the given intersection. 116 | [[nodiscard]] vec2d get_uv(ray_cast_result) const; 117 | 118 | /// Samples the surface of this primitive. 119 | /// 120 | /// \return A point on the surface and the surface normal at that point. 121 | [[nodiscard]] primitives::surface_sample sample_surface(vec2d) const; 122 | /// Returns the surface area of this primitive. 123 | [[nodiscard]] double surface_area() const; 124 | 125 | union_t value; ///< The value of this primitive. 126 | entity_info *entity = nullptr; ///< The entity associated with this primitive. 127 | }; 128 | } -------------------------------------------------------------------------------- /include/fluid/math/vec_simd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// SIMD vector types. 5 | 6 | #include 7 | 8 | #include "vec.h" 9 | 10 | namespace fluid { 11 | /// 4D double vectors implemented using AVX instructions. The lower bits contain the x value. 12 | struct vec4d_avx { 13 | /// Initializes \ref value. 14 | explicit vec4d_avx(__m256d v) : value(v) { 15 | } 16 | /// Sets all components of this vector. Intel did not give cycle counts for this operation. 17 | vec4d_avx(double x, double y, double z, double w) : value(_mm256_setr_pd(x, y, z, w)) { 18 | } 19 | 20 | /// Returns the zero vector. 21 | inline static vec4d_avx zero() { 22 | return vec4d_avx(_mm256_setzero_pd()); 23 | } 24 | /// Loads data from four aligned doubles. The input values are stored in xyzw order. 25 | inline static vec4d_avx load_aligned(double arr[4]) { 26 | return vec4d_avx(_mm256_load_pd(arr)); 27 | } 28 | /// Loads data from four doubles that may or may not be aligned. The input values are stored in xyzw order. 29 | inline static vec4d_avx load_unaligned(double arr[4]) { 30 | return vec4d_avx(_mm256_loadu_pd(arr)); 31 | } 32 | /// Loads a single double value into all components of the vector. 33 | inline static vec4d_avx uniform(double v) { 34 | return vec4d_avx(_mm256_broadcast_sd(&v)); 35 | } 36 | 37 | /// Returns the x coordinate. 38 | double x() const { 39 | return _mm256_cvtsd_f64(value); 40 | } 41 | /// Stores the contents of this vector into the given aligned array. 42 | void store_aligned(double arr[4]) const { 43 | _mm256_store_pd(arr, value); 44 | } 45 | /// Stores the contents of this vector into the given unaligned array. 46 | void store_unaligned(double arr[4]) const { 47 | _mm256_storeu_pd(arr, value); 48 | } 49 | /// Converts this vector to a \ref vec4d. 50 | explicit operator vec4d() const { 51 | alignas(__m256d) vec4d result; 52 | store_aligned(&result.x); 53 | return result; 54 | } 55 | 56 | 57 | /// Permutes the components of this vector and returns the result. Each template parameter indicates which 58 | /// component to put on that position, rather than where to put that component. 59 | template vec4d_avx permuted() const { 60 | return vec4d_avx(_mm256_permute4x64_pd(value, _MM_SHUFFLE(W, Z, Y, X))); 61 | } 62 | 63 | 64 | // arithmetic 65 | /// Horizontal addition. The result is (lhs.x + lhs.y, rhs.x + rhs.y, lhs.z + lhs.w, rhs.z + rhs.w). 66 | inline static vec4d_avx horizontal_add(vec4d_avx lhs, vec4d_avx rhs) { 67 | return vec4d_avx(_mm256_hadd_pd(lhs.value, rhs.value)); 68 | } 69 | /// Horizontal subtraction. The result is 70 | /// (lhs.x - lhs.y, rhs.x - rhs.y, lhs.z - lhs.w, rhs.z - rhs.w). 71 | inline static vec4d_avx horizontal_sub(vec4d_avx lhs, vec4d_avx rhs) { 72 | return vec4d_avx(_mm256_hsub_pd(lhs.value, rhs.value)); 73 | } 74 | 75 | /// Addition. 76 | friend vec4d_avx operator+(vec4d_avx lhs, vec4d_avx rhs) { 77 | return vec4d_avx(_mm256_add_pd(lhs.value, rhs.value)); 78 | } 79 | /// In-place addition. 80 | vec4d_avx &operator+=(vec4d_avx rhs) { 81 | return (*this) = (*this) + rhs; 82 | } 83 | /// Subtraction. 84 | friend vec4d_avx operator-(vec4d_avx lhs, vec4d_avx rhs) { 85 | return vec4d_avx(_mm256_sub_pd(lhs.value, rhs.value)); 86 | } 87 | /// In-place subtraction. 88 | vec4d_avx &operator-=(vec4d_avx rhs) { 89 | return (*this) = (*this) - rhs; 90 | } 91 | 92 | __m256d value; ///< The value of this vector. 93 | }; 94 | 95 | namespace vec_ops { 96 | namespace memberwise { 97 | /// Memberwise multiplication. 98 | inline vec4d_avx mul(vec4d_avx lhs, vec4d_avx rhs) { 99 | return vec4d_avx(_mm256_mul_pd(lhs.value, rhs.value)); 100 | } 101 | /// Memberwise division. 102 | inline vec4d_avx div(vec4d_avx lhs, vec4d_avx rhs) { 103 | return vec4d_avx(_mm256_div_pd(lhs.value, rhs.value)); 104 | } 105 | 106 | /// Square root. 107 | inline vec4d_avx sqrt(vec4d_avx value) { 108 | return vec4d_avx(_mm256_sqrt_pd(value.value)); 109 | } 110 | 111 | /// Returns the minimum of each component. 112 | inline vec4d_avx min(vec4d_avx lhs, vec4d_avx rhs) { 113 | return vec4d_avx(_mm256_min_pd(lhs.value, rhs.value)); 114 | } 115 | /// Returns the maximum of each component. 116 | inline vec4d_avx max(vec4d_avx lhs, vec4d_avx rhs) { 117 | return vec4d_avx(_mm256_max_pd(lhs.value, rhs.value)); 118 | } 119 | 120 | /// Flooring. 121 | inline vec4d_avx floor(vec4d_avx v) { 122 | return vec4d_avx(_mm256_floor_pd(v.value)); 123 | } 124 | /// Ceiling. 125 | inline vec4d_avx ceil(vec4d_avx v) { 126 | return vec4d_avx(_mm256_ceil_pd(v.value)); 127 | } 128 | /// Rounding. 129 | inline vec4d_avx round(vec4d_avx v) { 130 | return vec4d_avx(_mm256_round_pd(v.value, _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC)); 131 | } 132 | } 133 | 134 | /// Dot product. This is less efficient than performing multiple dot products at once. 135 | inline double dot(vec4d_avx lhs, vec4d_avx rhs) { 136 | vec4d_avx res = memberwise::mul(lhs, rhs); 137 | res = vec4d_avx::horizontal_add(res, res); 138 | res = res.permuted<0, 2, 0, 2>(); 139 | res = vec4d_avx::horizontal_add(res, res); 140 | return res.x(); 141 | } 142 | /// Simultaneously computes the dot product of 4 pairs of vectors. 143 | inline vec4d_avx dot4( 144 | vec4d_avx a1, vec4d_avx b1, vec4d_avx a2, vec4d_avx b2, 145 | vec4d_avx a3, vec4d_avx b3, vec4d_avx a4, vec4d_avx b4 146 | ) { 147 | vec4d_avx 148 | m1 = memberwise::mul(a1, b1), m2 = memberwise::mul(a2, b2), 149 | m3 = memberwise::mul(a3, b3), m4 = memberwise::mul(a4, b4); 150 | vec4d_avx s13 = vec4d_avx::horizontal_add(m1, m3), s24 = vec4d_avx::horizontal_add(m2, m4); 151 | return vec4d_avx::horizontal_add(s13.permuted<0, 2, 1, 3>(), s24.permuted<0, 2, 1, 3>()); 152 | } 153 | 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /plugins/maya/commands/add_fluid_source.cpp: -------------------------------------------------------------------------------- 1 | #include "add_fluid_source.h" 2 | 3 | /// \file 4 | /// Implementation of the addFluidSource command. 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "../misc.h" 12 | #include "../nodes/grid_node.h" 13 | #include "../nodes/voxelizer_node.h" 14 | 15 | namespace fluid::maya { 16 | void *add_fluid_source_command::creator() { 17 | return new add_fluid_source_command(); 18 | } 19 | 20 | MStatus add_fluid_source_command::doIt(const MArgList &args) { 21 | MStatus stat; 22 | 23 | // get selected grid and geometry 24 | MSelectionList selection; 25 | FLUID_MAYA_CHECK_RETURN(MGlobal::getActiveSelectionList(selection), "retrieve selection"); 26 | MObject grid = MObject::kNullObj; 27 | std::vector mesh_plugs; 28 | for (unsigned int i = 0; i < selection.length(); ++i) { 29 | { // check for fluid grid 30 | MObject node; 31 | FLUID_MAYA_CHECK_RETURN(selection.getDependNode(i, node), "retrieve selection"); 32 | MFnDependencyNode fn_node(node, &stat); 33 | FLUID_MAYA_CHECK(stat, "retrieve selection"); 34 | MTypeId id = fn_node.typeId(&stat); 35 | FLUID_MAYA_CHECK(stat, "retrieve selection"); 36 | if (id == grid_node::id) { 37 | if (grid.isNull()) { 38 | grid = std::move(node); 39 | } else { 40 | // TODO print error message 41 | } 42 | continue; 43 | } 44 | } 45 | { // check for meshes 46 | MDagPath dag_path; 47 | FLUID_MAYA_CHECK_RETURN(selection.getDagPath(i, dag_path), "retrieve selection"); 48 | FLUID_MAYA_CHECK_RETURN(dag_path.extendToShape(), "retrieve selection"); 49 | MObject node = dag_path.node(&stat); 50 | FLUID_MAYA_CHECK(stat, "retrieve selection"); 51 | MFnDependencyNode fn_node(node, &stat); 52 | FLUID_MAYA_CHECK(stat, "retrieve selection"); 53 | MPlug mesh_plug = fn_node.findPlug("worldMesh", false, &stat); 54 | FLUID_MAYA_CHECK(stat, "retrieve selection"); 55 | mesh_plugs.emplace_back(mesh_plug.elementByLogicalIndex(0, &stat)); 56 | FLUID_MAYA_CHECK(stat, "retrieve selection"); 57 | continue; 58 | } 59 | // TODO print error message 60 | } 61 | 62 | if (grid.isNull()) { // no grid selected 63 | // TODO print error message 64 | return MStatus::kInvalidParameter; 65 | } 66 | 67 | MFnDependencyNode fn_grid(grid, &stat); 68 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 69 | MPlug sources_plug = fn_grid.findPlug(grid_node::attr_sources, false, &stat); 70 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 71 | MPlug ref_cell_size_plug = fn_grid.findPlug(grid_node::attr_cell_size, false, &stat); 72 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 73 | MPlug ref_grid_offset_plug = fn_grid.findPlug(grid_node::attr_grid_offset, false, &stat); 74 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 75 | MPlug ref_grid_size_plug = fn_grid.findPlug(grid_node::attr_grid_size, false, &stat); 76 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 77 | 78 | unsigned int num_sources = sources_plug.numElements(&stat); 79 | FLUID_MAYA_CHECK(stat, "attribute connection"); 80 | 81 | for (MPlug &mesh_plug : mesh_plugs) { 82 | MObject voxelizer = _graph_modifier.createNode(voxelizer_node::id, &stat); 83 | FLUID_MAYA_CHECK(stat, "node creation"); 84 | 85 | MFnDependencyNode fn_voxelizer(voxelizer, &stat); 86 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 87 | MPlug vox_mesh_plug = fn_voxelizer.findPlug(voxelizer_node::attr_input_mesh, false, &stat); 88 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 89 | MPlug vox_cell_size_plug = fn_voxelizer.findPlug(voxelizer_node::attr_cell_size, false, &stat); 90 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 91 | MPlug vox_ref_grid_offset_plug = 92 | fn_voxelizer.findPlug(voxelizer_node::attr_ref_grid_offset, false, &stat); 93 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 94 | MPlug vox_ref_grid_size_plug = fn_voxelizer.findPlug(voxelizer_node::attr_ref_grid_size, false, &stat); 95 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 96 | MPlug vox_cells_plug = fn_voxelizer.findPlug(voxelizer_node::attr_output_cells_ref, false, &stat); 97 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 98 | MPlug vox_include_interior_plug = fn_voxelizer.findPlug( 99 | voxelizer_node::attr_include_interior, false, &stat 100 | ); 101 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 102 | MPlug vox_include_surface_plug = fn_voxelizer.findPlug( 103 | voxelizer_node::attr_include_surface, false, &stat 104 | ); 105 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 106 | 107 | // set attributes 108 | FLUID_MAYA_CHECK_RETURN(vox_include_interior_plug.setValue(true), "set attributes"); 109 | FLUID_MAYA_CHECK_RETURN(vox_include_surface_plug.setValue(true), "set attributes"); 110 | 111 | // mesh -> voxelizer 112 | FLUID_MAYA_CHECK_RETURN(_graph_modifier.connect(mesh_plug, vox_mesh_plug), "attribute connection"); 113 | 114 | // grid -> voxelizer 115 | FLUID_MAYA_CHECK_RETURN( 116 | _graph_modifier.connect(ref_cell_size_plug, vox_cell_size_plug), "attribute connection" 117 | ); 118 | FLUID_MAYA_CHECK_RETURN( 119 | _graph_modifier.connect(ref_grid_offset_plug, vox_ref_grid_offset_plug), "attribute connection" 120 | ); 121 | FLUID_MAYA_CHECK_RETURN( 122 | _graph_modifier.connect(ref_grid_size_plug, vox_ref_grid_size_plug), "attribute connection" 123 | ); 124 | 125 | // voxelizer -> grid 126 | MPlug source_element = sources_plug.elementByLogicalIndex(num_sources, &stat); 127 | FLUID_MAYA_CHECK(stat, "attribute connection"); 128 | source_element = source_element.child(grid_node::attr_source_cells, &stat); 129 | FLUID_MAYA_CHECK(stat, "attribute connection"); 130 | FLUID_MAYA_CHECK_RETURN( 131 | _graph_modifier.connect(vox_cells_plug, source_element), "attribute connection" 132 | ); 133 | ++num_sources; 134 | } 135 | 136 | FLUID_MAYA_CHECK_RETURN(_graph_modifier.doIt(), "execute queued operations"); 137 | 138 | return MStatus::kSuccess; 139 | } 140 | 141 | MStatus add_fluid_source_command::undoIt() { 142 | return _graph_modifier.undoIt(); 143 | } 144 | 145 | MStatus add_fluid_source_command::redoIt() { 146 | return _graph_modifier.doIt(); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /plugins/maya/commands/add_obstacle.cpp: -------------------------------------------------------------------------------- 1 | #include "add_obstacle.h" 2 | 3 | /// \file 4 | /// Implementation of the add_obstacle command. 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "../misc.h" 14 | #include "../nodes/grid_node.h" 15 | #include "../nodes/voxelizer_node.h" 16 | 17 | namespace fluid::maya { 18 | void *add_obstacle_command::creator() { 19 | return new add_obstacle_command(); 20 | } 21 | 22 | MStatus add_obstacle_command::doIt(const MArgList&) { 23 | MStatus stat; 24 | 25 | // get selected grid and geometry 26 | MSelectionList selection; 27 | FLUID_MAYA_CHECK_RETURN(MGlobal::getActiveSelectionList(selection), "retrieve selection"); 28 | MObject grid = MObject::kNullObj; 29 | std::vector mesh_plugs; 30 | for (unsigned int i = 0; i < selection.length(); ++i) { 31 | { // check for fluid grid 32 | MObject node; 33 | FLUID_MAYA_CHECK_RETURN(selection.getDependNode(i, node), "retrieve selection"); 34 | MFnDependencyNode fn_node(node, &stat); 35 | FLUID_MAYA_CHECK(stat, "retrieve selection"); 36 | MTypeId id = fn_node.typeId(&stat); 37 | FLUID_MAYA_CHECK(stat, "retrieve selection"); 38 | if (id == grid_node::id) { 39 | if (grid.isNull()) { 40 | grid = std::move(node); 41 | } else { 42 | // TODO print error message 43 | } 44 | continue; 45 | } 46 | } 47 | { // check for meshes 48 | MDagPath dag_path; 49 | FLUID_MAYA_CHECK_RETURN(selection.getDagPath(i, dag_path), "retrieve selection"); 50 | FLUID_MAYA_CHECK_RETURN(dag_path.extendToShape(), "retrieve selection"); 51 | MObject node = dag_path.node(&stat); 52 | FLUID_MAYA_CHECK(stat, "retrieve selection"); 53 | MFnDependencyNode fn_node(node, &stat); 54 | FLUID_MAYA_CHECK(stat, "retrieve selection"); 55 | MPlug mesh_plug = fn_node.findPlug("worldMesh", false, &stat); 56 | FLUID_MAYA_CHECK(stat, "retrieve selection"); 57 | mesh_plugs.emplace_back(mesh_plug.elementByLogicalIndex(0, &stat)); 58 | FLUID_MAYA_CHECK(stat, "retrieve selection"); 59 | continue; 60 | } 61 | // TODO print error message 62 | } 63 | 64 | if (grid.isNull()) { // no grid selected 65 | // TODO print error message 66 | return MStatus::kInvalidParameter; 67 | } 68 | 69 | MFnDependencyNode fn_grid(grid, &stat); 70 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 71 | MPlug obstacles_plug = fn_grid.findPlug(grid_node::attr_obstacles, false, &stat); 72 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 73 | MPlug ref_cell_size_plug = fn_grid.findPlug(grid_node::attr_cell_size, false, &stat); 74 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 75 | MPlug ref_grid_offset_plug = fn_grid.findPlug(grid_node::attr_grid_offset, false, &stat); 76 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 77 | MPlug ref_grid_size_plug = fn_grid.findPlug(grid_node::attr_grid_size, false, &stat); 78 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 79 | 80 | unsigned int num_obstacles = obstacles_plug.numElements(&stat); 81 | FLUID_MAYA_CHECK(stat, "attribute connection"); 82 | 83 | for (MPlug &mesh_plug : mesh_plugs) { 84 | MObject voxelizer = _graph_modifier.createNode(voxelizer_node::id, &stat); 85 | FLUID_MAYA_CHECK(stat, "node creation"); 86 | 87 | MFnDependencyNode fn_voxelizer(voxelizer, &stat); 88 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 89 | MPlug vox_mesh_plug = fn_voxelizer.findPlug(voxelizer_node::attr_input_mesh, false, &stat); 90 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 91 | MPlug vox_cell_size_plug = fn_voxelizer.findPlug(voxelizer_node::attr_cell_size, false, &stat); 92 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 93 | MPlug vox_ref_grid_offset_plug = 94 | fn_voxelizer.findPlug(voxelizer_node::attr_ref_grid_offset, false, &stat); 95 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 96 | MPlug vox_ref_grid_size_plug = fn_voxelizer.findPlug(voxelizer_node::attr_ref_grid_size, false, &stat); 97 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 98 | MPlug vox_cells_plug = fn_voxelizer.findPlug(voxelizer_node::attr_output_cells_ref, false, &stat); 99 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 100 | MPlug vox_include_interior_plug = fn_voxelizer.findPlug( 101 | voxelizer_node::attr_include_interior, false, &stat 102 | ); 103 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 104 | MPlug vox_include_surface_plug = fn_voxelizer.findPlug( 105 | voxelizer_node::attr_include_surface, false, &stat 106 | ); 107 | FLUID_MAYA_CHECK(stat, "plug retrieval"); 108 | 109 | // set attributes 110 | FLUID_MAYA_CHECK_RETURN(vox_include_interior_plug.setValue(true), "set attributes"); 111 | FLUID_MAYA_CHECK_RETURN(vox_include_surface_plug.setValue(false), "set attributes"); 112 | 113 | // mesh -> voxelizer 114 | FLUID_MAYA_CHECK_RETURN(_graph_modifier.connect(mesh_plug, vox_mesh_plug), "attribute connection"); 115 | 116 | // grid -> voxelizer 117 | FLUID_MAYA_CHECK_RETURN( 118 | _graph_modifier.connect(ref_cell_size_plug, vox_cell_size_plug), "attribute connection" 119 | ); 120 | FLUID_MAYA_CHECK_RETURN( 121 | _graph_modifier.connect(ref_grid_offset_plug, vox_ref_grid_offset_plug), "attribute connection" 122 | ); 123 | FLUID_MAYA_CHECK_RETURN( 124 | _graph_modifier.connect(ref_grid_size_plug, vox_ref_grid_size_plug), "attribute connection" 125 | ); 126 | 127 | // voxelizer -> grid 128 | MPlug obstacle_element = obstacles_plug.elementByLogicalIndex(num_obstacles, &stat); 129 | FLUID_MAYA_CHECK(stat, "attribute connection"); 130 | obstacle_element = obstacle_element.child(grid_node::attr_obstacle_cells, &stat); 131 | FLUID_MAYA_CHECK(stat, "attribute connection"); 132 | FLUID_MAYA_CHECK_RETURN( 133 | _graph_modifier.connect(vox_cells_plug, obstacle_element), "attribute connection" 134 | ); 135 | ++num_obstacles; 136 | } 137 | 138 | FLUID_MAYA_CHECK_RETURN(_graph_modifier.doIt(), "execute queued operations"); 139 | 140 | return MStatus::kSuccess; 141 | } 142 | 143 | MStatus add_obstacle_command::undoIt() { 144 | return _graph_modifier.undoIt(); 145 | } 146 | 147 | MStatus add_obstacle_command::redoIt() { 148 | return _graph_modifier.doIt(); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /plugins/maya/main.cpp: -------------------------------------------------------------------------------- 1 | /// \file 2 | /// Contains entry points of the plugin. 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "misc.h" 12 | #include "nodes/grid_manipulator_node.h" 13 | #include "nodes/grid_node.h" 14 | #include "nodes/mesher_node.h" 15 | #include "nodes/point_cloud_loader_node.h" 16 | #include "nodes/voxelizer_node.h" 17 | #include "commands/create_simulation_grid.h" 18 | #include "commands/add_fluid_source.h" 19 | #include "commands/add_obstacle.h" 20 | 21 | namespace fluid::maya { 22 | MTypeId 23 | grid_node::id{ 0x98765432 }; 24 | const MTypeId 25 | mesher_node::id{ 0x98765433 }, 26 | point_cloud_loader_node::id{ 0x98765434 }, 27 | voxelizer_node::id{ 0x98765435 }, 28 | grid_manipulator_node::id{ 0x98765436 }; 29 | 30 | const MString 31 | create_simulation_grid_command::name{ "libfluidCreateSimulationGrid" }, 32 | add_fluid_source_command::name{ "libfluidAddFluidSource" }, 33 | add_obstacle_command::name{ "libfluidAddObstacle" }; 34 | } 35 | 36 | const MString top_level_menu_name{ "libfluid" }; 37 | const MString create_simulation_grid_menu_name{ "Create Simulation Grid" }; 38 | const MString add_fluid_source_menu_name{ "Add as Fluid Source" }; 39 | const MString add_obstacle_menu_name{ "Add as Obstacle" }; 40 | 41 | MStringArray create_simulation_grid_menu; 42 | MStringArray add_fluid_source_menu; 43 | MStringArray add_obstacle_menu; 44 | 45 | /// Initializes the plugin. 46 | MStatus initializePlugin(MObject obj) { 47 | MStatus stat = MStatus::kSuccess; 48 | MFnPlugin plugin(obj, "Xuanyi Zhou", "0.1", "Any", &stat); 49 | FLUID_MAYA_CHECK(stat, "plugin creation"); 50 | 51 | 52 | FLUID_MAYA_CHECK_RETURN( 53 | plugin.registerNode( 54 | "MesherNode", fluid::maya::mesher_node::id, 55 | fluid::maya::mesher_node::creator, fluid::maya::mesher_node::initialize 56 | ), 57 | "node registration" 58 | ); 59 | FLUID_MAYA_CHECK_RETURN( 60 | plugin.registerNode( 61 | "PointCloudLoaderNode", fluid::maya::point_cloud_loader_node::id, 62 | fluid::maya::point_cloud_loader_node::creator, fluid::maya::point_cloud_loader_node::initialize 63 | ), 64 | "node registration" 65 | ); 66 | FLUID_MAYA_CHECK_RETURN( 67 | plugin.registerNode( 68 | "GridNode", fluid::maya::grid_node::id, 69 | fluid::maya::grid_node::creator, fluid::maya::grid_node::initialize 70 | ), 71 | "node registration" 72 | ); 73 | FLUID_MAYA_CHECK_RETURN( 74 | plugin.registerNode( 75 | "GridNodeManip", fluid::maya::grid_manipulator_node::id, 76 | fluid::maya::grid_manipulator_node::creator, fluid::maya::grid_manipulator_node::initialize, 77 | MPxNode::kManipContainer 78 | ), 79 | "node registration" 80 | ); 81 | FLUID_MAYA_CHECK_RETURN( 82 | plugin.registerNode( 83 | "VoxelizerNode", fluid::maya::voxelizer_node::id, 84 | fluid::maya::voxelizer_node::creator, fluid::maya::voxelizer_node::initialize 85 | ), 86 | "node registration" 87 | ); 88 | 89 | 90 | FLUID_MAYA_CHECK_RETURN( 91 | plugin.registerCommand( 92 | fluid::maya::create_simulation_grid_command::name, 93 | fluid::maya::create_simulation_grid_command::creator 94 | ), 95 | "command registration" 96 | ); 97 | FLUID_MAYA_CHECK_RETURN( 98 | plugin.registerCommand( 99 | fluid::maya::add_fluid_source_command::name, 100 | fluid::maya::add_fluid_source_command::creator 101 | ), 102 | "command registration" 103 | ); 104 | FLUID_MAYA_CHECK_RETURN( 105 | plugin.registerCommand( 106 | fluid::maya::add_obstacle_command::name, 107 | fluid::maya::add_obstacle_command::creator 108 | ), 109 | "command registration" 110 | ); 111 | 112 | 113 | MString top_level_menu_path; 114 | FLUID_MAYA_CHECK_RETURN( 115 | MGlobal::executeCommand("menu -parent MayaWindow -label " + top_level_menu_name + ";", top_level_menu_path), 116 | "top level menu registration" 117 | ); 118 | 119 | create_simulation_grid_menu = plugin.addMenuItem( 120 | create_simulation_grid_menu_name, top_level_menu_path, 121 | fluid::maya::create_simulation_grid_command::name, "", 122 | false, nullptr, &stat 123 | ); 124 | FLUID_MAYA_CHECK(stat, "menu item registration"); 125 | 126 | add_fluid_source_menu = plugin.addMenuItem( 127 | add_fluid_source_menu_name, top_level_menu_path, 128 | fluid::maya::add_fluid_source_command::name, "", 129 | false, nullptr, &stat 130 | ); 131 | FLUID_MAYA_CHECK(stat, "menu item registration"); 132 | 133 | add_obstacle_menu = plugin.addMenuItem( 134 | add_obstacle_menu_name, top_level_menu_path, 135 | fluid::maya::add_obstacle_command::name, "", 136 | false, nullptr, &stat 137 | ); 138 | FLUID_MAYA_CHECK(stat, "menu item registration"); 139 | 140 | return MStatus::kSuccess; 141 | } 142 | 143 | /// Uninitializes the plugin. 144 | MStatus uninitializePlugin(MObject obj) { 145 | MFnPlugin plugin(obj); 146 | 147 | FLUID_MAYA_CHECK_RETURN(plugin.deregisterNode(fluid::maya::mesher_node::id), "node deregisteration"); 148 | FLUID_MAYA_CHECK_RETURN(plugin.deregisterNode(fluid::maya::point_cloud_loader_node::id), "node deregisteration"); 149 | FLUID_MAYA_CHECK_RETURN(plugin.deregisterNode(fluid::maya::grid_node::id), "node deregistration"); 150 | FLUID_MAYA_CHECK_RETURN(plugin.deregisterNode(fluid::maya::grid_manipulator_node::id), "node deregistration"); 151 | FLUID_MAYA_CHECK_RETURN(plugin.deregisterNode(fluid::maya::voxelizer_node::id), "node deregistration"); 152 | 153 | FLUID_MAYA_CHECK_RETURN( 154 | plugin.deregisterCommand(fluid::maya::create_simulation_grid_command::name), "command deregisteration" 155 | ); 156 | FLUID_MAYA_CHECK_RETURN( 157 | plugin.deregisterCommand(fluid::maya::add_fluid_source_command::name), "command deregisteration" 158 | ); 159 | FLUID_MAYA_CHECK_RETURN( 160 | plugin.deregisterCommand(fluid::maya::add_obstacle_command::name), "command deregisteration" 161 | ); 162 | 163 | FLUID_MAYA_CHECK_RETURN(plugin.removeMenuItem(create_simulation_grid_menu), "menu item deregistration"); 164 | FLUID_MAYA_CHECK_RETURN(plugin.removeMenuItem(add_fluid_source_menu), "menu item deregistration"); 165 | FLUID_MAYA_CHECK_RETURN(plugin.removeMenuItem(add_obstacle_menu), "menu item deregistration"); 166 | 167 | return MStatus::kSuccess; 168 | } 169 | -------------------------------------------------------------------------------- /src/math/intersection.cpp: -------------------------------------------------------------------------------- 1 | #include "fluid/math/intersection.h" 2 | 3 | /// \file 4 | /// Implementation of various intersection algorithms. 5 | 6 | #include 7 | 8 | namespace fluid { 9 | // https://fileadmin.cs.lth.se/cs/Personal/Tomas_Akenine-Moller/pubs/tribox.pdf 10 | bool aab_triangle_overlap(vec3d box_center, vec3d half_extent, vec3d p1, vec3d p2, vec3d p3) { 11 | p1 -= box_center; 12 | p2 -= box_center; 13 | p3 -= box_center; 14 | 15 | auto [xmin, xmax] = std::minmax({ p1.x, p2.x, p3.x }); 16 | if (xmin > half_extent.x || xmax < half_extent.x) { 17 | return false; 18 | } 19 | auto [ymin, ymax] = std::minmax({ p1.y, p2.y, p3.y }); 20 | if (ymin > half_extent.y || ymax < half_extent.y) { 21 | return false; 22 | } 23 | auto [zmin, zmax] = std::minmax({ p1.z, p2.z, p3.z }); 24 | if (zmin > half_extent.z || zmax < half_extent.z) { 25 | return false; 26 | } 27 | 28 | return aab_triangle_overlap_bounded_center(half_extent, p1, p2, p3); 29 | } 30 | 31 | bool aab_triangle_overlap_bounded(vec3d box_center, vec3d half_extent, vec3d p1, vec3d p2, vec3d p3) { 32 | p1 -= box_center; 33 | p2 -= box_center; 34 | p3 -= box_center; 35 | return aab_triangle_overlap_bounded_center(half_extent, p1, p2, p3); 36 | } 37 | 38 | bool aab_triangle_overlap_bounded_center(vec3d half_extent, vec3d p1, vec3d p2, vec3d p3) { 39 | vec3d f[]{ p2 - p1, p3 - p2, p1 - p3 }, normal = vec_ops::cross(f[0], f[1]); 40 | double 41 | center_off = vec_ops::dot(p1, normal), 42 | radius_n = vec_ops::dot( 43 | vec_ops::apply(static_cast(std::abs), normal), half_extent 44 | ); 45 | if (std::abs(center_off) > std::abs(radius_n)) { 46 | return false; 47 | } 48 | 49 | vec3d v[]{ p1, p2, p3 }; 50 | // (1, 0, 0) -> (0, -z, y) 51 | for (std::size_t i = 0; i < 3; ++i) { 52 | vec3d v1 = v[i], v2 = v[(i + 2) % 3], fi = f[i]; 53 | double p0 = v1.z * fi.y - v1.y * fi.z, p1 = v2.z * fi.y - v2.y * fi.z; 54 | auto [pmin, pmax] = std::minmax(p0, p1); 55 | double r = half_extent.y * std::abs(fi.z) + half_extent.z * std::abs(fi.y); 56 | if (pmin > r || pmax < -r) { 57 | return false; 58 | } 59 | } 60 | // (0, 1, 0) -> (z, 0, -x) 61 | for (std::size_t i = 0; i < 3; ++i) { 62 | vec3d v1 = v[i], v2 = v[(i + 2) % 3], fi = f[i]; 63 | double p0 = v1.x * fi.z - v1.z * fi.x, p1 = v2.x * fi.z - v2.z * fi.x; 64 | auto [pmin, pmax] = std::minmax(p0, p1); 65 | double r = half_extent.x * std::abs(fi.z) + half_extent.z * std::abs(fi.x); 66 | if (pmin > r || pmax < -r) { 67 | return false; 68 | } 69 | } 70 | // (0, 0, 1) -> (-y, x, 0) 71 | for (std::size_t i = 0; i < 3; ++i) { 72 | vec3d v1 = v[i], v2 = v[(i + 2) % 3], fi = f[i]; 73 | double p0 = v1.y * fi.x - v1.x * fi.y, p1 = v2.y * fi.x - v2.x * fi.y; 74 | auto [pmin, pmax] = std::minmax(p0, p1); 75 | double r = half_extent.x * std::abs(fi.y) + half_extent.y * std::abs(fi.x); 76 | if (pmin > r || pmax < -r) { 77 | return false; 78 | } 79 | } 80 | 81 | return true; 82 | } 83 | 84 | 85 | // Moller-Trumbore 86 | vec3d ray_triangle_intersection( 87 | vec3d origin, vec3d direction, vec3d p1, vec3d p2, vec3d p3, double parallel_epsilon 88 | ) { 89 | return ray_triangle_intersection_edges(origin, direction, p1, p2 - p1, p3 - p1, parallel_epsilon); 90 | } 91 | 92 | vec3d ray_triangle_intersection_edges( 93 | vec3d origin, vec3d direction, vec3d p1, vec3d e12, vec3d e13, double parallel_epsilon 94 | ) { 95 | vec3d pvec = vec_ops::cross(direction, e13); 96 | double det = vec_ops::dot(e12, pvec); 97 | if (std::abs(det) < parallel_epsilon) { // return if parallel 98 | return vec3d(std::numeric_limits::quiet_NaN(), 0.0, 0.0); 99 | } 100 | double inv_det = 1.0 / det; 101 | 102 | vec3d e1o = origin - p1; 103 | double u = vec_ops::dot(e1o, pvec) * inv_det; 104 | if (u < 0.0 || u > 1.0) { 105 | return vec3d(std::numeric_limits::quiet_NaN(), 0.0, 0.0); 106 | } 107 | 108 | vec3d qvec = vec_ops::cross(e1o, e12); 109 | double v = vec_ops::dot(direction, qvec) * inv_det; 110 | if (v < 0.0 || u + v > 1.0) { 111 | return vec3d(std::numeric_limits::quiet_NaN(), 0.0, 0.0); 112 | } 113 | 114 | double t = vec_ops::dot(e13, qvec) * inv_det; 115 | if (t > 0.0) { 116 | return vec3d(t, u, v); 117 | } 118 | return vec3d(std::numeric_limits::quiet_NaN(), 0.0, 0.0); 119 | } 120 | 121 | 122 | // https://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-box-intersection 123 | vec2d aab_ray_intersection(vec3d min, vec3d max, vec3d vo, vec3d vd) { 124 | double tmin = (min.x - vo.x) / vd.x; 125 | double tmax = (max.x - vo.x) / vd.x; 126 | if (tmin > tmax) { 127 | std::swap(tmin, tmax); 128 | } 129 | 130 | double tymin = (min.y - vo.y) / vd.y; 131 | double tymax = (max.y - vo.y) / vd.y; 132 | if (tymin > tymax) { 133 | std::swap(tymin, tymax); 134 | } 135 | 136 | if (tmin > tymax || tymin > tmax) { 137 | return vec2d(std::numeric_limits::quiet_NaN(), 0.0); 138 | } 139 | 140 | tmin = std::max(tmin, tymin); 141 | tmax = std::min(tmax, tymax); 142 | 143 | double tzmin = (min.z - vo.z) / vd.z; 144 | double tzmax = (max.z - vo.z) / vd.z; 145 | if (tzmin > tzmax) { 146 | std::swap(tzmin, tzmax); 147 | } 148 | 149 | if (tmin > tzmax || tzmin > tmax) { 150 | return vec2d(std::numeric_limits::quiet_NaN(), 0.0); 151 | } 152 | tmin = std::max(tmin, tzmin); 153 | tmax = std::min(tmax, tzmax); 154 | if (tmax > 0.0) { 155 | return vec2d(tmin, tmax); 156 | } 157 | return vec2d(std::numeric_limits::quiet_NaN(), 0.0); 158 | } 159 | 160 | 161 | vec2d unit_radius_sphere_ray_intersection(vec3d vo, vec3d vd) { 162 | double sqr_diff_len = vd.squared_length(); 163 | double mid_t = -vec_ops::dot(vo, vd) / sqr_diff_len; 164 | vec3d dist = vo + vd * mid_t; 165 | double sqr_dist_len = dist.squared_length(); 166 | if (sqr_dist_len >= 1.0) { // no intersection 167 | return vec2d(std::numeric_limits::quiet_NaN(), 0.0); 168 | } 169 | double tdiff = std::sqrt((1.0 - sqr_dist_len) / sqr_diff_len); 170 | double t1 = mid_t - tdiff, t2 = mid_t + tdiff; 171 | if (t2 > 0.0) { 172 | return vec2d(t1, t2); 173 | } 174 | // no intersection 175 | return vec2d(std::numeric_limits::quiet_NaN(), 0.0); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/renderer/primitive.cpp: -------------------------------------------------------------------------------- 1 | #include "fluid/renderer/primitive.h" 2 | 3 | /// \file 4 | /// Implementation of primitives. 5 | 6 | #include "fluid/math/constants.h" 7 | #include "fluid/math/warping.h" 8 | 9 | namespace fluid::renderer { 10 | namespace primitives { 11 | aab3d triangle_primitive::get_bounding_box() const { 12 | return aab3d::containing(point1, point1 + edge12, point1 + edge13); 13 | } 14 | 15 | ray_cast_result triangle_primitive::ray_cast(const ray &r) const { 16 | ray_cast_result result; 17 | vec3d hit = ray_triangle_intersection_edges(r.origin, r.direction, point1, edge12, edge13); 18 | result.t = hit.x; 19 | result.custom[0] = hit.y; 20 | result.custom[1] = hit.z; 21 | return result; 22 | } 23 | 24 | vec3d triangle_primitive::get_geometric_normal(ray_cast_result) const { 25 | return geometric_normal; 26 | } 27 | 28 | vec2d triangle_primitive::get_uv(ray_cast_result hit) const { 29 | return uv_p1 + hit.custom[0] * uv_e12 + hit.custom[1] * uv_e13; 30 | } 31 | 32 | surface_sample triangle_primitive::sample_surface(vec2d pos) const { 33 | surface_sample result; 34 | if (pos.x > pos.y) { 35 | pos.x = 1.0 - pos.x; 36 | result.geometric_normal = geometric_normal; 37 | } else { 38 | pos.y = 1.0 - pos.y; 39 | result.geometric_normal = -geometric_normal; 40 | } 41 | result.position = point1 + edge12 * pos.x + edge13 * pos.y; 42 | result.uv = uv_p1 + uv_e12 * pos.x + uv_e13 * pos.y; 43 | result.pdf = 1.0 / double_surface_area; 44 | return result; 45 | } 46 | 47 | double triangle_primitive::surface_area() const { 48 | return double_surface_area; 49 | } 50 | 51 | void triangle_primitive::compute_attributes() { 52 | vec3d cross = vec_ops::cross(edge12, edge13); 53 | auto [norm, area] = cross.normalized_length_unchecked(); 54 | geometric_normal = norm; 55 | double_surface_area = area; 56 | } 57 | 58 | 59 | aab3d sphere_primitive::get_bounding_box() const { 60 | // https://tavianator.com/exact-bounding-boxes-for-spheres-ellipsoids/ 61 | vec3d 62 | r1 = local_to_world.row(0), 63 | r2 = local_to_world.row(1), 64 | r3 = local_to_world.row(2); 65 | vec3d half_extent(r1.length(), r2.length(), r3.length()); 66 | return aab3d(local_to_world_offset - half_extent, local_to_world_offset + half_extent); 67 | } 68 | 69 | ray_cast_result sphere_primitive::ray_cast(const ray &r) const { 70 | ray_cast_result result; 71 | vec3d 72 | origin = world_to_local * r.origin + world_to_local_offset, 73 | direction = world_to_local * r.direction; 74 | vec2d hit = unit_radius_sphere_ray_intersection(origin, direction); 75 | if (std::isnan(hit.x)) { // no intersection 76 | result.t = hit.x; 77 | return result; 78 | } 79 | result.t = hit.x > 0.0 ? hit.x : hit.y; 80 | vec3d hit_point = origin + result.t * direction; 81 | result.custom[0] = hit_point.x; 82 | result.custom[1] = hit_point.y; 83 | result.custom[2] = hit_point.z; 84 | return result; 85 | } 86 | 87 | vec3d sphere_primitive::get_geometric_normal(ray_cast_result r) const { 88 | vec3d normal(r.custom[0], r.custom[1], r.custom[2]); 89 | normal = world_to_local.transposed() * normal; 90 | return normal.normalized_unchecked(); 91 | } 92 | 93 | vec2d sphere_primitive::get_uv(ray_cast_result r) const { 94 | vec2d result; 95 | result.x = ((std::atan2(r.custom[2], r.custom[0]) / constants::pi) + 1.0) * 0.5; 96 | // for performance 97 | result.y = (r.custom[1] + 1.0) * 0.5; 98 | return result; 99 | } 100 | 101 | surface_sample sphere_primitive::sample_surface(vec2d pos) const { 102 | vec3d pos_local = warping::unit_sphere_from_unit_square(pos); 103 | surface_sample result; 104 | // position, this can be nonuniform after transforming 105 | result.position = local_to_world * pos_local + local_to_world_offset; 106 | // compute uv 107 | result.uv.x = ((std::atan2(pos_local.z, pos_local.x) / constants::pi) + 1.0) * 0.5; 108 | result.uv.y = (pos_local.y + 1.0) * 0.5; // for performance 109 | // normal 110 | result.geometric_normal = world_to_local.transposed() * pos_local; 111 | // https://math.stackexchange.com/questions/942561/surface-area-of-transformed-sphere 112 | result.pdf = 1.0; 113 | return result; 114 | } 115 | 116 | double sphere_primitive::surface_area() const { 117 | return 0.0; 118 | } 119 | 120 | void sphere_primitive::set_transformation(rmat3x4d trans) { 121 | local_to_world = rmat3d::from_rows( 122 | vec_ops::slice<0, 3>(trans.row(0)), 123 | vec_ops::slice<0, 3>(trans.row(1)), 124 | vec_ops::slice<0, 3>(trans.row(2)) 125 | ); 126 | local_to_world_offset = trans.column(3); 127 | rmat4d full = rmat4d::from_rows(trans.row(0), trans.row(1), trans.row(2), vec4d(0.0, 0.0, 0.0, 1.0)); 128 | full = full.get_inverse(); 129 | world_to_local = rmat3d::from_rows( 130 | vec_ops::slice<0, 3>(full.row(0)), 131 | vec_ops::slice<0, 3>(full.row(1)), 132 | vec_ops::slice<0, 3>(full.row(2)) 133 | ); 134 | world_to_local_offset = vec_ops::slice<0, 3>(full.column(3)); 135 | } 136 | } 137 | 138 | 139 | aab3d primitive::get_bounding_box() const { 140 | return std::visit( 141 | [](const auto &prim) { 142 | return prim.get_bounding_box(); 143 | }, 144 | value 145 | ); 146 | } 147 | 148 | ray_cast_result primitive::ray_cast(const ray &r) const { 149 | return std::visit( 150 | [&](const auto &prim) { 151 | return prim.ray_cast(r); 152 | }, 153 | value 154 | ); 155 | } 156 | 157 | vec3d primitive::get_geometric_normal(ray_cast_result hit) const { 158 | return std::visit( 159 | [&](const auto &prim) { 160 | return prim.get_geometric_normal(hit); 161 | }, 162 | value 163 | ); 164 | } 165 | 166 | vec2d primitive::get_uv(ray_cast_result hit) const { 167 | return std::visit( 168 | [&](const auto &prim) { 169 | return prim.get_uv(hit); 170 | }, 171 | value 172 | ); 173 | } 174 | 175 | primitives::surface_sample primitive::sample_surface(vec2d pos) const { 176 | return std::visit( 177 | [&](const auto &prim) { 178 | return prim.sample_surface(pos); 179 | }, 180 | value 181 | ); 182 | } 183 | 184 | double primitive::surface_area() const { 185 | return std::visit( 186 | [](const auto &prim) { 187 | return prim.surface_area(); 188 | }, 189 | value 190 | ); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /plugins/maya/nodes/mesher_node.cpp: -------------------------------------------------------------------------------- 1 | #include "mesher_node.h" 2 | 3 | /// \file 4 | /// Implementation of the grid node. 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | #include "../misc.h" 17 | 18 | namespace fluid::maya { 19 | MObject 20 | mesher_node::attr_cell_size, 21 | mesher_node::attr_grid_size, 22 | mesher_node::attr_grid_offset, 23 | mesher_node::attr_particles, 24 | mesher_node::attr_particle_size, 25 | mesher_node::attr_particle_extents, 26 | mesher_node::attr_particle_check_radius, 27 | mesher_node::attr_output_mesh; 28 | 29 | void *mesher_node::creator() { 30 | return new mesher_node(); 31 | } 32 | 33 | MStatus mesher_node::initialize() { 34 | MStatus stat; 35 | 36 | MFnNumericAttribute cell_size; 37 | attr_cell_size = cell_size.create("cellSize", "cell", MFnNumericData::kDouble, 0.4, &stat); 38 | FLUID_MAYA_CHECK(stat, "parameter creation"); 39 | 40 | MFnNumericAttribute grid_size; 41 | attr_grid_size = grid_size.create("gridSize", "grid", MFnNumericData::k3Int, 150.0, &stat); 42 | FLUID_MAYA_CHECK(stat, "parameter creation"); 43 | 44 | MFnNumericAttribute grid_offset; 45 | attr_grid_offset = grid_offset.create("gridOffset", "goff", MFnNumericData::k3Double, 0.0, &stat); 46 | FLUID_MAYA_CHECK(stat, "parameter creation"); 47 | 48 | MFnTypedAttribute particles; 49 | attr_particles = particles.create("particles", "p", MFnData::kPointArray, MObject::kNullObj, &stat); 50 | FLUID_MAYA_CHECK(stat, "parameter creation"); 51 | 52 | MFnNumericAttribute particle_size; 53 | attr_particle_size = particle_size.create("particleSize", "ps", MFnNumericData::kDouble, 0.5, &stat); 54 | FLUID_MAYA_CHECK(stat, "parameter creation"); 55 | 56 | MFnNumericAttribute particle_extents; 57 | attr_particle_extents = particle_extents.create( 58 | "particleExtents", "pe", MFnNumericData::kDouble, 2.0, &stat 59 | ); 60 | FLUID_MAYA_CHECK(stat, "parameter creation"); 61 | 62 | MFnNumericAttribute particle_check_radius; 63 | attr_particle_check_radius = particle_check_radius.create( 64 | "particleCheckRadius", "pcr", MFnNumericData::kInt, 3, &stat 65 | ); 66 | FLUID_MAYA_CHECK(stat, "parameter creation"); 67 | 68 | MFnTypedAttribute output_mesh; 69 | attr_output_mesh = output_mesh.create("outputMesh", "out", MFnData::kMesh, MObject::kNullObj, &stat); 70 | FLUID_MAYA_CHECK(stat, "parameter creation"); 71 | FLUID_MAYA_CHECK_RETURN(output_mesh.setWritable(false), "parameter creation"); 72 | FLUID_MAYA_CHECK_RETURN(output_mesh.setStorable(false), "parameter creation"); 73 | 74 | FLUID_MAYA_CHECK_RETURN(addAttribute(attr_cell_size), "parameter registration"); 75 | FLUID_MAYA_CHECK_RETURN(addAttribute(attr_grid_size), "parameter registration"); 76 | FLUID_MAYA_CHECK_RETURN(addAttribute(attr_particles), "parameter registration"); 77 | FLUID_MAYA_CHECK_RETURN(addAttribute(attr_grid_offset), "parameter registration"); 78 | FLUID_MAYA_CHECK_RETURN(addAttribute(attr_particle_size), "parameter registration"); 79 | FLUID_MAYA_CHECK_RETURN(addAttribute(attr_particle_extents), "parameter registration"); 80 | FLUID_MAYA_CHECK_RETURN(addAttribute(attr_particle_check_radius), "parameter registration"); 81 | FLUID_MAYA_CHECK_RETURN(addAttribute(attr_output_mesh), "parameter registration"); 82 | 83 | FLUID_MAYA_CHECK_RETURN(attributeAffects(attr_cell_size, attr_output_mesh), "parameter registration"); 84 | FLUID_MAYA_CHECK_RETURN(attributeAffects(attr_grid_size, attr_output_mesh), "parameter registration"); 85 | FLUID_MAYA_CHECK_RETURN(attributeAffects(attr_particles, attr_output_mesh), "parameter registration"); 86 | FLUID_MAYA_CHECK_RETURN(attributeAffects(attr_grid_offset, attr_output_mesh), "parameter registration"); 87 | FLUID_MAYA_CHECK_RETURN(attributeAffects(attr_particle_size, attr_output_mesh), "parameter registration"); 88 | FLUID_MAYA_CHECK_RETURN(attributeAffects(attr_particle_extents, attr_output_mesh), "parameter registration"); 89 | FLUID_MAYA_CHECK_RETURN( 90 | attributeAffects(attr_particle_check_radius, attr_output_mesh), "parameter registration" 91 | ); 92 | 93 | return MStatus::kSuccess; 94 | } 95 | 96 | MStatus mesher_node::compute(const MPlug &plug, MDataBlock &data_block) { 97 | if (plug != attr_output_mesh) { 98 | return MStatus::kUnknownParameter; 99 | } 100 | 101 | MStatus stat; 102 | 103 | MDataHandle cell_size_data = data_block.inputValue(attr_cell_size, &stat); 104 | FLUID_MAYA_CHECK(stat, "retrieve attribute"); 105 | MDataHandle grid_size_data = data_block.inputValue(attr_grid_size, &stat); 106 | FLUID_MAYA_CHECK(stat, "retrieve attribute"); 107 | MDataHandle particles_data = data_block.inputValue(attr_particles, &stat); 108 | FLUID_MAYA_CHECK(stat, "retrieve attribute"); 109 | MDataHandle grid_offset_data = data_block.inputValue(attr_grid_offset, &stat); 110 | FLUID_MAYA_CHECK(stat, "retrieve attribute"); 111 | MDataHandle particle_size_data = data_block.inputValue(attr_particle_size, &stat); 112 | FLUID_MAYA_CHECK(stat, "retrieve attribute"); 113 | MDataHandle particle_extents_data = data_block.inputValue(attr_particle_extents, &stat); 114 | FLUID_MAYA_CHECK(stat, "retrieve attribute"); 115 | MDataHandle particle_check_radius_data = data_block.inputValue(attr_particle_check_radius, &stat); 116 | FLUID_MAYA_CHECK(stat, "retrieve attribute"); 117 | MDataHandle output_mesh_data = data_block.outputValue(attr_output_mesh, &stat); 118 | FLUID_MAYA_CHECK(stat, "retrieve attribute"); 119 | 120 | // set mesher parameters 121 | mesher mesh_generator; 122 | // cell size 123 | mesh_generator.cell_size = cell_size_data.asDouble(); 124 | // grid size 125 | const int3 &grid_size = grid_size_data.asInt3(); 126 | for (std::size_t i = 0; i < 3; ++i) { 127 | if (grid_size[i] <= 0) { 128 | return MStatus::kInvalidParameter; 129 | } 130 | } 131 | mesh_generator.resize(vec3s(vec3i(grid_size[0], grid_size[1], grid_size[2]))); 132 | // particle offset 133 | const double3 &grid_offset = grid_offset_data.asDouble3(); 134 | mesh_generator.grid_offset = vec3d(grid_offset[0], grid_offset[1], grid_offset[2]); 135 | // particle extent 136 | mesh_generator.particle_extent = particle_extents_data.asDouble(); 137 | // particle check radius 138 | int check_radius = particle_check_radius_data.asInt(); 139 | if (check_radius < 0) { 140 | return MStatus::kInvalidParameter; 141 | } 142 | mesh_generator.cell_radius = static_cast(check_radius); 143 | 144 | // collect particles 145 | MFnPointArrayData particles_array(particles_data.data(), &stat); 146 | FLUID_MAYA_CHECK(stat, "particle retrieval"); 147 | MPointArray particles = particles_array.array(&stat); 148 | FLUID_MAYA_CHECK(stat, "particle retrieval"); 149 | unsigned num_particles = particles.length(); 150 | std::vector particle_positions(num_particles); 151 | for (unsigned i = 0; i < num_particles; ++i) { 152 | const MPoint &mp = particles[i]; 153 | particle_positions[i] = vec3d(mp.x, mp.y, mp.z); 154 | } 155 | 156 | // create mesh 157 | mesher::mesh_t m = mesh_generator.generate_mesh(particle_positions, particle_size_data.asDouble()); 158 | MPointArray points; 159 | MIntArray face_counts, face_connects; 160 | for (vec3d p : m.positions) { 161 | FLUID_MAYA_CHECK_RETURN(points.append(MPoint(p.x, p.y, p.z)), "mesh creation"); 162 | } 163 | for (std::size_t i = 0; i + 2 < m.indices.size(); i += 3) { 164 | FLUID_MAYA_CHECK_RETURN(face_counts.append(3), "mesh creation"); 165 | FLUID_MAYA_CHECK_RETURN(face_connects.append(static_cast(m.indices[i + 2])), "mesh creation"); 166 | FLUID_MAYA_CHECK_RETURN(face_connects.append(static_cast(m.indices[i + 1])), "mesh creation"); 167 | FLUID_MAYA_CHECK_RETURN(face_connects.append(static_cast(m.indices[i])), "mesh creation"); 168 | } 169 | 170 | MFnMeshData output_mesh; 171 | MObject output_mesh_value = output_mesh.create(&stat); 172 | FLUID_MAYA_CHECK(stat, "mesh creation"); 173 | MFnMesh mesh_fn; 174 | MObject output_mesh_object = mesh_fn.create( 175 | points.length(), face_counts.length(), points, face_counts, face_connects, output_mesh_value, &stat 176 | ); 177 | FLUID_MAYA_CHECK(stat, "mesh creation"); 178 | 179 | FLUID_MAYA_CHECK_RETURN(output_mesh_data.set(output_mesh_value), "finalize compute"); 180 | output_mesh_data.setClean(); 181 | return MStatus::kSuccess; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /include/fluid/data_structures/short_vec.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Short vectors. 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace fluid { 12 | /// A short vector. The vector itself has capacity for \p Count elements, and if more elements are added all 13 | /// elements will be moved to heap-allocated memory. 14 | template struct short_vec { 15 | template friend struct short_vec; 16 | public: 17 | using value_type = T; ///< Value type. 18 | using size_type = std::size_t; ///< Size type. 19 | using difference_type = std::ptrdiff_t; ///< Difference type. 20 | 21 | using reference = value_type&; ///< Reference type; 22 | using const_reference = const value_type&; ///< Const reference type; 23 | using pointer = value_type*; ///< Pointer type. 24 | using const_pointer = const value_type*; ///< Const pointer type. 25 | 26 | using iterator = T*; ///< Iterator type. 27 | using const_iterator = const T*; ///< Const iterator type. 28 | 29 | /// Initializes this short vector to be empty. 30 | short_vec() : _first(_local), _last(_local) { 31 | } 32 | /// Copy construction. 33 | template short_vec(const short_vec &source) { 34 | size_type src_count = source.size(); 35 | if (src_count <= Count) { // don't allocate 36 | _first = _local; 37 | _last = _local + src_count; 38 | } else { // allocate 39 | size_type cap = src_count; 40 | _first = _alloc(cap); 41 | _last = _capacity = _first + cap; 42 | } 43 | // copy elements 44 | auto src = source.begin(); 45 | for (T *dst = _first; dst != _last; ++src, ++dst) { 46 | new (dst) T(*src); 47 | } 48 | } 49 | /// Move construction. 50 | template short_vec(short_vec &&source) { 51 | if (!source._using_internal()) { // always take over memory 52 | _first = source._first; 53 | _last = source._last; 54 | _capacity = source._capacity; 55 | } else { // prefer using internal storage 56 | size_type src_count = source.size(); 57 | if (src_count <= Count) { // use internal storage 58 | _first = _local; 59 | _last = _local + src_count; 60 | } else { 61 | size_type cap = src_count; 62 | _first = _alloc(cap); 63 | _last = _capacity = _first + cap; 64 | } 65 | // move elements 66 | for (T *src = source._first, *dst = _first; dst != _last; ++src, ++dst) { 67 | new (dst) T(std::move(*src)); 68 | src->~T(); 69 | } 70 | } 71 | source._first = source._last = source._local; // reset source 72 | } 73 | /// Copy assignment. 74 | template short_vec &operator=(const short_vec &source) { 75 | // first destroy all existing elements 76 | for (T *cur = _first; cur != _last; ++cur) { 77 | cur->~T(); 78 | } 79 | // decide where to store elements, result stored in _first 80 | size_type src_count = source.size(); 81 | if (src_count <= Count) { // can use local storage 82 | if (!_using_internal()) { // has heap storage 83 | if (capacity() < src_count) { // not enough capacity, just use local instead 84 | _free(_first); 85 | _first = _local; 86 | } // otherwise use heap storage 87 | } 88 | } else { 89 | if (_using_internal()) { // allocate memory 90 | size_type cap = src_count; 91 | _first = _alloc(cap); 92 | _capacity = _first + cap; 93 | } else { // already has allocated heap memory 94 | if (capacity() < src_count) { // not enough space 95 | _free(_first); 96 | size_type cap = src_count; 97 | _first = _alloc(cap); 98 | _capacity = _first + cap; 99 | } // otherwise use old memory 100 | } 101 | } 102 | _last = _first + src_count; // adjust _last 103 | // copy elements 104 | auto src = source.begin(); 105 | for (T *dst = _first; dst != _last; ++src, ++dst) { 106 | new (dst) T(*src); 107 | } 108 | return *this; 109 | } 110 | /// Move assignment. 111 | template short_vec &operator=(short_vec &&source) { 112 | // first destroy all existing elements 113 | for (T *cur = _first; cur != _last; ++cur) { 114 | cur->~T(); 115 | } 116 | if (source._using_internal()) { 117 | size_type src_count = source.size(); 118 | if (src_count > capacity()) { // have to allocate new memory no matter what 119 | if (!_using_internal()) { 120 | _free(_first); 121 | } 122 | size_type cap = src_count; 123 | _first = _alloc(cap); 124 | _capacity = _first + cap; 125 | } // otherwise simply move elements over 126 | _last = _first + src_count; // update _last 127 | // move elements over 128 | for (T *src = source._first, *dst = _first; dst != _last; ++src, ++dst) { 129 | new (dst) T(std::move(*src)); 130 | src->~T(); 131 | } 132 | } else { // just take over 133 | if (!_using_internal()) { 134 | _free(_first); 135 | } 136 | _first = source._first; 137 | _last = source._last; 138 | _capacity = source._capacity; 139 | } 140 | source._first = source._last = source._local; // reset source 141 | return *this; 142 | } 143 | /// Destroys all elements and, if necessary, frees allocated memory. 144 | ~short_vec() { 145 | for (T *cur = _first; cur != _last; ++cur) { 146 | cur->~T(); 147 | } 148 | if (!_using_internal()) { 149 | _free(_first); 150 | } 151 | } 152 | 153 | /// Returns the size of this vector. 154 | size_type size() const { 155 | return _last - _first; 156 | } 157 | /// Returns the capacity of this vector. 158 | size_type capacity() const { 159 | return _using_internal() ? Count : _capacity - _first; 160 | } 161 | /// Returns whether this vector is empty. 162 | bool empty() const { 163 | return _first == _last; 164 | } 165 | 166 | 167 | // modification 168 | /// Constructs a new element in-place at the back of this vector. 169 | template reference emplace_back(Args &&...args) { 170 | size_type cur_size = size(); 171 | if (cur_size == capacity()) { // full, needs extension 172 | size_type new_cap = _extended_capacity(); 173 | T *new_array = _alloc(new_cap); 174 | // construct the new element first 175 | new (new_array + cur_size) T(std::forward(args)...); 176 | // then move the rest elements 177 | for (T *src = _first, *dst = new_array; src != _last; ++src, ++dst) { 178 | new (dst) T(std::move(*src)); 179 | src->~T(); 180 | } 181 | if (!_using_internal()) { // free previously allocated memory 182 | _free(_first); 183 | } 184 | // no member has been altered yet, start now 185 | _first = new_array; 186 | _last = new_array + cur_size; 187 | _capacity = new_array + new_cap; 188 | } else { // simply construct in-place 189 | new (_last) T(std::forward(args)...); 190 | } 191 | return *(_last++); 192 | } 193 | /// Returns the last element. 194 | reference back() { 195 | assert(!empty()); 196 | return *(_last - 1); 197 | } 198 | /// \overload 199 | const_reference back() const { 200 | assert(!empty()); 201 | return *(_last - 1); 202 | } 203 | /// Pops an element from the back of this vector. 204 | void pop_back() { 205 | assert(!empty()); 206 | --_last; 207 | _last->~T(); 208 | // TODO shrink? 209 | } 210 | /// Clears the contents of this vector without freeing heap memory. 211 | void clear() { 212 | for (T *cur = _first; cur != _last; ++cur) { 213 | cur->~T(); 214 | } 215 | _last = _first; 216 | } 217 | 218 | 219 | // indexing 220 | /// Returns an iterator to the first element. 221 | iterator begin() { 222 | return _first; 223 | } 224 | /// Returns an iterator past the last element. 225 | iterator end() { 226 | return _last; 227 | } 228 | /// \overload 229 | const_iterator begin() const { 230 | return _first; 231 | } 232 | /// \overload 233 | const_iterator end() const { 234 | return _last; 235 | } 236 | 237 | /// Indexing. 238 | reference at(size_type i) { 239 | assert(i < size()); 240 | return _first[i]; 241 | } 242 | /// \overload 243 | const_reference at(size_type i) const { 244 | assert(i < size()); 245 | return _first[i]; 246 | } 247 | /// Indexing. 248 | reference operator[](size_type i) { 249 | return at(i); 250 | } 251 | /// \overload 252 | const_reference operator[](size_type i) const { 253 | return at(i); 254 | } 255 | private: 256 | union { 257 | T _local[Count]; ///< Elements that are stored on the stack. 258 | T *_capacity; ///< The capacity of (past) this vector, when using heap-allocated memory. 259 | }; 260 | T 261 | *_first = nullptr, ///< Pointer to the first element. 262 | *_last = nullptr; ///< Pointer past the last element. 263 | 264 | /// Allocates memory for the specified number of elements. 265 | inline static T *_alloc(size_type count) { 266 | #ifdef _MSC_VER 267 | return static_cast(::_aligned_malloc(sizeof(T) * count, alignof(T))); 268 | #else 269 | return static_cast(std::aligned_alloc(alignof(T), sizeof(T) * count)); 270 | #endif 271 | } 272 | /// Frees memory. 273 | inline static void _free(T *ptr) { 274 | #ifdef _MSC_VER 275 | ::_aligned_free(ptr); 276 | #else 277 | std::free(ptr); 278 | #endif 279 | } 280 | 281 | /// Returns whether this vector is using the internal array. 282 | bool _using_internal() const { 283 | return _first == _local; 284 | } 285 | /// Returns the capacity if it were to be extended. 286 | size_type _extended_capacity() const { 287 | return static_cast(capacity() * 1.5); // TODO magic number 288 | } 289 | }; 290 | } 291 | -------------------------------------------------------------------------------- /include/fluid/data_structures/grid.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Implementation of the MAC grid. 5 | 6 | #include 7 | 8 | #include "../math/vec.h" 9 | 10 | namespace fluid { 11 | /// A N-dimensional grid. The cells are stored in X-Y-Z- etc. order, i.e., cells with consecutive X coordinates 12 | /// are stored consecutively in memory. 13 | template class grid { 14 | public: 15 | using size_type = vec; ///< The type used to store the size of this grid and indices. 16 | 17 | /// Default constructor. 18 | grid() = default; 19 | /// Initializes the cell storage. 20 | explicit grid(size_type size) : grid(size, Cell{}) { 21 | } 22 | /// Initializes the cell storage, seting all cells to the given value. 23 | grid(size_type size, const Cell &c) : _cells(get_array_size(size), c), _size(size) { 24 | std::size_t mul = 1; 25 | vec_ops::for_each( 26 | [&mul](std::size_t &offset, std::size_t size) { 27 | offset = mul; 28 | mul *= size; 29 | }, 30 | _layer_offset, get_size() 31 | ); 32 | } 33 | 34 | /// Indexing. 35 | Cell &at(size_type i) { 36 | return _cells[index_to_raw(i)]; 37 | } 38 | /// Indexing. 39 | const Cell &at(size_type i) const { 40 | return _cells[index_to_raw(i)]; 41 | } 42 | /// Indexing. 43 | template std::enable_if_t &operator()(Args &&...args) { 44 | return at(size_type(std::forward(args)...)); 45 | } 46 | /// Indexing. 47 | Cell &operator()(size_type i) { 48 | return at(i); 49 | } 50 | /// Indexing. 51 | template std::enable_if_t operator()( 52 | Args &&...args 53 | ) const { 54 | return at(size_type(std::forward(args)...)); 55 | } 56 | /// Indexing. 57 | const Cell &operator()(size_type i) const { 58 | return at(i); 59 | } 60 | 61 | /// Raw indexing. 62 | Cell &at_raw(std::size_t i) { 63 | return _cells[i]; 64 | } 65 | /// Raw indexing. 66 | const Cell &at_raw(std::size_t i) const { 67 | return _cells[i]; 68 | } 69 | /// Raw indexing. 70 | Cell &operator[](std::size_t i) { 71 | return at_raw(i); 72 | } 73 | /// Raw indexing. 74 | const Cell &operator[](std::size_t i) const { 75 | return at_raw(i); 76 | } 77 | 78 | /// Returns the size of this grid. 79 | size_type get_size() const { 80 | return _size; 81 | } 82 | 83 | /// Fills the entire grid using the given value. 84 | void fill(const Cell &value) { 85 | for (Cell &c : _cells) { 86 | c = value; 87 | } 88 | } 89 | 90 | /// Returns whether the cell at the given index is at the border of this grid. 91 | bool is_border_cell(size_type i) const { 92 | bool result = false; 93 | vec_ops::for_each( 94 | [&result](std::size_t val, std::size_t max) { 95 | if (val == 0 || val == max - 1) { 96 | result = true; 97 | } 98 | }, 99 | i, get_size() 100 | ); 101 | return result; 102 | } 103 | 104 | /// Executes the given callback for each cell in the grid. The order in which the cells are visited is the 105 | /// same as the order in which they're stored in memory. 106 | template void for_each(Callback &&cb) { 107 | _for_each_impl_wrapper(std::forward(cb), size_type(), _size, std::make_index_sequence()); 108 | } 109 | /// Executes the given callback for the given range of the grid. The order in which the cells are visited is 110 | /// as close to the order in which they're stored in memory as possible. The range must completely lie inside 111 | /// the grid. 112 | template void for_each_in_range_unchecked(Callback &&cb, size_type min, size_type max) { 113 | _for_each_impl_wrapper(std::forward(cb), min, max, std::make_index_sequence()); 114 | } 115 | /// Executes the given callback for the given range of the grid. 116 | template void for_each_in_range_checked(Callback &&cb, size_type min, size_type max) { 117 | vec_ops::for_each( 118 | [](std::size_t &coord, std::size_t max) { 119 | coord = std::min(coord, max); 120 | }, 121 | max, _size 122 | ); 123 | for_each_in_range_unchecked(std::forward(cb), min, max); 124 | } 125 | /// Executes the given callback for the given range of the grid. 126 | template void for_each_in_range_checked( 127 | Callback &&cb, size_type center, size_type diffmin, size_type diffmax 128 | ) { 129 | size_type min_corner = vec_ops::apply( 130 | [](std::size_t center, std::size_t offset) { 131 | return center < offset ? 0 : center - offset; 132 | }, center, diffmin 133 | ); 134 | for_each_in_range_checked(std::forward(cb), min_corner, center + diffmax + vec3s(1, 1, 1)); 135 | } 136 | 137 | /// March through the grid. The callback's parameters are the (signed) index of the next cell, the direction 138 | /// (dimension) of the face that's hit, the surface normal, and the \p t value of the intersection point. The 139 | /// callback should return \p false to stop marching. 140 | template void march_cells(Callback &&cb, vec from, vec to) { 141 | using _vecd = vec; 142 | using _veci = vec; 143 | 144 | auto _get_cell = [](_vecd pos) -> _veci { 145 | return vec_ops::apply<_veci>( 146 | [](double coord) { 147 | return static_cast(std::floor(coord)); 148 | }, 149 | pos 150 | ); 151 | }; 152 | 153 | size_type coord; 154 | for (std::size_t i = 0; i < Dim; ++i) { 155 | coord[i] = i; 156 | } 157 | 158 | auto 159 | from_cell = _get_cell(from), 160 | to_cell = _get_cell(to); 161 | _vecd diff = to - from, inv_abs_diff; 162 | _veci advance, face_pos; 163 | vec_ops::for_each( 164 | [](int &adv, int &face, double &inv, double coord) { 165 | if (coord > 0.0) { 166 | adv = 1; 167 | face = 1; 168 | } else { 169 | adv = -1; 170 | face = 0; 171 | } 172 | inv = 1.0 / std::abs(coord); 173 | }, 174 | advance, face_pos, inv_abs_diff, diff 175 | ); 176 | _vecd normal = -_vecd(advance); 177 | 178 | auto t = vec_ops::memberwise::mul( 179 | vec_ops::apply<_vecd>( 180 | static_cast(std::abs), _vecd(from_cell + face_pos) - from 181 | ), 182 | inv_abs_diff 183 | ); 184 | for (_veci current = from_cell; current != to_cell; ) { 185 | std::size_t min_coord = 0; 186 | double mint = 2.0; 187 | vec_ops::for_each( 188 | [&mint, &min_coord](double tv, std::size_t coord) { 189 | if (tv < mint) { 190 | mint = tv; 191 | min_coord = coord; 192 | } 193 | }, 194 | t, coord 195 | ); 196 | if (!(mint <= 1.0)) { 197 | // emergency break - some floating point error has led us to outside of the path 198 | break; 199 | } 200 | 201 | current[min_coord] += advance[min_coord]; 202 | _vecd n; 203 | n[min_coord] = normal[min_coord]; 204 | if (!cb(current, min_coord, n, t[min_coord])) { 205 | break; 206 | } 207 | t[min_coord] += inv_abs_diff[min_coord]; 208 | } 209 | } 210 | 211 | /// Converts a (x, y, z) position into a raw index for \ref _cells. 212 | std::size_t index_to_raw(size_type i) const { 213 | #ifndef NDEBUG 214 | vec_ops::for_each( 215 | [](std::size_t coord, std::size_t size) { 216 | assert(coord < size); 217 | }, 218 | i, _size 219 | ); 220 | #endif 221 | return vec_ops::dot(i, _layer_offset); 222 | } 223 | /// Converts a raw index for \ref _cells to a (x, y, z) position. 224 | size_type index_from_raw(std::size_t i) const { 225 | assert(i < get_array_size(get_size())); 226 | return vec_ops::apply( 227 | [&i](std::size_t max) { 228 | std::size_t v = i % max; 229 | i /= max; 230 | return v; 231 | }, 232 | get_size() 233 | ); 234 | } 235 | 236 | /// Returns the array size given the grid size. 237 | inline static std::size_t get_array_size(size_type size) { 238 | std::size_t result = 1; 239 | vec_ops::for_each( 240 | [&result](std::size_t sc) { 241 | result *= sc; 242 | }, 243 | size 244 | ); 245 | return result; 246 | } 247 | private: 248 | std::vector _cells; ///< Cell storage. 249 | size_type 250 | _size, ///< The size of this grid. 251 | /// The offset that is used to obtain consecutive cells in a certain dimension. For example, 252 | /// \p _layer_offset[0] is 1, \p _layer_offset[1] is \p _size[0], \p _layer_offset[2] is 253 | /// _size[0] * _size[1], and so on. 254 | _layer_offset; 255 | 256 | /// Wrapper around \ref _for_each_impl(). Call this with \p std::make_index_sequence. 257 | template FLUID_FORCEINLINE void _for_each_impl_wrapper( 258 | Callback &&cb, size_type min, size_type max, std::index_sequence 259 | ) { 260 | size_type cur = min; 261 | auto it = _cells.begin() + index_to_raw(min); 262 | _for_each_impl<(Dim - 1 - Dims)...>(std::forward(cb), it, min, max, cur); 263 | } 264 | /// The implementation of the \ref for_each() function, for one dimension. 265 | template < 266 | std::size_t ThisDim, std::size_t ...OtherDims, typename Callback, typename It 267 | > FLUID_FORCEINLINE void _for_each_impl( 268 | Callback &&cb, const It &it, size_type min, size_type max, size_type &cur 269 | ) const { 270 | if (min[ThisDim] >= max[ThisDim]) { 271 | return; 272 | } 273 | It my_it = it; 274 | std::size_t &i = cur[ThisDim]; 275 | i = min[ThisDim]; 276 | _for_each_impl(std::forward(cb), my_it, min, max, cur); 277 | for (++i; i < max[ThisDim]; ++i) { 278 | my_it += _layer_offset[ThisDim]; 279 | _for_each_impl(std::forward(cb), my_it, min, max, cur); 280 | } 281 | } 282 | /// End of recursion, where the callback is actually invoked. 283 | template FLUID_FORCEINLINE void _for_each_impl( 284 | Callback &&cb, It it, size_type, size_type, size_type cur 285 | ) const { 286 | cb(cur, *it); 287 | } 288 | }; 289 | 290 | template using grid2 = grid<2, Cell>; ///< Shorthand for 2D grids. 291 | template using grid3 = grid<3, Cell>; ///< Shorthand for 3D grids. 292 | } 293 | -------------------------------------------------------------------------------- /include/fluid/math/mat.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// \file 4 | /// Matrices. 5 | 6 | #include "vec.h" 7 | 8 | namespace fluid { 9 | /// Row-major matrices. 10 | template < 11 | typename T, std::size_t W, std::size_t H, template typename VecT = vec 12 | > struct rmat { 13 | constexpr static std::size_t 14 | width = W, ///< The number of columns in this matrix. 15 | height = H; ///< The number of rows in this matrix. 16 | constexpr static bool is_square = W == H; ///< Indicates whether this is a square matrix. 17 | 18 | using row_type = VecT; ///< The type of a row. 19 | using column_type = VecT; ///< The type of a column. 20 | using value_type = T; ///< The type of elements in this matrix. 21 | using storage_type = VecT; ///< The type used to store the matrix. 22 | 23 | 24 | /// Zero-initializes this matrix. 25 | rmat() = default; 26 | private: 27 | /// Directly initializes this matrix from the storage. 28 | explicit rmat(const storage_type &mat_rows) : rows(mat_rows) { 29 | } 30 | 31 | public: 32 | /// Creates a new \ref rmat from its rows. 33 | template [[nodiscard]] inline static rmat from_rows(Args &&...args) { 34 | return rmat(storage_type(std::forward(args)...)); 35 | } 36 | 37 | /// Returns the identity matrix. 38 | [[nodiscard]] inline static rmat identity() { 39 | rmat result; 40 | std::size_t dim = 0; 41 | vec_ops::for_each( 42 | [&dim](row_type &row) { 43 | row[dim++] = static_cast(1); 44 | }, 45 | result.rows 46 | ); 47 | return result; 48 | } 49 | 50 | 51 | // indexing 52 | /// Returns the element at the specified location. 53 | value_type &at(std::size_t x, std::size_t y) { 54 | assert(y < H); 55 | return rows[y][x]; 56 | } 57 | /// \override 58 | value_type at(std::size_t x, std::size_t y) const { 59 | assert(y < H); 60 | return rows[y][x]; 61 | } 62 | 63 | /// Returns the element at the specified location. 64 | value_type &operator()(std::size_t x, std::size_t y) { 65 | return at(x, y); 66 | } 67 | /// \overload 68 | value_type operator()(std::size_t x, std::size_t y) const { 69 | return at(x, y); 70 | } 71 | 72 | /// Returns the row at the given index. 73 | row_type row(std::size_t y) const { 74 | assert(y < H); 75 | return rows[y]; 76 | } 77 | /// Returns the column at the given index. 78 | column_type column(std::size_t x) const { 79 | assert(x < W); 80 | return vec_ops::apply( 81 | [x](const row_type &row) { 82 | return row[x]; 83 | }, 84 | rows 85 | ); 86 | } 87 | 88 | 89 | // arithmetic 90 | /// In-place addition. 91 | rmat &operator+=(const rmat &rhs) { 92 | rows += rhs.rows; 93 | return *this; 94 | } 95 | /// Addition. 96 | [[nodiscard]] friend rmat operator+(const rmat &lhs, const rmat &rhs) { 97 | return rmat(lhs) += rhs; 98 | } 99 | 100 | /// In-place subtraction. 101 | rmat &operator-=(const rmat &rhs) { 102 | rows -= rhs.rows; 103 | return *this; 104 | } 105 | /// Subtraction. 106 | [[nodiscard]] friend rmat operator-(const rmat &lhs, const rmat &rhs) { 107 | return rmat(lhs) -= rhs; 108 | } 109 | /// Negation. 110 | [[nodiscard]] friend rmat operator-(const rmat &lhs) { 111 | return rmat(-lhs.rows); 112 | } 113 | 114 | /// In-place scalar division. 115 | rmat &operator/=(const T &rhs) { 116 | rows /= rhs; 117 | return *this; 118 | } 119 | /// Scalar division. 120 | [[nodiscard]] friend rmat operator/(rmat lhs, const T &rhs) { 121 | return lhs /= rhs; 122 | } 123 | 124 | /// In-place scalar multiplication. 125 | rmat &operator*=(const T &rhs) { 126 | rows *= rhs; 127 | return *this; 128 | } 129 | /// Scalar multiplication. 130 | [[nodiscard]] friend rmat operator*(rmat lhs, const T &rhs) { 131 | return lhs *= rhs; 132 | } 133 | /// Scalar multiplication. 134 | [[nodiscard]] friend rmat operator*(const T &lhs, rmat rhs) { 135 | return rhs *= lhs; 136 | } 137 | 138 | /// Matrix-vector multiplication. 139 | [[nodiscard]] friend column_type operator*(const rmat &lhs, const row_type &rhs) { 140 | return vec_ops::apply( 141 | [&rhs](const row_type &row) { 142 | return vec_ops::dot(row, rhs); 143 | }, 144 | lhs.rows 145 | ); 146 | } 147 | 148 | 149 | // basic operations 150 | /// Returns the transposed matrix. 151 | rmat transposed() const { 152 | using _trans_t = rmat; 153 | 154 | _trans_t result; 155 | std::size_t dim = 0; 156 | vec_ops::for_each( 157 | [this, &dim](typename _trans_t::row_type &row) { 158 | row = column(dim++); 159 | }, 160 | result.rows 161 | ); 162 | return result; 163 | } 164 | 165 | /// Returns the submatrix that is used to calculate the minor of this matrix, i.e., this matrix without the 166 | /// \p er-th row and \p ec-th column. 167 | rmat get_submatrix_minor(std::size_t er, std::size_t ec) const { 168 | using _result_t = rmat; 169 | _result_t result; 170 | std::size_t rid = 0; 171 | vec_ops::for_each( 172 | [&](typename _result_t::row_type &row) { 173 | row_type myrow = rows[rid >= er ? rid + 1 : rid]; 174 | std::size_t cid = 0; 175 | vec_ops::for_each( 176 | [&](T &col) { 177 | col = myrow[cid >= ec ? cid + 1 : cid]; 178 | ++cid; 179 | }, 180 | row 181 | ); 182 | ++rid; 183 | }, 184 | result.rows 185 | ); 186 | return result; 187 | } 188 | 189 | private: 190 | /// Shorthand for \p std::enable_if_t. 191 | template using _enable_if_t = 192 | std::enable_if_t, U>; 193 | /// \p std::enable_if_t for square matrices. 194 | template using _enable_if_square_t = _enable_if_t; 195 | public: 196 | /// Calculates the determinant of this matrix. 197 | template _enable_if_square_t get_determinant() const { 198 | if constexpr (W == 1) { 199 | return rows[0][0]; 200 | } else { 201 | if constexpr (W == 2) { // termination condition 202 | return rows[0][0] * rows[1][1] - rows[0][1] * rows[1][0]; 203 | } else { 204 | T result = static_cast(0); 205 | bool positive = true; 206 | std::size_t rid = 0; 207 | vec_ops::for_each( 208 | [&](const row_type &row) { 209 | T value = row[0] * get_submatrix_minor(rid, 0).get_determinant(); 210 | result = positive ? result + value : result - value; 211 | ++rid; 212 | positive = !positive; 213 | }, 214 | rows 215 | ); 216 | return result; 217 | } 218 | } 219 | } 220 | 221 | /// Calculates the inverse matrix. 222 | template _enable_if_square_t get_inverse() const { 223 | rmat result; 224 | // compute adjugate 225 | std::size_t rid = 0; 226 | bool rinv = false; 227 | vec_ops::for_each( 228 | [&](row_type &row) { 229 | std::size_t cid = 0; 230 | bool cinv = rinv; 231 | vec_ops::for_each( 232 | [&](T &col) { 233 | // transposed here 234 | col = get_submatrix_minor(cid, rid).get_determinant(); 235 | if (cinv) { 236 | col = -col; 237 | } 238 | ++cid; 239 | cinv = !cinv; 240 | }, 241 | row 242 | ); 243 | ++rid; 244 | rinv = !rinv; 245 | }, 246 | result.rows 247 | ); 248 | return result / get_determinant(); 249 | } 250 | 251 | 252 | storage_type rows; ///< The rows. 253 | }; 254 | 255 | template < 256 | typename T, std::size_t H1, std::size_t W1H2, std::size_t W2, 257 | template typename VecT 258 | > inline rmat operator*( 259 | const rmat &lhs, const rmat &rhs 260 | ) { 261 | using _lhs_t = rmat; 262 | using _rhs_trans_t = rmat; 263 | using _result_t = rmat; 264 | 265 | _rhs_trans_t rhs_trans = rhs.transposed(); 266 | _result_t result; 267 | vec_ops::for_each( 268 | [&](typename _result_t::row_type &res_row, const typename _lhs_t::row_type &lhs_row) { 269 | vec_ops::for_each( 270 | [&](T &dest, const typename _rhs_trans_t::row_type &rhs_row) { 271 | dest = vec_ops::dot(lhs_row, rhs_row); 272 | }, 273 | res_row, rhs_trans.rows 274 | ); 275 | }, 276 | result.rows, lhs.rows 277 | ); 278 | return result; 279 | } 280 | 281 | template using rmat3 = rmat; ///< Shorthand for 3x3 matrix types. 282 | using rmat3d = rmat3; ///< Double 3x3 matrices. 283 | 284 | template using rmat4 = rmat; ///< Shorthand for 4x4 matrix types. 285 | using rmat4d = rmat4; ///< Double 4x4 matrices. 286 | 287 | template using rmat3x4 = rmat; ///< Shorthand for 3x4 matrix types. 288 | using rmat3x4d = rmat3x4; ///< Double 3x4 matrices. 289 | 290 | 291 | namespace transform { 292 | /// Returns a scaling matrix. 293 | inline rmat3d scale(vec3d s) { 294 | rmat3d result; 295 | result(0, 0) = s.x; 296 | result(1, 1) = s.y; 297 | result(2, 2) = s.z; 298 | return result; 299 | } 300 | /// Rotation using Euler angles using Z -> Y -> X as the rotation order. 301 | inline rmat3d rotate_euler(vec3d angle) { 302 | double 303 | sx = std::sin(angle.x), cx = std::cos(angle.x), 304 | sy = std::sin(angle.y), cy = std::cos(angle.y), 305 | sz = std::sin(angle.z), cz = std::cos(angle.z); 306 | 307 | rmat3d result; 308 | 309 | result(0, 0) = cy * cz; 310 | result(1, 0) = sx * sy * cz - cx * sz; 311 | result(2, 0) = cx * sy * cz + sx * sz; 312 | 313 | result(0, 1) = cy * sz; 314 | result(1, 1) = sx * sy * sz + cx * cz; 315 | result(2, 1) = cx * sy * sz - sx * cz; 316 | 317 | result(0, 2) = -sy; 318 | result(1, 2) = cy * sx; 319 | result(2, 2) = cy * cx; 320 | 321 | return result; 322 | } 323 | 324 | /// Scales, rotates, and then translates the coordinate system. 325 | inline rmat3x4d scale_rotate_translate(vec3d sc, vec3d euler, vec3d translate) { 326 | rmat3d left = rotate_euler(euler) * scale(sc); 327 | rmat3x4d result; 328 | 329 | result(0, 0) = left(0, 0); 330 | result(1, 0) = left(1, 0); 331 | result(2, 0) = left(2, 0); 332 | result(3, 0) = translate.x; 333 | 334 | result(0, 1) = left(0, 1); 335 | result(1, 1) = left(1, 1); 336 | result(2, 1) = left(2, 1); 337 | result(3, 1) = translate.y; 338 | 339 | result(0, 2) = left(0, 2); 340 | result(1, 2) = left(1, 2); 341 | result(2, 2) = left(2, 2); 342 | result(3, 2) = translate.z; 343 | 344 | return result; 345 | } 346 | } 347 | } 348 | -------------------------------------------------------------------------------- /testbed/test_scenes.cpp: -------------------------------------------------------------------------------- 1 | #include "test_scenes.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace fluid; 8 | using namespace fluid::renderer; 9 | 10 | scene::mesh_t create_plane() { 11 | scene::mesh_t mesh; 12 | 13 | mesh.positions.emplace_back(-0.5, 0.0, -0.5); 14 | mesh.positions.emplace_back(0.5, 0.0, -0.5); 15 | mesh.positions.emplace_back(0.5, 0.0, 0.5); 16 | mesh.positions.emplace_back(-0.5, 0.0, 0.5); 17 | 18 | mesh.indices.emplace_back(0); 19 | mesh.indices.emplace_back(1); 20 | mesh.indices.emplace_back(2); 21 | 22 | mesh.indices.emplace_back(0); 23 | mesh.indices.emplace_back(2); 24 | mesh.indices.emplace_back(3); 25 | 26 | return mesh; 27 | } 28 | 29 | scene::mesh_t create_box() { 30 | scene::mesh_t mesh; 31 | 32 | mesh.positions.emplace_back(-0.5, -0.5, -0.5); 33 | mesh.positions.emplace_back(0.5, -0.5, -0.5); 34 | mesh.positions.emplace_back(0.5, 0.5, -0.5); 35 | mesh.positions.emplace_back(-0.5, 0.5, -0.5); 36 | mesh.positions.emplace_back(-0.5, -0.5, 0.5); 37 | mesh.positions.emplace_back(0.5, -0.5, 0.5); 38 | mesh.positions.emplace_back(0.5, 0.5, 0.5); 39 | mesh.positions.emplace_back(-0.5, 0.5, 0.5); 40 | 41 | mesh.indices = std::vector( 42 | { 43 | 0, 3, 1, 3, 2, 1, 44 | 1, 2, 5, 2, 6, 5, 45 | 5, 6, 4, 6, 7, 4, 46 | 4, 7, 0, 7, 3, 0, 47 | 3, 7, 2, 7, 6, 2, 48 | 4, 0, 5, 0, 1, 5 49 | } 50 | ); 51 | 52 | return mesh; 53 | } 54 | 55 | 56 | std::pair red_green_box(double asp_ratio) { 57 | scene result; 58 | 59 | material matte_white; 60 | { 61 | auto &lambert = matte_white.value.emplace(); 62 | lambert.reflectance.modulation = spectrum::from_rgb(vec3d(0.725, 0.71, 0.68)); 63 | } 64 | 65 | material matte_red; 66 | { 67 | auto &lambert = matte_red.value.emplace(); 68 | lambert.reflectance.modulation = spectrum::from_rgb(vec3d(0.63, 0.065, 0.05)); 69 | } 70 | 71 | material matte_green; 72 | { 73 | auto &lambert = matte_green.value.emplace(); 74 | lambert.reflectance.modulation = spectrum::from_rgb(vec3d(0.14, 0.45, 0.091)); 75 | } 76 | 77 | material mirror; 78 | { 79 | auto &specular = mirror.value.emplace(); 80 | specular.reflectance.modulation = spectrum::identity; 81 | } 82 | 83 | scene::mesh_t plane = create_plane(); 84 | 85 | entity_info floor; 86 | floor.mat = matte_white; 87 | result.add_mesh_entity( 88 | plane, 89 | transform::scale_rotate_translate( 90 | vec3d(10.0, 1.0, 10.0), vec3d(constants::pi, 0.0, 0.0), vec3d(0.0, -2.5, 0.0) 91 | ), 92 | floor 93 | ); 94 | 95 | entity_info left_wall; 96 | left_wall.mat = matte_red; 97 | result.add_mesh_entity( 98 | plane, 99 | transform::scale_rotate_translate( 100 | vec3d(10.0, 1.0, 10.0), vec3d(0.0, 0.0, -0.5 * constants::pi), vec3d(5.0, 2.5, 0.0) 101 | ), 102 | left_wall 103 | ); 104 | 105 | entity_info right_wall; 106 | right_wall.mat = matte_green; 107 | result.add_mesh_entity( 108 | plane, 109 | transform::scale_rotate_translate( 110 | vec3d(10.0, 1.0, 10.0), vec3d(0.0, 0.0, 0.5 * constants::pi), vec3d(-5.0, 2.5, 0.0) 111 | ), 112 | right_wall 113 | ); 114 | 115 | entity_info back_wall; 116 | /*back_wall.mat = mirror;*/ 117 | back_wall.mat = matte_white; 118 | result.add_mesh_entity( 119 | plane, 120 | transform::scale_rotate_translate( 121 | vec3d(10.0, 1.0, 10.0), vec3d(0.5 * constants::pi, 0.0, 0.0), vec3d(0.0, 2.5, 5.0) 122 | ), 123 | back_wall 124 | ); 125 | 126 | entity_info ceiling; 127 | ceiling.mat = matte_white; 128 | result.add_mesh_entity( 129 | plane, 130 | transform::scale_rotate_translate( 131 | vec3d(10.0, 1.0, 10.0), vec3d(0.0, 0.0, 0.0), vec3d(0.0, 7.5, 0.0) 132 | ), 133 | ceiling 134 | ); 135 | 136 | camera cam = camera::from_parameters( 137 | vec3d(0.0, 5.5, -30.0), vec3d(0.0, 2.5, 0.0), vec3d(0.0, 1.0, 0.0), 138 | 19.5 * constants::pi / 180.0, asp_ratio 139 | ); 140 | 141 | return { std::move(result), cam }; 142 | } 143 | 144 | std::pair cornell_box_base(double asp_ratio) { 145 | auto [result, cam] = red_green_box(asp_ratio); 146 | 147 | material matte_white; 148 | { 149 | auto &lambert = matte_white.value.emplace(); 150 | lambert.reflectance.modulation = spectrum::from_rgb(vec3d(0.725, 0.71, 0.68)); 151 | } 152 | 153 | scene::mesh_t plane = create_plane(), box = create_box(); 154 | 155 | entity_info long_cube; 156 | long_cube.mat = matte_white; 157 | result.add_mesh_entity( 158 | box, 159 | transform::scale_rotate_translate( 160 | vec3d(3.0, 6.0, 3.0), vec3d(0.0, 27.5 * constants::pi / 180.0, 0.0), vec3d(2.0, 0.0, 3.0) 161 | ), 162 | long_cube 163 | ); 164 | 165 | entity_info short_cube; 166 | short_cube.mat = matte_white; 167 | result.add_mesh_entity( 168 | box, 169 | transform::scale_rotate_translate( 170 | vec3d(3.0, 3.0, 3.0), vec3d(0.0, -17.5 * constants::pi / 180.0, 0.0), vec3d(-2.0, -1.0, 0.75) 171 | ), 172 | short_cube 173 | ); 174 | 175 | return { std::move(result), cam }; 176 | } 177 | 178 | std::pair cornell_box_one_light(double asp_ratio) { 179 | auto [result, cam] = cornell_box_base(asp_ratio); 180 | 181 | material matte_white; 182 | { 183 | auto &lambert = matte_white.value.emplace(); 184 | lambert.reflectance.modulation = spectrum::from_rgb(vec3d(0.725, 0.71, 0.68)); 185 | } 186 | 187 | scene::mesh_t plane = create_plane(); 188 | 189 | entity_info light; 190 | light.mat = matte_white; 191 | light.mat.emission.modulation = spectrum::from_rgb(2.0 * vec3d(17.0, 12.0, 4.0)); 192 | result.add_mesh_entity( 193 | plane, 194 | transform::scale_rotate_translate( 195 | vec3d(3.0, 1.0, 3.0), vec3d(0.0, 0.0, 0.0), vec3d(0.0, 7.45, 0.0) 196 | ), 197 | light 198 | ); 199 | 200 | return { std::move(result), cam }; 201 | } 202 | 203 | std::pair cornell_box_two_lights(double asp_ratio) { 204 | auto [result, cam] = cornell_box_base(asp_ratio); 205 | 206 | material matte_white; 207 | { 208 | auto &lambert = matte_white.value.emplace(); 209 | lambert.reflectance.modulation = spectrum::from_rgb(vec3d(0.725, 0.71, 0.68)); 210 | } 211 | 212 | scene::mesh_t plane = create_plane(); 213 | 214 | entity_info light_yellow; 215 | light_yellow.mat = matte_white; 216 | light_yellow.mat.emission.modulation = spectrum::from_rgb(vec3d(17.0, 12.0, 4.0)); 217 | result.add_mesh_entity( 218 | plane, 219 | transform::scale_rotate_translate( 220 | vec3d(3.0, 1.0, 3.0), vec3d(0.0, 0.0, 0.0), vec3d(2.0, 7.45, 0.0) 221 | ), 222 | light_yellow 223 | ); 224 | 225 | entity_info light_blue; 226 | light_blue.mat = matte_white; 227 | light_blue.mat.emission.modulation = spectrum::from_rgb(vec3d(4.0, 12.0, 17.0)); 228 | result.add_mesh_entity( 229 | plane, 230 | transform::scale_rotate_translate( 231 | vec3d(3.0, 1.0, 3.0), vec3d(0.0, 0.0, 0.0), vec3d(-2.0, 7.45, 0.0) 232 | ), 233 | light_blue 234 | ); 235 | 236 | return { std::move(result), cam }; 237 | } 238 | 239 | std::pair glass_ball_box(double asp_ratio) { 240 | auto [result, cam] = red_green_box(asp_ratio); 241 | 242 | material matte_white; 243 | { 244 | auto &lambert = matte_white.value.emplace(); 245 | lambert.reflectance.modulation = spectrum::from_rgb(vec3d(0.725, 0.71, 0.68)); 246 | } 247 | 248 | material glass; 249 | { 250 | auto &specular = glass.value.emplace(); 251 | specular.skin.modulation = spectrum::identity; 252 | specular.index_of_refraction = 1.55; 253 | } 254 | 255 | scene::mesh_t plane = create_plane(); 256 | 257 | entity_info sphere; 258 | sphere.mat = glass; 259 | primitives::sphere_primitive prim; 260 | prim.set_transformation(transform::scale_rotate_translate( 261 | vec3d(3.0, 3.0, 3.0), vec3d(0.0, 27.5 * constants::pi / 180.0, 0.0), vec3d(0.0, 1.25, 0.0) 262 | )); 263 | result.add_primitive_entity(prim, sphere); 264 | 265 | entity_info light; 266 | light.mat = matte_white; 267 | light.mat.emission.modulation = spectrum::from_rgb(2.0 * vec3d(17.0, 12.0, 4.0)); 268 | result.add_mesh_entity( 269 | plane, 270 | transform::scale_rotate_translate( 271 | vec3d(3.0, 1.0, 3.0), vec3d(0.0, 0.0, 0.0), vec3d(0.0, 7.45, 0.0) 272 | ), 273 | light 274 | ); 275 | 276 | return { std::move(result), cam }; 277 | } 278 | 279 | 280 | std::pair fluid_box(vec3d min, vec3d max, double fovy, double asp_ratio) { 281 | scene result; 282 | 283 | material matte_white; 284 | { 285 | auto &lambert = matte_white.value.emplace(); 286 | lambert.reflectance.modulation = spectrum::from_rgb(vec3d(0.725, 0.71, 0.68)); 287 | } 288 | 289 | material matte_red; 290 | { 291 | auto &lambert = matte_red.value.emplace(); 292 | lambert.reflectance.modulation = spectrum::from_rgb(vec3d(0.63, 0.065, 0.05)); 293 | } 294 | 295 | material matte_green; 296 | { 297 | auto &lambert = matte_green.value.emplace(); 298 | lambert.reflectance.modulation = spectrum::from_rgb(vec3d(0.14, 0.45, 0.091)); 299 | } 300 | 301 | scene::mesh_t plane = create_plane(); 302 | 303 | vec3d center = 0.5 * (min + max), size = max - min; 304 | 305 | entity_info floor; 306 | floor.mat = matte_white; 307 | result.add_mesh_entity( 308 | plane, 309 | transform::scale_rotate_translate( 310 | size, vec3d(constants::pi, 0.0, 0.0), vec3d(center.x, min.y, center.z) 311 | ), 312 | floor 313 | ); 314 | 315 | entity_info ceiling; 316 | ceiling.mat = matte_white; 317 | result.add_mesh_entity( 318 | plane, 319 | transform::scale_rotate_translate( 320 | size, vec3d(-constants::pi, 0.0, 0.0), vec3d(center.x, max.y, center.z) 321 | ), 322 | ceiling 323 | ); 324 | 325 | entity_info left_wall; 326 | left_wall.mat = matte_red; 327 | result.add_mesh_entity( 328 | plane, 329 | transform::scale_rotate_translate( 330 | size, vec3d(0.0, 0.0, 0.5 * constants::pi), vec3d(min.x, center.y, center.z) 331 | ), 332 | left_wall 333 | ); 334 | 335 | entity_info right_wall; 336 | right_wall.mat = matte_green; 337 | result.add_mesh_entity( 338 | plane, 339 | transform::scale_rotate_translate( 340 | size, vec3d(0.0, 0.0, -0.5 * constants::pi), vec3d(max.x, center.y, center.z) 341 | ), 342 | right_wall 343 | ); 344 | 345 | entity_info back_wall; 346 | back_wall.mat = matte_white; 347 | result.add_mesh_entity( 348 | plane, 349 | transform::scale_rotate_translate( 350 | size, vec3d(0.5 * constants::pi, 0.0, 0.0), vec3d(center.x, center.y, max.z) 351 | ), 352 | back_wall 353 | ); 354 | 355 | // lights 356 | entity_info light_yellow; 357 | light_yellow.mat = matte_white; 358 | light_yellow.mat.emission.modulation = spectrum::from_rgb(vec3d(17.0, 12.0, 4.0)); 359 | result.add_mesh_entity( 360 | plane, 361 | transform::scale_rotate_translate( 362 | vec3d(0.3 * size.x, 1.0, 0.3 * size.z), vec3d(0.0, 0.0, 0.0), vec3d(center.x - 0.25 * size.x, max.y - 0.05, center.z) 363 | ), 364 | light_yellow 365 | ); 366 | 367 | entity_info light_blue; 368 | light_blue.mat = matte_white; 369 | light_blue.mat.emission.modulation = spectrum::from_rgb(vec3d(4.0, 12.0, 17.0)); 370 | result.add_mesh_entity( 371 | plane, 372 | transform::scale_rotate_translate( 373 | vec3d(0.3 * size.x, 1.0, 0.3 * size.z), vec3d(0.0, 0.0, 0.0), vec3d(center.x + 0.25 * size.x, max.y - 0.05, center.z) 374 | ), 375 | light_blue 376 | ); 377 | 378 | double tan_half_y = std::tan(0.5 * fovy), tan_half_x = asp_ratio * tan_half_y; 379 | double dist_y = 0.5 * size.y / tan_half_y, dist_x = 0.5 * size.x / tan_half_x; 380 | camera cam = camera::from_parameters( 381 | vec3d(center.x, center.y, min.z - std::max(dist_x, dist_y) - 10.0), center, vec3d(0.0, 1.0, 0.0), 382 | fovy, asp_ratio 383 | ); 384 | 385 | return { std::move(result), cam }; 386 | } 387 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | --------------------------------------------------------------------------------