# An approach to manipulating 3D objects

``````function setup()
--displayMode(FULLSCREEN_NO_BUTTONS)
-- Make better use of the available screen
supportedOrientations(LANDSCAPE_ANY)
-- Table for our cubes
cubez={}
-- Number of cubes to create
ncubes = 5
-- Marker for which cube was last selected
lastcube = ncubes
-- Create the cubes at random points in a [-2,2] region
for i=1,ncubes do
cubez[i]=Cube(4*vec3(math.random()-.5,
math.random()-.5,
math.random()-.5),
vec3(math.random()+.5,
math.random()+.5,
math.random()+.5)
)
end
-- Make the initial order vaguely sensible
table.sort(cubez,function(a,b) return a.pos.z>b.pos.z end)
-- Allow the user to rotate he viewport
parameter.integer("azimuth",-180,180,0)
parameter.integer("zenith",-90,90,0)
end

function draw()
background(40, 40, 50)
--[[
-- uncomment this section to see a small circle at the touch point
-- useful for checking that what you think is happening actually is
noSmooth()
noStroke()
fill(0, 255, 238, 255)
ellipse(CurrentTouch.x,CurrentTouch.y,15)
--]]
-- Set the projection matrix
perspective(40, WIDTH/HEIGHT)
-- Set the view matrix
camera(0,0,10, 0,0,0, 0,1,0)
-- Apply the user-specified rotations
rotate(zenith,1,0,0)
rotate(azimuth,0,1,0)
-- Draw each shape
for i,c in pairs(cubez) do
c:draw()
end
end

function touched(touch)
if touch.state == BEGAN then
-- New touch, our goal is to see if it touched anything
cube = nil
local j
-- We step through the objects asking each one if it was
-- touched.
-- The starting point of the array is offset so that the
-- object that was just touched is asked last.
-- This means that by tapping an object we move it to the
-- end of the list and so can select objects behind it next
-- time.
for i=1,ncubes do
j = ((i+lastcube-1)%ncubes)+1
if cubez[j]:isTouchedBy(t) then
cube = cubez[j]
lastcube = j
break
end
end
else
-- Old touch, so hand it off to the selected object for
-- processing (assuming one was selected).
if cube then
cube:processTouch(touch)
end
end
end

-- This is a class for defining and handling a cube
Cube = class()

-- For simplicity, all our cubes are the same so we use the same
-- mesh to draw them.
-- The locality of these variables is not significant here, but
-- if this were on another tab they would be hidden from the
-- main code since tabs are "chunks".
local __cube = mesh()
local corners = {}
for l=0,7 do
i,j,k=l%2,math.floor(l/2)%2,math.floor(l/4)%2
table.insert(corners,{vec3(i,j,k),color(255*i,255*j,255*k)})
end
local vertices = {}
local colours = {}
local u
for l=0,2 do
for i=0,1 do
for k=0,1 do
for j=0,2 do
u = (i*2^l + ((j+k)%2)*2^((l+1)%3)
+ (math.floor((j+k)/2)%2)*2^((l+2)%3)) + 1
table.insert(vertices,corners[u])
table.insert(colours,corners[u])
end
end
end
end
__cube.vertices = vertices
__cube.colors = colours
-- We're done with the temporary variables now
vertices = nil
colours = nil
corners = nil

-- All a cube needs to know at the start is its position and size
-- We actually want to apply the position first, but the user
-- will think of the position as applying after the scale, so
-- we adjust the position accordingly
function Cube:init(v,s)
self.pos = vec3(v.x/s.x,v.y/s.y,v.z/s.z)
self.size = s
end

-- To draw, we move to the position and draw ourselves
function Cube:draw()
pushMatrix()
scale(self.size.x,self.size.y,self.size.z)
translate(self.pos.x,self.pos.y,self.pos.z)
-- We save the mayrix in place at time of draw for checking
-- against touches.  This could be optimised as we only need
-- the matrix from the time the screen was touched.
self.matrix = modelMatrix() * viewMatrix() * projectionMatrix()
__cube:draw()
popMatrix()
end

-- This returns "true" if we claim the touch
function Cube:isTouchedBy(t)
-- Store the matrix in effect at the start of the touch
self.smatrix = self.matrix
-- Compute the vector along the ray defined by the touch
local n = screennormal(t,self.matrix)
local plane
-- The next segments of code ask if the touch fell on one of the
-- faces of the cube.  We use the normal vector to determine
-- which faces are towards the viewer.  Then for each face that
-- is towards the viewer, we test if the touch point was on that
-- face.
if n.z > 0 then
plane = {vec3(0,0,1),vec3(1,0,0),vec3(0,1,0)}
else
plane = {vec3(0,0,0),vec3(1,0,0),vec3(0,1,0)}
end
if self:touchFace(plane,t) then
return true
end
if n.y > 0 then
plane = {vec3(0,1,0),vec3(1,0,0),vec3(0,0,1)}
else
plane = {vec3(0,0,0),vec3(1,0,0),vec3(0,0,1)}
end
if self:touchFace(plane,t) then
return true
end
if n.x > 0 then
plane = {vec3(1,0,0),vec3(0,1,0),vec3(0,0,1)}
else
plane = {vec3(0,0,0),vec3(0,1,0),vec3(0,0,1)}
end
if self:touchFace(plane,t) then
return true
end
return false
end

