Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,11 @@ set(SOURCES
src/scene.cpp
src/shapes/objmesh.cpp
src/shapes/plymesh.cpp
src/shapes/triangle_mesh.cpp
src/shapes/inline_triangle_mesh.cpp
src/shapes/shape.cpp
src/shapes/triangle.cpp
src/shapes/instance.cpp
src/scene_parser.cpp
src/rayintersectinfo.cpp
src/textures/image_texture.cpp
Expand Down
4 changes: 4 additions & 0 deletions include/scene_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ namespace Caramel{
private:
Shape* parse_shape(const Json &shape_json) const;

// Expands a {"type":"instance", "shapes":[...], "instances":[...]} entry into one
// Instance per (template sub-shape x placement); templates are loaded once and shared.
void parse_instanced_shapes(const Json &shape_json, std::vector<Shape*> &out) const;

Light* parse_light(const Json &light_json) const;

// Other lights are handled in `parse_light()`
Expand Down
113 changes: 52 additions & 61 deletions include/shape.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,59 +123,42 @@ namespace Caramel{
const bool is_tx_exists;
};

// Interface for triangle mesh shapes (used by acceleration structures)
class TriangleMesh : public Shape{
public:
using Shape::Shape;
virtual Index get_triangle_num() const = 0;
virtual std::pair<bool, RayIntersectInfo> get_triangle_ray_intersect(Index i, const Ray &ray, Float maxt) const = 0;
virtual AABB get_triangle_aabb(Index i) const = 0;
virtual Float get_triangle_area(Index i) const = 0;
virtual std::tuple<Vector3f, Vector3f, Float> get_triangle_sample_point(Index i, Sampler &sampler) const = 0;
virtual std::tuple<Vector3f, Vector3f, Vector3f> get_triangle_vertices(Index i) const = 0;
virtual Index sample_triangle_index(Float u) const = 0;
virtual Float triangle_select_pdf(Index i) const = 0;
};

class OBJMesh final : public TriangleMesh{
public:
OBJMesh(const std::filesystem::path &path, BSDF *bsdf, AreaLight *arealight = nullptr, const Matrix44f &transform = Matrix44f::identity());
TriangleMesh(BSDF *bsdf, AreaLight *arealight);
~TriangleMesh() override;

std::pair<bool, RayIntersectInfo> ray_intersect(const Ray &ray, Float maxt) const override;
AABB get_aabb() const override;
Float get_area() const override;
// point, normal, probability
std::tuple<Vector3f, Vector3f, Float> sample_point(Sampler &sampler) const override;
Float pdf_solidangle(const Vector3f &hitpos_world, const Vector3f &shapepos_world, const Vector3f &shape_normal_world) const override;

std::pair<bool, RayIntersectInfo> get_triangle_ray_intersect(Index i, const Ray &ray, Float maxt) const override;
AABB get_triangle_aabb(Index i) const override;
Float get_triangle_area(Index i) const override;
std::tuple<Vector3f, Vector3f, Float> get_triangle_sample_point(Index i, Sampler &sampler) const override;
std::tuple<Vector3f, Vector3f, Vector3f> get_triangle_vertices(Index i) const override;
Index sample_triangle_index(Float u) const override;
Float triangle_select_pdf(Index i) const override;

Index get_triangle_num() const override {
return m_vertex_indices.size();
}

bool is_solid_angle_sampling_possible() const override;
const std::vector<Vector3f>& get_polygon_vertices() const override;

private:
Index get_triangle_num() const { return m_face_indices.size(); }
std::pair<bool, RayIntersectInfo> get_triangle_ray_intersect(Index i, const Ray &ray, Float maxt) const;
AABB get_triangle_aabb(Index i) const;
Float get_triangle_area(Index i) const;
std::tuple<Vector3f, Vector3f, Float> get_triangle_sample_point(Index i, Sampler &sampler) const;
std::tuple<Vector3f, Vector3f, Vector3f> get_triangle_vertices(Index i) const;
Index sample_triangle_index(Float u) const;
Float triangle_select_pdf(Index i) const;

protected:
void finalize(AreaLight *arealight, const std::string &name);

Distrib1D m_triangle_pdf;
Float m_area;
Float m_area = Float0;
AABB m_aabb;
bool is_vn_exists;
bool is_tx_exists;
bool is_vn_exists = false;
bool is_tx_exists = false;
std::unique_ptr<MeshAccel> m_accel;
std::vector<Vector3f> m_vertices;
std::vector<Vector3f> m_normals;
std::vector<Vector2f> m_tex_coords;
std::vector<Vector3i> m_vertex_indices;
std::vector<Vector3i> m_normal_indices;
std::vector<Vector3i> m_tex_coord_indices;
std::vector<Vector3i> m_face_indices;

// for solid angle sampling
std::vector<Vector3f> m_polygon_vertices;
Expand All @@ -185,42 +168,50 @@ namespace Caramel{
class PLYMesh final : public TriangleMesh{
public:
PLYMesh(const std::filesystem::path &path, BSDF *bsdf, AreaLight *arealight = nullptr, const Matrix44f &transform = Matrix44f::identity());
};

class OBJMesh final : public TriangleMesh{
public:
OBJMesh(const std::filesystem::path &path, BSDF *bsdf, AreaLight *arealight = nullptr, const Matrix44f &transform = Matrix44f::identity());
};

class InlineTriangleMesh final : public TriangleMesh{
public:
InlineTriangleMesh(std::vector<Vector3f> positions,
std::vector<Vector3i> indices,
std::vector<Vector3f> normals,
BSDF *bsdf, AreaLight *arealight = nullptr,
const Matrix44f &transform = Matrix44f::identity());
};

// Instanced geometry. Shares a template Shape (kept in LOCAL space) and
// applies a per-placement transform at intersection time, so one template's
// geometry can be reused across many placements without copying vertices.
class Instance final : public Shape{
public:
// bsdf belongs to this placement: the scene BVH overwrites info.shape with
// the top-level Shape* (this Instance), so the integrator reads get_bsdf()
// from here, not from the template.
Instance(const Shape *geometry, const Matrix44f &to_world, BSDF *bsdf, AreaLight *arealight = nullptr);

std::pair<bool, RayIntersectInfo> ray_intersect(const Ray &ray, Float maxt) const override;
AABB get_aabb() const override;
Float get_area() const override;
// point, normal, probability
std::tuple<Vector3f, Vector3f, Float> sample_point(Sampler &sampler) const override;
Float pdf_solidangle(const Vector3f &hitpos_world, const Vector3f &shapepos_world, const Vector3f &shape_normal_world) const override;

std::pair<bool, RayIntersectInfo> get_triangle_ray_intersect(Index i, const Ray &ray, Float maxt) const override;
AABB get_triangle_aabb(Index i) const override;
Float get_triangle_area(Index i) const override;
std::tuple<Vector3f, Vector3f, Float> get_triangle_sample_point(Index i, Sampler &sampler) const override;
std::tuple<Vector3f, Vector3f, Vector3f> get_triangle_vertices(Index i) const override;
Index sample_triangle_index(Float u) const override;
Float triangle_select_pdf(Index i) const override;

Index get_triangle_num() const override {
return m_face_indices.size();
}

bool is_solid_angle_sampling_possible() const override;
const std::vector<Vector3f>& get_polygon_vertices() const override;

private:
Distrib1D m_triangle_pdf;
Float m_area;
AABB m_aabb;
bool is_vn_exists;
std::unique_ptr<MeshAccel> m_accel;
std::vector<Vector3f> m_vertices;
std::vector<Vector3f> m_normals;
std::vector<Vector3i> m_face_indices;
// The shared template geometry pointer (test accessor for sharing checks).
const Shape *geometry() const { return m_geometry; }

// for solid angle sampling
std::vector<Vector3f> m_polygon_vertices;
bool m_is_solid_angle_sampling_possible = false;
private:
const Shape *m_geometry; // shared template, LOCAL space (intersection only)
Matrix44f m_to_world;
Matrix44f m_to_local; // Inverse(to_world)
AABB m_world_aabb;
Float m_world_area = Float0;
std::vector<Vector3f> m_world_polygon_vertices;
};

// u, v, t
Expand Down
91 changes: 90 additions & 1 deletion src/scene_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,12 +148,62 @@ namespace Caramel{
const Json child = get_unique_first_elem(m_scene_json, "shape");
if(child.is_array()){
for(const auto &ch : child){
shapes.emplace_back(parse_shape(ch));
if(parse_string(ch, "type") == "instance"){
parse_instanced_shapes(ch, shapes);
}
else{
shapes.emplace_back(parse_shape(ch));
}
}
}
return shapes;
}

void SceneParser::parse_instanced_shapes(const SceneParser::Json &shape_json, std::vector<Shape*> &out) const {
// Build the template once (group-local space): parallel geometry + bsdf arrays.
// shapes-form : many sub-shapes, each carrying its own geometry + bsdf.
std::vector<const Shape*> geometries;
std::vector<BSDF*> bsdfs;
std::vector<Vector3f> radiances;

const Json sub_shapes = get_unique_first_elem(shape_json, "shapes");
if(!sub_shapes.is_array() || sub_shapes.empty()){
CRM_ERROR("instance 'shapes' must be a non-empty array");
}

// One Instance per (template sub-shape x placement); geometry shared across placements.
const Json instance_list = get_unique_first_elem(shape_json, "instances");
if(!instance_list.is_array() || instance_list.empty()){
CRM_ERROR("instance 'instances' must be a non-empty array");
}

for(const auto &s : sub_shapes){
Comment thread
pjessesco marked this conversation as resolved.
Json geom = s;
geom.erase("arealight");
Shape *geometry = parse_shape(geom);
geometries.emplace_back(geometry);
bsdfs.emplace_back(geometry->get_bsdf());

if(s.contains("arealight")){
const Json al_child = get_unique_first_elem(s, "arealight");
radiances.emplace_back(parse_vector3f(al_child, "radiance"));
} else {
radiances.emplace_back(Vector3f{-1.0f, -1.0f, -1.0f});
}
}

for(const auto &inst : instance_list){
const Matrix44f to_world = parse_matrix44f(inst, "to_world");
for(std::size_t i = 0; i < geometries.size(); ++i){
AreaLight *al = nullptr;
if(radiances[i][0] >= 0.0f){
al = AreaLight::Create(radiances[i]);
}
out.emplace_back(Shape::Create<Instance>(geometries[i], to_world, bsdfs[i], al));
}
}
}

std::vector<Light*> SceneParser::parse_lights() const{
std::vector<Light*> lights;

Expand Down Expand Up @@ -212,6 +262,45 @@ namespace Caramel{
parse_bsdf(shape_json));
}
}
else if(type=="trianglemesh"){
const Json Pj = get_unique_first_elem(shape_json, "P");
const Json Ij = get_unique_first_elem(shape_json, "indices");
if(!Pj.is_array() || Pj.size() % 3 != 0){
CRM_ERROR("trianglemesh 'P' must be a flat array of 3*N numbers");
}
if(!Ij.is_array() || Ij.size() % 3 != 0){
CRM_ERROR("trianglemesh 'indices' must be a flat array of 3*M integers");
}
std::vector<Vector3f> positions;
for(std::size_t i = 0; i + 2 < Pj.size(); i += 3){
positions.push_back(Vector3f{static_cast<Float>(Pj[i]), static_cast<Float>(Pj[i+1]), static_cast<Float>(Pj[i+2])});
}
std::vector<Vector3i> indices;
for(std::size_t i = 0; i + 2 < Ij.size(); i += 3){
indices.push_back(Vector3i{static_cast<Int>(Ij[i]), static_cast<Int>(Ij[i+1]), static_cast<Int>(Ij[i+2])});
}
Comment thread
pjessesco marked this conversation as resolved.
Comment thread
pjessesco marked this conversation as resolved.
for(const auto &t : indices){
for(int k = 0; k < 3; ++k){
if(t[k] < 0 || static_cast<std::size_t>(t[k]) >= positions.size()){
CRM_ERROR("trianglemesh index out of range");
}
}
}
std::vector<Vector3f> normals;
if(shape_json.contains("N")){
const Json Nj = get_unique_first_elem(shape_json, "N");
if(!Nj.is_array() || Nj.size() != Pj.size()){
CRM_ERROR("trianglemesh 'N' must be a flat per-vertex array matching 'P' length");
}
for(std::size_t i = 0; i + 2 < Nj.size(); i += 3){
normals.push_back(Vector3f{static_cast<Float>(Nj[i]), static_cast<Float>(Nj[i+1]), static_cast<Float>(Nj[i+2])});
}
}
return Shape::Create<InlineTriangleMesh>(positions, indices, normals,
parse_bsdf(shape_json),
shape_json.contains("arealight") ? parse_arealight(shape_json) : nullptr,
shape_json.contains("to_world") ? parse_matrix44f(shape_json, "to_world") : Matrix44f::identity());
}

CRM_ERROR("Unsupported shape type : " + type);
return nullptr;
Expand Down
57 changes: 57 additions & 0 deletions src/shapes/inline_triangle_mesh.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// This software is released under the MIT license.
//
// Copyright (c) 2022-2026 Jino Park
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//

#include <utility>

#include <shape.h>

#include <mesh_accel.h>
#include <transform.h>

namespace Caramel {
InlineTriangleMesh::InlineTriangleMesh(std::vector<Vector3f> positions,
std::vector<Vector3i> indices,
std::vector<Vector3f> normals,
BSDF *bsdf, AreaLight *arealight,
const Matrix44f &transform)
: TriangleMesh(bsdf, arealight) {
is_vn_exists = !normals.empty();

m_vertices.reserve(positions.size());
for (const auto &p : positions) {
m_vertices.emplace_back(transform_point(p, transform));
}

if (is_vn_exists) {
m_normals.reserve(normals.size());
for (const auto &n : normals) {
m_normals.emplace_back(transform_normal(n, transform));
}
}

m_face_indices = std::move(indices);

finalize(arealight, "trianglemesh");
}
}
Loading
Loading