© 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_10

10. Stereoscopic parametric 3D Plots

Moisés Cywiak1   and David Cywiak2
(1)
Leon, Guanajuato, Mexico
(2)
Queretaro, Mexico
 
In this chapter, we present basic elements for constructing parametric 3D plots. A screenshot of a circular helix generated with this program running on an Android cell phone is shown in Figure 10-1.
../images/510726_1_En_10_Chapter/510726_1_En_10_Fig1_HTML.jpg
Figure 10-1

Android cell phone screenshot of a circular helix generated with this program

The buttons have the same functionality as described in Chapter 7.

10.1 Generating the Function

The code to generate the circular helix is shown in Listing 10-1.
#Number of pixels for representing the function
N=100;
x=np.zeros(N+1); z=np.zeros(N+1); y=np.zeros(N+1);
L=50;  Pi=np.pi;
for n in range (0,N+1):
    x[n]=L*np.sin(6*Pi/N*n);
    z[n]=L*np.cos(6*Pi/N*n) +D/2;
for n in range(0,N+1):
    y[n]=n/N*100;
Listing 10-1

Generating the Circular Helix

10.2 Creating the PIL Images for the Stereoscopic Effect

The Initialize(B, *args) function obtains the dimensions of the screen and creates PilImage1 and PilImage2. Additionally, the function creates the instances Draw1 and Draw2, where the left and right plots will be drawn. A TrueType Font (TTF) is selected to display the 3D Images text when the program starts. The code for Initialize(B, *args) is shown in Listing 10-2.
def Initialize(B, *args):
        global W,H, XC,YC;
        global PilImage1,PilImage2, Draw1,Draw2;
        # P= Percentage of the D distance
        global P, Amplitude;
        W,H=B.ids.Screen1.size;
        XC=int (W/2)+20;
        YC=int(H/2)+60;
        PilImage1= Image.new('RGB', (W-10, H-10),
                                     (60, 70, 30));
        Draw1 = ImageDraw.Draw(PilImage1);
        PilImage2= Image.new('RGB', (W-10, H-10),
                                     (60, 70, 30));
        Draw2 = ImageDraw.Draw(PilImage2);
        Font = ImageFont.truetype('Gargi.ttf', 70);
        Draw1.text( (30,200), "3D Images", fill =(
                            255,0,0,1), font=Font);
        Draw2.text( (50,200), "3D Images", fill =
                        (0,255,255,1), font=Font);
        ShowScene(B);
Listing 10-2

The Initialize Function

10.3 Plotting the Function

As described in Chapter 8, drawing memory on the screen is a time-consuming process. Therefore, it is convenient to allocate a memory region of the appropriate size to store the complete set of consecutive points for circular helix. This way, we can draw the plot with only one access to the screen memory, reducing the processing time.

We create the array by means of this directive:
PointList=np.zeros( (N+1,2) )
Next, we fill the array with the list of points. After this, we use this directive to convert the data stored in the array into the appropriate tuple list required by PIL for drawing:
List=tuple( map(tuple,PointList) )
Finally, the following directive draws the plot:
Draw.line( List, fill=(r,g,b), width=6 )

As the helix aspect is similar to a wire, for better visualization, we set the drawing line width equal to 6.

These directives are enclosed in the GraphFunction(VX, VY, VZ, Which) function. This function receives the parameters corresponding to the left or to the right projections, depending on whether the variable Which receives the value 0 or 1. The code for GraphFunction(VX, VY, VZ, Which) is shown in Listing 10-3.
PointList=np.zeros( (N+1,2) );
def GraphFunction(VX,VY,VZ,Which):
    global x,y, z, N;
    if (Which==0):
        r,g,b = 250, 0, 0; #red Image
        Draw=Draw1
    else:
        r,g,b= 0, 250, 250; #cyan image
        Draw=Draw2
    for n in range (0,N+1):    #Drawing
        Factor=(D-VZ)/(D-z[n]-VZ);
        xA=XC+Factor*(x[n]-VX)+VX+ShiftX;
        yA=YC-Factor*(y[n]-VY)-VY+ShiftY;
        PointList[n]=xA,yA;
    List=tuple( map(tuple,PointList) );
    Draw.line( List, fill=(r,g,b), width=6 );
