A 3D Engine with MooTools

Wednesday, 14 April 2010 

Here's the first project on Grownseed, a basic 3D engine made with MooTools. For those of you who have never heard of MooTools, it's a Javascript framework that makes life much easier. The engine was originally based on the work of Devirtuoso as you can see here with JQuery. I've tried to take it further by making it a bit more versatile, particularly by making the CSS modifiers totally customisable and by making the rendering slightly more efficient. It's also very compact (at the moment anyway) and works with MooTools 1.11 (for the Joomla users) as well as MooTools 1.2.*.

I put it together as a proof of concept more than anything else, but I'd like to see people find a proper use for it. Have a look at the example below for a better idea, and if you want to see how to use it, read on.

Any suggestions are welcome, if you want to use it click here to get the engine and this example.

You can move the mouse around to change the camera (within the boundaries of the div below), you can also use the mouse wheel to trigger an animation (or similarly the up and down keys).

I will be working on making the code tidier (and probably more efficient) as I go ; in the mean time please look at the code below to understand how the example works.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
window.addEvent('load', function()
{
//coordinates of the container for the scene
var container_coords = $('moo3dexample').getCoordinates();
 
/*
declare the Moo3D scene:
- the rotation axis is given a slight angle to make it look like we're looking from higher
- the origins will simply help put our images at the right place on the page
- you could also use camera:{x,y,z,focalLength} to position the camera and/or change its focal length
*/
var scene = new Moo3D({rotationAxis: {x: 0.1}, origins: {x: container_coords.left + container_coords.width / 2, y: container_coords.top + container_coords.height / 2}});
 
//array to contain 3D points
var line = [];
 
//collection of all images
var images = $('moo3dexample').getElements('img');
 
//add a red border to images
images.setStyle('border', 'solid 1px #ac0000');
 
//number of images
var points_nb = images.length;
 
//distance between images
var distance = 100;
 
//go through all images
images.each(function(image, i)
{
var image_width = image.width;
var image_height = image.height;
 
/*
adding each image as a 3D point to line
- the element to be transformed
- x, y and z define the position in the scene
- the CSS modifiers accept a function that has 'this' referring to the point itself, or a fixed value
*/
line.push(
{
element: image,
x: 0 - image_width / 2,
y: 0 - 50,
z: i * distance,
modifiers:
{
'left': function(){return this.projection.x + scene.options.origins.x;},
'top': function(){return this.projection.y + scene.options.origins.y;},
'width': function(){return this.projection.scale * image_width},
'height': function(){return this.projection.scale * image_height},
'opacity': function(){return this.projection.scale.limit(0, 1);},
'z-index': function(){return Math.round(this.projection.scale * 100);}
}
});
});
 
//add line to the scene
scene.add(line);
 
//animation set up
 
var front_image = 0;
var back_image = points_nb - 1;
 
//animation paths
var animation_path_forward =
[
{y: line[front_image].y - 180, z: line[front_image].z, duration: 200},
{y: line[front_image].y - 180, z: line[back_image].z, duration: 600},
{y: line[front_image].y, z: line[back_image].z, duration: 200}
];
var animation_path_backward =
[
{y: line[back_image].y - 180, z: line[back_image].z, duration: 200},
{y: line[back_image].y - 180, z: line[front_image].z, duration: 600},
{y: line[back_image].y, z: line[front_image].z, duration: 200}
];
//total animation time
var total_animation_time = 1000;
 
//render interval in milliseconds
var refresh = 20;
 
//z increment per render for basic animation
var z_step = refresh * distance / total_animation_time;
 
var animating = false;
 
$('moo3dexample').addEvents(
{
//change rotation axis depending on mouse position
'mousemove': function(e)
{
e = new Event(e);
scene.options.rotationAxis.x = 0.1 + (container_coords.height / 2 - e.page.y + container_coords.top) / 800;
scene.options.rotationAxis.y = (0 - (container_coords.width / 2 - e.page.x + container_coords.left)) / 1000;
if (!animating) scene.render();
},
//trigger animation in mousewheel direction
'mousewheel': function(e)
{
e = new Event(e);
e.stop();
 
if (!animating)
{
//cumulates time
var total_refresh = 0;
 
//progression
var animation_step = 0;
 
//total duration
var total_duration = 0;
 
if (e.wheel == 1)
{
var starting_coords = {y: line[front_image].y, z: line[front_image].z};
var animation_path = animation_path_forward;
}else{
var starting_coords = {y: line[back_image].y, z: line[back_image].z};
var animation_path = animation_path_backward;
}
 
var animate = function()
{
line.each(function(image, i)
{
if ((e.wheel == 1 && i == front_image) || (e.wheel == -1 && i == back_image))
{
image.y = starting_coords.y + (total_refresh - total_duration) * (animation_path[animation_step].y - starting_coords.y) / animation_path[animation_step].duration;
image.z = starting_coords.z + (total_refresh - total_duration) * (animation_path[animation_step].z - starting_coords.z) / animation_path[animation_step].duration;
 
if ((total_refresh - total_duration) >= animation_path[animation_step].duration)
{
starting_coords.y = image.y;
starting_coords.z = image.z;
total_duration += animation_path[animation_step].duration;
animation_step++;
}
}else{
image.z -= e.wheel * z_step;
}
});
 
scene.render();
 
//stop rendering if total animation time reached
if (total_refresh >= total_animation_time)
{
animating = false;
$clear(animation);
 
//update front and back images
if (e.wheel == 1)
{
back_image = front_image;
front_image = (front_image < points_nb - 1 ? front_image + 1 : 0);
}else{
front_image = back_image;
back_image = (back_image > 0 ? back_image - 1 : points_nb - 1);
}
}
 
total_refresh += refresh;
}
 
animating = true;
animation = animate.periodical(refresh);
}
}
});
 
//tie up and down keys to wheel up and wheel down for people without a wheel
window.addEvent('keyup', function(e)
{
e = new Event(e);
 
if (e.key == 'up' || e.key == 'down')
{
e.stop();
e.wheel = (e.key == 'up' ? 1 : -1);
$('moo3dexample').fireEvent('mousewheel', e);
}
});
 
//render the entire scene - could also do "scene.render(line);" which would only render the one object
scene.render();
});
 

Comments 

 
#5 grownseed 2010-07-10 14:20
Quoting Santiago:
Wow, simply amazing!

Will keep a close eye in this for sure!

Great job pal! :)


Thanks, I've just created Moo3DFx and created a 3D carousel as an example for it, so if you're interested have a look at the post
Quote
 
 
#4 2010-07-03 08:55
Wow, simply amazing!

Will keep a close eye in this for sure!

Great job pal! :)
Quote
 
 
#3 2010-06-25 13:11
Awesome!
Some suggested applications:

1. 3D tag cloud.
2. Rotating thumbnail sphere.
3. Carousel
Quote
 
 
#2 grownseed 2010-05-31 12:51
Thanks Israel, much appreciated!
I'd like to take it a bit further before adding it to the MooTools Forge. I'm currently working on implementing (Fx) transitions to my engine which I'm hoping to make available shortly.
Quote
 
 
#1 2010-05-28 13:11
Great work!
Works smooth in my Firefox.
Define your class as Fx Extension and add it to MooTools Forge.

http://mootools.net/forge/
Quote
 

Add comment


Security code
Refresh