# Is there a way to interactively read a point from a Graphics3D?

Suppose I have a tesselation, e.g. the Stanford bunny:

bunny = Import["http://graphics.stanford.edu/~mdfisher/Data/Meshes/bunny.obj", "OBJ"]


I'd like to be able to click on one of the vertices of the tessellation and either get its coordinates, or even better, its index in the GraphicsComplex.

Is there a way to do this?

-

The following works pretty well, I think. The problem is choosing the point that is nearest the mouse click. MousePosition["Graphics3DBoxIntercepts"] returns the coordinates of the two points of the bounding box below the mouse click, which I think of as determining a line. The function projCoords converts the points in the bunny to "coordinates" that are the distance from the line determined by these points divided by 10 and the distance of the projection of the vector from the front mouse-click intercept to the point on the bunny. The "divide by 10" factor makes the distance from the line dominant. Sometimes a point behind the surface may be chosen, especially in non-convex regions where there are not many points in front that are close to the line. If 10 is made much smaller, then a point close to the front box intercept but far from the line may be chosen.

The point and the index in the GraphicsComplex are shown in the plot label.

bunny = Import["http://graphics.stanford.edu/~mdfisher/Data/Meshes/bunny.obj", "OBJ"];

projCoords[{v1_, v2_}] := (* #/10 emphasizes distance from line of sight *)
Function[{u}, Norm /@ ({#/10, u - v1 - #} &@Projection[u - v1, v2 - v1])]

DynamicModule[{img = bunny, pts0, clicked = {}, mp = {}},
Cases[img, GraphicsComplex[pts_, ___] :> (pts0 = pts), Infinity, 1];
EventHandler[
MouseAppearance[
Show[img,
Graphics3D[{Red, PointSize[Large],
Dynamic@If[VectorQ[clicked, NumericQ],
{Point[clicked], Blue, Line[mp]},
{}]}],
Boxed -> True, Axes -> True, AxesLabel -> {x, y, z},
PlotLabel -> Dynamic[{clicked, Position[pts0, clicked]}],
AbsoluteOptions[img, PlotRange], SphericalRegion -> True],
"Arrow"],
{"MouseClicked" :> ({clicked} =
Nearest[(projCoords[mp = MousePosition["Graphics3DBoxIntercepts"]] /@
pts0) -> pts0, {0, 0}])}, PassEventsDown -> True]
]


The option PassEventsDown -> True allows the bunny to be rotated.

Edit: Added PlotRange control and a MouseAppearance that makes it easier to pinpoint a vertex.

-
This approach is similar to what I was trying (and so far failing) to do, so I'm going to accept it. I intend to enhance it so that only vertices with face normals pointing toward the eye point are considered. Thanks for the help! –  Codie CodeMonkey Jul 3 '13 at 14:14

Perhaps you can use something like this: I took the points in the GraphisComplex and made an additional set of Points representing the vertices in Yellow. The points are extracted and then put back by means of a replacement rule:

data = ExampleData[{"Geometry3D", "StanfordBunny"},
"VertexData"];
bunny =
ListSurfacePlot3D[data, MaxPlotPoints -> 50, Boxed -> False,
Axes -> None, Mesh -> False, PlotStyle -> Brown];

Print@Show[
bunny /. g_GraphicsComplex :>
GraphicsComplex[g[[1]],
Join[g[[2]], {Opacity[0.5], Yellow, PointSize[.01],
Tooltip[Point[#], #] & /@ Range[Length[g[[1]]]]}]],
ImageSize -> 500
]


You can still use the mouse to drag and rotate the object, but when you hover over a vertex (which sticks out of the surface as a yellow object), the index of that point in the GraphicsComplex is displayed as a Tooltip. I thought this is better than making the vertices clickable because that interferes with the 3D rotation.

The extra Print in front of the Show is needed in order to suppress a warning triggered by the large size of the Graphics3D output.

Edit

If you want something that respons to mouse clicks in a useful way, it's straightforward to combine the above approach with another answer where I showed a way to convert Tooltip into Button objects. If you then use the toolSpoolRule from that answer, you can get a permanent record of the vertices that you have clicked, in the form of a List on the Clipboard which can be pasted anywhere you want.

Here I repeat the only additional definition we need:

toolSpoolRule =
Tooltip[t__] :>
Button[Tooltip[t],
CopyToClipboard[
First@Append[
Cases[NotebookGet[ClipboardNotebook[]],
TooltipBox[x_, "\"Clicked Points\"", ___] :>
Tooltip[Append[ToExpression[x], Last[{t}]],
"Clicked Points"], Infinity],
Tooltip[{Last[{t}]}, "Clicked Points"]]]];


Now replace the plot command by the following:

Print[
Show[bunny /.
g_GraphicsComplex :>
GraphicsComplex[g[[1]],
Join[g[[2]], {Opacity[0.5], Yellow, PointSize[.01],
Tooltip[Point[#], #] & /@ Range[Length[g[[1]]]]}]],
ImageSize -> 500] /. toolSpoolRule
]


You get visually the same output, but when you see a Tooltip appearing near a vertex that you hover on, you can now click the mouse to add the vertex index to the clipboard. When you're done, press the paste keys and you get a list of the clicked vertices.

-
Thanks for this! I'm not clear on why clicking on a vertex interferes with rotation, but clicking on one of the added yellow points does, could you clarify? –  Codie CodeMonkey Jul 3 '13 at 12:11
I've had some more time to pick through this, lots of good stuff! –  Codie CodeMonkey Jul 3 '13 at 14:19
I guess the clicking doesn't interfere with the rotation in practice - that was just my initial concern before I edited the question: if you try to rotate but instead catch a vertex marker, then you can't drag after the mouse is down. You have to avoid the vertices in order to rotate. –  Jens Jul 3 '13 at 15:40