Skip to content

Commit

Permalink
fix(geometry): Add context_block and receiver options to ray tracing
Browse files Browse the repository at this point in the history
  • Loading branch information
chriswmackey authored and Chris Mackey committed Jun 7, 2024
1 parent 4da04cc commit 289d881
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 11 deletions.
27 changes: 24 additions & 3 deletions ladybug_grasshopper/json/LB_Surface_Ray_Tracing.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "1.8.0",
"version": "1.8.1",
"nickname": "SrfRayTrace",
"outputs": [
[
Expand Down Expand Up @@ -37,7 +37,14 @@
{
"access": "list",
"name": "context_",
"description": "Breps or meshes for conext geometry, which will reflect the sun rays\nafter they bounce off of the _source_geo.",
"description": "Breps or meshes for conext geometry, which will reflect the sun rays\nafter they bounce off of the _source_geo. They can also block the\nsun rays to the _source_geo if context_block_ is set to True.",
"type": "GeometryBase",
"default": null
},
{
"access": "list",
"name": "receiver_",
"description": "Optional breps or meshes for receiver geometry for which sun ray\nintersection is being studied. If specified, only sun rays that\nreflect off of the _source_geo and have their last bounce intersect\nthis receiver within the _last_length_ distance will appear in\nthe result. This can help filter results to only an area of\nintersect where the impact of reflected sun rays is a concern.",
"type": "GeometryBase",
"default": null
},
Expand Down Expand Up @@ -68,10 +75,24 @@
"description": "A positive number in Rhino model units representing the length\nof the sun ray after the last bounce. If unspecified, this will be\nthe diagonal of the bounding box surrounding all input geometries.",
"type": "double",
"default": null
},
{
"access": "item",
"name": "context_block_",
"description": "Set to \"True\" to count the input _context as something that fully\nblocks rays as opposed to having the rays simply bounce off of it.\nThis can be particularly useful in cases with lots of rays where\nyou are only concered about the rays that can actually hit the\n_source_geo. (Default: False).",
"type": "bool",
"default": null
},
{
"access": "item",
"name": "_run",
"description": "Set to \"True\" to run the component and perform the ray tracing study.",
"type": "bool",
"default": null
}
],
"subcategory": "3 :: Analyze Geometry",
"code": "\nimport math\n\ntry:\n from ladybug_geometry.geometry3d.ray import Ray3D\n from ladybug_geometry.geometry3d.polyline import Polyline3D\nexcept ImportError as e:\n raise ImportError('\\nFailed to import ladybug_geometry:\\n\\t{}'.format(e))\n\ntry:\n from ladybug_{{cad}}.togeometry import to_joined_gridded_mesh3d, to_point3d, \\\n to_vector3d\n from ladybug_{{cad}}.fromgeometry import from_point3d, from_vector3d, from_ray3d, \\\n from_polyline3d\n from ladybug_{{cad}}.intersect import join_geometry_to_brep, bounding_box_extents, \\\n trace_ray, normal_at_point\n from ladybug_{{cad}}.{{plugin}} import all_required_inputs, list_to_data_tree, \\\n hide_output\nexcept ImportError as e:\n raise ImportError('\\nFailed to import ladybug_{{cad}}:\\n\\t{}'.format(e))\n\n\nif all_required_inputs(ghenv.Component):\n # check the _bounce_count_\n _bounce_count_ = 0 if _bounce_count_ is None else _bounce_count_ - 1\n assert _bounce_count_ >= 0, 'The input _bounce_count_ must be greater ' \\\n 'than zero. Got {}.'.format(_bounce_count_ + 1)\n # process the input sun vector\n lb_vec = to_vector3d(_vector).normalize()\n neg_lb_vec = -lb_vec\n vec = from_vector3d(lb_vec)\n\n # convert all of the _source_geo and contex into a single Brep for ray tracing\n rtrace_brep = join_geometry_to_brep(_source_geo + context_)\n\n # autocompute the first and last bounce length if it's unspecified\n if _first_length_ is None or _last_length_ is None:\n max_pt, min_pt = (to_point3d(p) for p in bounding_box_extents(rtrace_brep))\n diag_dist = max_pt.distance_to_point(min_pt)\n _first_length_ = diag_dist if _first_length_ is None else _first_length_\n _last_length_ = diag_dist if _last_length_ is None else _last_length_\n\n # create the gridded mesh from the _source_geo and set up the starting rays\n study_mesh = to_joined_gridded_mesh3d(_source_geo, _grid_size)\n move_vec = neg_lb_vec * _first_length_\n source_points = [pt + move_vec for pt in study_mesh.face_centroids]\n lb_rays = [Ray3D(pt, lb_vec) for pt in source_points]\n start_rays = [from_ray3d(ray) for ray in lb_rays]\n\n # trace each ray through the geometry\n cutoff_ang = math.pi / 2\n rtrace_geo = [rtrace_brep]\n rays, int_pts = [], []\n for ray, pt, norm in zip(start_rays, source_points, study_mesh.face_normals):\n if norm.angle(neg_lb_vec) < cutoff_ang:\n pl_pts = trace_ray(ray, rtrace_geo, _bounce_count_ + 2)\n # if the intersection was successful, create a polyline represeting the ray\n if pl_pts:\n # gather all of the intersection points\n all_pts = [pt]\n for i_pt in pl_pts:\n all_pts.append(to_point3d(i_pt))\n # compute the last point\n if len(pl_pts) < _bounce_count_ + 2:\n int_norm = normal_at_point(rtrace_brep, pl_pts[-1])\n int_norm = to_vector3d(int_norm)\n last_vec = all_pts[-2] - all_pts[-1]\n last_vec = last_vec.normalize()\n final_vec = last_vec.reflect(int_norm).reverse()\n final_pt = all_pts[-1] + (final_vec * _last_length_)\n all_pts.append(final_pt)\n # create a Polyline3D from the points\n lb_ray_line = Polyline3D(all_pts)\n rays.append(from_polyline3d(lb_ray_line))\n int_pts.append([from_point3d(p) for p in all_pts])\n\n # convert the intersection points to a data tree\n int_pts = list_to_data_tree(int_pts)\n hide_output(ghenv.Component, 1)\n",
"code": "\nimport math\n\ntry:\n from ladybug_geometry.geometry3d.ray import Ray3D\n from ladybug_geometry.geometry3d.polyline import Polyline3D\nexcept ImportError as e:\n raise ImportError('\\nFailed to import ladybug_geometry:\\n\\t{}'.format(e))\n\ntry:\n from ladybug_{{cad}}.config import tolerance\n from ladybug_{{cad}}.togeometry import to_joined_gridded_mesh3d, to_point3d, \\\n to_vector3d\n from ladybug_{{cad}}.fromgeometry import from_point3d, from_vector3d, from_ray3d, \\\n from_polyline3d\n from ladybug_{{cad}}.intersect import join_geometry_to_brep, join_geometry_to_mesh, \\\n intersect_mesh_rays, bounding_box_extents, trace_ray, normal_at_point, \\\n intersect_mesh_rays_distance\n from ladybug_{{cad}}.{{plugin}} import all_required_inputs, list_to_data_tree, \\\n hide_output, show_output\nexcept ImportError as e:\n raise ImportError('\\nFailed to import ladybug_{{cad}}:\\n\\t{}'.format(e))\n\n\nif all_required_inputs(ghenv.Component) and _run:\n # check the _bounce_count_\n _bounce_count_ = 0 if _bounce_count_ is None else _bounce_count_ - 1\n assert _bounce_count_ >= 0, 'The input _bounce_count_ must be greater ' \\\n 'than zero. Got {}.'.format(_bounce_count_ + 1)\n # process the input sun vector\n lb_vec = to_vector3d(_vector).normalize()\n neg_lb_vec = -lb_vec\n vec = from_vector3d(lb_vec)\n\n # convert all of the _source_geo and contex into a single Brep for ray tracing\n rtrace_brep = join_geometry_to_brep(_source_geo + context_)\n\n # autocompute the first and last bounce length if it's unspecified\n if _first_length_ is None or _last_length_ is None:\n max_pt, min_pt = (to_point3d(p) for p in bounding_box_extents(rtrace_brep))\n diag_dist = max_pt.distance_to_point(min_pt)\n _first_length_ = diag_dist if _first_length_ is None else _first_length_\n _last_length_ = diag_dist if _last_length_ is None else _last_length_\n\n # create the gridded mesh from the _source_geo and set up the starting rays\n study_mesh = to_joined_gridded_mesh3d(_source_geo, _grid_size)\n move_vec = neg_lb_vec * _first_length_\n source_points = [pt + move_vec for pt in study_mesh.face_centroids]\n source_normals = study_mesh.face_normals\n lb_rays = [Ray3D(pt, lb_vec) for pt in source_points]\n start_rays = [from_ray3d(ray) for ray in lb_rays]\n\n # if context_block_ is set to True, filter the source_points\n if context_block_:\n shade_mesh = join_geometry_to_mesh(context_)\n rev_vec = [from_vector3d(to_vector3d(_vector).reverse())]\n normals = [from_vector3d(vec) for vec in study_mesh.face_normals]\n points = [from_point3d(pt) for pt in study_mesh.face_centroids]\n int_matrix, angles = intersect_mesh_rays(\n shade_mesh, points, rev_vec, normals, cpu_count=1)\n new_start_rays, new_source_points, new_source_normals = [], [], []\n for ray, pt, norm, inter in zip(start_rays, source_points, source_normals, int_matrix):\n if inter[0] == 1:\n new_start_rays.append(ray)\n new_source_points.append(pt)\n new_source_normals.append(norm)\n start_rays, source_points, source_normals = \\\n new_start_rays, new_source_points, new_source_normals\n\n # trace each ray through the geometry\n cutoff_ang = math.pi / 2\n rtrace_geo = [rtrace_brep]\n rays, int_pts = [], []\n for ray, pt, norm in zip(start_rays, source_points, source_normals):\n if norm.angle(neg_lb_vec) < cutoff_ang:\n pl_pts = trace_ray(ray, rtrace_geo, _bounce_count_ + 2)\n # if the intersection was successful, create a polyline represeting the ray\n if pl_pts:\n # gather all of the intersection points\n all_pts = [pt]\n for i_pt in pl_pts:\n all_pts.append(to_point3d(i_pt))\n # compute the last point\n if len(pl_pts) < _bounce_count_ + 2:\n int_norm = normal_at_point(rtrace_brep, pl_pts[-1])\n int_norm = to_vector3d(int_norm)\n last_vec = all_pts[-2] - all_pts[-1]\n last_vec = last_vec.normalize()\n final_vec = last_vec.reflect(int_norm).reverse()\n final_pt = all_pts[-1] + (final_vec * _last_length_)\n all_pts.append(final_pt)\n # create a Polyline3D from the points\n lb_ray_line = Polyline3D(all_pts)\n rays.append(from_polyline3d(lb_ray_line))\n int_pts.append([from_point3d(p) for p in all_pts])\n\n # if a receiver is specified, filter the output rays for intersection\n if len(receiver_) != 0:\n rec_mesh = join_geometry_to_mesh(receiver_)\n new_rays, new_int_pts = [], []\n for pl_pts, sun_ray in zip(int_pts, rays):\n start_point = pl_pts[-2]\n end_vec3d = to_point3d(pl_pts[-1]) - to_point3d(pl_pts[-2])\n end_vec = from_vector3d(end_vec3d.normalize())\n dist_list = intersect_mesh_rays_distance(rec_mesh, start_point, [end_vec])\n if 0 < dist_list[0] <= end_vec3d.magnitude + tolerance:\n new_rays.append(sun_ray)\n new_int_pts.append(pl_pts)\n rays, int_pts = new_rays, new_int_pts\n\n # convert the intersection points to a data tree\n int_pts = list_to_data_tree(int_pts)\n hide_output(ghenv.Component, 2)\n",
"category": "Ladybug",
"name": "LB Surface Ray Tracing",
"description": "Get a ray tracing visualization of direct sunlight rays reflected off of _source_geo\nand subsequently bouncing through a set of context_ geometries.\n_\nExamples where this visualization could be useful include understading the\nreflection of light by a light shelf or testing to see whether a parabolic\nglass or metal building geometry might focus sunlight to dangerous levels at\ncertain times of the year.\n_\nNote that this component assumes that all sun light is reflected specularly\n(like a mirror) and, for more detailed raytracing analysis with diffuse\nscattering, the Honeybee Radiance components should be used.\n-"
Expand Down
64 changes: 56 additions & 8 deletions ladybug_grasshopper/src/LB Surface Ray Tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,14 @@
bounce. Lists of breps or meshes are also acceptable. These surfaces
will be used to generate the initial sun rays in a grid-like pattern.
context_: Breps or meshes for conext geometry, which will reflect the sun rays
after they bounce off of the _source_geo.
after they bounce off of the _source_geo. They can also block the
sun rays to the _source_geo if context_block_ is set to True.
receiver_: Optional breps or meshes for receiver geometry for which sun ray
intersection is being studied. If specified, only sun rays that
reflect off of the _source_geo and have their last bounce intersect
this receiver within the _last_length_ distance will appear in
the result. This can help filter results to only an area of
intersect where the impact of reflected sun rays is a concern.
_grid_size: A positive number in Rhino model units for the average distance between
sun ray points to generate along the _source_geo.
_bounce_count_: An positive integer for the number of ray bounces to trace
Expand All @@ -40,8 +47,15 @@
_last_length_: A positive number in Rhino model units representing the length
of the sun ray after the last bounce. If unspecified, this will be
the diagonal of the bounding box surrounding all input geometries.
context_block_: Set to "True" to count the input _context as something that fully
blocks rays as opposed to having the rays simply bounce off of it.
This can be particularly useful in cases with lots of rays where
you are only concered about the rays that can actually hit the
_source_geo. (Default: False).
_run: Set to "True" to run the component and perform the ray tracing study.
Returns:
report: Reports, errors, warnings, etc.
rays: A list of polylines representing the sun rays traced forward onto
the _source_geo and then through the context_.
int_pts: A data tree of intersection points one one branch for each of the
Expand All @@ -50,7 +64,7 @@

