g_tumbleweed_list = {} g_tumbleweed_vars = { update_rate = 20, -- milliseconds between updates max_speed = 7, -- max units per update height = 11, -- Y offset to centre of model min_scale = 60, max_scale = 180 } function tumbleweed_init(e) g_tumbleweed_list[e] = nil SendMessageI("collisionoff",e) end function tumbleweed_main(e) local tw_vars = g_tumbleweed_vars if g_tumbleweed_list[e] == nil then if g_Entity[e] ~= nil then local Ent = g_Entity[e] local scale = math.random(tw_vars.min_scale, tw_vars.max_scale) Scale(e, scale) local scale_height = tw_vars.height * (scale / 100) local direction = tw_WrapAng(math.rad(Ent.angley)) local xr = math.rad(math.random() * 179.9 * math.random(-1,1)) local yr = math.rad(math.random() * 89.9 * math.random(-1,1)) local zr = math.rad(math.random() * 179.9 * math.random(-1,1)) -- create quaternion local qinit_rot = tw_EulerToQuat(xr, yr, zr) -- work out starting position local XO,YO,ZO = tw_Rotate3D(0,scale_height,0,xr,yr,zr) local xi, yi, zi = Ent.x - XO, Ent.y + 2 + scale_height - YO, Ent.z - ZO -- calculate direction vector local xd, yd, zd = tw_Rotate3D(0,0,1,0,direction,0) -- now calculate turning quaternion local rndspd = tw_vars.max_speed / 2 + (math.random() * tw_vars.max_speed / 2) local rotangle = math.rad(rndspd) local rx, ry, rz = tw_Rotate3D(rotangle, 0, 0, 0, direction, 0) local qturn = tw_EulerToQuat(tw_Rotate3D(rx, ry, rz, tw_QuatToEuler(qinit_rot))) xd, zd = xd * rndspd, zd * rndspd g_tumbleweed_list[e] = {x = xi, y = yi, z = zi, xo = xi, yo = yi, zo = zi, ntime = nil, dvec = {x = xd, z = zd}, height = scale_height, distance = Ent.health, dist_left = Ent.health, step = math.sqrt(xd*xd+zd*zd), -- store initial rotation qini = qinit_rot, -- store rotation quaternion qrot = qturn, -- store entity quaternion qat = qinit_rot} end return else local tw = g_tumbleweed_list[e] if tw.ntime == nil then tw.ntime = g_Time + tw_vars.update_rate return elseif g_Time > tw.ntime then -- this bit rotates the entity tw.qat = tw_QuatMul(tw.qat, tw.qrot) -- get the new Euler angles local xr, yr, zr = tw_QuatToEuler(tw.qat) --PromptLocal(e, math.modf(math.deg(xr)) .. "," .. -- math.modf(math.deg(yr)) .. "," .. -- math.modf(math.deg(zr))) -- Now move it tw.x, tw.z = tw.x + tw.dvec.x, tw.z + tw.dvec.z local XO, YO, ZO = tw_Rotate3D(0, tw.height, 0, xr, yr, zr) local terh = GetTerrainHeight(tw.x, tw.z) -- SendMessageI("collisionoff",e) ResetPosition(e, tw.x - XO, terh + 2 + tw.height - YO, tw.z - ZO) ResetRotation(e, math.deg(xr), math.deg(yr), math.deg(zr)) -- SendMessageI("collisionon",e) tw.ntime = g_Time + tw_vars.update_rate tw.dist_left = tw.dist_left - tw.step if tw.dist_left < 0 then tw.dist_left = tw.distance tw.qat = tw.qini tw.x, tw.y, tw.z = tw.xo, tw.yo, tw.zo local scale = math.random(tw_vars.min_scale, tw_vars.max_scale) Scale(e, scale) tw.height = tw_vars.height * (scale / 100) end end end end function tumbleweed_exit (e) g_tumbleweed_list[e] = nil end function tw_WrapAng(Ang) local P2 = math.pi * 2 if Ang > P2 then return Ang - P2 elseif Ang < -P2 then return Ang + P2 end return Ang end function tw_Rotate3D (x, y, z, xrot, yrot, zrot) function RotatePoint2D (x, y, Ang) -- Ang in radians local Sa, Ca = math.sin(Ang), math.cos(Ang) return x*Ca - y*Sa, x*Sa + y*Ca end local NX, NY, NZ = x, y, z -- X NZ, NY = RotatePoint2D (NZ, NY, -xrot) -- Y NX, NZ = RotatePoint2D (NX, NZ, -yrot) -- Z NY, NX = RotatePoint2D (NY, NX, -zrot) return NX, NY, NZ end function tw_EulerToQuat(pitch, yaw, roll) -- radians local cr = math.cos(roll/2) local cp = math.cos(pitch/2) local cy = math.cos(yaw/2) local sr = math.sin(roll/2) local sp = math.sin(pitch/2) local sy = math.sin(yaw/2) local cycp = cy * cp local sysp = sy * sp local cysp = cy * sp local sycp = sy * cp return {w = (cr * cycp) + (sr * sysp), x = (cr * cysp) - (sr * sycp), y = (cr * sycp) + (sr * cysp), z = (sr * cycp) - (cr * sysp)} end function tw_QuatToEuler(q) local sqw = q.w*q.w local sqx = q.x*q.x local sqy = q.y*q.y local sqz = q.z*q.z local h = -2.0 * (q.x*q.z - q.y*q.w) if math.abs(h) < 0.99999 then return math.atan2(2.0 * (q.y*q.z + q.x*q.w),(-sqx - sqy + sqz + sqw)), -- x ang math.asin(-2.0 * (q.x*q.z - q.y*q.w)), -- y ang math.atan2(2.0 * (q.x*q.y + q.z*q.w),(sqx - sqy - sqz + sqw)) -- z ang else return math.atan2(2.0 * (q.y*q.z + q.x*q.w),(-sqx - sqy + sqz + sqw)), -- x ang (math.pi / 2) * h, -- y ang math.atan2(2.0 * (q.x*q.y + q.z*q.w),(sqx - sqy - sqz + sqw)) -- z ang end end function tw_QuatMul(q1, q2) local A = (q1.w + q1.x)*(q2.w + q2.x) local B = (q1.z - q1.y)*(q2.y - q2.z) local C = (q1.w - q1.x)*(q2.y + q2.z) local D = (q1.y + q1.z)*(q2.w - q2.x) local E = (q1.x + q1.z)*(q2.x + q2.y) local F = (q1.x - q1.z)*(q2.x - q2.y) local G = (q1.w + q1.y)*(q2.w - q2.z) local H = (q1.w - q1.y)*(q2.w + q2.z) return {w = B + (-E - F + G + H)/2, x = A - ( E + F + G + H)/2, y = C + ( E - F + G - H)/2, z = D + ( E - F - G + H)/2} end