78: 'struct 101' has no member named 'rotate'


script_name('mouseFlight')
script_author('(^^)')
script_description('i like war thunder.')

local maf 		= require 'maf'
local socket 	= require "socket"

local lastUpdateTime = socket.gettime()


--- Main
function main()
	if not isSampLoaded() or not isCleoLoaded() or not isSampfuncsLoaded() then return end
	while not isSampAvailable() do wait(100) end
	sampRegisterChatCommand("maf", initialize)
	while true do wait(0)
		update()
	end
end

-- script variables
local aircraft 				= nil
local mouseAim 				= nil
local cameraRig 			= nil
local cam 					= nil

local useFixed 				= true      -- Follow aircraft using fixed update loop
local camSmoothSpeed 		= 5         -- How quickly the camera tracks the mouse aim point
local mouseSensitivity 		= 3         -- Mouse sensitivity for the mouse flight target
local aimDistance 			= 500		-- How far the boresight and mouse flight are from the aircraft
local showDebugInfo 		= false		-- How far the boresight and mouse flight are from the aircraft

local horisontal_inversion	= false
local vertical_inversion	= false

local frozenDirection 		= maf.vector(0, 1, 0)
local isMouseAimFrozen 		= false

local cameraDistance		= 5
local cameraOffset			= maf.vector(0, 0, 2)

function getBoresightPos()
	return getPlanePosition() + (getPlaneRotation() * maf.vector(0, aimDistance, 0))
end

function getMouseAimPos()
	if isMouseAimFrozen then
        return getPlanePosition() + (frozenDirection * aimDistance)
    else
        return getPlanePosition() + (mouseAim * aimDistance)
    end
end

function update()
	deltaTime = socket.gettime() - lastUpdateTime
	if isActive then
		updateCameraPos()                
		rotateRig()
		if showDebugInfo then DrawGizmos() end
	end
	lastUpdateTime = socket.gettime()
end

function rotateRig()
    if not mouseAim or not cam or not cameraRig then return end
    
    isMouseAimFrozen = isKeyDown(67)
	if isMouseAimFrozen then frozenDirection:set(mouseAim * maf.vector(0, 1, 0)) end
	
	local screenW, screenH 	= getScreenResolution()
	local mouseX, mouseY	= getPcMouseMovement()
	local halfW 			= screenW / 2
	local halfH 			= screenH / 2
	
	local mouseX = (mouseX)/halfW * mouseSensitivity * (( horisontal_inversion 	and -1) or 1)
	local mouseY = (mouseY)/halfH * mouseSensitivity * (( vertical_inversion 	and -1) or 1)

    mouseAim:rotate(maf.quat():setAngleAxis(mouseX, cameraRig * maf.vector(1, 0, 0))) -- !!! cam ->> cameraRig !!!
    mouseAim:rotate(maf.quat():setAngleAxis(mouseY, cameraRig * maf.vector(0, 0, 1))) -- !!! cam ->> cameraRig !!!

    local upVec = math.abs((mouseAim * maf.vector(0, 1, 0)).y) > 0.9 and (cameraRig * maf.vector(0, 0, 1)) or maf.vector(0, 0, 1)


	cameraRig:setRotation(damp(cameraRig,
						  lookRotation((mouseAim * maf.vector(0, 1, 0)), upVec),
						  camSmoothSpeed,
						  deltaTime))
end

function updateCameraPos()
	local cameraPos = (cameraRig * maf.vector(0, cameraDistance*-1, 0)) -- cam position vec3 w/o offset
	print(cameraPos:unpack())
	local cameraVec = -cameraPos										-- inverted to look from cam to plane 
	print(cameraVec:unpack())
	cameraPos = cameraPos + cameraOffset
	cam = maf.rotation.fromDirection(cameraVec)
	
	local camX, camY, camZ = cameraPos:unpack()
	local rotX, rotY, rotZ = quaternionToEuler(cameraRig:unpack())
	attachCameraToVehicle(storeCarCharIsInNoSave(playerPed), camX, camY, camZ, rotX, rotY, rotZ, 0, 1)
end

local function damp(a, b, lambda, dt)
    return a:slerp(b, 1 - math.exp(-lambda * dt))
end


