#### Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

# Monument Valley-style orthogonal projection 3D

Mod
edited February 2015 Posts: 2,020

Like many others I loved Monument Valley, and especially the Escher-esque puzzles that it creates by taking perspective out of the equation.

That got me thinking about how to achieve that projection in Codea (ie objects do not recede with distance, and parallel lines stay parallel).

For the 3D experts on the forum this probably seems obvious, but I couldn't find any discussion of this on the forum so I thought I'd post.

In the 3D Lab demo that ships with Codea,

• set the "Angle" parameter to default to 45 degrees:
``````parameter.number("Angle",-360, 360, 45)
``````
• add this anywhere in the `setup` function:
``````centre=vec2(WIDTH/4,HEIGHT/4)
``````
• and, the key part: comment out the `perspective` line in the `draw` function and replace it with this:
``````    --perspective(FieldOfView, WIDTH/HEIGHT)
ortho(-centre.x,centre.x,-centre.y,centre.y,-2000,2000) --orthogonal projection
``````

This is what you get:

Now you just have to come up with the compelling level design, atmospheric graphic style, elliptic story-telling, immersive soundscapes, and Escher-esque puzzles, and you've got a certified iOS hit!

Tagged:

• Posts: 1,976

I had tried to do this, but it crashed whenever I called ortho(). I didn't know there were any other parameters. What are the parameters?

• Mod
Posts: 2,020

From the reference:

ortho( left, right, bottom, top,

near, far )

Sets the projection matrix to the orthographic projection defined by the parameters left, right, bottom, top, near and far. The near and far values specify the closest and farthest distance an object can be without being clipped by the view frustum.

When called with no arguments, sets up the default orthographic projection, equivalent to ortho( 0, WIDTH, 0, HEIGHT, -10, 10 ).

• Mod
Posts: 2,020

The left, right, top, and bottom parameters can be adjusted to effect the scale of the image.

near and far should be set to how deep your world extends.

• edited February 2015 Posts: 502

there seems to be no such thing as a view frustum.. although its stated in the documentation..

• Posts: 1,976

@se24vad Things get clipped, but only if they're really far away.

• Posts: 502

I have to test this. but when things are right in front (and even go much outside the view) they seem not to be clipped.

• Mod
Posts: 2,020

Reviving a thread from a few months ago...

For setting the view frustum in orthogonal projection, this works for me:

`ortho(0, WIDTH, 0, HEIGHT,-2000,2000) --orthogonal projection`

Objects outside of the -2000 to 2000 depth range don't get drawn.

Is that what you meant?

• Posts: 502

yes, kind of, but instead for 3d

• Mod
Posts: 2,020

Does the near and far variable in the `perspective` command not work? Far works, haven't experimented with near

• edited July 2015 Posts: 502

I havent experimented as well.. and thats why I assumed there was no frustum culling for 3d.. but I still could be wrong) -cheers

Edit: I was working on a obj importer, ages ago^^, and then this question came to me.. but I discontinued dev on this, so.. now its just nice to know but as I said, I havent tried.

Here is the video of my Project: http://t.co/S6zUfsAchU

• Mod
Posts: 2,020

That's a nice model!

Did you see @Ignatz 's .obj importer? I added frame interpolation, for animations:

• Posts: 502

@yojimbo2000 yes, I actually saw that thread and you both did tremendous work! I played with the idea using your code to extend mine.. but for that I need to have frustum culling for the whole 3d scene and other usefull optimizations... maybe I come back to this project one day, because right now I'm just not feeling like it))))

• edited December 2016 Posts: 12

@yojimbo2000 Oh wow this is awesome, thank you so much for sharing! I also loved Monument Valley and have been playing around with doing some MV-esque stuff Funny I stumbled across this while searching up ways of getting the isometric view. I'm going to take a closer look at remixing the code in the 3D Lab.

Here is just how it looks at the moment, most of the code is thanks to @Valgo ; I'm just remixing to play around:

That one uses `rotate(90,0,-1,1)` to get the camera angle but I think there's still perspective and the camera is a little too high up. Also, the directions are a little mixed up from that; going up on the screen becomes going in the positive X direction.

There's more of my discussion on that here on doing the shading over time and here on adding the background (kind of funnily, we're discussing on another forum )

• edited December 2016 Posts: 12

I've been able to get a test drawing in view with using the camera now.

I rotated the structure 45 degrees around the Y axis using `rotate(45, 0, 1, 0)` (which I did try before as well, this gets the basic isometric view but at the moment it is flat).

