Оригинал: Making a 3D Engine in jQuery
Что мы будем делать
Мы собираемся сделать 3D движок, который позволит создавать фигуры, просто создавая массив точеку. В этом движке мы будем иметь Камеру, Сцену и Объект. Он будет работать также хорошо, как и настоящая жизнь. Чем ближе камера к объекту, тем больше он становится. Но это не будет полноценным движком и не сможет отрисовывать больше, чем один объект.
Скачайте или Посмотрите пример
Объект ‘Display’
Этот объект более или менее подходит, чтобы задавать свойства и методы каждого объекта. Чтобы создать куб, я должен сначала сделать наследование класса DisplayObject3D. Это даст объекту свойства, необходимые для всей работы.
Вот этот класс:
var DisplayObject3D = function(){
return this;
};
DisplayObject3D.prototype._x = 0;
DisplayObject3D.prototype._y = 0;
//Create 3d Points
DisplayObject3D.prototype.make3DPoint = function(x,y,z) {
var point = {};
point.x = x;
point.y = y;
point.z = z;
return point;
};
//Create 2d Points
DisplayObject3D.prototype.make2DPoint = function(x,y, depth, scaleFactor){
var point = {};
point.x = x;
point.y = y;
point.depth = depth;
point.scaleFactor = scaleFactor;
return point;
};
//Holds the container
DisplayObject3D.prototype.container = undefined;
//Holds an array of 3d points.
DisplayObject3D.prototype.pointsArray = [];
// Set the container and create place holders if
// there is no <ul> in the container
DisplayObject3D.prototype.init = function (container){
this.container = $(container);
this.containerId = this.container.attr("id");
//if there isn't a ul than it creates a list of +'s
if ($(container+":has(ul)").length === 0){
for (i=0; i < this.pointsArray.length; i++){
this.container.append('<b id="item'+i+'">+</b>');
}
}
};
Если это выглядит немного странно для Вас, то рекомендую посмотреть пост Object Oriented Programming with JavaScript
Создаем 3d/2d точки
Эти функции просто создают объект, один для 3d (x,y,z), а другой для 2d (x,y,глубина). Они будут нужны для отрисовки сцены.
Инициализация
Функция init назначает переменные к контейнеру, в котором инициализируется. Контейнер - это где Вы хотите отображать свой 3D объект. Здесь также проверяется, имеется ли в контейнере неупорядоченный список. Если не существует, то функция просто создает набор “+”. Но по настоящему в этом нет надобности. Я сделал это, чтобы не создавать несортированный список каждый раз, чтобы посмотреть, отрисовывается ли мой объект.
Камера
Я думаю, что по имени Вы уже догадались, что делает этот класс. Он как настоящая камера - просто создает точку просмотра.
Класс Camera:
var Camera3D = function (){};
//The x,y,z of the camera
Camera3D.prototype.x = 0;
Camera3D.prototype.y = 0;
Camera3D.prototype.z = 500;
//Determines the zoom
Camera3D.prototype.focalLength = 1000;
//Figure out how large the object should be in
//reference to the camera.
Camera3D.prototype.scaleRatio = function(item){
return this.focalLength/(this.focalLength + item.z - this.z);
};
//Initialize the camera with values.
Camera3D.prototype.init = function (x,y,z,focalLength){
this.x = x;
this.y = y;
this.z = z;
this.focalLength = focalLength;
};
Фокусное расстояние просто вычисляет, насколько большим будет выглядеть объект в зависимости от расстояния от камеры. Подумайте об этом как о линзах. Вы можете приблизить или отдалить объект.
scaleRatio создает волшебство. Он выясняет, насколько крупным должен быть объект при приближении к камере.
Объект 3D
Этот класс не слишком важен. Я просто добавил этот класс, чтобы я мог добавить более одного объекта в будущем. Прямо сейчас движок может обрабатывать только один объект за раз. Object3D - это массив. Когда Вы добавляете новый элемент в массив, он запускает функцию.
// class creates an array of objects to
// be rendered, and initializes them.
var Object3D = function (container){
this.container = $(container);
};
Object3D.prototype.objects = [];
//Add object to the list of objects.
Object3D.prototype.addChild = function (object3D){
this.objects.push(object3D);
object3D.init(this.container);
return object3D;
};
Сцена
Вот здесь происходят наиболее сложные вещи. Я не хочу углубляться в математику, но образования высшей школы будет достаточно
Я дам поверхностное описание кода, но думаю будет достаточно, чтобы понять, что он делает.
var Scene3D = function (){};
Scene3D.prototype.sceneItems = [];
//Adds objects to the list of items to be rendered.
Scene3D.prototype.addToScene = function (object){
this.sceneItems.push(object);
};
//Converts a 3d point into a 2d point.
Scene3D.prototype.Transform3DPointsTo2DPoints = function(points, axisRotations,camera){
var TransformedPointsArray = [];
var sx = Math.sin(axisRotations.x);
var cx = Math.cos(axisRotations.x);
var sy = Math.sin(axisRotations.y);
var cy = Math.cos(axisRotations.y);
var sz = Math.sin(axisRotations.z);
var cz = Math.cos(axisRotations.z);
var x,y,z, xy,xz, yx,yz, zx,zy, scaleFactor;
var i = points.length;
while (i--){
x = points[i].x;
y = points[i].y;
z = points[i].z;
// rotation around x
xy = cx*y - sx*z;
xz = sx*y + cx*z;
// rotation around y
yz = cy*xz - sy*x;
yx = sy*xz + cy*x;
// rotation around z
zx = cz*yx - sz*xy;
zy = sz*yx + cz*xy;
scaleFactor = camera.focalLength/(camera.focalLength + yz);
x = zx*scaleFactor;
y = zy*scaleFactor;
z = yz;
var displayObject = new DisplayObject3D();
TransformedPointsArray[i] = displayObject.make2DPoint(x, y, -z, scaleFactor);
}
return TransformedPointsArray;
};
//Takes the converted 2d and applies the appropriate CSS.
Scene3D.prototype.renderCamera = function (camera){
// Loop through all objects in the scene.
for(var i = 0 ; i< this.sceneItems.length; i++){
var obj = this.sceneItems[i].objects[0];
//transform the points in the object to 2d points.
var screenPoints = this.Transform3DPointsTo2DPoints(obj.pointsArray, axisRotation, camera);
//does the container have a ul inside of it.
var hasList = (document.getElementById(obj.containerId).getElementsByTagName("ul").length > 0);
//Cycle through each point in the object.
for (k=0; k < obj.pointsArray.length; k++){
var currItem = null;
//if the container has a list then select the lis
if (hasList){
currItem = document.getElementById(obj.containerId).getElementsByTagName("ul")[0].getElementsByTagName("li")[k];
}else{
//otherwise select whatever is there.
currItem = document.getElementById(obj.containerId).getElementsByTagName("*")[k];
}
//If there are items to render then...
if(currItem){
currItem._x = screenPoints[k].x;
currItem._y = screenPoints[k].y;
currItem.scale = screenPoints[k].scaleFactor;
//Render the CSS.
currItem.style.position = "absolute";
currItem.style.top = currItem._y+'px';
currItem.style.left = currItem._x+'px';
currItem.style.fontSize = 100*currItem.scale+'%';
$(currItem).css({opacity:(currItem.scale-.5)});
}
}
}
};
//Center for rotation
var axisRotation = new DisplayObject3D().make3DPoint(0,0,0);
Создание сцены
Первое, что Вы можете увидеть, это функция addToScene. В основном она заполняет массив, который используется для визуализации сцены. Если Ваш объект в этом массиве, он будет отрисован, если нет… Вы пропустили свой автобус.
Трансформация 3D в 2D
Следующей идет функция Transform3DPointsTo2DPoints. Возможно, это наиболее важная часть движка. Она берет 3D точки в пространстве и с помощью небольшой магии преобразовывает их в 2D, чтобы можно было отобразить на экране.
Камера
Функция renderCamera не делает ничего сложного. Она просто отображает содержимое на экране. Она делает это путем перебора в цикле объектов сцены и опрашивая функцию Transform3DPointsTo2DPoints. Функция возвращает 2D координаты, чтобы можно было установить объект с помощью CSS.
Вы, наверное, заметили, что я использую JavaScript для работы с CSS и выбора DOM вместо jQuery. Изначально я использовал jQuery, Но обнаружил, что браузер стал тормозить, и поэтому я вернулся к старой школе.
Куб
Я поместил класс Cube в отдельный файл, поэтому Вы можете импортировать те объекты, какие Вам нужны, вместо того чтобы загружать то, что не нужно.
Класс не слишком сложен. Он создает массив и помещает все точки на кубе в него. Класс использует функцию make3DPoint из класса DisplayObject3D, чтобы создать каждую точку.
А вот и код:
var Cube = function (size){
//if the size is not set then give a default
if (size === undefined){
size = 10;
}
//Create a 3d point for every point on the cube.
this.pointsArray = [
//make3dpoint is a function inherited from
//DisplayObject3D
this.make3DPoint(-size,-size,-size),
this.make3DPoint(size,-size,-size),
this.make3DPoint(size,-size,size),
this.make3DPoint(-size,-size,size),
this.make3DPoint(-size,size,-size),
this.make3DPoint(size,size,-size),
this.make3DPoint(size,size,size),
this.make3DPoint(-size,size,size),
this.make3DPoint(0,size,-size),
this.make3DPoint(size,size,0),
this.make3DPoint(0,size,size),
this.make3DPoint(-size,size,0),
this.make3DPoint(0,-size,-size),
this.make3DPoint(size,-size,0),
this.make3DPoint(0,-size,size),
this.make3DPoint(-size,-size,0),
this.make3DPoint(-size,0,-size),
this.make3DPoint(size,0,-size),
this.make3DPoint(size,0,size),
this.make3DPoint(-size,0,size)
];
};
//Inherit DisplayObject3d methods and properties
Cube.prototype = new DisplayObject3D();
Использование 3D движка
Вот несколько шагов, которые необходимы, чтобы всё заработало. Сначала нам нужен некоторый html. Я просто сделал div с id и поместил <ul> полный смайликов. Если хотите, можете оставить div пустым, но тогда он заполнится автоматически плюсиками.
Вот HTML:
<body>
<div id="item">
<!-- List of smiley faces -->
<ul>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
</ul>
</div>
</body>
<script type="text/javascript" src="jquery-1.3.2.min.js"></script>
<script src="3DEngine.js" type="text/javascript" charset="utf-8"></script>
<!--
Each 3d object is a seperate js so you only
have to import what you need.
-->
<script src="Cube.js" type="text/javascript" charset="utf-8"></script>
Далее идет jQuery. Тут немного шагов, которые Вам потребуется сделать:
1. Создайте и проинициализируйте камеру;
2. Создайте Object Holder (Object3D);
3. Создайте объект и поместите его в holder;
4. Создайте сцену и поместите в неё holder.
Вот код jQuery:
$(document).ready(function() {
//Create a camera
var camera = new Camera3D();
//initialize it.
camera.init(0,0,0,300);
//Create an object holder.
var item = new Object3D($("#item"));
//add a new cube.
item.addChild(new Cube(100));
//Create a scene
var scene = new Scene3D();
//Place the object Holder in the scene.
scene.addToScene(item);
//Animates the cube.
var animateIt = function(){
//rotates on the y and x axis.
axisRotation.y += .01
axisRotation.x -= .01
//Render the scene.
scene.renderCamera(camera);
};
setInterval(animateIt, 20);
});
Я не делал интерактивности от движения мышкой. Вы можете сами это сделать путем изменения значений axisRotation x, y и z когда мышка двигается. Возможно, это будет мой следующий пост.
Пока это всё
Обращайте внимание на несколько следующих постов. Я планирую сделать несколько разных фигур. Если Вы чувствуете себя уверенным, то попробуйте сделать свои фигуры. Просто сделайте массив из 3D кординат.
А теперь давайте соберем весь код вместе.
3DEngine.js
/*
* DisplayObject3D ----------------------------------------------
*/
var DisplayObject3D = function(){
return this;
};
DisplayObject3D.prototype._x = 0;
DisplayObject3D.prototype._y = 0;
//Create 3d Points
DisplayObject3D.prototype.make3DPoint = function(x,y,z) {
var point = {};
point.x = x;
point.y = y;
point.z = z;
return point;
};
//Create 2d Points
DisplayObject3D.prototype.make2DPoint = function(x,y, depth, scaleFactor){
var point = {};
point.x = x;
point.y = y;
point.depth = depth;
point.scaleFactor = scaleFactor;
return point;
};
DisplayObject3D.prototype.container = undefined;
DisplayObject3D.prototype.pointsArray = [];
DisplayObject3D.prototype.init = function (container){
this.container = $(container);
this.containerId = this.container.attr("id");
//if there isn't a ul than it creates a list of +'s
if ($(container+":has(ul)").length === 0){
for (i=0; i < this.pointsArray.length; i++){
this.container.append('<b id="item'+i+'">+</b>');
}
}
};
/*
* DisplayObject3D End ----------------------------------------------
*/
/*
* Camera3D ----------------------------------------------
*/
var Camera3D = function (){};
Camera3D.prototype.x = 0;
Camera3D.prototype.y = 0;
Camera3D.prototype.z = 500;
Camera3D.prototype.focalLength = 1000;
Camera3D.prototype.scaleRatio = function(item){
return this.focalLength/(this.focalLength + item.z - this.z);
};
Camera3D.prototype.init = function (x,y,z,focalLength){
this.x = x;
this.y = y;
this.z = z;
this.focalLength = focalLength;
};
/*
* Camera3D End ----------------------------------------------
*/
/*
* Object3D ----------------------------------------------
*/
var Object3D = function (container){
this.container = $(container);
};
Object3D.prototype.objects = [];
Object3D.prototype.addChild = function (object3D){
this.objects.push(object3D);
object3D.init(this.container);
return object3D;
};
/*
* Object3D End ----------------------------------------------
*/
/*
* Scene3D ----------------------------------------------
*/
var Scene3D = function (){};
Scene3D.prototype.sceneItems = [];
Scene3D.prototype.addToScene = function (object){
this.sceneItems.push(object);
};
Scene3D.prototype.Transform3DPointsTo2DPoints = function(points, axisRotations,camera){
var TransformedPointsArray = [];
var sx = Math.sin(axisRotations.x);
var cx = Math.cos(axisRotations.x);
var sy = Math.sin(axisRotations.y);
var cy = Math.cos(axisRotations.y);
var sz = Math.sin(axisRotations.z);
var cz = Math.cos(axisRotations.z);
var x,y,z, xy,xz, yx,yz, zx,zy, scaleFactor;
var i = points.length;
while (i--){
x = points[i].x;
y = points[i].y;
z = points[i].z;
// rotation around x
xy = cx*y - sx*z;
xz = sx*y + cx*z;
// rotation around y
yz = cy*xz - sy*x;
yx = sy*xz + cy*x;
// rotation around z
zx = cz*yx - sz*xy;
zy = sz*yx + cz*xy;
scaleFactor = camera.focalLength/(camera.focalLength + yz);
x = zx*scaleFactor;
y = zy*scaleFactor;
z = yz;
var displayObject = new DisplayObject3D();
TransformedPointsArray[i] = displayObject.make2DPoint(x, y, -z, scaleFactor);
}
return TransformedPointsArray;
};
Scene3D.prototype.renderCamera = function (camera){
for(var i = 0 ; i< this.sceneItems.length; i++){
var obj = this.sceneItems[i].objects[0];
var screenPoints = this.Transform3DPointsTo2DPoints(obj.pointsArray, axisRotation, camera);
var hasList = (document.getElementById(obj.containerId).getElementsByTagName("ul").length > 0);
for (k=0; k < obj.pointsArray.length; k++){
var currItem = null;
if (hasList){
currItem = document.getElementById(obj.containerId).getElementsByTagName("ul")[0].getElementsByTagName("li")[k];
}else{
currItem = document.getElementById(obj.containerId).getElementsByTagName("*")[k];
}
if(currItem){
currItem._x = screenPoints[k].x;
currItem._y = screenPoints[k].y;
currItem.scale = screenPoints[k].scaleFactor;
currItem.style.position = "absolute";
currItem.style.top = currItem._y+'px';
currItem.style.left = currItem._x+'px';
currItem.style.fontSize = 100*currItem.scale+'%';
$(currItem).css({opacity:(currItem.scale-.5)});
}
}
}
};
/*
* Scene3D End ----------------------------------------------
*/
//Center for rotation
var axisRotation = new DisplayObject3D().make3DPoint(0,0,0);
Cube.js
var Cube = function (size){
if (size === undefined){
size = 10;
}
this.pointsArray = [
this.make3DPoint(-size,-size,-size),
this.make3DPoint(size,-size,-size),
this.make3DPoint(size,-size,size),
this.make3DPoint(-size,-size,size),
this.make3DPoint(-size,size,-size),
this.make3DPoint(size,size,-size),
this.make3DPoint(size,size,size),
this.make3DPoint(-size,size,size),
this.make3DPoint(0,size,-size),
this.make3DPoint(size,size,0),
this.make3DPoint(0,size,size),
this.make3DPoint(-size,size,0),
this.make3DPoint(0,-size,-size),
this.make3DPoint(size,-size,0),
this.make3DPoint(0,-size,size),
this.make3DPoint(-size,-size,0),
this.make3DPoint(-size,0,-size),
this.make3DPoint(size,0,-size),
this.make3DPoint(size,0,size),
this.make3DPoint(-size,0,size)
];
};
Cube.prototype = new DisplayObject3D();
index.html
<html>
<head>
<title>Untitled Page</title>
<style type="text/css" media="screen">
#item{
width:100px;
height:100px;
margin:0 auto;
top:300px;
position: relative;
}
ul{
list-style-type: none;
}
body{
background-color: #111;
color: #69c;
font-family: Arial, "MS Trebuchet", sans-serif;
font-weight: bold;
font-size:2em;
}
</style>
</head>
<body>
<div id="item">
<ul>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
<li>☺</li>
</ul>
</div>
</body>
<script type="text/javascript" src="jquery-1.3.2.min.js"></script>
<script src="3DEngine.js" type="text/javascript" charset="utf-8"></script>
<script src="Cube.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
//<![CDATA[
$(document).ready(function() {
var camera = new Camera3D();
camera.init(0,0,0,300);
var item = new Object3D($("#item"));
item.addChild(new Cube(100));
var scene = new Scene3D();
scene.addToScene(item);
var animateIt = function(){
axisRotation.y += .01
axisRotation.x -= .01
scene.renderCamera(camera);
};
setInterval(animateIt, 20);
});
//]]>
</script>
</html>
Скачайте или Посмотрите пример
Популярность: 76%
ROM
RSS Записей
Поддержи сайт!






