i’d expect to be able to rotate a vec with a quat, but no. can they do anything?
@RonJeffries - show us your code. I’m assuming you mean quaternions and they can rotate objects. Lots of code here. I posted an example in The Final Frontier.
Correction that was Euler angles.
I have no code, because quat*anything is an error. I see no connection between quat and anything useful. Thus my question. And yes, I see no use of quat in The Final Frontier. ![]()
Here’s an example.
Code updated below.
@RonJeffries Your comment “no, they can’t do much of anything” isn’t true. For Codea, they’re very useful for rotating an object in 3 dimensions without hitting a lock.
Drag your finger on the screen to rotate the box and you’ll notice that you’ll eventually reach points where you can’t rotate the box any more it the direction you were going. You have to change directions and rotate it again and you’ll hit another lock.
Using quat, you can rotate in any direction with hitting a lock.
Use the x,y,z sliders to rotate the box using quat in any direction from -1440 degrees to +1440 degrees. You can increase the slider values if you want and you’ll still not reach a lock.
As for what can be done with all the other quat functions, I don’t know. Not sure what they’re used for.
viewer.mode=STANDARD
function setup()
limit=1440
parameter.integer("x",-limit,limit,0)
parameter.integer("y",-limit,limit,0)
parameter.integer("z",-limit,limit,0)
assert(OrbitViewer, "Please include Cameras as a dependency")
scene = craft.scene()
v=scene.camera:add(OrbitViewer, vec3(0,0,0), 100, 0, 200)
v.rx,v.ry=20,20
createRect()
end
function draw()
update(DeltaTime)
scene:draw()
end
function update(dt)
scene:update(dt)
c1.rotation = quat.eulerAngles(x,y,z)
end
function createRect()
c1=scene:entity()
w=c1:add(craft.rigidbody,STATIC)
c1.position=vec3(0,0,0)
c1.model = craft.model.cube(vec3(25,10,10))
c1:add(craft.shape.model,c1.model)
c1.material = craft.material(asset.builtin.Materials.Standard)
c1.material.map = readImage(asset.builtin.Surfaces.Basic_Bricks_AO)
end
I take that as mostly “no, they can’t do much of anything”? I guess we can compose them with each other, sometimes. Maybe? But they can’ t do what quaternions usually do?
@RonJeffries What do you want them to do. There’s a section in the documentation for different quat functions. I don’t use quarts, so I’m not sure exactly what they do other than if you use them, you don’t run into gimbal lock rotating objects.
normally I’d expect to be able to use a quat to rotate vectors. they’re not just a little closed algebra or group or whatever, they can do things like veg*quat = rotated vector.
Anyway I’m asking, can they do anything? Are they connected to anything else in Codea / Lua? @Simeon ?
The same is true of matrices. They can typically be multiplied times vectors to transform the vectors. That happens internally in Codea but can, in principle, be done outside. That requires special handling, if I recall, because you need a column vector x y z 0 on the left.
Anyway, just wondering if they are connected to anything and what they might be useful for.
if you want to connect quats to rotation and see it animate based on your quat you should add a tween, tweens literally make everything connected in Codea
@RonJeffries I don’t know much about it, but before Craft, matrices and quaternions seemed to be discussed an awful lot regarding 3-D stuff.
Is the problem down to the touch and the camera settings in the cameras dependency ?
@Bri_G Not sure what the problem is because Cameras uses quat for rotations.
I understand gymbal lock and that quats deal with that. But quats have math properties, like vectors and matrices, and in fact you can define vector*quat as an operation that rotates the vector according to the quat.
It does turn out that you can multiple two quats together to get a third. That basically “adds” the second “angle” to the first. You could increment an object’s rotation slowly that way.
Are there other operations like * that are undocumented? That’s my question.
@dave1707 @RonJeffries - both the first person viewer and the OrbitViewer cameras are clamped at -90 to +90 so you won’t see continuous circulation plus you’ve got to consider the further away from an object the lower the angle change from touch on the screen.
you can rotate an object slowly by adjusting a quat by multiplying by a quat with incremental angles. here’s dave’s demo modified.
viewer.mode=STANDARD
function setup()
limit=1440
parameter.integer("x",-limit,limit,0)
parameter.integer("y",-limit,limit,0)
parameter.integer("z",-limit,limit,0)
assert(OrbitViewer, "Please include Cameras as a dependency")
scene = craft.scene()
v=scene.camera:add(OrbitViewer, vec3(0,0,0), 100, 0, 200)
v.rx,v.ry=20,20
createRect()
qq = quat.eulerAngles(1,.1,.3)
qa = quat.eulerAngles(0,0,0)
end
function draw()
update(DeltaTime)
scene:draw()
end
function update(dt)
scene:update(dt)
qa = qa*qq
c1.rotation = qa
end
function createRect()
c1=scene:entity()
w=c1:add(craft.rigidbody,STATIC)
c1.position=vec3(0,0,0)
c1.model = craft.model.cube(vec3(25,10,10))
c1:add(craft.shape.model,c1.model)
c1.material = craft.material(asset.builtin.Materials.Standard)
c1.material.map = readImage(asset.builtin.Surfaces.Basic_Bricks_AO)
end
I realize it has been a few years, but I found this thread while trying to do the same.
For example, I wanted to draw the gnomon (the RGB lines that represent the XYZ axes of the transform matrix) of an entity.
Here is a simple function that you can use to rotate a vec3 by a quat:
--function rotateVecByQuat(v, q)
function rvq(v, q)
local u = vec3(q.x, q.y, q.z)
local s = q.w
return 2.0 * u:dot(v) * u
+ (s*s - u:dot(u)) * v
+ 2.0 * s * u:cross(v)
end
Adapted from this C++ example:
void rotate_vector_by_quaternion(const Vector3& v, const Quaternion& q, Vector3& vprime)
{
// Extract the vector part of the quaternion
Vector3 u(q.x, q.y, q.z);
// Extract the scalar part of the quaternion
float s = q.w;
// Do the math
vprime = 2.0f * dot(u, v) * u
+ (s*s - dot(u, u)) * v
+ 2.0f * s * cross(u, v);
}
In the top answer of the thread here: https://gamedev.stackexchange.com/questions/28395/rotating-vector3-by-a-quaternion
Here’s a brief example of how I am using it to draw the transform matrix of an entity:
function setup()
viewer.mode = FULLSCREEN
world = World()
scene = world.scene
cam = scene.camera:get(craft.camera)
ball = Ball(scene, 1, 1)
-- More setup code --
end
--function rotateVecByQuat(v, q)
function rvq(v, q)
local u = vec3(q.x, q.y, q.z)
local s = q.w
return 2.0 * u:dot(v) * u
+ (s*s - u:dot(u)) * v
+ 2.0 * s * u:cross(v)
end
function line3D(a, b, c)
local sa = cam:worldToScreen(a)
local sb = cam:worldToScreen(b)
if c then
stroke(c)
end
line(sa.x, sa.y, sb.x, sb.y)
end
function drawMatrix(e, size)
pushStyle()
strokeWidth(10)
local pos = e.position
local rot = e.rotation
local ox = rvq(vec3(size,0,0), rot)
local oy = rvq(vec3(0,size,0), rot)
local oz = rvq(vec3(0,0,size), rot)
line3D(pos, pos + ox, color(233, 78, 80))
line3D(pos, pos + oy, color(78, 233, 98))
line3D(pos, pos + oz, color(78, 94, 233))
popStyle()
end
function draw()
world:update(DeltaTime, ball)
scene:draw()
drawMatrix(ball.e, 1)
end
Note: This is not a full example but rather a summary of how this can be used with your own entity, which in this case is stored in ball.e
The rvq (rotateVectorByQuat) calls are used to calculate the offsets ox, oy, and oz in the drawMatrix function.
It would be great if we could simply use quat * vec3 or vec3 * quat directly in Codea without needing to add a helper function. Same goes for matrix * vec3 rotation/transformation.
It is possible to implement something to extend the default quaternion multiplication. The approach to do this is to replace the quat.__mul and vec3.__mul operators with custom versions in their metatables. I do this here with function hooking (extensions.hook). I have a fairly general ‘extension’ approach which I use to do make an vec3 * quat expression work.
This is from a project based on the modern 4.0 runtime, but is also possible in legacy although with slightly different implementation. The main requirement is obtaining a lua metatable from a quat or vec3 to be able to replace the operator magic functions.
Here’s my fairly generic approach for setting up small classes to add extra functionality/extend the Codea api generally at setup.
This is the base code required for defining classes as ‘extensions’. This also comes with an extension to type allowing extra type checking functions to be made available such as type.is(instance, type). Which is then used to check if the parameter type is a quat or vec3 later.
extensions = {}
extensions.all = {}
extensions.enabled = {}
extensions.hook_base_nil = function() end
extensions.hook = function(base,func)
if func == nil then
func = base
base = nil
end
base = base or extensions.hook_base_nil
return function(...)
return func(base,...)
end
end
extensions.register = function(extension)
local extensionType = type(extension)
assert(extensionType == "function" or extensionType == "table")
extensions.all[extension] = extension
end
Extension = class()
function Extension:init()
end
extensions.extension = function(base)
base = base or Extension
local ext = class(base)
extensions.register(ext)
return ext
end
extensions.setup = function(...)
local args = {...}
local enabled = {}
if #args == 0 then
for ext in pairs(extensions.all) do
enabled[ext] = ext
end
elseif #args == 1 then
local arg = args[1]
if type(arg) == "table" then
local exclude = arg.exclude
if exclude then
for ext in pairs(extensions.all) do
enabled[ext] = ext
end
for i,ext in ipairs(exclude) do
enabled[ext] = nil
end
end
local include = arg.include or (arg.exclude == nil and arg)
for i,ext in ipairs(include) do
enabled[ext]=ext
end
end
else
for i,ext in ipairs(args) do
enabled[ext]=ext
end
end
for i,extension in pairs(enabled) do
local extensionType = type(extension)
if extensionType == "function" then
extension()
elseif extensionType == "table" and extension.setup then
local ext = extension()
ext:setup()
else
error("Unsupported extension")
end
end
extensions.enabled = enabled
end
TypeExtensions = extensions.extension()
function TypeExtensions:setup()
local typeFunc = type
local typeTable = {}
local is_class = function(value)
return typeof(value) == "class"
end
typeTable.is_class = is_class
typeTable.is = function(value, typ)
local t = typeFunc(value)
if typ == "class" then
return is_class(value)
end
local tt = typeFunc(typ)
if (t == "table" or t == "userdata") and tt == "table" then
-- easier built-in instance check
if t == "table" and value.isInstanceOf and value:isInstanceOf(typ) then
return true -- compare instance class and class definition
end
-- more thorough tree check
local classType = nil
if is_class(value) and is_class(typ) then -- compare class definition and class definition
classType = value
else
classType = getmetatable(value) -- compare instance/userdata and class definition
end
while classType ~= nil do
if classType.class_cast ~= nil and classType.class_cast == typ.class_cast then
return true
elseif classType.__instanceDict ~= nil and classType.__instanceDict == typ.__instanceDict then
return true
end
classType = classType.super
end
return false
end
return t == typ
end
local typeMetaTable = {__call = function(tbl,...) return typeFunc(...) end}
setmetatable(typeTable, typeMetaTable)
type = typeTable
end
Then the extension class to extend vec3 and quat metatables __mul functions and make the multiplication operator work as desired:
QuatExtensions = extensions.extension()
function QuatExtensions:setup()
qt = getmetatable(quat)
function rvq(v, q)
local u = vec3(q.x, q.y, q.z)
local s = q.w
return 2.0 * u:dot(v) * u + (s*s - u:dot(u)) * v + 2.0 * s * u:cross(v)
end
qt.__mul = extensions.hook(qt.__mul, function(base, q, v)
if type.is(v, vec3) then
return rvq(v, q)
end
return base(q,v)
end)
v3 = getmetatable(vec3)
v3.__mul = extensions.hook(v3.__mul, function(base, x, y)
if type.is(y, quat) then
return rvq(x, y)
end
return base(x,y)
end)
end
Put the above code all in a single tab, name it anything you like e.g ExtensionsLibrary. Then at the top of your main function simply call the main extensions.setup function. Which runs the setup functions for each enabled extension in the order they are defined / registered.
function setup()
extensions.setup()
end
After calling extensions.setup() you can then multiply a vec3 with quat or quat with vec3. Example:
print(vec3(10,10,10) * quat.eulerAngles(0,180,0))
print(quat.eulerAngles(0,180,0) * vec3(10,10,10))
This technique works for most of Codeas api, both in modern and in legacy this general approach works too. However obtaining metatables and type.is from TypeExtensions will need adjusting to handle type detection correctly in legacy as this implementation is specific to the modern runtime. Whereas legacy can need to use an instance of userdatas to obtain their metatables.
You can also extend this to work with matrices too or thereabouts anything else as well. The only note I’d say is that sometimes it can make errors a bit harder to debug in some cases.
I had an implementation of quat to matrix and matrix to quat conversions somewhere as well for the legacy runtime, although I would need to try track this down.
Better version including matrix conversions. This also has a couple of fixes to enforce extension classes loading order and accounting for userdata metatables __type member in type.is, which may have been renamed from __instanceDict, either table is unique per userdata subclass/metadata so it makes a good type id for userdata objects.
extensions = {}
extensions.all = {}
extensions.enabled = {}
extensions.hook_base_nil = function() end
extensions.hook = function(base,func)
if func == nil then
func = base
base = nil
end
base = base or extensions.hook_base_nil
return function(...)
return func(base,...)
end
end
extensions.register = function(extension)
local extensionType = type(extension)
assert(extensionType == "function" or extensionType == "table")
table.insert(extensions.all, extension)
end
Extension = class()
function Extension:init()
end
extensions.extension = function(base)
base = base or Extension
local ext = class(base)
extensions.register(ext)
return ext
end
extensions.setup = function(...)
local args = {...}
local enabled = {}
if #args == 0 then
for i,v in ipairs(extensions.all) do
table.insert(enabled, v)
end
elseif #args == 1 then
local arg = args[1]
if type(arg) == "table" then
local exclude = arg.exclude
excluded = {}
if exclude then
for i,ext in ipairs(exclude) do
excluded[ext] = ext
end
end
local include = arg.include or (arg.exclude == nil and arg)
included = {}
for i,ext in ipairs(include) do
included[ext]=ext
end
for i,ext in ipairs(extensions.all) do
if excluded[ext] == nil or included[ext] ~= nil then
table.insert(enabled, ext)
end
end
end
else
for i,ext in ipairs(args) do
table.insert(enabled,ext)
end
end
for i,extension in ipairs(enabled) do
local extensionType = type(extension)
if extensionType == "function" then
extension()
elseif extensionType == "table" and extension.setup then
local ext = extension()
ext:setup()
else
error("Unsupported extension")
end
end
extensions.enabled = enabled
end
TypeExtensions = extensions.extension()
function TypeExtensions:setup()
local typeFunc = type
local typeTable = {}
local is_class = function(value)
return typeof(value) == "class"
end
typeTable.is_class = is_class
typeTable.is = function(value, typ)
local t = typeFunc(value)
if typ == "class" then
return is_class(value)
end
local tt = typeFunc(typ)
if (t == "table" or t == "userdata") and tt == "table" then
-- easier built-in instance check
if t == "table" and value.isInstanceOf and value:isInstanceOf(typ) then
return true -- compare instance class and class definition
end
-- more thorough tree check
local classType = nil
if is_class(value) and is_class(typ) then -- compare class definition and class definition
classType = value
else
classType = getmetatable(value) -- compare instance/userdata and class definition
end
while classType ~= nil do
if classType.class_cast ~= nil and classType.class_cast == typ.class_cast then
return true
elseif classType.__type ~= nil and classType.__type == typ.__type then
return true
elseif classType.__instanceDict ~= nil and classType.__instanceDict == typ.__instanceDict then
return true -- maybe deprecated / renamed to __type
end
classType = classType.super
end
return false
end
return t == typ
end
local typeMetaTable = {__call = function(tbl,...) return typeFunc(...) end}
setmetatable(typeTable, typeMetaTable)
type = typeTable
end
MathExtensions = extensions.extension()
function MathExtensions:setup()
math.round = function(value)
local v = math.floor(value)
local d = value - v
if d < 0.5 then
return v
end
return math.ceil(value)
end
math.sign = function(value, sign)
if (sign < 0 and value > 0) or (sign > 0 and value < 0) then
return -value
end
return value
end
end
QuatExtensions = extensions.extension()
function QuatExtensions:setup()
function quat_rotate_vec3(q,v)
local u = vec3(q.x, q.y, q.z)
local s = q.w
return 2.0 * u:dot(v) * u + (s*s - u:dot(u)) * v + 2.0 * s * u:cross(v)
end
function quat_to_mat3(q)
x,y,z,w = q.x,q.y,q.z,q.w
xx, yy, zz = x * x, y * y, z * z
xy, xz, yz = x * y, x * z, y * z
sx, sy, sz = x * w, y * w, z * w
xx2, yy2, zz2 = 2*xx, 2*yy, 2*zz
xy2, xz2, yz2 = 2*xy, 2*xz, 2*yz
sx2, sy2, sz2 = 2*sx, 2*sy, 2*sz
return mat3(
1 - yy2 - zz2, xy2 - xz2, xz2 + sy2,
xy2 + sz2, 1 - xx2 - zz2, yz2 + sx2,
xz2 - sy2, yz2 + sx2, 1 - xx2 - yy2
)
end
function quat_to_mat4(q)
m = quat_to_mat3(q)
return mat4(
m[1][1], m[1][2],m[1][3], 0,
m[2][1], m[2][2],m[2][3], 0,
m[3][1], m[3][2],m[3][3], 0,
0,0,0,1
)
end
function mat_to_quat(m)
m00, m11, m22 = m[1][1], m[2][2], m[3][3]
m01, m02, m10, m12, m20, m21 = m[1][2], m[1][3], m[2][1], m[2][3], m[3][1], m[3][2]
x = math.sqrt( math.max( 0, 1 + m00 - m11 - m22 ) ) / 2;
y = math.sqrt( math.max( 0, 1 - m00 + m11 - m22 ) ) / 2;
z = math.sqrt( math.max( 0, 1 - m00 - m11 + m22 ) ) / 2;
w = math.sqrt( math.max( 0, 1 + m00 + m11 + m22 ) ) / 2;
return quat(math.sign(x,m21-m12),math.sign(y, m02-m20),math.sign(z,m01-m10),w)
end
qt = getmetatable(quat())
qt.__mul = extensions.hook(qt.__mul, function(base, q, v)
if type.is(v, vec3) then
return v * q -- rotate a vec3 (optional)
end
if type.is(v, mat3) or type.is(v, mat4) then
v = v:quat()
end
return base(q,v)
end)
quat.mat3 = quat_to_mat3
quat.mat4 = quat_to_mat4
v3 = getmetatable(vec3())
v3.__mul = extensions.hook(v3.__mul, function(base, x, y)
if type.is(y, quat) then
return quat_rotate_vec3(y, x) -- rotate vec3 with quat
elseif type.is(y, mat3) or type.is(y, mat4) then
return y * x -- multiply vec with matrix (optional)
end
return base(x,y)
end)
m3 = getmetatable(mat3())
m3.__mul = extensions.hook(m3.__mul, function(base, x, y)
if type.is(y, quat) then
y = y:mat3()
end
return base(x,y)
end)
mat3.quat = mat_to_quat
m4 = getmetatable(mat4())
m4.__mul = extensions.hook(m4.__mul, function(base, x, y)
if type.is(y, vec3) then
v = base(x,vec4(y.x,y.y,y.z,1))
return vec3(v.x,v.y,v.z)
elseif type.is(y, quat) then
y = y:mat4()
end
return base(x,y)
end)
mat4.quat = mat_to_quat
end