Then the arguments for `ortho()` (`left, right, bottom, top, near, far`) are set specifically for the size of the drawing in this project, I also found that increasing `far` stopped the drawing from being clipped.

For the `camera()` part, the first three arguments (EyeX, EyeY, EyeZ) I had to play around with until the drawing showed up. (You can see me setting parameters for them in the pic above.)

I set the `centreX, centreY, centreZ` arguments to `(0,0,0)` because that was where everything was in the project. For the last three arguments I left it at the default `(0,1,0)`. (I tried playing around with them in the 3D Lab demo but all I've discovered at the moment is that changing `upX` seems to rotate the view. So I'm not quite sure what it is for yet.)

But now I just need to find a good position for the camera so that the scene appears like it has 'no perspective'. I'll probably bringing some more blocks so I can gauge whether it looks like there is perspective.

Thank you again to everyone!

I would like to share the code if that's of any interest, I just need permission and just need to give credit

And I am explaining a lot of what I found out as I did not know before as well.

• Posts: 12

@Valgo may I share the code for this here with credit to you?

• Posts: 28

@t1_ that's fine, as long as you give credit

• Posts: 12

@Valgo yep of course I will thank you!

• Mod
Posts: 2,020

Looking very nice! Good work @Valgo and @t1_

• edited March 2017 Posts: 12

Thank you for the encouragement yojimbo2000

this is an older thing I wrote a while ago... but I'll put it here anyway

At the moment I made it so you can change the colours for two different block colours using parameters, but it'll be relatively straightforward to add more colours for blocks. And it picks random colours each time you restart the project too.

Valgo's code for generating structures is still there (I put it under a tab called SceneDataModel)

Here is a quick pic of a structure I just decided to try out:

And here is the link to the code in a pastebin, but if anyone happens to take a peek at it, you're going to have to forgive me — things are all over the place I'm still learning I'd greatly appreciate any tips or ideas anyone may have too.

