{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "nbsphinx": "hidden"
   },
   "outputs": [],
   "source": [
    "import open3d as o3d\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import copy\n",
    "import os\n",
    "import sys\n",
    "\n",
    "# only needed for tutorial, monkey patches visualization\n",
    "sys.path.append('..')\n",
    "import open3d_tutorial as o3dtut\n",
    "# change to True if you want to interact with the visualization windows\n",
    "o3dtut.interactive = not \"CI\" in os.environ"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Transformation\n",
    "The geometry types of Open3D have a number of transformation methods. In this tutorial we show how to use `translate`, `rotate`, `scale`, and `transform`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Translate\n",
    "The first transformation method we want to look at is `translate`. The translate method takes a single 3D vector $t$ as input and translates all points/vertices of the geometry by this vector, $v_t = v + t$. The code below shows how the mesh is translated once in the x-directon and once in the y-direction."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mesh = o3d.geometry.TriangleMesh.create_coordinate_frame()\n",
    "mesh_tx = copy.deepcopy(mesh).translate((1.3, 0, 0))\n",
    "mesh_ty = copy.deepcopy(mesh).translate((0, 1.3, 0))\n",
    "print(f'Center of mesh: {mesh.get_center()}')\n",
    "print(f'Center of mesh tx: {mesh_tx.get_center()}')\n",
    "print(f'Center of mesh ty: {mesh_ty.get_center()}')\n",
    "o3d.visualization.draw_geometries([mesh, mesh_tx, mesh_ty])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-info\">\n",
    "    \n",
    "**Note:** \n",
    "\n",
    "The method `get_center` returns the mean of the `TriangleMesh` vertices. That means that for a coordinate frame created at the origin `[0,0,0]`, `get_center` will return `[0.05167549 0.05167549 0.05167549]`.\n",
    "\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The method takes a second argument `relative` that is by default set to `True`. If set to `False`, the center of the geometry is translated directly to the position specified in the first argument."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mesh = o3d.geometry.TriangleMesh.create_coordinate_frame()\n",
    "mesh_mv = copy.deepcopy(mesh).translate((2, 2, 2), relative=False)\n",
    "print(f'Center of mesh: {mesh.get_center()}')\n",
    "print(f'Center of translated mesh: {mesh_mv.get_center()}')\n",
    "o3d.visualization.draw_geometries([mesh, mesh_mv])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Rotation\n",
    "The geometry types of Open3D can also be rotated with the method `rotate`. It takes as first argument a rotation matrix `R`. As rotations in 3D can be parametrized in a number of ways, Open3D provides convenience functions to convert from different parametrizations to rotation matrices:\n",
    "\n",
    "- Convert from [Euler angles](https://en.wikipedia.org/wiki/Euler_angles) with `get_rotation_matrix_from_xyz` (where `xyz` can also be of the form `yzx`, `zxy`, `xzy`, `zyx`, and `yxz`)\n",
    "- Convert from [Axis-angle representation](https://en.wikipedia.org/wiki/Axis%E2%80%93angle_representation) with `get_rotation_matrix_from_axis_angle`\n",
    "- Convert from [Quaternions](https://en.wikipedia.org/wiki/Quaternion) with `get_rotation_matrix_from_quaternion`\n",
    "\n",
    "In the code below we rotate the mesh using Euler angles."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mesh = o3d.geometry.TriangleMesh.create_coordinate_frame()\n",
    "mesh_r = copy.deepcopy(mesh)\n",
    "R = mesh.get_rotation_matrix_from_xyz((np.pi / 2, 0, np.pi / 4))\n",
    "mesh_r.rotate(R, center=(0, 0, 0))\n",
    "o3d.visualization.draw_geometries([mesh, mesh_r])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The function `rotate` has a second argument `center` that is by default set to `True`. This indicates that the object is first centered prior to applying the rotation and then moved back to its previous center. If this argument is set to `False`, then the rotation will be applied directly, such that the whole geometry is rotated around the coordinate center. This implies that the mesh center can be changed after the rotation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mesh = o3d.geometry.TriangleMesh.create_coordinate_frame()\n",
    "mesh_r = copy.deepcopy(mesh).translate((2, 0, 0))\n",
    "mesh_r.rotate(mesh.get_rotation_matrix_from_xyz((np.pi / 2, 0, np.pi / 4)),\n",
    "              center=(0, 0, 0))\n",
    "o3d.visualization.draw_geometries([mesh, mesh_r])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Scale\n",
    "Vertices and points of Open3D geometry types can also be scaled using `scale`, $v_s = s \\cdot v$. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mesh = o3d.geometry.TriangleMesh.create_coordinate_frame()\n",
    "mesh_s = copy.deepcopy(mesh).translate((2, 0, 0))\n",
    "mesh_s.scale(0.5, center=mesh_s.get_center())\n",
    "o3d.visualization.draw_geometries([mesh, mesh_s])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `scale` method also has a second argument `center` that is set to `True` by default. If it is set to `False`, then the object is not centered prior to scaling such that the center of the object can move due to the scaling operation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mesh = o3d.geometry.TriangleMesh.create_coordinate_frame()\n",
    "mesh_s = copy.deepcopy(mesh).translate((2, 1, 0))\n",
    "mesh_s.scale(0.5, center=(0, 0, 0))\n",
    "o3d.visualization.draw_geometries([mesh, mesh_s])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## General transformation\n",
    "Open3D also supports a general transformation defined by a $4\\times4$ homogeneous transformation matrix using the method `transform`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mesh = o3d.geometry.TriangleMesh.create_coordinate_frame()\n",
    "T = np.eye(4)\n",
    "T[:3, :3] = mesh.get_rotation_matrix_from_xyz((0, np.pi / 3, np.pi / 2))\n",
    "T[0, 3] = 1\n",
    "T[1, 3] = 1.3\n",
    "print(T)\n",
    "mesh_t = copy.deepcopy(mesh).transform(T)\n",
    "o3d.visualization.draw_geometries([mesh, mesh_t])"
   ]
  }
 ],
 "metadata": {
  "celltoolbar": "Edit Metadata",
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