Listing 10-3

Code for Plotting the Function

This code generates the left and right images with the required plots. Now, the ShowScene(B) function will create the necessary arrays for ORing the PIL images and displaying the result on the screen, in the same manner as described in previous chapters. The code for ShowScene(B) is shown in Listing 10-4.
def ShowScene(B):
    Array1=np.array(PilImage1);
    Array2=np.array(PilImage2);
    Array3=Array1 | Array2;
    PilImage3=Image.fromarray(Array3);
    Memory=io.BytesIO();
    PilImage3.save(Memory, format="png");
    Memory.seek(0);
    ImagePNG=CoreImage(Memory, ext="png");
    B.ids.Screen1.texture=ImagePNG.texture;
    ImagePNG.remove_from_cache()
    Memory.close();
    PilImage3.close();
    Array1=None;
    Array2=None;
    Array3=None;
Listing 10-4

Code for Displaying the Scene

The code for main.py and File.kv are shown in Listings 10-5 and 10-6, respectively.
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.graphics import Line, Color
from kivy.clock import Clock
from kivy.core.image import Image as CoreImage
from PIL import Image, ImageDraw, ImageFont
import io
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');
D=1000;
VX1=180; VY1=200; VZ1=0;
VX2=200; VY2=200; VZ2=0;
ShiftX=VX1; ShiftY=-VY1;
#Number of pixels for representing the function
N=100;
x=np.zeros(N+1); z=np.zeros(N+1); y=np.zeros(N+1);
L=50;  Pi=np.pi;
for n in range (0,N+1):
    x[n]=L*np.sin(6*Pi/N*n);
    z[n]=L*np.cos(6*Pi/N*n) +D/2;
for n in range(0,N+1):
    y[n]=n/N*100;
PointList=np.zeros( (N+1,2) );
def GraphFunction(VX,VY,VZ,Which):
    global x,y, z, N;
    if (Which==0):
        r,g,b = 250, 0, 0; #red Image
        Draw=Draw1
    else:
        r,g,b= 0, 250, 250; #cyan image
        Draw=Draw2
    for n in range (0,N+1):    #Drawing
        Factor=(D-VZ)/(D-z[n]-VZ);
        xA=XC+Factor*(x[n]-VX)+VX+ShiftX;
        yA=YC-Factor*(y[n]-VY)-VY+ShiftY;
        PointList[n]=xA,yA;
    List=tuple( map(tuple,PointList) );
    Draw.line( List, fill=(r,g,b), width=6 );
def ShowScene(B):
    Array1=np.array(PilImage1);
    Array2=np.array(PilImage2);
    Array3=Array1 | Array2;
    PilImage3=Image.fromarray(Array3);
    Memory=io.BytesIO();
    PilImage3.save(Memory, format="png");
    Memory.seek(0);
    ImagePNG=CoreImage(Memory, ext="png");
    B.ids.Screen1.texture=ImagePNG.texture;
    ImagePNG.remove_from_cache()
    Memory.close();
    PilImage3.close();
    Array1=None;
    Array2=None;
    Array3=None;
def ClearObjects():
    Draw1.rectangle( (0, 0, W-10, H-10),
                     fill=(60, 70, 30, 1) );
    Draw2.rectangle( (0, 0, W-10, H-10),
                     fill=(60, 70, 30, 1) );
