comparison thesis/cortex.org @ 474:57c7d5aec8d5

mix in touch; need to clean it up.
author Robert McIntyre <rlm@mit.edu>
date Fri, 28 Mar 2014 21:05:12 -0400
parents 486ce07f5545
children 3ec428e096e5
comparison
equal deleted inserted replaced
473:486ce07f5545 474:57c7d5aec8d5
1552 jMonkeyEngine3 community and is used to record audio for demo 1552 jMonkeyEngine3 community and is used to record audio for demo
1553 videos. 1553 videos.
1554 1554
1555 ** Touch uses hundreds of hair-like elements 1555 ** Touch uses hundreds of hair-like elements
1556 1556
1557 Touch is critical to navigation and spatial reasoning and as such I
1558 need a simulated version of it to give to my AI creatures.
1559
1560 Human skin has a wide array of touch sensors, each of which
1561 specialize in detecting different vibrational modes and pressures.
1562 These sensors can integrate a vast expanse of skin (i.e. your
1563 entire palm), or a tiny patch of skin at the tip of your finger.
1564 The hairs of the skin help detect objects before they even come
1565 into contact with the skin proper.
1566
1567 However, touch in my simulated world can not exactly correspond to
1568 human touch because my creatures are made out of completely rigid
1569 segments that don't deform like human skin.
1570
1571 Instead of measuring deformation or vibration, I surround each
1572 rigid part with a plenitude of hair-like objects (/feelers/) which
1573 do not interact with the physical world. Physical objects can pass
1574 through them with no effect. The feelers are able to tell when
1575 other objects pass through them, and they constantly report how
1576 much of their extent is covered. So even though the creature's body
1577 parts do not deform, the feelers create a margin around those body
1578 parts which achieves a sense of touch which is a hybrid between a
1579 human's sense of deformation and sense from hairs.
1580
1581 Implementing touch in jMonkeyEngine follows a different technical
1582 route than vision and hearing. Those two senses piggybacked off
1583 jMonkeyEngine's 3D audio and video rendering subsystems. To
1584 simulate touch, I use jMonkeyEngine's physics system to execute
1585 many small collision detections, one for each feeler. The placement
1586 of the feelers is determined by a UV-mapped image which shows where
1587 each feeler should be on the 3D surface of the body.
1588
1589 *** Defining Touch Meta-Data in Blender
1590
1591 Each geometry can have a single UV map which describes the
1592 position of the feelers which will constitute its sense of touch.
1593 This image path is stored under the ``touch'' key. The image itself
1594 is black and white, with black meaning a feeler length of 0 (no
1595 feeler is present) and white meaning a feeler length of =scale=,
1596 which is a float stored under the key "scale".
1597
1598 #+name: meta-data
1599 #+begin_src clojure
1600 (defn tactile-sensor-profile
1601 "Return the touch-sensor distribution image in BufferedImage format,
1602 or nil if it does not exist."
1603 [#^Geometry obj]
1604 (if-let [image-path (meta-data obj "touch")]
1605 (load-image image-path)))
1606
1607 (defn tactile-scale
1608 "Return the length of each feeler. Default scale is 0.01
1609 jMonkeyEngine units."
1610 [#^Geometry obj]
1611 (if-let [scale (meta-data obj "scale")]
1612 scale 0.1))
1613 #+end_src
1614
1615 Here is an example of a UV-map which specifies the position of touch
1616 sensors along the surface of the upper segment of the worm.
1617
1618 #+attr_html: width=755
1619 #+caption: This is the tactile-sensor-profile for the upper segment of the worm. It defines regions of high touch sensitivity (where there are many white pixels) and regions of low sensitivity (where white pixels are sparse).
1620 [[../images/finger-UV.png]]
1621
1622 *** Implementation Summary
1623
1624 To simulate touch there are three conceptual steps. For each solid
1625 object in the creature, you first have to get UV image and scale
1626 parameter which define the position and length of the feelers.
1627 Then, you use the triangles which comprise the mesh and the UV
1628 data stored in the mesh to determine the world-space position and
1629 orientation of each feeler. Then once every frame, update these
1630 positions and orientations to match the current position and
1631 orientation of the object, and use physics collision detection to
1632 gather tactile data.
1633
1634 Extracting the meta-data has already been described. The third
1635 step, physics collision detection, is handled in =touch-kernel=.
1636 Translating the positions and orientations of the feelers from the
1637 UV-map to world-space is itself a three-step process.
1638
1639 - Find the triangles which make up the mesh in pixel-space and in
1640 world-space. =triangles= =pixel-triangles=.
1641
1642 - Find the coordinates of each feeler in world-space. These are the
1643 origins of the feelers. =feeler-origins=.
1644
1645 - Calculate the normals of the triangles in world space, and add
1646 them to each of the origins of the feelers. These are the
1647 normalized coordinates of the tips of the feelers. =feeler-tips=.
1648
1649 *** Triangle Math
1650
1651 The rigid objects which make up a creature have an underlying
1652 =Geometry=, which is a =Mesh= plus a =Material= and other important
1653 data involved with displaying the object.
1654
1655 A =Mesh= is composed of =Triangles=, and each =Triangle= has three
1656 vertices which have coordinates in world space and UV space.
1657
1658 Here, =triangles= gets all the world-space triangles which comprise a
1659 mesh, while =pixel-triangles= gets those same triangles expressed in
1660 pixel coordinates (which are UV coordinates scaled to fit the height
1661 and width of the UV image).
1662
1663 #+name: triangles-2
1664 #+begin_src clojure
1665 (in-ns 'cortex.touch)
1666 (defn triangle
1667 "Get the triangle specified by triangle-index from the mesh."
1668 [#^Geometry geo triangle-index]
1669 (triangle-seq
1670 (let [scratch (Triangle.)]
1671 (.getTriangle (.getMesh geo) triangle-index scratch) scratch)))
1672
1673 (defn triangles
1674 "Return a sequence of all the Triangles which comprise a given
1675 Geometry."
1676 [#^Geometry geo]
1677 (map (partial triangle geo) (range (.getTriangleCount (.getMesh geo)))))
1678
1679 (defn triangle-vertex-indices
1680 "Get the triangle vertex indices of a given triangle from a given
1681 mesh."
1682 [#^Mesh mesh triangle-index]
1683 (let [indices (int-array 3)]
1684 (.getTriangle mesh triangle-index indices)
1685 (vec indices)))
1686
1687 (defn vertex-UV-coord
1688 "Get the UV-coordinates of the vertex named by vertex-index"
1689 [#^Mesh mesh vertex-index]
1690 (let [UV-buffer
1691 (.getData
1692 (.getBuffer
1693 mesh
1694 VertexBuffer$Type/TexCoord))]
1695 [(.get UV-buffer (* vertex-index 2))
1696 (.get UV-buffer (+ 1 (* vertex-index 2)))]))
1697
1698 (defn pixel-triangle [#^Geometry geo image index]
1699 (let [mesh (.getMesh geo)
1700 width (.getWidth image)
1701 height (.getHeight image)]
1702 (vec (map (fn [[u v]] (vector (* width u) (* height v)))
1703 (map (partial vertex-UV-coord mesh)
1704 (triangle-vertex-indices mesh index))))))
1705
1706 (defn pixel-triangles
1707 "The pixel-space triangles of the Geometry, in the same order as
1708 (triangles geo)"
1709 [#^Geometry geo image]
1710 (let [height (.getHeight image)
1711 width (.getWidth image)]
1712 (map (partial pixel-triangle geo image)
1713 (range (.getTriangleCount (.getMesh geo))))))
1714 #+end_src
1715
1716 *** The Affine Transform from one Triangle to Another
1717
1718 =pixel-triangles= gives us the mesh triangles expressed in pixel
1719 coordinates and =triangles= gives us the mesh triangles expressed in
1720 world coordinates. The tactile-sensor-profile gives the position of
1721 each feeler in pixel-space. In order to convert pixel-space
1722 coordinates into world-space coordinates we need something that takes
1723 coordinates on the surface of one triangle and gives the corresponding
1724 coordinates on the surface of another triangle.
1725
1726 Triangles are [[http://mathworld.wolfram.com/AffineTransformation.html ][affine]], which means any triangle can be transformed into
1727 any other by a combination of translation, scaling, and
1728 rotation. The affine transformation from one triangle to another
1729 is readily computable if the triangle is expressed in terms of a $4x4$
1730 matrix.
1731
1732 \begin{bmatrix}
1733 x_1 & x_2 & x_3 & n_x \\
1734 y_1 & y_2 & y_3 & n_y \\
1735 z_1 & z_2 & z_3 & n_z \\
1736 1 & 1 & 1 & 1
1737 \end{bmatrix}
1738
1739 Here, the first three columns of the matrix are the vertices of the
1740 triangle. The last column is the right-handed unit normal of the
1741 triangle.
1742
1743 With two triangles $T_{1}$ and $T_{2}$ each expressed as a matrix like
1744 above, the affine transform from $T_{1}$ to $T_{2}$ is
1745
1746 $T_{2}T_{1}^{-1}$
1747
1748 The clojure code below recapitulates the formulas above, using
1749 jMonkeyEngine's =Matrix4f= objects, which can describe any affine
1750 transformation.
1751
1752 #+name: triangles-3
1753 #+begin_src clojure
1754 (in-ns 'cortex.touch)
1755
1756 (defn triangle->matrix4f
1757 "Converts the triangle into a 4x4 matrix: The first three columns
1758 contain the vertices of the triangle; the last contains the unit
1759 normal of the triangle. The bottom row is filled with 1s."
1760 [#^Triangle t]
1761 (let [mat (Matrix4f.)
1762 [vert-1 vert-2 vert-3]
1763 (mapv #(.get t %) (range 3))
1764 unit-normal (do (.calculateNormal t)(.getNormal t))
1765 vertices [vert-1 vert-2 vert-3 unit-normal]]
1766 (dorun
1767 (for [row (range 4) col (range 3)]
1768 (do
1769 (.set mat col row (.get (vertices row) col))
1770 (.set mat 3 row 1)))) mat))
1771
1772 (defn triangles->affine-transform
1773 "Returns the affine transformation that converts each vertex in the
1774 first triangle into the corresponding vertex in the second
1775 triangle."
1776 [#^Triangle tri-1 #^Triangle tri-2]
1777 (.mult
1778 (triangle->matrix4f tri-2)
1779 (.invert (triangle->matrix4f tri-1))))
1780 #+end_src
1781
1782 *** Triangle Boundaries
1783
1784 For efficiency's sake I will divide the tactile-profile image into
1785 small squares which inscribe each pixel-triangle, then extract the
1786 points which lie inside the triangle and map them to 3D-space using
1787 =triangle-transform= above. To do this I need a function,
1788 =convex-bounds= which finds the smallest box which inscribes a 2D
1789 triangle.
1790
1791 =inside-triangle?= determines whether a point is inside a triangle
1792 in 2D pixel-space.
1793
1794 #+name: triangles-4
1795 #+begin_src clojure
1796 (defn convex-bounds
1797 "Returns the smallest square containing the given vertices, as a
1798 vector of integers [left top width height]."
1799 [verts]
1800 (let [xs (map first verts)
1801 ys (map second verts)
1802 x0 (Math/floor (apply min xs))
1803 y0 (Math/floor (apply min ys))
1804 x1 (Math/ceil (apply max xs))
1805 y1 (Math/ceil (apply max ys))]
1806 [x0 y0 (- x1 x0) (- y1 y0)]))
1807
1808 (defn same-side?
1809 "Given the points p1 and p2 and the reference point ref, is point p
1810 on the same side of the line that goes through p1 and p2 as ref is?"
1811 [p1 p2 ref p]
1812 (<=
1813 0
1814 (.dot
1815 (.cross (.subtract p2 p1) (.subtract p p1))
1816 (.cross (.subtract p2 p1) (.subtract ref p1)))))
1817
1818 (defn inside-triangle?
1819 "Is the point inside the triangle?"
1820 {:author "Dylan Holmes"}
1821 [#^Triangle tri #^Vector3f p]
1822 (let [[vert-1 vert-2 vert-3] [(.get1 tri) (.get2 tri) (.get3 tri)]]
1823 (and
1824 (same-side? vert-1 vert-2 vert-3 p)
1825 (same-side? vert-2 vert-3 vert-1 p)
1826 (same-side? vert-3 vert-1 vert-2 p))))
1827 #+end_src
1828
1829 *** Feeler Coordinates
1830
1831 The triangle-related functions above make short work of calculating
1832 the positions and orientations of each feeler in world-space.
1833
1834 #+name: sensors
1835 #+begin_src clojure
1836 (in-ns 'cortex.touch)
1837
1838 (defn feeler-pixel-coords
1839 "Returns the coordinates of the feelers in pixel space in lists, one
1840 list for each triangle, ordered in the same way as (triangles) and
1841 (pixel-triangles)."
1842 [#^Geometry geo image]
1843 (map
1844 (fn [pixel-triangle]
1845 (filter
1846 (fn [coord]
1847 (inside-triangle? (->triangle pixel-triangle)
1848 (->vector3f coord)))
1849 (white-coordinates image (convex-bounds pixel-triangle))))
1850 (pixel-triangles geo image)))
1851
1852 (defn feeler-world-coords
1853 "Returns the coordinates of the feelers in world space in lists, one
1854 list for each triangle, ordered in the same way as (triangles) and
1855 (pixel-triangles)."
1856 [#^Geometry geo image]
1857 (let [transforms
1858 (map #(triangles->affine-transform
1859 (->triangle %1) (->triangle %2))
1860 (pixel-triangles geo image)
1861 (triangles geo))]
1862 (map (fn [transform coords]
1863 (map #(.mult transform (->vector3f %)) coords))
1864 transforms (feeler-pixel-coords geo image))))
1865
1866 (defn feeler-origins
1867 "The world space coordinates of the root of each feeler."
1868 [#^Geometry geo image]
1869 (reduce concat (feeler-world-coords geo image)))
1870
1871 (defn feeler-tips
1872 "The world space coordinates of the tip of each feeler."
1873 [#^Geometry geo image]
1874 (let [world-coords (feeler-world-coords geo image)
1875 normals
1876 (map
1877 (fn [triangle]
1878 (.calculateNormal triangle)
1879 (.clone (.getNormal triangle)))
1880 (map ->triangle (triangles geo)))]
1881
1882 (mapcat (fn [origins normal]
1883 (map #(.add % normal) origins))
1884 world-coords normals)))
1885
1886 (defn touch-topology
1887 "touch-topology? is not a function."
1888 [#^Geometry geo image]
1889 (collapse (reduce concat (feeler-pixel-coords geo image))))
1890 #+end_src
1891 *** Simulated Touch
1892
1893 =touch-kernel= generates functions to be called from within a
1894 simulation that perform the necessary physics collisions to collect
1895 tactile data, and =touch!= recursively applies it to every node in
1896 the creature.
1897
1898 #+name: kernel
1899 #+begin_src clojure
1900 (in-ns 'cortex.touch)
1901
1902 (defn set-ray [#^Ray ray #^Matrix4f transform
1903 #^Vector3f origin #^Vector3f tip]
1904 ;; Doing everything locally reduces garbage collection by enough to
1905 ;; be worth it.
1906 (.mult transform origin (.getOrigin ray))
1907 (.mult transform tip (.getDirection ray))
1908 (.subtractLocal (.getDirection ray) (.getOrigin ray))
1909 (.normalizeLocal (.getDirection ray)))
1910
1911 (import com.jme3.math.FastMath)
1912
1913 (defn touch-kernel
1914 "Constructs a function which will return tactile sensory data from
1915 'geo when called from inside a running simulation"
1916 [#^Geometry geo]
1917 (if-let
1918 [profile (tactile-sensor-profile geo)]
1919 (let [ray-reference-origins (feeler-origins geo profile)
1920 ray-reference-tips (feeler-tips geo profile)
1921 ray-length (tactile-scale geo)
1922 current-rays (map (fn [_] (Ray.)) ray-reference-origins)
1923 topology (touch-topology geo profile)
1924 correction (float (* ray-length -0.2))]
1925
1926 ;; slight tolerance for very close collisions.
1927 (dorun
1928 (map (fn [origin tip]
1929 (.addLocal origin (.mult (.subtract tip origin)
1930 correction)))
1931 ray-reference-origins ray-reference-tips))
1932 (dorun (map #(.setLimit % ray-length) current-rays))
1933 (fn [node]
1934 (let [transform (.getWorldMatrix geo)]
1935 (dorun
1936 (map (fn [ray ref-origin ref-tip]
1937 (set-ray ray transform ref-origin ref-tip))
1938 current-rays ray-reference-origins
1939 ray-reference-tips))
1940 (vector
1941 topology
1942 (vec
1943 (for [ray current-rays]
1944 (do
1945 (let [results (CollisionResults.)]
1946 (.collideWith node ray results)
1947 (let [touch-objects
1948 (filter #(not (= geo (.getGeometry %)))
1949 results)
1950 limit (.getLimit ray)]
1951 [(if (empty? touch-objects)
1952 limit
1953 (let [response
1954 (apply min (map #(.getDistance %)
1955 touch-objects))]
1956 (FastMath/clamp
1957 (float
1958 (if (> response limit) (float 0.0)
1959 (+ response correction)))
1960 (float 0.0)
1961 limit)))
1962 limit])))))))))))
1963
1964 (defn touch!
1965 "Endow the creature with the sense of touch. Returns a sequence of
1966 functions, one for each body part with a tactile-sensor-profile,
1967 each of which when called returns sensory data for that body part."
1968 [#^Node creature]
1969 (filter
1970 (comp not nil?)
1971 (map touch-kernel
1972 (filter #(isa? (class %) Geometry)
1973 (node-seq creature)))))
1974 #+end_src
1975
1976
1977 Armed with the =touch!= function, =CORTEX= becomes capable of giving
1978 creatures a sense of touch. A simple test is to create a cube that is
1979 outfitted with a uniform distrubition of touch sensors. It can feel
1980 the ground and any balls that it touches.
1981
1982 # insert touch cube image; UV map
1983 # insert video
1984
1557 ** Proprioception is the sense that makes everything ``real'' 1985 ** Proprioception is the sense that makes everything ``real''
1558 1986
1559 ** Muscles are both effectors and sensors 1987 ** Muscles are both effectors and sensors
1560 1988
1561 ** =CORTEX= brings complex creatures to life! 1989 ** =CORTEX= brings complex creatures to life!
1562 1990
1563 ** =CORTEX= enables many possiblities for further research 1991 ** =CORTEX= enables many possiblities for further research
1564 1992
1565 * COMMENT Empathy in a simulated worm 1993 * COMMENT Empathy in a simulated worm
1566 1994
1567 Here I develop a computational model of empathy, using =CORTEX= as a 1995 Here I develop a computational model of empathy, using =CORTEX= as a
1568 base. Empathy in this context is the ability to observe another 1996 base. Empathy in this context is the ability to observe another
1569 creature and infer what sorts of sensations that creature is 1997 creature and infer what sorts of sensations that creature is