ghenv.Component.Name = 'LB Surface Ray Tracing'
ghenv.Component.NickName = 'SrfRayTrace'
ghenv.Component.Message = '1.8.0'
ghenv.Component.Message = '1.8.1'
ghenv.Component.Category = 'Ladybug'
ghenv.Component.SubCategory = '3 :: Analyze Geometry'
ghenv.Component.AdditionalHelpFromDocStrings = '3'
Expand All @@ -64,19 +78,21 @@
raise ImportError('\nFailed to import ladybug_geometry:\n\t{}'.format(e))

try:
from ladybug_rhino.config import tolerance
from ladybug_rhino.togeometry import to_joined_gridded_mesh3d, to_point3d, \
to_vector3d
from ladybug_rhino.fromgeometry import from_point3d, from_vector3d, from_ray3d, \
from_polyline3d
from ladybug_rhino.intersect import join_geometry_to_brep, bounding_box_extents, \
trace_ray, normal_at_point
from ladybug_rhino.intersect import join_geometry_to_brep, join_geometry_to_mesh, \
intersect_mesh_rays, bounding_box_extents, trace_ray, normal_at_point, \
intersect_mesh_rays_distance
from ladybug_rhino.grasshopper import all_required_inputs, list_to_data_tree, \
hide_output
hide_output, show_output
except ImportError as e:
raise ImportError('\nFailed to import ladybug_rhino:\n\t{}'.format(e))


