lib/robot/robot_links.ex

defmodule Robot.Links do

    @moduledoc """
    Provide functions that import robot links
    """

    import SweetXml
    import Robot.Utils
    import Robot.Mesh
    alias ELA.Matrix, as: Matrix
    alias ELA.Vector, as: Vector


    @doc """
        Get base frame information in a Map
        TODO: develop information model for robot.
    ## Parameters
        - xmldoc: xml file readed
    ## Examples
        result = get_base_frame(xmldoc)
    """
    @spec get_base_frame(binary()) :: Robot.Frame
    def get_base_frame(xmldoc) do
        result = xmldoc |> xpath(
            ~x"//library_visual_scenes/visual_scene",
            translates: ~x"./node/node/node[@name=\"base\"]/translate/text()"l,
            rotate_axis_sid: ~x"./node/node/node[@name=\"base\"]/rotate/@sid",
            rotates: ~x"./node/node/node[@name=\"base\"]/rotate/text()"l,
        )
        frame = convert_to_frame(result, "base")
    end


    @doc """
        Get base link information in a Map
        TODO: develop information model for robot.
    ## Parameters
        - xmldoc: xml file readed
    ## Examples
        result = get_base_link(xmldoc)
    """
    @spec get_base_link(binary()) :: Robot.Link
    def get_base_link(xmldoc) do
        #result = xmldoc |> xpath(

         #   ~x"///library_visual_scenes/visual_scene",

        #    link_name: ~x"./node/node/@name",

        ##    instance_geometry: ~x"./node/node/instance_geometry/@url",

        #    translate: ~x"./node/node/translate/text()",

         #   rotate: ~x"./node/node/rotate/text()"

        link_name = 'base_link'
        
        link_base_path = get_link_path(link_name)

        result = get_link(xmldoc, link_base_path, link_name)

        frame = get_base_frame(xmldoc)

        link_base = %Robot.Link{link_name: result[:link_name], instance_geometry: result[:instance_geometry], frame: frame, linked_links: result[:linked_links]}
    
    end
    

    @doc """
        Get link_1 information and put it in a Map
        TODO: develop information model for robot.
    ## Parameters
        - xmldoc: xml file readed
    ## Examples
        iex> get_link_1(xmldoc)
        %{frame: 
            %{frames: 
                [%{rotate: [1.0, 0.0, 0.0, 0.0], translate: [0.0, 0.0, 0.0]}, 
                 %{rotate: [1.0, 0.0, 0.0, 0.0], translate: [0.0, 0.0, 0.227]}, 
                 %{rotate: [1.0, 0.0, 0.0, 0.0], translate: [0.0, 0.0, 0.0]}], 
              name: 'link_1', 
              rotate_axis: [0.0, 0.0, 1.0, 0.0], 
              rotate_axis_sid: 'node_joint_1_axis0'}, 
          instance_geometry: ['#gkmodel0_link_1_geom0'], 
          link_name: 'link_1', 
          linked_links: ['link_2', 'link_cylinder']}
    """
    @spec get_link_1(binary()) :: Robot.Link
    def get_link_1(xmldoc) do
        link_name = 'link_1'
        
        link_1_path = get_link_path(link_name)

        result = get_link(xmldoc, link_1_path, link_name)

        frame = get_link_frame(xmldoc, link_1_path, link_name)

        link_1 = %Robot.Link{link_name: result[:link_name], instance_geometry: result[:instance_geometry], frame: frame, linked_links: result[:linked_links]}
    end


    @doc """
    TODO: merge to get_link_n, after development of information model
    """
    def get_link_2(xmldoc) do
        link_name = 'link_2'
        link_2_path=get_link_path(link_name)
        result = get_link(xmldoc, link_2_path, link_name)
        frame = get_link_frame(xmldoc, link_2_path, link_name)
        link_2 = %Robot.Link{link_name: result[:link_name], instance_geometry: result[:instance_geometry], frame: frame, linked_links: result[:linked_links]}

    end
    @doc """
    TODO: merge to get_link_n, after development of information model
    """
    def get_link_3(xmldoc) do
        link_name = 'link_3'
        link_3_path = get_link_path(link_name)
        result = get_link(xmldoc, link_3_path, link_name)
        frame = get_link_frame(xmldoc, link_3_path, link_name)
        link_3 = %Robot.Link{link_name: result[:link_name], instance_geometry: result[:instance_geometry], frame: frame, linked_links: result[:linked_links]}
    end
    @doc """
    TODO: merge to get_link_n, after development of information model
    """
    def get_link_4(xmldoc) do
        link_name = 'link_4'
        link_4_path = get_link_path(link_name)
        result = get_link(xmldoc, link_4_path, link_name)
        frame = get_link_frame(xmldoc, link_4_path, link_name)
        link_4 = %Robot.Link{link_name: result[:link_name], instance_geometry: result[:instance_geometry], frame: frame, linked_links: result[:linked_links]}
    end
    @doc """
    TODO: merge to get_link_n, after development of information model
    """
    def get_link_5(xmldoc) do
        link_name = 'link_5'
        link_5_path = get_link_path(link_name)
        result = get_link(xmldoc, link_5_path, link_name)
        frame = get_link_frame(xmldoc, link_5_path, link_name)
        link_5 = %Robot.Link{link_name: result[:link_name], instance_geometry: result[:instance_geometry], frame: frame, linked_links: result[:linked_links]}
    end
    @doc """
    TODO: merge to get_link_n, after development of information model
    """
    def get_link_6(xmldoc) do
        link_name = 'link_6'
        link_6_path = get_link_path(link_name)
        result = get_link(xmldoc, link_6_path, link_name)
        frame = get_link_frame(xmldoc, link_6_path, link_name)
        link_6 = %Robot.Link{link_name: result[:link_name], instance_geometry: result[:instance_geometry], frame: frame, linked_links: result[:linked_links]}
    end

    @doc """
        Get link path based on link name
        TODO: develop information model for robot.
    ## Parameters
        - link: name of link
    ## Examples
        link_name = 'link_6'
        link_6_path = get_link_path(link_name)
    """
    @spec get_link_path(Charlist) :: %SweetXpath{}
    def get_link_path(link) do
        link_base_name = 'base_link'
        link_1_name = 'link_1'
        link_2_name = 'link_2'
        link_3_name = 'link_3'
        link_4_name = 'link_4'
        link_5_name = 'link_5'
        link_6_name = 'link_6'
        link_base_path = %SweetXpath{path: '//library_visual_scenes/visual_scene/node'}
        link_1_path = %SweetXpath{path: '//library_visual_scenes/visual_scene/node/node'}
        link_2_path = %SweetXpath{path: '//library_visual_scenes/visual_scene/node/node/node[@name=\"'++link_1_name++'\"]'}
        link_3_path = %SweetXpath{path: '//library_visual_scenes/visual_scene/node/node/node[@name=\"'++link_1_name++'\"]/node[@name=\"'++link_2_name++'\"]'}
        link_4_path = %SweetXpath{path: '//library_visual_scenes/visual_scene/node/node/node[@name=\"'++link_1_name++'\"]/node[@name=\"'++link_2_name++'\"]/node[@name=\"'++link_3_name++'\"]'}
        link_5_path = %SweetXpath{path: '//library_visual_scenes/visual_scene/node/node/node[@name=\"'++link_1_name++'\"]/node[@name=\"'++link_2_name++'\"]/node[@name=\"'++link_3_name++'\"]/node[@name=\"'++link_4_name++'\"]'}
        link_6_path = %SweetXpath{path: '//library_visual_scenes/visual_scene/node/node/node[@name=\"'++link_1_name++'\"]/node[@name=\"'++link_2_name++'\"]/node[@name=\"'++link_3_name++'\"]/node[@name=\"'++link_4_name++'\"]/node[@name=\"'++link_5_name++'\"]'}
        link_paths = [
                        %{name: link_base_name, path: link_base_path},
                        %{name: link_1_name, path: link_1_path},
                        %{name: link_2_name, path: link_2_path},
                        %{name: link_3_name, path: link_3_path},
                        %{name: link_4_name, path: link_4_path},
                        %{name: link_5_name, path: link_5_path},
                        %{name: link_6_name, path: link_6_path}
                     ]
        link_paths
        |> Enum.find(fn map -> map[:name] == link end)
        |> Map.fetch!(:path)
    end

    @doc """
        Get link information and return a Map based on name input
        TODO: develop information model for robot.
    ## Parameters
        - xmldoc: xml file readed
        - link_path: link node path in xml
        - link_name: link name 
    ## Examples
        link_name = 'link_6'
        link_6_path = get_link_path(link_name)
        result = get_link(xmldoc, link_6_path, link_name)
    """
    @spec get_link(binary(), %SweetXpath{}, Charlist) :: %{link_name: Charlist,
                                       instance_geometry: List, 
                                       linked_links: List}
    def get_link(xmldoc, link_path, link_name) do
        link_name_path = %SweetXpath{path: './node[@name=\"'++link_name++'\"]/@name'}
        instance_geometry_path = %SweetXpath{path: './node[@name=\"'++link_name++'\"]/instance_geometry/@url', is_list: true}
        linked_links_path = %SweetXpath{path: './node[@name=\"'++link_name++'\"]/node/@name', is_list: true}
        result = xmldoc |> xpath(
            link_path,
            link_name: link_name_path,
            instance_geometry: instance_geometry_path,
            linked_links: linked_links_path
        )
    end

    @doc """
        Get link frames information and return a Map based on name input
        TODO: develop information model for robot.
    ## Parameters
        - xmldoc: xml file readed
        - link_path: link node path in xml
        - link_name: link name 
    ## Examples
        link_name = 'link_6'
        link_6_path = get_link_path(link_name)
        result = get_link(xmldoc, link_6_path, link_name)
    """
    @spec get_link_frame(binary(), %SweetXpath{}, Charlist) :: Robot.Frame
    def get_link_frame(xmldoc, link_path, link_name) do
        translates_path = %SweetXpath{path: './node[@name=\"'++link_name++'\"]/translate/text()', is_list: true}
        rotate_axis_sid_path = %SweetXpath{path: './node[@name=\"'++link_name++'\"]/rotate/@sid'}
        rotates_path = %SweetXpath{path: './node[@name=\"'++link_name++'\"]/rotate/text()', is_list: true}
        result = xmldoc |> xpath(
            link_path,
            translates: instance_geometry_path = translates_path,
            rotate_axis_sid: rotate_axis_sid_path,
            rotates: rotates_path
        )
        frame = convert_to_frame(result, link_name)
    end

    @doc """
    TODO
    """
    def get_tool_frame do
        
    end

    @doc """
        Convert string list to float list in frame, and add name to frame
    ## Parameters
        - xmlresult: frame
        - name: link node path in xml
    ## Examples
        translates_path = %SweetXpath{path: './node[@name=\"'++link_name++'\"]/translate/text()', is_list: true}
        rotate_axis_sid_path = %SweetXpath{path: './node[@name=\"'++link_name++'\"]/rotate/@sid'}
        rotates_path = %SweetXpath{path: './node[@name=\"'++link_name++'\"]/rotate/text()', is_list: true}
        result = xmldoc |> xpath(
            link_path,
            translates: instance_geometry_path = translates_path,
            rotate_axis_sid: rotate_axis_sid_path,
            rotates: rotates_path
        )
        frame = convert_to_frame(result, link_name)
    """   
    @spec convert_to_frame(binary(), Charlist) :: Robot.Frame
    def convert_to_frame(xmlresult,name) do
        [t1 | t] = xmlresult[:translates]
        [t2 | [t3]] = t
        [r1 | r] = xmlresult[:rotates]
        [r2 | r_] = r
        [r3 | [r4]] = r_

        frame = %Robot.Frame{name: name,
                  rotate_axis_sid: xmlresult[:rotate_axis_sid], 
                  frames: [%{translate: charlist_to_flist(t1), rotate: charlist_to_flist(r1)}, 
                           %{translate: charlist_to_flist(t2), rotate: charlist_to_flist(r2)}, 
                           %{translate: charlist_to_flist(t3), rotate: charlist_to_flist(r4)}],
                  rotate_axis: charlist_to_flist(r3)
                           }
    end

    @doc """
        Convert List of Char to a List of FLoat.
    ## Parameters
        - charlist: Charlist Example: '1 2 3'
    ## Examples
        iex(6)> charlist_to_flist('1 2 3') 
        [1.0, 2.0, 3.0]
    """
    @spec charlist_to_flist(Charlist) :: List
    def charlist_to_flist(charlist) do
        pos_list_f = String.split(to_string(charlist))
                        |> Enum.map(fn s -> Float.parse(s) |> elem(0) end) 
    end

    @doc """
        Add meshes and transform info to robot model by using the name of the link
    ## Parameters
        - robot_model: robot model used to store mesh and tranform information
        - geometries: List of geometries to store geometry data
        - names: names of the gerometries
        - link: robto link
        - name: name of the geometry
        - joint_value: angle of the joint in rad
    ## Examples
        geometries = File.read!("geometries.txt") |> :erlang.binary_to_term
        link_base = File.read!("link_base.txt") |> :erlang.binary_to_term
        link_1 = File.read!("link_1.txt") |> :erlang.binary_to_term
        link_2 = File.read!("link_2.txt") |> :erlang.binary_to_term
        link_3 = File.read!("link_3.txt") |> :erlang.binary_to_term
        link_4 = File.read!("link_4.txt") |> :erlang.binary_to_term
        link_5 = File.read!("link_5.txt") |> :erlang.binary_to_term
        link_6 = File.read!("link_6.txt") |> :erlang.binary_to_term
        robot_model = %{points: [], indices: [], translates: [], rotates: []}
        robot_model = add_link_to_robot_model_by_name(robot_model, geometries, names, link_base, 'gkmodel0_base_link_geom0', 0.0)
        robot_model = add_link_to_robot_model_by_name(robot_model, geometries, names, link_1, 'gkmodel0_link_1_geom0', :math.pi/6)
        robot_model = add_link_to_robot_model_by_name(robot_model, geometries, names, link_2, 'gkmodel0_link_2_geom0', :math.pi/6)
        robot_model = add_link_to_robot_model_by_name(robot_model, geometries, names, link_3, 'gkmodel0_link_3_geom0', :math.pi/6)
        robot_model = add_link_to_robot_model_by_name(robot_model, geometries, names, link_4, 'gkmodel0_link_4_geom0', :math.pi/6)
        robot_model = add_link_to_robot_model_by_name(robot_model, geometries, names, link_5, 'gkmodel0_link_5_geom0', :math.pi/6)
        robot_model = add_link_to_robot_model_by_name(robot_model, geometries, names, link_6, 'gkmodel0_link_6_geom0', :math.pi/6)
    """
    @spec add_link_to_robot_model_by_name(%{points: List, indices: List, translates: List, rotates: List}, List, List, %{link_name: Charlist, instance_geometry: List, frame: %{name: Charlist, rotate_axis_sid: Charlist, frames: List, rotate_axis: List}, linked_links: List}, Charlist, Float) :: Rbot.Mesh
    def add_link_to_robot_model_by_name(robot_model, geometries, names, link, name, joint_value) do
        index = Enum.find_index(names,fn x-> x==name end) 
        points = robot_model.points
        indices = robot_model.indices
        translates = robot_model.translates
        rotates = robot_model.rotates
        new_points = List.insert_at(points, length(points),Enum.at(geometries,index)|>Map.fetch!(:positions))
        robot_model = %{robot_model | points: new_points}
        new_indices = List.insert_at(indices, length(indices),Enum.at(geometries,index)|>Map.fetch!(:triangles))
        robot_model = %{robot_model | indices: new_indices}
        
        tranf_temp = apply_joint(List.last(robot_model.translates),List.last(robot_model.rotates), get_trans_or_rot_from_link(link, :translate),get_trans_or_rot_from_link(link, :rotate), get_joint_from_link(link, joint_value))
        
        new_translates = List.insert_at(translates, length(translates),tranf_temp[:trans])
        
        
        robot_model = %{robot_model | translates: new_translates}
        new_rotates = List.insert_at(rotates, length(rotates),tranf_temp[:rot])
        robot_model = %{robot_model | rotates: new_rotates}
    end

    defp get_trans_or_rot_from_link(link, key) do
        link|>Map.fetch!(:frame)|>Map.fetch!(:frames)|>Enum.at(1)|>Map.fetch!(key)
    end

    @doc """
        Get rotaion vector based on rotation axis and input joint angle.
    ## Parameters
        - link: link
        - joint_value: angle of the joint in rad
    ## Examples
        get_joint_from_link(link, joint_value)
    """
    @spec get_joint_from_link(%{link_name: Charlist, instance_geometry: List, frame: %{name: Charlist, rotate_axis_sid: Charlist, frames: List, rotate_axis: List}, linked_links: List}, Float) :: List
    defp get_joint_from_link(link, joint_value) do

        rot = link|>Map.fetch!(:frame)|>Map.fetch!(:rotate_axis)
        
        rot = List.replace_at(rot, 3,joint_value)

        rot
    end

    #def add_two_list(old_trans, new_trans) do

    #    if old_trans do

     #       trans = new_trans|> Enum.with_index |> Enum.map(fn({x, i}) -> x + Enum.at(old_trans,i) end)

     #   else

     #       trans = new_trans

     #   end

    #end



    @doc """
        Perform frame transformation.
    ## Parameters
        - old_trans: the first translation vector
        - old_rots:  the first rotation axis+angle 
        - new_trans: the secord tranlation vector 
        - new_rots:  the secord rotation axis+angle 
        - joint_vector: the joint rotaion axis+angle
    """
    @spec apply_joint(List, List, List, List, List) :: List
    def apply_joint(old_trans, old_rots, new_trans, new_rots, joint_vector) when old_trans==nil do
        apply_joint([0.0,0.0,0.0], [1.0,0.0,0.0,0.0], new_trans, new_rots, joint_vector)
    end

    def apply_joint(old_trans, old_rots, new_trans, new_rots, joint_vector) do

        
        old_mat = matrixFromAxisAngle(old_rots, old_trans)
        new_mat = matrixFromAxisAngle(new_rots, new_trans)
        joint_vector = matrixFromAxisAngle(joint_vector, [0.0,0.0,0.0])
        mat_temp = Matrix.mult(old_mat, new_mat)
        axisAngleFromMatrix(Matrix.mult(mat_temp, joint_vector))
    end


    @doc """
        Generate Robot mesh based on joints
    ## Parameters
        - joints: Joints input
    ## Examples
        joints = %Robot.Joints{joint1: :math.pi/6, joint2: :math.pi/6, joint3: :math.pi/6, joint4: :math.pi/6, joint5: :math.pi/6, joint6: :math.pi/6}
        robot_model = get_robot_model_based_on_joint_value(joints)
        points = robot_model.points
        indices = robot_model.indices
        translates = robot_model.translates
        rotates = robot_model.rotates
        draw(points,indices,translates,rotates)
    """
    @spec get_robot_model_based_on_joint_value(Robot.Joints) :: Robot.Mesh
    def get_robot_model_based_on_joint_value(joints) do

        geometries = File.read!("geometries.txt") |> :erlang.binary_to_term
        link_base = File.read!("link_base.txt") |> :erlang.binary_to_term
        link_1 = File.read!("link_1.txt") |> :erlang.binary_to_term
        link_2 = File.read!("link_2.txt") |> :erlang.binary_to_term
        link_3 = File.read!("link_3.txt") |> :erlang.binary_to_term
        link_4 = File.read!("link_4.txt") |> :erlang.binary_to_term
        link_5 = File.read!("link_5.txt") |> :erlang.binary_to_term
        link_6 = File.read!("link_6.txt") |> :erlang.binary_to_term

        robot_model = %Robot.Mesh{points: [], indices: [], translates: [], rotates: []}

        
        #points = geometries |> Enum.map(fn map -> map[:positions] end)

        #indices = geometries |> Enum.map(fn map -> map[:triangles] end)

        #translates = geometries |> Enum.map(fn map -> [:rand.uniform() * 5, :rand.uniform() * 5,:rand.uniform() * 5] end)

        #rotates = geometries |> Enum.map(fn map -> [:rand.uniform() * 5, :rand.uniform() * 5,:rand.uniform() * 5] end)

        names = geometries |> Enum.map(fn map -> map[:name] end)

        #draw([Enum.at(points,1)],[Enum.at(indices,1)],[[0.0,0.0,0.0]],[[0.0,0.0,0.0]])


        robot_model = add_link_to_robot_model_by_name(robot_model, geometries, names, link_base, 'gkmodel0_base_link_geom0', 0.0)
        robot_model = add_link_to_robot_model_by_name(robot_model, geometries, names, link_1, 'gkmodel0_link_1_geom0', joints.joint1)
        robot_model = add_link_to_robot_model_by_name(robot_model, geometries, names, link_2, 'gkmodel0_link_2_geom0', joints.joint2)
        robot_model = add_link_to_robot_model_by_name(robot_model, geometries, names, link_3, 'gkmodel0_link_3_geom0', joints.joint3)
        robot_model = add_link_to_robot_model_by_name(robot_model, geometries, names, link_4, 'gkmodel0_link_4_geom0', joints.joint4)
        robot_model = add_link_to_robot_model_by_name(robot_model, geometries, names, link_5, 'gkmodel0_link_5_geom0', joints.joint5)
        robot_model = add_link_to_robot_model_by_name(robot_model, geometries, names, link_6, 'gkmodel0_link_6_geom0', joints.joint6)
    end
end