Mercurial > cortex
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 |