if all_required_inputs(ghenv.Component):
if all_required_inputs(ghenv.Component) and _run:
# check the _bounce_count_
_bounce_count_ = 0 if _bounce_count_ is None else _bounce_count_ - 1
assert _bounce_count_ >= 0, 'The input _bounce_count_ must be greater ' \
Expand All @@ -100,14 +116,32 @@
study_mesh = to_joined_gridded_mesh3d(_source_geo, _grid_size)
move_vec = neg_lb_vec * _first_length_
source_points = [pt + move_vec for pt in study_mesh.face_centroids]
source_normals = study_mesh.face_normals
lb_rays = [Ray3D(pt, lb_vec) for pt in source_points]
start_rays = [from_ray3d(ray) for ray in lb_rays]

# if context_block_ is set to True, filter the source_points
if context_block_:
shade_mesh = join_geometry_to_mesh(context_)
rev_vec = [from_vector3d(to_vector3d(_vector).reverse())]
normals = [from_vector3d(vec) for vec in study_mesh.face_normals]
points = [from_point3d(pt) for pt in study_mesh.face_centroids]
int_matrix, angles = intersect_mesh_rays(
shade_mesh, points, rev_vec, normals, cpu_count=1)
new_start_rays, new_source_points, new_source_normals = [], [], []
for ray, pt, norm, inter in zip(start_rays, source_points, source_normals, int_matrix):
if inter[0] == 1:
new_start_rays.append(ray)
new_source_points.append(pt)
new_source_normals.append(norm)
start_rays, source_points, source_normals = \
new_start_rays, new_source_points, new_source_normals

