18
$\begingroup$

There are lots of basic graphic-primitives in Mathematica, such as 2D- primitive: Line[], Circle[], Disk[] and 3D-primitive: Cylinder[], BSplineSurface[], Sphere[], Cuboid[],etc.

In general, they own the following basic usage if memory serves:

Graphics(*Graphics3D*)[
  {primitiveStyle1, primitive1[arg1],
   primitiveStyle2, primitive2[arg2]}]

Graphics(*Graphics3D*)[
  {{primitiveStyle1, primitive1[arg1],
    primitiveStyle2, primitive2[arg2]}}]

where, primitiveStyle is one/several of the member of Thick, Green and so on.

For this question, the MSE has owned two similar threads like:

For the first question, Simon Woods has given a good answer for 2D-case. However, the detail about the implementation for the 2D primitive was not described. In addtion, m_goldberg also given a solution with the help of the built-in primitive.


For instance, now I have to create a 3D primitive BezierSurface[] via the built-in BezierFunction[], I tried using UpValues[] as follows:

BezierSurface /: Graphics3D[BezierSurface[ctrlnets_]] :=
  ParametricPlot3D[
    BezierFunction[ctrlnets][u, v], {u, 0, 1}, {v, 0, 1}]

Now we have a test

cpts = Table[{i, j, RandomReal[{-1, 1}]}, {i, 5}, {j, 5}];
Graphics3D[BezierSurface[cpts]]

enter image description here

But, for Graphics3D[{BezierSurface[cpts]}], it failed:(. I tried to change the UpValues[] defintion:

BezierSurface /: Graphics3D[{BezierSurface[ctrlnets_]}] :=
  ParametricPlot3D[
    BezierFunction[ctrlnets][u, v], {u, 0, 1}, {v, 0, 1}]

which gives me TagSetDelayed::tagpos: error.

enter image description here

For the BSplineSurface[], which own many usages

(*basic usage 1*)
Graphics3D[BSplineSurface[cpts]]

(*basic usage 2*)
Graphics3D[{BSplineSurface[cpts]}]

(*adding style*)
Graphics3D[{Pink, EdgeForm[{Thick, Blue}], Specularity[White, 50], 
            BSplineSurface[cpts]}]

(*adding other primitives*)
Graphics3D[{PointSize[Medium], Blue, Map[Point, cpts], 
            Pink, BSplineSurface[cpts]}]

(*another usage*)
Graphics3D[{{Blue, Cylinder[]}, 
            {Red, Sphere[{0, 0, 2}]}}]

So my question is:

  • how to make user-defined 3D primitive BezierSurface[] works like built-in BSplineSurface[]?
$\endgroup$
  • 1
    $\begingroup$ What you call primitiveStyle is often referred to as a directive. $\endgroup$ – J. M. will be back soon Mar 30 '16 at 1:55
  • $\begingroup$ @J.M. Did you means Directive[g1,g2,...], represents a single graphics directive composed of the directives g1,g2,... ? $\endgroup$ – xyz Mar 30 '16 at 1:57
  • $\begingroup$ Yes, that is the symbol used to treat a group of directives as a single directive. :) $\endgroup$ – J. M. will be back soon Mar 30 '16 at 2:00
  • $\begingroup$ You could do something less fancy like, BezierSurface[ctrlnets_] := First@ParametricPlot3D[...]. $\endgroup$ – wxffles Mar 30 '16 at 2:02
  • 1
    $\begingroup$ @wxffles Yes, this is indeed a solution. However, BSplineSurface[cpts] just returns itself, while BezierSurface[cpts] returns a lot of 3D points group. $\endgroup$ – xyz Mar 30 '16 at 2:06
18
+500
$\begingroup$

If my answer for the 2D case lacks detail, it's because Typeset`MakeBoxes is an internal, undocumented function. That makes it hard to say anything authoritative about how it works. Essentially though, we are defining the custom primitive in such a way that the definition only applies during conversion of a graphics expression to boxes.

Here's a version of createPrimitive adapted for Graphics3D.

SetAttributes[createPrimitive3D, HoldAll]

createPrimitive3D[patt_, expr_] :=
 Typeset`MakeBoxes[p : patt, fmt_, Graphics3D] :=
  With[{e = expr}, Typeset`MakeBoxes[Interpretation[e, p], fmt, Graphics3D]]

You use it like SetDelayed, with a pattern as the first argument and the definition as the second:

createPrimitive3D[BezierSurface[ctrlnets_],
 FirstCase[
  ParametricPlot3D[BezierFunction[ctrlnets][u, v], {u, 0, 1}, {v, 0, 1}],
  _GraphicsComplex]]

BezierSurface does not evaluate...

cpts = Table[{i, j, RandomReal[{-1, 1}]}, {i, 3}, {j, 3}];    
BezierSurface[cpts]

(* BezierSurface[{{{1, 1, -0.559961}, {1, 2, -0.470345}, {1, 3, 0.271683}},
   {{2, 1, 0.855812}, {2, 2, -0.378208}, {2, 3, 0.476702}}, 
   {{3, 1, -0.106064}, {3, 2, -0.462438}, {3, 3, 0.269039}}}] *)

...except when displayed in Graphics3D

Graphics3D[BezierSurface[cpts]]

enter image description here

$\endgroup$
  • $\begingroup$ +1! Thanks very very much:) I tested the following cases:(1) Graphics3D[BezierSurface[cpts]], (2) Graphics3D[{BezierSurface[cpts]}], (3) Graphics3D[{BezierSurface[cpts], BSplineSurface[cpts]}], (4) Graphics3D[{Opacity[0.4], BezierSurface[cpts]}], (5) Graphics3D[{Specularity[White, 10], Red, BezierSurface[cpts]}]. And I discovered that the last case(5) failed. $\endgroup$ – xyz Jul 30 '16 at 14:53
  • $\begingroup$ In addtion, I also noticed that the built-in BSplineSurface[] didn't apply the ParametricPlot3D[] to sample the 3D points, please see here :) $\endgroup$ – xyz Jul 30 '16 at 14:59
  • $\begingroup$ The GraphicsComplex extracted from ParametricPlot3D contains its own colouring directives which is why your attempt to recolour it doesn't work. To get full control of the styling you probably want to extract only the polygons. $\endgroup$ – Simon Woods Jul 31 '16 at 13:03
  • 1
    $\begingroup$ BSplineSurface does not need to construct a polygon representation, it displays as a BSplineSurface3DBox which is drawn directly by the front end. $\endgroup$ – Simon Woods Jul 31 '16 at 13:10
  • $\begingroup$ So, is it possible for the user to write a analogous function called BezierSurface3DBox[] to let the front-end draw that surface directly? $\endgroup$ – xyz Jul 31 '16 at 13:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.