(I liked being able to separate things into different tabs but wasn't sure in many cases whether a new class might work better or if just a file would be fine. I can find things when I need to in either case, but it probably doesn't make much sense to anyone else at the moment which isn't ideal )

Right now I know which part does the gradient (it's in SceneRenderer) and I thought I made it so that it works like this:

• For each vertex in the vertices for the cube mesh:
• Get the distance between that vertex and a reference point, and mix in an amount of black according to that distance (more black if closer, less if further away)

And here was the code that I did:

``````function SceneRenderer:cubeColor(col, vecX, vecY, vecZ)
local ret = {}

vecRefPoint = vec3(-shapeWidth,-shapeWidth,-shapeWidth)

for vertIdx, vertVec in ipairs(cubeMeshVerts) do

local baseShadeColr = col:mix(color(0, 0, 0, col.a), 1 - math.floor(((vertIdx - 1) % 18) / 6) * 0.15)

vecToPoint = vec3((cubeSize*vecX)+vertVec.x,(cubeSize*vecY)+vertVec.y,(cubeSize*vecZ)+vertVec.z)
distToRefPoint = vecRefPoint:dist(vecToPoint)
maxDistAcross = vec3(-shapeWidth,-shapeWidth,-shapeWidth):dist(vec3(shapeWidth,shapeWidth,shapeWidth))

end

return ret
end
``````

However, when I change the coordinates for `vecRefPoint`it doesn't seem to change where the lighting is coming from; the darkest part still seems to be at `(0,0,0)`. But maybe I just haven't tried enough coordinates and enough structures to see yet, so I'll take a look at a few more.

Since then, I don't know if I changed the code but most recent pastebin here and since then, I did a random super long explanation of it here

• edited March 2017 Posts: 455

I had thought about this yonks ago in terms of trying to do old fashioned isometric style games but with 3d assets, but never got my head around what I'd have to do to the projection matrix. Didn't know you could ortho with parameters like this...

Below is an instanced shader example from the forum, but patched to ortho projection. It messes with your head because my brain assumes it's perspective so I think they slope away from each other, but it's all parallel. Very cool. You could build a Fez like thing using this very well as well I think...

``````function setup()
m = mesh()

m:setColors(color(255,200,0))
numInstances = 100

dummy = m:buffer("dummy") --test vec4 attribute
transform = m:buffer("transform") --test mat4 attribute

-- Here we resize the transform buffer to
-- the number of instances, rather than vertices
-- note that the buffer can be any size:
--  if you render N instances with a buffer sized to M
--  where N > M then the instances will use a divided
--  version of the buffer
--
-- E.g. I render 10 instances with 5 transforms then
--  instance 1,2 use transform 1
--  instance 3,4 use transform 2
--  instance 5,6 use transform 3
--  ... etc
transform:resize(numInstances)

-- We have to tell Codea to treat this buffer as an
-- instanced buffer, that is, it is not per-vertex
transform.instanced = true

for i=1,m.size do
-- Set per vertex
dummy[i] = vec4(0,0,0,0)
end

for i=1,numInstances do
-- Sets one transform *per instance*
local x = ((i-1)%10) - 5.5
local y = math.floor((i-1)/10) - 5.5
transform[i] = matrix():translate(x * 2, y * 2, 0)
end
rot = 0
pos = vec3(0,0,0)
end

function draw()
background(40, 40, 50)

--perspective()
ortho(-10,10,-10,10,-2000,2000)
camera(30,40,20, 0,0,0, 0,0,1)
--another angle:
--camera(30,0,0,0,0,0,0,0,1)
-- mesh:draw can now take a number of instances
-- it draws this many, instanced buffers can be
-- used to differentiate each instance within a
rot = (rot + 0.2%360)
--pos = pos + vec3(-0.02,0,0)
translate(pos:unpack())
rotate(rot)
m:draw(numInstances)
end

vert=[[
uniform mat4 modelViewProjection;

attribute mat4 transform;
attribute vec4 position;
attribute vec4 color;
attribute vec4 dummy;
attribute mediump vec2 texCoord;

varying lowp vec4 vColor;
varying mediump vec2 vTexCoord;

void main()
{
vColor = color;
vTexCoord = texCoord;
gl_Position = modelViewProjection * (transform * position + dummy); } ]]

frag=[[
uniform sampler2D texture;
varying lowp vec4 vColor;
varying mediump vec2 vTexCoord;
void main()
{
gl_FragColor = texture2D(texture, vTexCoord) * vColor; } ]]

local vertices = {
vec3(-0.5, -0.5,  0.5), -- Left  bottom front
vec3( 0.5, -0.5,  0.5), -- Right bottom front
vec3( 0.5,  0.5,  0.5), -- Right top    front
vec3(-0.5,  0.5,  0.5), -- Left  top    front
vec3(-0.5, -0.5, -0.5), -- Left  bottom back
vec3( 0.5, -0.5, -0.5), -- Right bottom back
vec3( 0.5,  0.5, -0.5), -- Right top    back
vec3(-0.5,  0.5, -0.5), -- Left  top    back
}

-- now construct a cube out of the vertices above
local cubeverts = {
-- Front
vertices[1], vertices[2], vertices[3],
vertices[1], vertices[3], vertices[4],
-- Right
vertices[2], vertices[6], vertices[7],
vertices[2], vertices[7], vertices[3],
-- Back
vertices[6], vertices[5], vertices[8],
vertices[6], vertices[8], vertices[7],
-- Left
vertices[5], vertices[1], vertices[4],
vertices[5], vertices[4], vertices[8],
-- Top
vertices[4], vertices[3], vertices[7],
vertices[4], vertices[7], vertices[8],
-- Bottom
vertices[5], vertices[6], vertices[2],
vertices[5], vertices[2], vertices[1],
}

-- all the unique texture positions needed
local texvertices = { vec2(0.03,0.24),
vec2(0.97,0.24),
vec2(0.03,0.69),
vec2(0.97,0.69) }

-- apply the texture coordinates to each triangle
local cubetexCoords = {
-- Front
texvertices[1], texvertices[2], texvertices[4],
texvertices[1], texvertices[4], texvertices[3],
-- Right
texvertices[1], texvertices[2], texvertices[4],
texvertices[1], texvertices[4], texvertices[3],
-- Back
texvertices[1], texvertices[2], texvertices[4],
texvertices[1], texvertices[4], texvertices[3],
-- Left
texvertices[1], texvertices[2], texvertices[4],
texvertices[1], texvertices[4], texvertices[3],
-- Top
texvertices[1], texvertices[2], texvertices[4],
texvertices[1], texvertices[4], texvertices[3],
-- Bottom
texvertices[1], texvertices[2], texvertices[4],
texvertices[1], texvertices[4], texvertices[3],
}
return cubeverts, cubetexCoords
end
``````