def RotateFunction(B, Sense):
    global x,y,z,D,N
    if Sense==-1:
        Teta=np.pi/180*(-0.1);
    else:
        Teta=np.pi/180*(0.1);
    Cos_Teta=np.cos(Teta)
    Sin_Teta=np.sin(Teta);
    X0=0;  Y0=0;  Z0=D/2 # Center of rotation
    for n in range(0,N+1):
        for m 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 +Y0;
                xP=-(y[n]-Y0)*Sin_Teta
                    +(x[n]-X0)*Cos_Teta + X0;
                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]-Z0)*Sin_Teta + Y0;
                zP=-(y[n]-Y0)*Sin_Teta
                        +(z[n]-Z0)*Cos_Teta + Z0;
                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]-Z0)*Sin_Teta + X0;
                zP=-(x[n]-X0)*Sin_Teta
                        +(z[n]-Z0)*Cos_Teta + Z0;
                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;
        global PilImage1,PilImage2, Draw1,Draw2;
        # P= Percentage of the D distance
        global P, Amplitude;
        W,H=B.ids.Screen1.size;
        XC=int (W/2)+20;
        YC=int(H/2)+60;
        PilImage1= Image.new('RGB', (W-10, H-10),
                                     (60, 70, 30));
        Draw1 = ImageDraw.Draw(PilImage1);
        PilImage2= Image.new('RGB', (W-10, H-10),
                                     (60, 70, 30));
        Draw2 = ImageDraw.Draw(PilImage2);
        Font = ImageFont.truetype('Gargi.ttf', 70);
        Draw1.text( (30,200), "3D Images", fill =(
                            255,0,0,1), font=Font);
        Draw2.text( (50,200), "3D Images", fill =
                        (0,255,255,1), font=Font);
        ShowScene(B);
    def Button1_Click(B):
        global Draw1, Draw2;
        ClearObjects(); # Clearing Draw1 and Draw2
        GraphFunction(VX1,VY1,VZ1,0);
        GraphFunction(VX2,VY2,VZ2,1);
        ShowScene(B);
    def Button2_Click(B):
        ClearObjects(); # Clearing Draw1 and Draw2
        Font = ImageFont.truetype('Gargi.ttf', 70)
        Draw1.text( (30,200), "3D Images", fill =
                            (255,0,0,1), font=Font);
        Draw2.text( (50,200), "3D Images", fill =
                        (0,255,255,1), font=Font);
        ShowScene(B);
    def Button3_Click(B):
        RotateFunction(B,1);
        ClearObjects(); # Clearing Draw1 and Draw2
        GraphFunction(VX1,VY1,VZ1,0);
        GraphFunction(VX2,VY2,VZ2,1);
        ShowScene(B);
    def Button4_Click(B):
        RotateFunction(B,-1),
        ClearObjects();
        GraphFunction(VX1,VY1,VZ1,0);
        GraphFunction(VX2,VY2,VZ2,1);
        ShowScene(B);
    def Button5_Click(B):
        RotateFunction(B,-1),
        ClearObjects();
        GraphFunction(VX1,VY1,VZ1,0);
        GraphFunction(VX2,VY2,VZ2,1);
        ShowScene(B);
    def Button6_Click(B):
        RotateFunction(B,1),
        ClearObjects();
        GraphFunction(VX1,VY1,VZ1,0);
        GraphFunction(VX2,VY2,VZ2,1);
        ShowScene(B);
    def Button7_Click(B):
        RotateFunction(B,-1),
        ClearObjects();
        GraphFunction(VX1,VY1,VZ1,0);
        GraphFunction(VX2,VY2,VZ2,1);
        ShowScene(B);
    def Button8_Click(B):
        RotateFunction(B,1),
        ClearObjects();
        GraphFunction(VX1,VY1,VZ1,0);
        GraphFunction(VX2,VY2,VZ2,1);
        ShowScene(B);
# This is the Start Up code.
class StartUp (App):
    def build (BU):
        BU.title="Form1"
        return Form1();
if __name__ =="__main__":
    StartUp().run();
Listing 10-5

Code for the main.py File

#:set W 440
#:set H 440
<Form1>:
    id : Form1
    Image:
        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 10-6

Code for the file.kv File

When plotting curves described by parametric equations, it can be hard to follow their paths from the proper perspective. You can see this in the plot in Figure 10-2. For these cases, stereoscopy can provide a better perception of the graph. The code to generate this function is shown in Listing 10-7.
for n in range (0,N+1):
    x[n]=L*np.sin(6*Pi/N*n);
    z[n]=L*np.cos(6*Pi/N*n);
    y[n]=60*np.sin(4*Pi/N*n)
Listing 10-7

Another Parametric Function Used as an Illustrative Example

../images/510726_1_En_10_Chapter/510726_1_En_10_Fig2_HTML.jpg
Figure 10-2

Stereoscopic 3D plot obtained with the program for a parametric function

10.4 Summary

In this chapter, we described elements for constructing stereoscopic parametric 3D plots. We also included two working examples.

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

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