diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index b39cee3b..33951c78 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -159,7 +159,7 @@ namespace mplot } // Compute the triangle normal for the ordered triplet of triangle vertices, tverts - sm::vec triangle_normal (const sm::vec, 3>& tverts) + sm::vec triangle_normal (const sm::vec, 3>& tverts) const { sm::vec n = (tverts[1] - tverts[0]).cross (tverts[2] - tverts[0]); n.renormalize(); @@ -317,10 +317,14 @@ namespace mplot * model. The length of vdir is used to avoid finding the intersection at the 'back' of the * model. * + * \param ti_ml The most likely triangle, if you know what it probably is, to reduce the + * search time. + * * \return a tuple containing crossing location, triangle identity (three indices) and triangle normal vector */ std::tuple, std::array, sm::vec> - find_triangle_crossing (const sm::vec& coord_mf, const sm::vec& vdir) const + find_triangle_crossing (const sm::vec& coord_mf, const sm::vec& vdir, + const std::array ti_ml = {std::numeric_limits::max()}) const { constexpr auto umax = std::numeric_limits::max(); constexpr auto fmax = std::numeric_limits::max(); @@ -333,6 +337,30 @@ namespace mplot auto isect_d = std::numeric_limits::max(); // distance to intersect + const auto vdsos = vdir.sos(); + + // Have we been passed a 'most likely triangle' to test first? If so, test it. + if (ti_ml[0] != std::numeric_limits::max()) { + sm::vec v0 = this->vertex[ti_ml[0]]; + sm::vec v1 = this->vertex[ti_ml[1]]; + sm::vec v2 = this->vertex[ti_ml[2]]; + auto [isect, p] = sm::geometry::ray_tri_intersection (v0, v1, v2, vstart, vdir); + if (isect) { + float d = (p - vstart).sos(); + if (d < vdsos) { + sm::vec, 3> tverts = { v0, v1, v2 }; + isect_p = p; + isect_ti = ti_ml; + isect_tn = this->triangle_normal (tverts); // compute tn + isect_d = d; + } + } + } + if (isect_d != std::numeric_limits::max()) { + // we found it already! + return { isect_p, isect_ti, isect_tn }; + } + for (auto tri : this->triangles) { auto [ti, tn, tnc, tnd] = tri; auto [isect, p] = sm::geometry::ray_tri_intersection (this->vertex[ti[0]], this->vertex[ti[1]], this->vertex[ti[2]], vstart, vdir); @@ -341,7 +369,7 @@ namespace mplot // closest one that isn't. if (isect) { float d = (p - vstart).sos(); - if (d < isect_d && d < vdir.sos()) { + if (d < isect_d && d < vdsos) { isect_p = p; isect_ti = ti; isect_tn = tn; @@ -350,8 +378,9 @@ namespace mplot } } - if (isect_p[0] == fmax) { - // Found no triangle intersection; check vertices, in case vdir points perfectly at a vertex + if (isect_p[0] == fmax && this->vertex.size() < 10000) { + // Found no triangle intersection; check vertices, in case vdir points perfectly at a vertex. + // This can be computationally expensive, hence the hacky check, above. for (uint32_t ti = 0; ti < this->vertex.size(); ++ti) { sm::vec vertex_n = this->find_vertex_normal (ti); // also loops vertex_n.renormalize(); @@ -805,17 +834,22 @@ namespace mplot * large, flat, one-sided landscape, we want to make vdir long. search_dist_mult is applied * to vdir. * + * \param ti_ml The most likely triangle, if you know what it probably is, to reduce the + * search time. + * * \return tuple containing: the hit point in scene coordinates; the triangle normal of the * triangle we hit; and the indices of the triangle we hit. */ std::tuple, sm::vec, std::array> find_triangle_hit (const sm::mat44& model_to_scene, - const sm::vec& camloc_mf, const sm::vec& vdir) + const sm::vec& camloc_mf, const sm::vec& vdir, + const std::array ti_ml = {std::numeric_limits::max()}) { this->ti0 = {}; this->tn0 = {}; sm::vec hit = {}; - std::tie (hit, this->ti0, this->tn0) = this->find_triangle_crossing (camloc_mf, vdir); + // Want to pass 'best tri' to this + std::tie (hit, this->ti0, this->tn0) = this->find_triangle_crossing (camloc_mf, vdir, ti_ml); if (this->ti0[0] == std::numeric_limits::max()) { std::cout << __func__ << ": No hit\n"; } diff --git a/mplot/compoundray/EyeVisual.h b/mplot/compoundray/EyeVisual.h index 63f29889..1b3fc42d 100644 --- a/mplot/compoundray/EyeVisual.h +++ b/mplot/compoundray/EyeVisual.h @@ -107,7 +107,7 @@ namespace mplot::compoundray if (show_3d) { // Replace colours for the 3D part of the model int num_vertices = disc_vertices; - if (this->show_cones == true) { + if (this->show_cones == true && this->cones_will_show) { num_vertices = cone_vertices + disc_vertices; } // else num_vertices = disc_vertices; @@ -280,19 +280,21 @@ namespace mplot::compoundray float ray_radius = ommrng.span().max() / 500.0f; // Find mean minimum ommatidial distance - sm::vvec dist_to_other (n_omm, 0.0f); - sm::vvec min_dist_to_other (n_omm, 0.0f); - for (size_t i = 0u; i < n_omm; ++i) { - for (size_t j = 0u; j < n_omm; ++j) { - if (i == j) { - dist_to_other[j] = 10000.0f; - } else { - dist_to_other[j] = ((*ommatidia)[i].relativePosition - (*ommatidia)[j].relativePosition).length(); + if (this->min_dist_to_other.empty()) { + sm::vvec dist_to_other (n_omm, 0.0f); + min_dist_to_other.resize (n_omm, 0.0f); + for (size_t i = 0u; i < n_omm; ++i) { + for (size_t j = 0u; j < n_omm; ++j) { + if (i == j) { + dist_to_other[j] = 10000.0f; + } else { + dist_to_other[j] = ((*ommatidia)[i].relativePosition - (*ommatidia)[j].relativePosition).length(); + } } + min_dist_to_other[i] = dist_to_other.min(); } - min_dist_to_other[i] = dist_to_other.min(); } - std::cerr << "Mean ommatidial distance: " << min_dist_to_other.mean() << std::endl; + std::cerr << "Mean ommatidial distance: " << this->min_dist_to_other.mean() << std::endl; // First find out if all focal points are 0 this->focal_point_sum = 0.0f; @@ -301,6 +303,7 @@ namespace mplot::compoundray } if (show_3d && this->focal_point_sum > 0.0f) { + std::cout << "Stanza 1\n"; // We have focal points, so draw with the relativePosition representing the centre // of the ommatidial lens - the base of a cone - which then extends back to the cone // tip, which can be thought of as the location of the ommatidial 'sensor' @@ -316,7 +319,7 @@ namespace mplot::compoundray sm::vec ommatidial_detector_point = pos - dir * focal_point; // work out a radius from acceptance angle and focal_point // The discs are based on the inter-ommatidial distances in space, which have to have been computed - float dw = min_dist_to_other[i]; + float dw = this->min_dist_to_other[i]; this->computeTube (pos, pos + (0.05f * dw * dir), colour, colour, dw * 0.5f, tube_faces); // This visualizes the optical cones @@ -324,6 +327,8 @@ namespace mplot::compoundray float optical_radius = focal_point * std::tan (angle / 2.0f); // Colour comes from ommData. ringoffset is 1.0f this->computeCone (pos, ommatidial_detector_point, 0.0f, colour, optical_radius, tube_faces); + this->cones_will_show = true; + } else if (this->show_fov == true) { // do a cone of angle 'acceptanceAngle' using user-supplied cone_length, starting FROM the disc to show field of view of the eye sm::vec ommatidial_cone_pos = pos + dir * this->cone_length; @@ -331,7 +336,15 @@ namespace mplot::compoundray this->computeCone (ommatidial_cone_pos, pos, 0.0f, colour, radius, tube_faces); } } + + if ((this->show_cones == true || this->show_fov == true) && n_omm > 0) { + this->cones_will_show = true; + } else { + this->cones_will_show = false; + } + } else if (show_3d && this->focal_point_sum <= 0.0f) { + std::cout << "Stanza 2\n"; // All our focal_points are 0. Don't have focal point offset to help define our // cones, only acceptance angle. Use manually specified tube_length (or computed // radius) to figure out the size of a cone, whose tip is the location of the @@ -344,7 +357,7 @@ namespace mplot::compoundray sm::vec dir = (*ommatidia)[i].relativeDirection; dir.renormalize(); - float dw = min_dist_to_other[i]; + float dw = this->min_dist_to_other[i]; this->computeTube (pos, pos + (0.05f * dw * dir), colour, colour, dw * 0.5f, tube_faces); // We don't have a focal length to show cones, but we can still show the acceptance angle if (this->show_fov == true) { @@ -354,6 +367,15 @@ namespace mplot::compoundray this->computeCone (ommatidial_cone_pos, pos, 0.0f, colour, radius, tube_faces); } } + + if (this->show_fov == true && n_omm > 0) { + this->cones_will_show = true; + } else { + this->cones_will_show = false; + } + + } else { + this->cones_will_show = false; } // 2D projections @@ -525,10 +547,14 @@ namespace mplot::compoundray // If true, show 'field of view' cones. Overrides show_cones. Control size of cones with // this->cone_length bool show_fov = false; + // either show_cones or show_fov are enabled and cones have been drawn + bool cones_will_show = false; // The colours detected by each ommatidium std::vector>* ommData = nullptr; // The position and orientation of each ommatidium std::vector* ommatidia = nullptr; + // Distances to the nearest ommatidia, for choosing disc size. Computed once only + sm::vvec min_dist_to_other = {}; // An optional head mesh const mplot::meshgroup* head_mesh = nullptr; // If sum is 0, then we have a special case for rendering the eye as we have no focal point