local function DrawGizmos()
    if showDebugInfo then
        -- Store old color
        local oldColorR, oldColorG, oldColorB, oldColorA = getDrawColor()

        -- Draw Boresight Position
        setDrawColor(255, 255, 255, 255)
        local boresightPos = GetBoresightPos()
        drawCircle(boresightPos.x, boresightPos.y, 10)

        -- Draw Mouse Aim Position
        setDrawColor(255, 0, 0, 255)
        local mouseAimPos = GetMouseAimPos()
        drawCircle(mouseAimPos.x, mouseAimPos.y, 10)

        -- Draw Axes for Mouse Aim Transform
        local aimPos = mouseAim:position()
        setDrawColor(0, 0, 255, 255)
        drawLine(aimPos, aimPos + mouseAim:forward() * 50)
        setDrawColor(0, 255, 0, 255)
        drawLine(aimPos, aimPos + mouseAim:up() * 50)
        setDrawColor(255, 0, 0, 255)
        drawLine(aimPos, aimPos + mouseAim:right() * 50)

        -- Restore old color
        setDrawColor(oldColorR, oldColorG, oldColorB, oldColorA)
    end
end




-- Initialization function
function initialize()
	if not isActive then 
		if isCharInAnyPlane(playerPed) then
			local planeHandle	= storeCarCharIsInNoSave(playerPed) 
			local x, y, z, w 	= getVehicleQuaternion(planeHandle)
			local planeQuat 	= maf.quat(x, y, z, w)
			mouseAim 			= planeQuat 
			cameraRig 			= planeQuat
			lastUpdateTime		= socket.gettime()
			
			isActive = true
		else 
			sampAddChatMessage("в самолет сядь", -1)
		end
	else 
		isActive = false
	end
end


function lookRotation(forward, upwards)

    forward = forward:normalize()
    local right = upwards:cross(forward):normalize()
    local correctedUpwards = forward:cross(right)

    local rotationMatrix = {
        {right.x, correctedUpwards.x, forward.x},
        {right.y, correctedUpwards.y, forward.y},
        {right.z, correctedUpwards.z, forward.z}
    }

    local qw = math.sqrt(1 + rotationMatrix[1][1] + rotationMatrix[2][2] + rotationMatrix[3][3]) / 2
    local w4 = (4 * qw)
    local qx = (rotationMatrix[3][2] - rotationMatrix[2][3]) / w4 
    local qy = (rotationMatrix[1][3] - rotationMatrix[3][1]) / w4 
    local qz = (rotationMatrix[2][1] - rotationMatrix[1][2]) / w4 

    return maf.quat(qx, qy, qz, qw)
end

function quaternionToEuler(qx, qy, qz, qw)
    -- Roll (x-axis rotation)
    local sinr_cosp = 2 * (qw * qx + qy * qz)
    local cosr_cosp = 1 - 2 * (qx * qx + qy * qy)
    local roll = math.atan2(sinr_cosp, cosr_cosp)

    -- Pitch (y-axis rotation)
    local sinp = 2 * (qw * qy - qz * qx)
    local pitch
    if math.abs(sinp) >= 1 then
        pitch = math.pi / 2 * (sinp / math.abs(sinp)) -- use 90 degrees if out of range
    else
        pitch = math.asin(sinp)
    end

    -- Yaw (z-axis rotation)
    local siny_cosp = 2 * (qw * qz + qx * qy)
    local cosy_cosp = 1 - 2 * (qy * qy + qz * qz)
    local yaw = math.atan2(siny_cosp, cosy_cosp)

    return roll, pitch, yaw
end

function getPlanePosition() 
	local planeHandle	= storeCarCharIsInNoSave(playerPed) 
	local x, y, z 		= getCarCoordinates(planeHandle)
	
	return maf.vector(x, y, z)
end

function getPlaneRotation()
	local planeHandle	= storeCarCharIsInNoSave(playerPed) 
	local x, y, z, w 	= getVehicleQuaternion(planeHandle)
	local planeQuat 	= maf.quat(x, y, z, w)
	
	return planeQuat
end

---


-- maf
-- https://github.com/bjornbytes/maf
-- MIT License

local ffi = type(jit) == 'table' and jit.status() and require 'ffi'
local vec3, quat

local forward
local vtmp1
local vtmp2
local qtmp1

