The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Vector::Object3D::Examples - Examples of how to use Vector::Object3D

DESCRIPTION

Vector::Object3D is Perl module providing most basic procedures to facilitate handling of vector object calculations in the 3D space, including fundamental transformations (translating, scaling, rotating) as well as perspective 2D casting.

EXAMPLES

tetrahedron.pl

This example shows how to use Vector::Object3D to precalculate rotating 3D tetrahedron and animate it using Tk:

#!/usr/bin/perl

use strict;
use warnings;

package My::Vector::Object3D::Polygon;

use Moose;
extends 'Vector::Object3D::Polygon';

has colour => (
  is  => 'rw',
  isa => 'Maybe[Str]',
);

override 'copy' => sub {
  my ($self) = @_;

  my $colour = $self->colour;
  my $copy = $self->super();
  $copy->colour($colour);

  return $copy;
};

package main;

use Readonly;
use Term::ProgressBar 2.00;
use Tk;
use Vector::Object3D;

Readonly our $distance => 100;
Readonly our $fps => 100;
Readonly our $height => 480;
Readonly our $num_frames => 360;
Readonly our $pi => 3.14159;
Readonly our $rotate_factor => 360 / $num_frames * $pi / 180;
Readonly our $scale => 50;
Readonly our $width => 640;

{
  my $current_rotation = 0;

  sub next_rotation {
    $current_rotation += $rotate_factor;

    return $current_rotation;
  }
}

my $object = define_object();
my $frames = prepare_frames();

my $mw = MainWindow->new;
my $canvas = $mw->Canvas(-width => $width, -height => $height, -background => '#AAEEAA')->pack;

draw_polygons(0);

MainLoop;

sub define_object {
  my $point1 = Vector::Object3D::Point->new(x => -3, y => 2, z => 0);
  my $point2 = Vector::Object3D::Point->new(x => 3, y => 2, z => 0);
  my $point3 = Vector::Object3D::Point->new(x => 0, y => -4, z => 0);
  my $point4 = Vector::Object3D::Point->new(x => 0, y => 0, z => 3);

  my $polygon1 = My::Vector::Object3D::Polygon->new(vertices => [$point2, $point1, $point3], colour => '#CCCC00');
  my $polygon2 = My::Vector::Object3D::Polygon->new(vertices => [$point1, $point4, $point3], colour => '#22CCCC');
  my $polygon3 = My::Vector::Object3D::Polygon->new(vertices => [$point2, $point3, $point4], colour => '#88CC22');
  my $polygon4 = My::Vector::Object3D::Polygon->new(vertices => [$point1, $point2, $point4], colour => '#22CC22');

  return Vector::Object3D->new(polygons => [$polygon1, $polygon2, $polygon3, $polygon4]);
}

sub prepare_frames {
  my @frames;

  my $progress = Term::ProgressBar->new({
    name  => 'Calculating',
    count => $num_frames,
  });

  for (my $i = 0; $i < $num_frames; $i++) {
    push @frames, setup_frame($object);

    $progress->update($i);
  }

  $progress->update($num_frames);

  return \@frames;
}

sub draw_polygons {
  my ($step) = @_;

  $step = 0 if ++$step == $num_frames;

  my @precalculated_polygons = @{ $frames->[$step] };

  $canvas->delete('polygon' . $_) for (0 .. @precalculated_polygons);

  for (my $i = 0; $i < @precalculated_polygons; $i++) {
    my $polygon = $precalculated_polygons[$i];
    my $colour = $polygon->{colour};
    my @vertices = @{ $polygon->{vertices} };
    $canvas->createPolygon(@vertices, -fill => $colour, -outline => '#002200', -width => 5, -tags => 'polygon' . $i);
  }

  $mw->after(1000 / $fps => sub { draw_polygons($step) });
}

sub setup_frame {
  my ($object) = @_;

  my $rotation = next_rotation();

  my @colours = map { $_->colour } $object->get_polygons;

  $object = $object->scale(scale_x => $scale, scale_y => $scale, scale_z => $scale);
  $object = $object->rotate(rotate_xy => 0, rotate_yz => 0, rotate_xz => $rotation);
  $object = $object->rotate(rotate_xy => -2 * $rotation, rotate_yz => 0, rotate_xz => 0);
  $object = $object->translate(shift_x => $width / 2, shift_y => $height / 2, shift_z => 0);

  my @polygons = $object->get_polygons;

  $_->colour(shift @colours) for @polygons;

  my @polygons_visible = grep { $_->is_plane_visible } @polygons;

  my @polygons_casted = map { project_polygon($_) } @polygons_visible;

  return \@polygons_casted;
}

sub project_polygon {
  my $polygon = shift;
  my @vertices = map { $_->get_xy } $polygon->cast(type => 'parallel')->get_vertices;
  return {
    colour   => $polygon->colour,
    vertices => \@vertices,
  };
}

cube-outline.pl

This example shows how to use Vector::Object3D to precalculate rotating 3D cube, hide object's invisible faces and animate its outlines using Tk:

#!/usr/bin/perl

use strict;
use warnings;

use Readonly;
use Term::ProgressBar 2.00;
use Tk;
use Vector::Object3D::Point;
use Vector::Object3D::Polygon;

Readonly our $draw_scale => 3;
Readonly our $height => $draw_scale * 128;
Readonly our $width => $draw_scale * 128;
Readonly our $fps => 50;
Readonly our $num_frames => 256;
Readonly our $pi => 3.14159;
Readonly our $push_away => 50;
Readonly our $distance => 200;
Readonly our $scale => 9;

my @polygon = setup_object_polygons();

