{
  "cells": [
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "%matplotlib inline"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Beam Shape {#light_beam_shape_example}\n\nThe default directional lights are infinitely distant point sources, for\nwhich the only geometric customization option is the choice of beam\ndirection defined by the light\\'s position and focal point. Positional\nlights, however, have more options for beam customization.\n\nConsider two hemispheres:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "import pyvista as pv\n\nplotter = pv.Plotter()\n\nhemi = pv.Sphere().clip()\nhemi.translate((-1, 0, 0), inplace=True)\nplotter.add_mesh(hemi, color='cyan', smooth_shading=True)\n\nhemi = hemi.rotate_z(180, inplace=False)\nplotter.add_mesh(hemi, color='cyan', smooth_shading=True)\n\nplotter.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "We can see that the default lighting does a very good job of\narticulating the shape of the hemispheres.\n\nLet\\'s shine a directional light on them, positioned between the\nhemispheres and oriented along their centers:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "plotter = pv.Plotter(lighting='none')\n\nhemi = pv.Sphere().clip()\nhemi.translate((-1, 0, 0), inplace=True)\nplotter.add_mesh(hemi, color='cyan', smooth_shading=True)\n\nhemi = hemi.rotate_z(180, inplace=False)\nplotter.add_mesh(hemi, color='cyan', smooth_shading=True)\n\nlight = pv.Light(position=(0, 0, 0), focal_point=(-1, 0, 0))\nplotter.add_light(light)\n\nplotter.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Both hemispheres have their surface lit on the side that faces the\nlight. This is consistent with the point source positioned at infinity,\ndirected from the light\\'s nominal position toward the focal point.\n\nNow let\\'s change the light to a positional light (but not a spotlight):\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "plotter = pv.Plotter(lighting='none')\n\nhemi = pv.Sphere().clip()\nhemi.translate((-1, 0, 0), inplace=True)\nplotter.add_mesh(hemi, color='cyan', smooth_shading=True)\n\nhemi = hemi.rotate_z(180, inplace=False)\nplotter.add_mesh(hemi, color='cyan', smooth_shading=True)\n\nlight = pv.Light(position=(0, 0, 0), focal_point=(-1, 0, 0))\nlight.positional = True\nlight.cone_angle = 90\nplotter.add_light(light)\n\nplotter.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Now the inner surface of both hemispheres is lit. A positional light\nwith a cone angle of 90 degrees (or more) acts as a point source located\nat the light\\'s nominal position. It could still display attenuation,\nsee the `attenuation_example`{.interpreted-text role=\"ref\"} example.\n\nSwitching to a spotlight (i.e. a positional light with a cone angle less\nthan 90 degrees) will enable beam shaping using the\n`pyvista.Light.exponent`{.interpreted-text role=\"py:attr\"} property.\nLet\\'s put our hemispheres side by side for this, and put a light in the\ncenter of each: one spotlight, one merely positional.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "plotter = pv.Plotter(lighting='none')\n\nhemi = pv.Sphere().clip()\nplotter.add_mesh(hemi, color='cyan', smooth_shading=True)\n\noffset = 1.5\nhemi = hemi.translate((0, offset, 0), inplace=False)\nplotter.add_mesh(hemi, color='cyan', smooth_shading=True)\n\n# non-spot positional light in the center of the first hemisphere\nlight = pv.Light(position=(0, 0, 0), focal_point=(-1, 0, 0))\nlight.positional = True\nlight.cone_angle = 90\n# add attenuation to reduce cross-talk between the lights\nlight.attenuation_values = (0, 0, 2)\nplotter.add_light(light)\n\n# spotlight in the center of the second hemisphere\nlight = pv.Light(position=(0, offset, 0), focal_point=(-1, offset, 0))\nlight.positional = True\nlight.cone_angle = 89.9\n# add attenuation to reduce cross-talk between the lights\nlight.attenuation_values = (0, 0, 2)\nplotter.add_light(light)\n\nplotter.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Even though the two lights only differ by a fraction of a degree in cone\nangle, the beam shaping effect enabled for spotlights causes a marked\ndifference in the result.\n\nOnce we have a spotlight we can change its\n`pyvista.Light.exponent`{.interpreted-text role=\"py:attr\"} to make the\nbeam shape sharper or broader. Three spotlights with varying sharpness:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "plotter = pv.Plotter(lighting='none')\nhemi_template = pv.Sphere().clip()\n\ncenters = [(0, 0, 0), (0, 1.5, 0), (0, 1.5 * 0.5, 1.5 * 3**0.5 / 2)]\nexponents = [1, 0.3, 5]\n\nfor center, exponent in zip(centers, exponents):\n    hemi = hemi_template.copy()\n    hemi.translate(center, inplace=True)\n    plotter.add_mesh(hemi, color='cyan', smooth_shading=True)\n\n    # spotlight in the center of the hemisphere, shining into it\n    focal_point = center[0] - 1, center[1], center[2]\n    light = pv.Light(position=center, focal_point=focal_point)\n    light.positional = True\n    light.cone_angle = 89.9\n    light.exponent = exponent\n    # add attenuation to reduce cross-talk between the lights\n    light.attenuation_values = (0, 0, 2)\n    plotter.add_light(light)\n\nplotter.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "The spotlight with exponent 0.3 is practically uniform, and the one with\nexponent 5 is visibly focused along the axis of the light.\n"
      ]
    }
  ],
  "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.12.2"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}