vec3 = {
  __call = function(_, x, y, z)
    return setmetatable({ x = x or 0, y = y or 0, z = z or 0 }, vec3)
  end,

  __tostring = function(v)
    return string.format('(%f, %f, %f)', v.x, v.y, v.z)
  end,

  __add = function(v, u) return v:add(u, vec3()) end,
  __sub = function(v, u) return v:sub(u, vec3()) end,
  __mul = function(v, u)
    if vec3.isvec3(u) then return v:mul(u, vec3())
    elseif type(u) == 'number' then return v:scale(u, vec3())
    else error('vec3s can only be multiplied by vec3s and numbers') end
  end,
  __div = function(v, u)
    if vec3.isvec3(u) then return v:div(u, vec3())
    elseif type(u) == 'number' then return v:scale(1 / u, vec3())
    else error('vec3s can only be divided by vec3s and numbers') end
  end,
  __unm = function(v) return v:scale(-1) end,
  __len = function(v) return v:length() end,

  __index = {
    isvec3 = function(x)
      return ffi and ffi.istype('vec3', x) or getmetatable(x) == vec3
    end,

    clone = function(v)
      return vec3(v.x, v.y, v.z)
    end,

    unpack = function(v)
      return v.x, v.y, v.z
    end,

    set = function(v, x, y, z)
      if vec3.isvec3(x) then x, y, z = x.x, x.y, x.z end
      v.x = x
      v.y = y
      v.z = z
      return v
    end,

    add = function(v, u, out)
      out = out or v
      out.x = v.x + u.x
      out.y = v.y + u.y
      out.z = v.z + u.z
      return out
    end,

    sub = function(v, u, out)
      out = out or v
      out.x = v.x - u.x
      out.y = v.y - u.y
      out.z = v.z - u.z
      return out
    end,

    mul = function(v, u, out)
      out = out or v
      out.x = v.x * u.x
      out.y = v.y * u.y
      out.z = v.z * u.z
      return out
    end,

    div = function(v, u, out)
      out = out or v
      out.x = v.x / u.x
      out.y = v.y / u.y
      out.z = v.z / u.z
      return out
    end,

    scale = function(v, s, out)
      out = out or v
      out.x = v.x * s
      out.y = v.y * s
      out.z = v.z * s
      return out
    end,

    length = function(v)
      return math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z)
    end,

    normalize = function(v, out)
      out = out or v
      local len = v:length()
      return len == 0 and v or v:scale(1 / len, out)
    end,

    distance = function(v, u)
      return vec3.sub(v, u, vtmp1):length()
    end,

    angle = function(v, u)
      return math.acos(v:dot(u) / (v:length() + u:length()))
    end,

    dot = function(v, u)
      return v.x * u.x + v.y * u.y + v.z * u.z
    end,

    cross = function(v, u, out)
      out = out or v
      local a, b, c = v.x, v.y, v.z
      out.x = b * u.z - c * u.y
      out.y = c * u.x - a * u.z
      out.z = a * u.y - b * u.x
      return out
    end,

    lerp = function(v, u, t, out)
      out = out or v
      out.x = v.x + (u.x - v.x) * t
      out.y = v.y + (u.y - v.y) * t
      out.z = v.z + (u.z - v.z) * t
      return out
    end,

    project = function(v, u, out)
      out = out or v
      local unorm = vtmp1
      u:normalize(unorm)
      local dot = v:dot(unorm)
      out.x = unorm.x * dot
      out.y = unorm.y * dot
      out.z = unorm.z * dot
      return out
    end,

    rotate = function(v, q, out)
      out = out or v
      local u, c, o = vtmp1, vtmp2, out
      u.x, u.y, u.z = q.x, q.y, q.z
      o.x, o.y, o.z = v.x, v.y, v.z
      u:cross(v, c)
      local uu = u:dot(u)
      local uv = u:dot(v)
      o:scale(q.w * q.w - uu)
      u:scale(2 * uv)
      c:scale(2 * q.w)
      return o:add(u:add(c))
    end
  }
}