# trace each ray through the geometry
cutoff_ang = math.pi / 2
rtrace_geo = [rtrace_brep]
rays, int_pts = [], []
for ray, pt, norm in zip(start_rays, source_points, study_mesh.face_normals):
for ray, pt, norm in zip(start_rays, source_points, source_normals):
if norm.angle(neg_lb_vec) < cutoff_ang:
pl_pts = trace_ray(ray, rtrace_geo, _bounce_count_ + 2)
# if the intersection was successful, create a polyline represeting the ray
Expand All @@ -130,6 +164,20 @@
rays.append(from_polyline3d(lb_ray_line))
int_pts.append([from_point3d(p) for p in all_pts])

# if a receiver is specified, filter the output rays for intersection
if len(receiver_) != 0:
rec_mesh = join_geometry_to_mesh(receiver_)
new_rays, new_int_pts = [], []
for pl_pts, sun_ray in zip(int_pts, rays):
start_point = pl_pts[-2]
end_vec3d = to_point3d(pl_pts[-1]) - to_point3d(pl_pts[-2])
end_vec = from_vector3d(end_vec3d.normalize())
dist_list = intersect_mesh_rays_distance(rec_mesh, start_point, [end_vec])
if 0 < dist_list[0] <= end_vec3d.magnitude + tolerance:
new_rays.append(sun_ray)
new_int_pts.append(pl_pts)
rays, int_pts = new_rays, new_int_pts

# convert the intersection points to a data tree
int_pts = list_to_data_tree(int_pts)
hide_output(ghenv.Component, 1)
hide_output(ghenv.Component, 2)
Binary file modified ladybug_grasshopper/user_objects/LB Surface Ray Tracing.ghuser
Binary file not shown.
Binary file modified samples/raytracing_study.gh
Binary file not shown.

0 comments on commit 289d881

Please sign in to comment.