Isovist Challenge

I’d like to pose a challenge to the Dynamo community:

Using only native dynamo nodes, create an “isovist” function that accepts the following inputs:

  • a Point representing the viewpoint
  • a list of 2d curves representing any possible obstructions
  • a number N representing the number of sample points
  • a number R representing the maximum vision radius

and returns a polycurve by points representing the visible polygon from the viewpoint:

The resulting polygon should terminate at the circle of the maximum radius if it doesn’t hit any obstructions before that:

I have solved this for myself, but I am eager to see how others might approach it more cleanly / elegantly. My approach
required quite a bit of list@level trickery + lacing etc. I’m happy to share my script once we have a diversity of solutions :slight_smile: I’d like to privilege node-based solutions over designscript-based ones - but if someone has a particularly beautiful way of accomplishing this with DS that would be great to see as well.

My ulterior motive here is that I will be teaching a course on dynamo list management at AU UK - I’d like to use this particular problem as a case study in the kinds of list management issues that arise in the course of building more sophisticated scripts. Anyone whose solution I use in my lab will of course get a shout out!

So - gauntlet thrown :slight_smile: show me your stuff Dynamaniacs!

4 Likes

Nice challenge. Can you define the following:

  1. What determines the direction of the vision radius (its bisector, I suppose)?
  2. You show an exterior rectangle; I take it these elements are the simply part of the list of 2D curves, or do you always assume the view is contained, i.e. are the external bounds a constant and not part of the 2D curve input?
  3. Non-linear obstructions or only linear?
  4. You mention sample points; what if the solution doesn’t need them…still a requirement?
  1. vision is calculated in a complete circle around the viewpoint in the XY plane
  2. the exterior rectangle is just one of the obstructions - it should work without
  3. any 2d curve is an acceptable obstruction
  4. sample points are optional I suppose; however, I’ve never seen a version of this algorithm that didn’t perform some kind of sampling.

Thanks, and can you post a screenshot showing a bspline obstruction or even a circle (basically any obstruction that turns in on itself) to gauge the flexibility of what you’ve done (and to set the bar) :wink:

1 Like

1 Like

@Andrew_Heumann Why anyone would attempt this using visual programming is beyond me, nevertheless, here’s a simple method using DS:


def IntersectSightLines(ptSight:var, sightLines:var[], testCurves:var[])
{
	return = [Imperative]
	{
		ptOUT = {};
		for (i in GetKeys(sightLines))
		{
			sLine = sightLines[i];
			ptIntersect = Flatten(sLine.Intersect(testCurves));
			intersectCount = List.Count(Manage.RemoveNulls(ptIntersect)["Cleaned"]);

			if (intersectCount == 0)
			{
				ptOUT[i] = sLine.EndPoint;
			}
			else
			{
				distance = ptSight.DistanceTo(ptIntersect);
 				//ClosestPointTo appears to have a bug so this is a workaround;
				ptClosest = List.SortByKey(ptIntersect, distance)["sorted list"][0];
				ptOUT[i] = ptClosest;
			}
		}
		pcrv = PolyCurve.ByPoints(ptOUT, true);
		return = Surface.ByPatch(pcrv);
	}
};
2 Likes

The point is less about accomplishing the specific task at hand - this problem is representative of the kinds of complexity that arise in visual programming day-to-day, and it’s an easy one to describe. My intent is to use this problem to explore various strategies of list management in the dynamo environment. It really shouldn’t be a crazy thing to tackle in visual programming. In Grasshopper it’s not even that complicated to set up:


(and that’s without cheating, like this:)

Of course, writing a script is absolutely a valid way to address situations of list management complexity - so thanks for contributing your solution!

1 Like

I guess it depends on which side of the fence you sit; coding ultimately trumps visual programming but its a catch-22 - what I like to call the “coding-paradox” - where problems, especially complex ones or ones dealing with complex data structures, get a lot easier when you code…the problem is, you need to know how to code! Interesting idea and GH solution!

1 Like

@Andrew_Heumann so there are some differences in how Grasshopper handles data trees vs. how Dynamo handles lists, but for the most part one can achieve the same result with either. The only major differences usually happen when Geometry gets involved and Dynamo just plain sucks at most of the geometrical operations. Anyways, here’s the same thing with just OOTB nodes.