quat = {
  __call = function(_, x, y, z, w)
    return setmetatable({ x = x, y = y, z = z, w = w }, quat)
  end,

  __tostring = function(q)
    return string.format('(%f, %f, %f, %f)', q.x, q.y, q.z, q.w)
  end,

  __add = function(q, r) return q:add(r, quat()) end,
  __sub = function(q, r) return q:sub(r, quat()) end,
  __mul = function(q, r)
    if quat.isquat(r) then return q:mul(r, quat())
    elseif vec3.isvec3(r) then return r:rotate(q, vec3())
    else error('quats can only be multiplied by quats and vec3s') end
  end,
  __unm = function(q) return q:scale(-1) end,
  __len = function(q) return q:length() end,

  __index = {
    isquat = function(x)
      return ffi and ffi.istype('quat', x) or getmetatable(x) == quat
    end,

    clone = function(q)
      return quat(q.x, q.y, q.z, q.w)
    end,

    unpack = function(q)
      return q.x, q.y, q.z, q.w
    end,

    set = function(q, x, y, z, w)
      if quat.isquat(x) then x, y, z, w = x.x, x.y, x.z, x.w end
      q.x = x
      q.y = y
      q.z = z
      q.w = w
      return q
    end,

    fromAngleAxis = function(angle, x, y, z)
      return quat():setAngleAxis(angle, x, y, z)
    end,

    setAngleAxis = function(q, angle, x, y, z)
      if vec3.isvec3(x) then x, y, z = x.x, x.y, x.z end
      local s = math.sin(angle * .5)
      local c = math.cos(angle * .5)
      q.x = x * s
      q.y = y * s
      q.z = z * s
      q.w = c
      return q
    end,

    getAngleAxis = function(q)
      if q.w > 1 or q.w < -1 then q:normalize() end
      local s = math.sqrt(1 - q.w * q.w)
      s = s < .0001 and 1 or 1 / s
      return 2 * math.acos(q.w), q.x * s, q.y * s, q.z * s
    end,

    between = function(u, v)
      return quat():setBetween(u, v)
    end,

    setBetween = function(q, u, v)
      local dot = u:dot(v)
      if dot > .99999 then
        q.x, q.y, q.z, q.w = 0, 0, 0, 1
        return q
      elseif dot < -.99999 then
        vtmp1.x, vtmp1.y, vtmp1.z = 1, 0, 0
        vtmp1:cross(u)
        if #vtmp1 < .00001 then
          vtmp1.x, vtmp1.y, vtmp1.z = 0, 1, 0
          vtmp1:cross(u)
        end
        vtmp1:normalize()
        return q:setAngleAxis(math.pi, vtmp1)
      end

      q.x, q.y, q.z = u.x, u.y, u.z
      vec3.cross(q, v)
      q.w = 1 + dot
      return q:normalize()
    end,

    fromDirection = function(x, y, z)
      return quat():setDirection(x, y, z)
    end,

    setDirection = function(q, x, y, z)
      if vec3.isvec3(x) then x, y, z = x.x, x.y, x.z end
      vtmp2.x, vtmp2.y, vtmp2.z = x, y, z
      return q:setBetween(forward, vtmp2)
    end,

    add = function(q, r, out)
      out = out or q
      out.x = q.x + r.x
      out.y = q.y + r.y
      out.z = q.z + r.z
      out.w = q.w + r.w
      return out
    end,

    sub = function(q, r, out)
      out = out or q
      out.x = q.x - r.x
      out.y = q.y - r.y
      out.z = q.z - r.z
      out.w = q.w - r.w
      return out
    end,

    mul = function(q, r, out)
      out = out or q
      local qx, qy, qz, qw = q:unpack()
      local rx, ry, rz, rw = r:unpack()
      out.x = qx * rw + qw * rx + qy * rz - qz * ry
      out.y = qy * rw + qw * ry + qz * rx - qx * rz
      out.z = qz * rw + qw * rz + qx * ry - qy * rx
      out.w = qw * rw - qx * rx - qy * ry - qz * rz
      return out
    end,

    scale = function(q, s, out)
      out = out or q
      out.x = q.x * s
      out.y = q.y * s
      out.z = q.z * s
      out.w = q.w * s
      return out
    end,

    length = function(q)
      return math.sqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w)
    end,

    normalize = function(q, out)
      out = out or q
      local len = q:length()
      return len == 0 and q or q:scale(1 / len, out)
    end,

    lerp = function(q, r, t, out)
      out = out or q
      r:scale(t, qtmp1)
      q:scale(1 - t, out)
      return out:add(qtmp1)
    end,

    slerp = function(q, r, t, out)
      out = out or q

      local dot = q.x * r.x + q.y * r.y + q.z * r.z + q.w * r.w
      if dot < 0 then
        dot = -dot
        r:scale(-1)
      end

      if 1 - dot < .0001 then
        return q:lerp(r, t, out)
      end

      local theta = math.acos(dot)
      q:scale(math.sin((1 - t) * theta), out)
      r:scale(math.sin(t * theta), qtmp1)
      return out:add(qtmp1):scale(1 / math.sin(theta))
    end
  }
}

if ffi then
  ffi.cdef [[
    typedef struct { double x, y, z; } vec3;
    typedef struct { double x, y, z, w; } quat;
  ]]

  vec3 = ffi.metatype('vec3', vec3)
  quat = ffi.metatype('quat', quat)
else
  setmetatable(vec3, vec3)
  setmetatable(quat, quat)
end

forward = vec3(0, 0, -1)
vtmp1 = vec3()
vtmp2 = vec3()
qtmp1 = quat()

return {
  vec3 = vec3,
  quat = quat,

  vector = vec3,
  rotation = quat
}