© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2021
M. Cywiak, D. CywiakMulti-Platform Graphics Programming with Kivyhttps://doi.org/10.1007/978-1-4842-7113-1_9

9. Parametric 3D Plotting

Moisés Cywiak1   and David Cywiak2
(1)
Leon, Guanajuato, Mexico
(2)
Queretaro, Mexico
 
In this chapter, we present basic elements for parametric 3D plotting. A typical example on this subject consists of a three-dimensional circular helix, shown in Figure 9-1, which was obtained from a screenshot of this program running on an Android cell phone.
../images/510726_1_En_9_Chapter/510726_1_En_9_Fig1_HTML.jpg
Figure 9-1

Screenshot of the program showing a helix, obtained with 3D parametric equations

The buttons in Figure 9-1 have the same functionality as described in Chapter 7.

9.1 Parametric Equations

Three-dimensional parametric equations consist of representing the three-dimensional spatial coordinates (x, y, z) as functions of a single parameter. For programming purposes, this parameter can be an integer that varies between 0 and a maximum value.

To begin with, as in previous chapters, we define the parameters, which are required to perform the projection:
D=4000;
VX=180; VY=200; VZ=0;
Next, we create three one-dimensional arrays of size N+1, ranging from entries 0 to N. The x and z arrays will store the horizontal and depth coordinates, and the y array will store the values corresponding to the function. The code looks as follows:
N=200;
x=np.zeros(N+1); z=np.zeros(N+1); y=np.zeros(N+1);
The parametric equations are shown in Listing 9-1.
Pi=np.pi;
L=50;
for n in range (0,N+1):
    x[n]=L*np.sin(12*Pi/N*n);
    z[n]=L*np.cos(12*Pi/N*n);
    y[n]=n/N*100;
Listing 9-1

Filling the Parametric Variables

In this code, the x[n] and z[n] arrays store the values that correspond to the parametric equations of a circle with radius equal to L. This can be verified by calculating (x[n]**2 + (z[n])**2), which equals L2. The number 12 in the sine and cosine arguments represents the speed of rotation of the helix. If this number is lowered, the helix will exhibit fewer turns.

In the for loop, the y[n]=n/N*100 directive fills the vertical array with values ranging from 0 to 100, which corresponds to the height of the helix.

To visualize how the helix is drawn, note that as n increases within the for loop, the path of a circle is generated on the x-z plane. At the same time, the vertical height, y, increases, thereby generating the circular helix.

9.2 Plotting

The plot is performed by the GraphFunction(B) function . First, the function creates an array to store the complete set of points that makes up the helix, using the PointList = np.zeros ((N + 1, 2)) directive. Then, we draw the lines that join the set of points, by utilizing this directive:
B.ids.Screen1.canvas.add( Line(points=
                PointList.tolist(), width=1.3)) ;
The corresponding code is shown in Listing 9-2.
#Array to store list of points
PointList=np.zeros( (N+1,2) );
def GraphFunction(B):
    global x,y, z, N, D, VX, VY, VZ, Shift;
    B.ids.Screen1.canvas.clear(); #Clear the screen
    #Color to draw
    B.ids.Screen1.canvas.add( Color(1,0,0) );
    for n in range (0,N+1):    #Drawing
        Factor=(D-VZ)/(D-(z[n]+ZG0)-VZ);
        xA=XC+Factor*(x[n]-VX)+VX+ (P/(1-P))*VX;
        yA=YC+Factor*(y[n]-VY)+VY+ (P/(1-P))*VY;
        PointList[n]=xA,yA;
    B.ids.Screen1.canvas.add( Line(points=
                PointList.tolist(), width=1.3)) ;
Listing 9-2

Code for Plotting the Function

The code listings for main.py and File.kv are shown in Listings 9-3 and 9-4, respectively.
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.graphics import Line, Color
from kivy.clock import Clock
import os
import numpy as np
from kivy.lang import Builder
Builder.load_file(
    os.path.join(os.path.dirname(os.path.abspath(__file__)), 'File.kv')
    )
#Avoid Form1 of being resizable
from kivy.config import Config
Config.set("graphics","resizable", False)
Config.set('graphics', 'width',  '480');
Config.set('graphics', 'height', '680');
#These values are adjusted by the function
#Initialize() after Clock.schedule_once
#has been executed.
#Canvas center= (XC,YC) and width and height =(W,H);
D=4000;
VX=180; VY=200; VZ=0;
#Factor0 =(D-VZ) / (D/2-VZ);
N=200;
x=np.zeros(N+1); z=np.zeros(N+1); y=np.zeros(N+1);
P=0.5;
ZG0=P*D;
Pi=np.pi;
L=50;
for n in range (0,N+1):
    x[n]=L*np.sin(12*Pi/N*n);
    z[n]=L*np.cos(12*Pi/N*n);
    y[n]=n/N*100;
#Array to store list of points
PointList=np.zeros( (N+1,2) );
def GraphFunction(B):
    global x,y, z, N, D, VX, VY, VZ, Shift;
    B.ids.Screen1.canvas.clear(); #Clear the screen
    #Color to draw
    B.ids.Screen1.canvas.add( Color(1,0,0) );
    for n in range (0,N+1):    #Drawing
        Factor=(D-VZ)/(D-(z[n]+ZG0)-VZ);
        xA=XC+Factor*(x[n]-VX)+VX+ (P/(1-P))*VX;
        yA=YC+Factor*(y[n]-VY)+VY+ (P/(1-P))*VY;
        PointList[n]=xA,yA;
    B.ids.Screen1.canvas.add( Line(points=
                PointList.tolist(), width=1.3)) ;