-- This tests if the touch point is on a particular face.
-- A face defines a plane in space and generically the touch line
-- will intersect that plane once.  We compute that point and
-- test if it is on the corresponding face.
-- If so, we save the plane as that will be our plane of movement
-- while this touch is active.
-- As the position and size are encoded in the matrix, when we test
-- coordinates we just need to test against the original cube where
-- the faces are [0,1]x[0,1]
function Cube:touchFace(plane,t)
local tc = screentoplane(t,
plane,
plane,
plane,
self.matrix)
if tc:dot(plane) > 0 and tc:dot(plane) < 1 and
tc:dot(plane) > 0 and tc:dot(plane) < 1 then
self.plane = plane
self.starttouch = tc - self.pos
return true
end
return false
end

-- This computes our displacement relative to the initial touch
-- and sets our position accordingly.
function Cube:processTouch(t)
local tc = screentoplane(t,
self.plane,
self.plane,
self.plane,
self.smatrix)
self.pos = tc - self.starttouch
end

-- These are all auxiliary macros, some predate matrix and
-- vector types in Codea so could now be optimised

-- Apply a 4-matrix to a 4-vector
function applymatrix4(v,m)
local u = {}
u = m*v + m*v + m*v + m*v
u = m*v + m*v + m*v + m*v
u = m*v + m*v + m*v + m*v
u = m*v + m*v + m*v + m*v
return u
end

-- Apply a 3-matrix to a 3-vector
function applymatrix3(v,m)
local u = {}
u = m*v + m*v + m*v
u = m*v + m*v + m*v
u = m*v + m*v + m*v
return u
end

-- Compute the cofactor matrix of a 3x3 matrix
function cofactor3(m)
local rm = {}
local sgn,l
local fm = {}
for k=1,9 do
fm = {}
l = math.floor((k-1)/3) + 1 + 3*((k-1)%3)
sgn = (-1)^(math.floor((k-1)/3))*(-1)^((k-1)%3)
for j=1,9 do
if j%3 ~= k%3
and math.floor((j-1)/3) ~= math.floor((k-1)/3)
then
table.insert(fm,m[j])
end
end
rm[l] = sgn*Det2(fm)
end
return rm
end

-- Determinant of a 2x2 matrix (needed for the cofactor of a 3x3)
function Det2(t)
return t*t - t*t
end

-- Given a plane in space, this computes the transformation
-- matrix from that plane to the screen
function __planetoscreen(o,u,v,A)
A = A or modelMatrix() * viewMatrix() * projectionMatrix()
o = o or vec3(0,0,0)
u = u or vec3(1,0,0)
v = v or vec3(0,1,0)
-- promote to 4-vectors
o = vec4(o.x,o.y,o.z,1)
u = vec4(u.x,u.y,u.z,0)
v = vec4(v.x,v.y,v.z,0)
local oA, uA, vA
oA = applymatrix4(o,A)
uA = applymatrix4(u,A)
vA = applymatrix4(v,A)
return { uA, uA, uA,
vA, vA, vA,
oA, oA, oA}
end

-- Given a plane in space, this computes the transformation
-- matrix from the screen to that plane
function screentoplane(t,o,u,v,A)
A = A or modelMatrix() * viewMatrix() * projectionMatrix()
o = o or vec3(0,0,0)
u = u or vec3(1,0,0)
v = v or vec3(0,1,0)
t = t or CurrentTouch
local m = __planetoscreen(o,u,v,A)
m = cofactor3(m)
local ndc = {}
local a
ndc = (t.x/WIDTH - .5)*2
ndc = (t.y/HEIGHT - .5)*2
ndc = 1
a = applymatrix3(ndc,m)
if (a == 0) then return end
a = vec2(a, a)/a
return o + a.x*u + a.y*v
end

-- This computes the vector along the "touch ray"
function screennormal(t,A)
A = A or modelMatrix() * viewMatrix() * projectionMatrix()
t = t or CurrentTouch
local u,v,w,x,y
u = vec3(A,A,A)
v = vec3(A,A,A)
w = vec3(A,A,A)
x = (t.x/WIDTH - .5)*2
y = (t.y/HEIGHT - .5)*2
u = u - x*w
v = v - y*w
return u:cross(v)
end
``````

Following the various discussions about touching and moving 3D objects, I thought I’d post my code. It’s an extension of the code (of mine) that Ignatz posted in another thread. The extensions are: the code is commented, and the objects are now cuboids, not cubes.

@Andrew_Stacey => That was great ! I understand until screen normal for touch, but after… I’m confused. I have made 2 classes Screen and Mesh, can you help me for the function Mesh:isTouched(touch, offset) please ?

Very nice indeed! I’m certainly going to be looking here for inspiration when I take the dreaded step into is something touched in 3D space or perhaps some extra matrix maths @Andrew_Stacey, if you can help me please ^^

Latest version includes spheres and flat rectangles to demonstrate different ways of reacting to touch information:

1. Cubes always move parallel to one of their faces
2. Spheres always move parallel to the screen
3. Rectangles move in their plane of definition if they are currently “face on”, but if “side on” move in a plane that includes the vector normal to their plane of definition.

Also now includes an initial scale and initial rotation.

@HyroVitalyProtago take a look at how the Picture object reacts to touches in this code.

Available as:

1. AutoGisted version: https://gist.github.com/loopspace/5937499
2. Self-installer: https://gist.github.com/loopspace/5937539

(@Briarfox: first attempt at an auto install - hope I got it right!)

mmm… I’ve tried to understand the system for plane (that i need), and to implement the same thing in my project, but that doesn’t work…

This is the project in a one file : https://gist.github.com/HyroVitalyProtago/5939178

@HyroVitalyProtago

As your mesh is not of unit size, you need to check the touch against its actual size: in `Mesh:isTouched` you should do the following.

``````    if tc.x < 0 or tc.x > self.dim.w or tc.y < 0 or tc.y > self.dim.h then
``````

Thanks god ^:)^

Great example, thanks.