my @precalculated_data;

for (my $i = 0; $i < @polygon; $i++) {
  printf "[POLYGON %d/%d]\n", $i + 1, scalar @polygon;
  push @precalculated_data, calculate_polygon_frames($polygon[$i]);
}

my $mw = MainWindow->new;
our $canvas = $mw->Canvas(
  -width => $width,
  -height => $height,
  -background => '#CCCCCC'
)->pack;

draw_object(0);
MainLoop;

sub setup_object_polygons {
  my $point = [
    [-1, -1, +1],
    [-1, +1, +1],
    [+1, +1, +1],
    [+1, -1, +1],
    [-1, -1, -1],
    [-1, +1, -1],
    [+1, +1, -1],
    [+1, -1, -1],
  ];

  my $vertex1 = Vector::Object3D::Point->new(coord => [$point->[0][0], $point->[0][1], $point->[0][2]]);
  my $vertex2 = Vector::Object3D::Point->new(coord => [$point->[1][0], $point->[1][1], $point->[1][2]]);
  my $vertex3 = Vector::Object3D::Point->new(coord => [$point->[2][0], $point->[2][1], $point->[2][2]]);
  my $vertex4 = Vector::Object3D::Point->new(coord => [$point->[3][0], $point->[3][1], $point->[3][2]]);

  my $vertex5 = Vector::Object3D::Point->new(coord => [$point->[4][0], $point->[4][1], $point->[4][2]]);
  my $vertex6 = Vector::Object3D::Point->new(coord => [$point->[5][0], $point->[5][1], $point->[5][2]]);
  my $vertex7 = Vector::Object3D::Point->new(coord => [$point->[6][0], $point->[6][1], $point->[6][2]]);
  my $vertex8 = Vector::Object3D::Point->new(coord => [$point->[7][0], $point->[7][1], $point->[7][2]]);

  my @vertices = (
    [$vertex1, $vertex2, $vertex3, $vertex4],
    [$vertex5, $vertex8, $vertex7, $vertex6],
    [$vertex1, $vertex5, $vertex6, $vertex2],
    [$vertex2, $vertex6, $vertex7, $vertex3],
    [$vertex3, $vertex7, $vertex8, $vertex4],
    [$vertex4, $vertex8, $vertex5, $vertex1],
  );

  return map { Vector::Object3D::Polygon->new(vertices => $_) } @vertices;
}

sub calculate_polygon_frames {
  my ($polygon) = @_;

  my %rotation = (
    rotation_xy => -2 * $pi / $num_frames,
    rotation_xz => +4 * $pi / $num_frames,
    rotation_yz => +2 * $pi / $num_frames,
  );

  my $observer = Vector::Object3D::Point->new(x => 0, y => 0, z => 0);

  my $progress = Term::ProgressBar->new({
    name  => 'Calculating',
    count => $num_frames,
  });

  my @frame;

  for (my $i = 0; $i < $num_frames; $i++) {
    my %current_step_rotation = map { $_ => $i * $rotation{$_} } keys %rotation;

    my $rotated_polygon = calculate_rotated_frame($polygon, \%current_step_rotation);

    my $is_plane_visible = $rotated_polygon->is_plane_visible(observer => $observer);

    if ($is_plane_visible) {
      my @vertices = get_polygon_vertices($rotated_polygon);

      $frame[$i] = \@vertices;
    }

    $progress->update($i);
  }

  $progress->update($num_frames);

  return \@frame;
}

sub calculate_rotated_frame {
  my ($polygon, $rotation) = @_;

  $polygon = $polygon->rotate(rotate_xy => 0, rotate_yz => 0, rotate_xz => $rotation->{rotation_xz});
  $polygon = $polygon->rotate(rotate_xy => 0, rotate_yz => $rotation->{rotation_yz}, rotate_xz => 0);
  $polygon = $polygon->rotate(rotate_xy => $rotation->{rotation_xy}, rotate_yz => 0, rotate_xz => 0);

  $polygon = $polygon->scale(scale_x => $scale, scale_y => $scale, scale_z => $scale);

  $polygon = $polygon->translate(shift_x => 0, shift_y => 0, shift_z => $push_away);

  return $polygon;
}

sub get_polygon_vertices {
  my ($polygon) = @_;

  my $casted_polygon = $polygon->cast(type => 'perspective', distance => $distance);
  my $translated_polygon = $casted_polygon->translate(shift_x => $width / 6, shift_y => $height / 6);

  my @vertices = $translated_polygon->get_vertices;

  return map { $draw_scale * int $_ } map { $_->get_xy } @vertices;
}

sub draw_object {
  my ($step) = @_;

  $canvas->delete('polygon' . $_) for (0 .. @precalculated_data);

  for (my $i = 0; $i < @precalculated_data; $i++) {
    my $polygon = $precalculated_data[$i];

    my $data = $polygon->[$step];
    next unless defined $data;

    $canvas->createPolygon(@{$data}, -fill => undef, -outline => '#002200', -width => 5, -tags => 'polygon' . $i);
  }

  $step = 0 if ++$step == $num_frames;

  $mw->after(1000 / $fps => sub { draw_object($step) });

  return;
}

SEE ALSO

Tk, Vector::Object3D.

AUTHOR

Pawel Krol, <pawelkrol@cpan.org>.

VERSION

Version 0.01 (2012-12-24)

COPYRIGHT AND LICENSE

Copyright (C) 2012 by Pawel Krol.

This library is free open source software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.6 or, at your option, any later version of Perl 5 you may have available.

PLEASE NOTE THAT IT COMES WITHOUT A WARRANTY OF ANY KIND!