Now, List.Map makes a comeback here since Dynamo’s List.Flatten node only works on Levels from Left>Right while Shift Paths in Grasshopper works in the opposite directions pruning branches from the outside>in.

Other than that I had to “clean” the list from Null values as Dynamo’s Polycurve would not take a null, unlike Grasshopper which does so pretty easily. Feeding this into Grasshopper Polyline is fine:

While Dynamo would crap itself when met with a null value so I had to remove them:

These are two ONLY differences in the first approach that you demonstrated and as you can see they are more of “Dynamo is sensitive to nulls” nature than anything else.

Here’s the whole thing:

Result:

Ps. Adding PruneDuplicates also removes extra points for better result:

2 Likes

We shouldn’t need that much null removal and pruining. Here’s my nodal take on this:

4 Likes

isovist.dyn (16.3 KB)

3 Likes

Design Script equivalent of the above node based approach

cir = Circle.ByCenterPointRadius(viewpoint,visionRadius);
dir = Vector.ByTwoPoints(viewpoint,cir.PointAtParameter(0..1..0.001));
pn1 = Flatten(Point.Project(viewpoint,Flatten({cir,obstructions})<2>,dir<1>)<1>);
pn2 = List.SortByKey(pn1<1>,pn1.DistanceTo(viewpoint)<1>);
pn3 = List.FirstItem(List.GetItemAtIndex(pn2<1>,0)<1>);
isv = PolyCurve.ByPoints(pn3).Patch();
2 Likes

well done mate!

3 Likes

Nice job all! Appreciate the cleverness and range of approaches.
@Zach_Kron’s is closest to my approach - I did almost the same except I sorted by distance to the center rather than along the parameter value:

for completeness, my node-to-code based DS solution:

NOW my question is this. What are the options if one wants to take multiple isovists from multiple viewpoints? So far I can see how to do this with a DS function like the above, passing it a list of points for the viewpoint argument - or with List.Map on a custom node. Is there a way that leverages only List@Level / Lacing as the management mechanism? I was unable to figure one out.

1 Like

@Andrew_Heumann Would this work for you?

isovists.dyn (19.7 KB)

3 Likes

ah, very nice! that preserve list nesting is key. Thanks for the example!

1 Like

@Everyone this is pretty cool! What are the possible applications/workflows? lighting analysis? what else?

1 Like

Accessibility (size and height of access openings, pathways, corridors, etc) and way-finding are another possibility, tho you might need to make things 3d first.

@Andrew_Heumann another interesting extension of your challenge would be to implement an optimisation algorithm, like a goal-seek (or even ML if appropriate) so a really coarse sample rate could be used to start, and increase the rate only where hits are found. It would mean that the effective rate of sampling could be increased exponentially, without any noticeable impact on performance compared to evenly distributed sampling.

DS multiple vistas :+1::

def IntersectSightLines(ptSight:var[], testCurves:var[], visionRadius:double, sampleRate:int)
{
	sampleRate = (1/sampleRate);
	return = [Imperative]
	{
		srfOUT = {};
		for (pt in GetKeys(ptSight))
		{
			ptEye = ptSight[pt];
			cirVisionRange = Circle.ByCenterPointRadius(ptEye, visionRadius);
			ptSample = cirVisionRange.PointAtParameter(sampleRate..1.0..sampleRate);
			sightLines = Line.ByStartPointEndPoint(ptEye, ptSample);

			ptClosest = {};
			for (i in GetKeys(sightLines))
			{
				sLine = sightLines[i];
				ptIntersect = Flatten(sLine.Intersect(testCurves));
				intersectCount = List.Count(Manage.RemoveNulls(ptIntersect)["Cleaned"]);

				if (intersectCount == 0)
				{
					ptClosest[i] = sLine.EndPoint;
				}
				else
				{
					distance = ptEye.DistanceTo(ptIntersect);
	 				//ClosestPointTo appears to have a bug so this is a workaround;
					ptClosest[i] = List.SortByKey(ptIntersect, distance)["sorted list"][0];
				}
			}
			pcrv = PolyCurve.ByPoints(ptClosest, true);
			srfOUT[pt] = pcrv;
		}
		return = srfOUT;
	}
};