There is some tutorial stuff here:
- bookvolume0 - 7.2.6 - Building 3D primitives from scratch
- and HyperDoc - Topics->Graphics->
But it not quite what I need so I have been putting together this information gathered from what I can work out from the source code. Since I would like to use ThreeSpace and related domains to output graphical information to a file I need to understand them.
ThreeSpace
space.spad
ThreeSpace is a domain that holds various geometry primitives. It holds separate lists of:
- Points
- Curves
- Polygons
- Meshes
So lets create the various geometry types and see what we can do with them.
Points
We can define:
- Points in two dimensions (x,y)
- Points in three dimensions (x,y,z)
- Points in four dimensions (x,y,z,colour)
First we can create some 3 dimensional points to work with, we can do this using the point function (defined in the Point domain), we could define the coordinates using Float values but I have used DoubleFloat here because that will be needed later when we call makeViewport3D to display the results:
(1) -> p1 := point[0.1@DoubleFloat,0.2@DoubleFloat,0.3@DoubleFloat]
(1) [0.1,0.2,0.30000000000000004]
Type: Point(DoubleFloat)
(2) -> p2 := point[0.3@DoubleFloat,0.4@DoubleFloat,0.5@DoubleFloat]
(2) [0.30000000000000004,0.4,0.5]
Type: Point(DoubleFloat)
(3) -> p3 := point[0.2@DoubleFloat,0.4@DoubleFloat,0.6@DoubleFloat]
(3) [0.2,0.4,0.6000000000000001]
Type: Point(DoubleFloat)
|
We can now put the points into ThreeSpace, we can do this using a different point function (this time the point function defined in ThreeSpace domain):
(4) -> ts1 := point(p1)
(4) 3-Space with 1 component
Type: ThreeSpace(DoubleFloat)
|
We can always check what our ThreeSpace contains by using its objects procedure:
(5) -> objects(ts1)
(5) [points= 1,curves= 0,polygons= 0,constructs= 0]
Type: Record(points: NonNegativeInteger,curves: NonNegativeInteger,
polygons: NonNegativeInteger,constructs: NonNegativeInteger)
|
We can add the other points to our ThreeSpace as follows:
(6) -> point(ts1,p2)
(6) 3-Space with 2 components
Type: ThreeSpace(DoubleFloat)
(7) -> point(ts1,p3)
(7) 3-Space with 3 components
Type: ThreeSpace(DoubleFloat)
|
To see these three points in an x-window we use makeViewport3D as follows:
(8) -> makeViewport3D(ts1,[toScale(true)])
Transmitting data...
(8)ThreeDimensionalViewport: "FriCAS3D"
Type: ThreeDimensionalViewport |
This brings up the x-window on the right, we can just see the three points in the window. |
|
We can add points of a different dimension, in this case 2, but I suggest you don't try this at home as it will prevent makeViewport3D from working, it does not work with mixed dimensions as you can see here:
(8) -> p4 := point[0.1@DoubleFloat,0.5@DoubleFloat]
(8) [0.1,0.5]
Type: Point(DoubleFloat)
(9) -> point(ts1,p4)
(9) 3-Space with 4 components
Type: ThreeSpace(DoubleFloat)
(10) -> makeViewport3D(ts1,[toScale(true)])
Transmitting data...
>> Error detected within library code:
All points should have the same dimension
|
Curves
Now lets add a curve, we can either create it from a list of points, or avoid point definitions by using a list of list of floats, here we will use explicit point definitions:
(9) ->p5 := point[0.1@DoubleFloat,0.9@DoubleFloat,0.3@DoubleFloat]
(9) [0.1,0.9,0.30000000000000004]
Type: Point(DoubleFloat)
(10) ->p6 := point[0.2@DoubleFloat,0.1@DoubleFloat,0.5@DoubleFloat]
(10) [0.2,0.1,0.5]
Type: Point(DoubleFloat)
(11) ->p7 := point[0.3@DoubleFloat,0.9@DoubleFloat,0.6@DoubleFloat]
(11) [0.30000000000000004,0.9,0.6000000000000001]
Type: Point(DoubleFloat)
(12) ->p8 := point[0.4@DoubleFloat,0.1@DoubleFloat,0.5@DoubleFloat]
(12) [0.4,0.1,0.5]
Type: Point(DoubleFloat)
(13) ->p9 := point[0.5@DoubleFloat,0.9@DoubleFloat,0.6@DoubleFloat]
(13) [0.5,0.9,0.6000000000000001]
Type: Point(DoubleFloat)
(14) ->curve(ts1,[p5,p6,p7,p8,p9])
(14) 3-Space with 4 components
Type: ThreeSpace(DoubleFloat)
(15) ->objects(ts1)
(15) [points= 3,curves= 1,polygons= 0,constructs= 0]
Type: Record(points: NonNegativeInteger,curves: NonNegativeInteger,_
polygons: NonNegativeInteger,constructs: NonNegativeInteger)
(16) ->makeViewport3D(ts1,[toScale(true)])
Transmitting data...
(16) ThreeDimensionalViewport: "FriCAS3D"
Type: ThreeDimensionalViewport |
This produces the output shown on the right:
The 'curve' produces a set of lines joining up the points.
We could have made this a 'closed curve', that is include a line from the last point back to the first point by changing the function call from:
curve(ts1,[p5,p6,p7,p8,p9])
to
closedCurve(ts1,[p5,p6,p7,p8,p9]) |
|
Polygons
Polygons are similar to curves except the points are in a plane and the polygon is always closed. Polygons are often used to construct a surface so the plane inside the polygon can be drawn filled in with colour:
p10 := point[0.1@DoubleFloat,0.9@DoubleFloat,0.9@DoubleFloat]
(17) [0.1,0.9,0.9]
Type: Point(DoubleFloat)
p11 := point[0.2@DoubleFloat,0.1@DoubleFloat,0.9@DoubleFloat]
(18) [0.2,0.1,0.9]
Type: Point(DoubleFloat)
p12 := point[0.3@DoubleFloat,0.5@DoubleFloat,0.9@DoubleFloat]
(19) [0.30000000000000004,0.5,0.9]
Type: Point(DoubleFloat)
p13 := point[0.4@DoubleFloat,0.1@DoubleFloat,0.9@DoubleFloat]
(20) [0.4,0.1,0.9]
Type: Point(DoubleFloat)
p14 := point[0.5@DoubleFloat,0.9@DoubleFloat,0.9@DoubleFloat]
(21) [0.5,0.9,0.9]
Type: Point(DoubleFloat)
polygon(ts1,[p10,p11,p12,p13,p14])
(22) 3-Space with 5 components
Type: ThreeSpace(DoubleFloat)
objects(ts1)
(23) [points= 3,curves= 1,polygons= 1,constructs= 0]
Type: Record(points: NonNegativeInteger,curves: NonNegativeInteger,
polygons: NonNegativeInteger,constructs: NonNegativeInteger)
makeViewport3D(ts1,[toScale(true)])
Transmitting data...
(24) ThreeDimensionalViewport: "FriCAS3D"
Type: ThreeDimensionalViewport |
The polygon is the purple line here: |
|
Mesh
A mesh can represent a complete curved surface. This in made up of an array of squares which can be defined by a two dimensional (list of list) array of points. This is very useful for producing graphical representations of functions.
p15 := point[0.1@DoubleFloat,0.1@DoubleFloat,0.9@DoubleFloat]
(25) [0.1,0.1,0.9]
Type: Point(DoubleFloat)
p16 := point[0.4@DoubleFloat,0.1@DoubleFloat,0.8@DoubleFloat]
(26) [0.4,0.1,0.8]
Type: Point(DoubleFloat)
p17 := point[0.7@DoubleFloat,0.1@DoubleFloat,0.7@DoubleFloat]
(27) [0.7000000000000001,0.1,0.7000000000000001]
Type: Point(DoubleFloat)
p18 := point[0.1@DoubleFloat,0.4@DoubleFloat,0.8@DoubleFloat]
(28) [0.1,0.4,0.8]
Type: Point(DoubleFloat)
p19 := point[0.4@DoubleFloat,0.4@DoubleFloat,0.8@DoubleFloat]
(29) [0.4,0.4,0.8]
Type: Point(DoubleFloat)
p20 := point[0.7@DoubleFloat,0.4@DoubleFloat,0.8@DoubleFloat]
(30) [0.7000000000000001,0.4,0.8]
Type: Point(DoubleFloat)
p21 := point[0.1@DoubleFloat,0.7@DoubleFloat,0.7@DoubleFloat]
(31) [0.1,0.7000000000000001,0.7000000000000001]
Type: Point(DoubleFloat)
p22 := point[0.4@DoubleFloat,0.7@DoubleFloat,0.8@DoubleFloat]
(32) [0.4,0.7000000000000001,0.8]
Type: Point(DoubleFloat)
p23 := point[0.7@DoubleFloat,0.7@DoubleFloat,0.9@DoubleFloat]
(33) [0.7000000000000001,0.7000000000000001,0.9]
Type: Point(DoubleFloat)
mesh(ts1,[[p15,p16,p17],[p18,p19,p20],[p21,p22,p23]],true,true)
(34) 3-Space with 6 components
Type: ThreeSpace(DoubleFloat)
objects(ts1)
(35) [points= 3,curves= 1,polygons= 1,constructs= 1]
Type: Record(points: NonNegativeInteger,curves: NonNegativeInteger,
polygons: NonNegativeInteger,constructs: NonNegativeInteger)
makeViewport3D(ts1,[toScale(true)])
Transmitting data...
(36) ThreeDimensionalViewport: "FriCAS3D"
Type: ThreeDimensionalViewport
|
The mesh is the blue grid here:
notice that the mesh is listed as a 'construct' when listed by the objects function. |
|
Internal structure of ThreeSpace
We have now put various point, curve, polygon and mesh components into our ThreeSpace instance: ts1, this is stored as a record as follows:
Rep := Record( subspaceField:SUBSPACE, compositesField:L SUBSPACE, _
rep3DField:REP3D, objectsField:OBJ3D, _
converted:B)
where:
REP3D ==> Record(lp:L POINT,llliPt:L L L NNI, llProp:L L PROP, lProp:L PROP)
so this holds the main geometric data.
OBJ3D ==> Record(points:NNI, curves:NNI, polygons:NNI, constructs:NNI)
this gives the number of each type of component.
We can get to this data with the following calls:
(42) -> components(ts1)
(42)
[3-Space with 1 component, 3-Space with 1 component,
3-Space with 1 component, 3-Space with 1 component,
3-Space with 1 component, 3-Space with 1 component]
Type: List(ThreeSpace(DoubleFloat))
(43) -> composites(ts1)
(43) []
Type: List(ThreeSpace(DoubleFloat))
(44) -> subspace(ts1)
(44) 3-Space with depth of 3 and 6 components
Type: SubSpace(3,DoubleFloat)
|
These components can have the following boolean properties:
- closed - (true or false) the last point is connected back to the first.
- solid - (true or false) each polygon is filled in.
we can test the property values with the llprop function:
(38) -> llprop(ts1)
(38)
[[Component is not closed, not solid], [Component is not closed, not solid],
[Component is not closed, not solid], [Component is not closed, not solid],
[Component is not closed, not solid,Component is not closed, not solid],
[Component is closed, not solid, Component is closed, not solid,
Component is closed, not solid]
]
Type: List(List(SubSpaceComponentProperty))
|
Solid Mesh using SubSpaceComponentProperty
In order to display the mesh so that it looks solid (as opposed to the wireframe view we have see so far) we need to make three changes from what we have been doing until now:
- Set a SubSpaceComponentProperty, set to solid, for the whole mesh and each row in the mesh
- Base the mesh on 4 dimensional points (x,y,z and colour)
- Set the drawStyle for the ThreeDimensionalViewport to "shade".
sscp := new()$SubSpaceComponentProperty
(41) Component is not closed, not solid
Type: SubSpaceComponentProperty
solid(sscp,true)
(42) true
Type: Boolean
close(sscp,true)
(43) true
Type: Boolean
p25 := point[0.1@DoubleFloat,0.1@DoubleFloat,0.9@DoubleFloat,1.0@DoubleFloat]
(44) [0.1,0.1,0.9,1.0]
Type: Point(DoubleFloat)
p26 := point[0.4@DoubleFloat,0.1@DoubleFloat,0.8@DoubleFloat,1.0@DoubleFloat]
(45) [0.4,0.1,0.8,1.0]
Type: Point(DoubleFloat)
p27 := point[0.7@DoubleFloat,0.1@DoubleFloat,0.7@DoubleFloat,1.0@DoubleFloat]
(46) [0.7000000000000001,0.1,0.7000000000000001,1.0]
Type: Point(DoubleFloat)
p28 := point[0.1@DoubleFloat,0.4@DoubleFloat,0.8@DoubleFloat,1.0@DoubleFloat]
(47) [0.1,0.4,0.8,1.0]
Type: Point(DoubleFloat)
p29 := point[0.4@DoubleFloat,0.4@DoubleFloat,0.8@DoubleFloat,1.0@DoubleFloat]
(48) [0.4,0.4,0.8,1.0]
Type: Point(DoubleFloat)
p30 := point[0.7@DoubleFloat,0.4@DoubleFloat,0.8@DoubleFloat,1.0@DoubleFloat]
(49) [0.7000000000000001,0.4,0.8,1.0]
Type: Point(DoubleFloat)
p31 := point[0.1@DoubleFloat,0.7@DoubleFloat,0.7@DoubleFloat,1.0@DoubleFloat]
(50) [0.1,0.7000000000000001,0.7000000000000001,1.0]
Type: Point(DoubleFloat)
p32 := point[0.4@DoubleFloat,0.7@DoubleFloat,0.8@DoubleFloat,1.0@DoubleFloat]
(51) [0.4,0.7000000000000001,0.8,1.0]
Type: Point(DoubleFloat)
p33 := point[0.7@DoubleFloat,0.7@DoubleFloat,0.9@DoubleFloat,1.0@DoubleFloat]
(52) [0.7000000000000001,0.7000000000000001,0.9,1.0]
Type: Point(DoubleFloat)
ts2 := point(p25)
(53) 3-Space with 1 component
Type: ThreeSpace(DoubleFloat)
vp:=makeViewport3D(ts2,[toScale(true)])
Transmitting data...
(55) ThreeDimensionalViewport: "FriCAS3D"
Type: ThreeDimensionalViewport
drawStyle(vp,"shade")
Type: Void
|
We can now see the mesh as a 'solid' surface: |
|
Displaying Functions and Parametric Equations Graphically.
Since axiom is a mathematics program its important to be able to create a mesh from equations, or a function, or parametrically.
ts3 := makeObject((x*x-y*y)/10,x=-10..10,y=-10..10)
Compiling function %B with type (DoubleFloat,DoubleFloat) ->
DoubleFloat
(57) 3-Space with 1 component
Type: ThreeSpace(DoubleFloat)
vp:=makeViewport3D(ts3,[toScale(true)])
Transmitting data...
(58) ThreeDimensionalViewport: "FriCAS3D"
Type: ThreeDimensionalViewport
drawStyle(vp,"shade")
Type: Void
|
This produces the output here:
The makeObject and makeViewport3D calls can be replaced by a single call to the 'draw' function. The draw function is well documented elsewhere. In many cases 'makeObject' is available with the same parameters as 'draw' so refer to the draw documentation for details. |
|
SubSpace
newpoint.spad
SubSpace stores geometry information so there is some overlap between SubSpace and ThreeSpace in that they are designed to hold the same kinds of geometry structures. SubSpace is a tree structure where the leaves are points, it does not hold different primitives like ThreeSpace (which has Points,Curves,Polygons and Meshes). In subSpace the primitive is implied by the number of dimensions.
- In two dimensions the primitives are curves (defined as a list of 2D points)
- In three dimensions the primitives are polygons (defined as a list of 3D points)
We can covert out geometric structure between the two structures using:
- subspace: (ThreeSpace) -> SubSpace
- create3Space : SubSpace(3,R) -> ThreeSpace
The point data is indexed so if a given point is the vertex of several polygons (which will happen frequently because surfaces are drawn as joining polygons) then we only define the point once, in the root. The leaf nodes only need to specify the point using its index.
The points are read by calling dataList on the root.
susp2 := subspace(ts2)
(61) 3-Space with depth of 3 and 2 components
Type: SubSpace(3,DoubleFloat)
pointData(susp2)
(62)
[[0.1,0.1,0.9,1.0], [0.1,0.1,0.9,1.0], [0.4,0.1,0.8,1.0],
[0.7000000000000001,0.1,0.7000000000000001,1.0], [0.1,0.4,0.8,1.0],
[0.4,0.4,0.8,1.0], [0.7000000000000001,0.4,0.8,1.0],
[0.1,0.7000000000000001,0.7000000000000001,1.0],
[0.4,0.7000000000000001,0.8,1.0],
[0.7000000000000001,0.7000000000000001,0.9,1.0]]
Type: List(Point(DoubleFloat))
extractIndex(susp2)
(63) 0
Type: NonNegativeInteger
extractProperty(susp2)
(64) Component is not closed, not solid
Type: SubSpaceComponentProperty
numberOfChildren(susp2)
(65) 2
Type: PositiveInteger
children(susp2)
(66)
[3-Space with depth of 2 and 1 component,
3-Space with depth of 2 and 3 components]
Type: List(SubSpace(3,DoubleFloat))
c1 := child(susp2,1)
(67) 3-Space with depth of 2 and 1 component
Type: SubSpace(3,DoubleFloat)
c2 := child(susp2,2)
(68) 3-Space with depth of 2 and 3 components
Type: SubSpace(3,DoubleFloat)
extractProperty(c1)
(69) Component is not closed, not solid
Type: SubSpaceComponentProperty
extractIndex(c1)
(70) 0
Type: NonNegativeInteger
extractProperty(c2)
(71) Component is closed, solid
Type: SubSpaceComponentProperty
extractIndex(c2)
(72) 0
Type: NonNegativeInteger
children(c1)
(73) [3-Space with depth of 1 and 1 component]
Type: List(SubSpace(3,DoubleFloat))
children(c2)
(74)
[3-Space with depth of 1 and 3 components,
3-Space with depth of 1 and 3 components,
3-Space with depth of 1 and 3 components]
Type: List(SubSpace(3,DoubleFloat))
c11 := child(c1,1)
(75) 3-Space with depth of 1 and 1 component
Type: SubSpace(3,DoubleFloat)
c111 := child(c11,1)
(76) 3-Space with depth of 0 and 0 components
Type: SubSpace(3,DoubleFloat)
extractIndex(c111)
(77) 1
Type: PositiveInteger
(78) ->
|