def RotateFunction(B, Sense):
    global x, y, z, D, N;
    if Sense==-1:
        Teta=np.pi/180*(-4.0);
    else:
        Teta=np.pi/180*(4.0);
    Cos_Teta=np.cos(Teta)
    Sin_Teta=np.sin(Teta);
    X0=0;  Y0=0;  Z0=ZG0 # Center of rotation
    for n in range(0,N+1):
        if (B.ids.Button3.state=="down" or
                B.ids.Button4.state=="down"):
            yP=(y[n]-Y0)*Cos_Teta
                    + (x[n]-X0)*Sin_Teta;
            xP=-(y[n]-Y0)*Sin_Teta
                    +(x[n]-X0)*Cos_Teta;
            y[n]=yP;
            x[n]=xP;
        if (B.ids.Button5.state=="down" or
                B.ids.Button6.state=="down"):
            yP=(y[n]-Y0)*Cos_Teta
                        + z[n]*Sin_Teta;
            zP=-(y[n]-Y0)*Sin_Teta
                        +z[n]*Cos_Teta;
            y[n]=yP;
            z[n]=zP;
        if (B.ids.Button7.state=="down" or
                    B.ids.Button8.state=="down"):
            xP=(x[n]-X0)*Cos_Teta
                        + z[n]*Sin_Teta;
            zP=-(x[n]-X0)*Sin_Teta
                        +z[n]*Cos_Teta;
            x[n]=xP;
            z[n]=zP;
class Form1(FloatLayout):
    def __init__(Handle, **kwargs):
        super(Form1, Handle).__init__(**kwargs);
        Event1=Clock.schedule_once(
                            Handle.Initialize);
    def Initialize(B, *args):
        global W,H, XC,YC;
        W,H=B.ids.Screen1.size ;
        XI,YI=B.ids.Screen1.pos
        XC=XI+int (W/2);
        YC=YI+int(H/2)-80;
    def Button1_Click(B):
        GraphFunction(B);
    def Button2_Click(B):
        B.ids.Screen1.canvas.clear();
    def Button3_Click(B):
        RotateFunction(B,1),
        GraphFunction(B);
    def Button4_Click(B):
        RotateFunction(B,-1),
        GraphFunction(B);
    def Button5_Click(B):
        RotateFunction(B,-1),
        GraphFunction(B);
    def Button6_Click(B):
        RotateFunction(B,1),
        GraphFunction(B);
    def Button7_Click(B):
        RotateFunction(B,-1),
        GraphFunction(B);
    def Button8_Click(B):
        RotateFunction(B,1),
        GraphFunction(B);
# This is the Start Up code .
class StartUp (App):
    def build (BU):
        BU.title="Form1"
        return Form1();
if __name__ =="__main__":
    StartUp().run();
Listing 9-3

Code for the main.py File

#:set W 440
#:set H 440
<Form1>:
    id : Form1
    StencilView:
        id: Screen1
        size_hint: None,None
        pos_hint: {"x":0.04, "y":0.34}
        size: W,H
        canvas.before:
            Color:
                rgba: 0.9, 0.9, 0, 1
            RoundedRectangle:
                pos:  self.pos
                size: self.size
    Button:
        id: Button1
        on_press: Form1.Button1_Click()
        text: "Button1"
        size_hint: None,None
        pos_hint: {"x": 0.2, "y":0.03}
        size: 100,30
    Button:
        id: Button2
        on_press: Form1.Button2_Click()
        text: "Button2"
        size_hint: None,None
        pos_hint: {"x": 0.63, "y":0.03}
        size: 100,30
    Button:
        id: Button3
        on_press: Form1.Button3_Click()
        text: "Button3"
        size_hint: None,None
        pos_hint: {"x": 0.05, "y":0.12}
        size: 100,30
        always_release: True
    Button:
        id: Button4
        on_press: Form1.Button4_Click()
        text: "Button4"
        size_hint: None,None
        pos_hint: {"x": 0.73, "y":0.12}
        size: 100,30
    Button:
        id: Button5
        on_press: Form1.Button5_Click()
        text: "Button5"
        size_hint: None,None
        pos_hint: {"x": 0.05, "y":0.20}
        size: 100,30
    Button:
        id: Button6
        on_press: Form1.Button6_Click()
        text: "Button6"
        size_hint: None,None
        pos_hint: {"x": 0.73, "y":0.20}
        size: 100,30
    Button:
        id: Button7
        on_press: Form1.Button7_Click()
        text: "Button7"
        size_hint: None,None
        pos_hint: {"x": 0.05, "y":0.28}
        size: 100,30
    Button:
        id: Button8
        on_press: Form1.Button8_Click()
        text: "Button8"
        size_hint: None,None
        pos_hint: {"x": 0.73, "y":0.28}
        size: 100,30
Listing 9-4

Cod e for the file.kv File

9.3 Summary

In this chapter, we described elements for parametric 3D plotting. As a typical example, we presented code for a three-dimensional circular helix.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset