基本上Svg是用来表示2D图形的,但也不是不可以表示3D图形。
首先是3D图形位置的处理,我们需要为3D图形建立3D坐标系,并实现3D坐标变换,包括平移,旋转等,具体的实现方法有欧拉法,旋转矩阵法和四元数法等。根据3D模型的顶点坐标,可以实现3D图形在位置上的的显示。由于ADOBE SVG VIEWER提供了丰富的坐标变换函数,包括平移变换(translate), 旋转变换(rotate), 伸缩变换(scale), 歪斜变换(skew), 矩阵变换(matrix)等,因此实现坐标变换相对简单。
第二步需要将3D 物体显示在平面上,即3D坐标到2D的变换,因为最终图形还是要在平面上表示出来,这需要考虑以下问题:
-
透视原理,距离越远图形越小,最终图形收缩到一个或多个点(消失点),具体有一点透视(例如例一),两点透视和三点透视。
-
显示顺序,根据SVG按顺序绘制的原则,图形的距离越近越后画,距离越远越先画,而且被靠近用户的图形覆盖(对复杂图形计算比较麻烦)
-
图形在不同位置上,根据自身特点(材质,形状)以及光源和摄像机的位置,具有不同的颜色,光照,纹理,清晰度等。这是最麻烦也是最影响速度的一点。
下面是两个例子,第一个非常简单,实现了在一个消失点飞出的球体,根据球体的位置进行了坐标变换,以及排序,以保证正确的绘制顺序。
<?
xml version
=
"
1.0
"
encoding
=
"
utf-8
"
?>
< svg id = " doc " viewbox = " 0 0 200 200 " zoomAndPan = " disable " onload = " init() " onresize = " sizeChange() " >
< desc > SVG test </ desc >
< script type = " text/javascript " ><! [CDATA[
var svgns = " http://www.w3.org/2000/svg " ;
var xlinkNS = " http://www.w3.org/1999/xlink " ;
var myNS = " http://www.svgfun.com/2006/others " ;
// Transform string
var t1;
var t2;
// Draw range and center point
var maxX,maxY,centerX,centerY;
var maxZ = 2000 ;
// Speed and accelerate
var maxSpeed = 100 ;
var maxAccelerate = 5 ;
// Refresh interval
var interval = 100 ;
// Object number
var count = 30 ;
var groupId = " g1 " ;
var objList = new Array();
// /
// / Bubble class
// /
function Bubble(id, cx, cy, cz, r, speed, accelerate, fill, transform)
... {
this.id = id;
this.cx = cx;
this.cy = cy;
this.cz = cz;
this.r = r;
this.speed = speed;
this.accelerate = accelerate;
this.fill = fill;
this.transform = transform;
this.draw = draw;
this.move = move;
}
function draw( groupId )
... {
var group = svgDocument.getElementById( groupId );
if(group == null)
...{
group = svgDocument.createElementNS(svgns, "g" )
group.setAttributeNS(null,"id",groupId);
svgDocument.documentElement.appendChild(group);
}
var elem = svgDocument.createElementNS(svgns, "circle" );
elem.setAttributeNS(null, "id", this.id);
elem.setAttributeNS(null, "cx", this.cx);
elem.setAttributeNS(null, "cy", this.cy);
elem.setAttributeNS(null, "r", this.r);
elem.setAttributeNS(myNS, "z", this.cz);
elem.setAttributeNS(myNS, "speed", this.speed);
elem.setAttributeNS(myNS, "accelerate", this.accelerate);
elem.setAttributeNS(null,"fill",this.fill);
elem.setAttributeNS(null,"transform",this.transform);
group.appendChild(elem);
}
function move()
... {
var newSpeed;
//Set new speed by accelerate
if( (this.speed + this.accelerate) < maxSpeed )
newSpeed = this.speed + this.accelerate;
else
newSpeed = maxSpeed;
//Set z distance
if( this.cz > this.speed )
...{
this.cz = this.cz - this.speed;
this.speed = newSpeed;
}
else
...{
//Set random position and object back to vinish point
this.cx = Math.random() * maxX;
this.cy = Math.random() * maxY;
this.cz = maxZ - 1;
this.speed = Math.random() * maxSpeed;
this.accelerate = Math.random() * (maxAccelerate);
}
//Set transform
var scale = (maxZ-this.cz)/maxZ;
var t3=" scale("+scale+") ";
this.transform = t1+t3+t2;
}
// /
// / Bubble class finish
// /
function setRange()
... {
maxX = window.innerWidth;
maxY = window.innerHeight;
centerX = maxX/2;
centerY = maxY/2;
t1 = "translate("+centerX+","+centerY+")";
t2 = "translate(-"+centerX+",-"+centerY+")";
//alert(t1+t2);
}
function sizeChange()
... {
setRange();
refresh();
}
function refresh()
... {
for (i in objList)
...{
objList[i].move();
}
objList.sort(sortByZ);
var group = document.getElementById( groupId );
document.documentElement.removeChild(group);
for (i in objList)
...{
objList[i].draw(groupId);
}
var group = document.getElementById( groupId );
//alert(printNode(group));
}
function init()
... {
setRange();
for(var i=0; i < count; i++)
...{
var id = "bubble"+i;
var cx = Math.random() * maxX;
var cy = Math.random() * maxY;
var cz = maxZ - 1;
var r = 50;
var speed = Math.random() * maxSpeed;
var accelerate = Math.random() * (maxAccelerate);
var fill = "url(#radial2)";
var transform = "scale(1)";
var bubble = new Bubble(id, cx, cy, cz, r, speed, accelerate, fill, transform);
objList[objList.length] = bubble;
}
//Set position and speed etc
refresh();
setInterval("refresh()",interval);
//alert(printNode(instance));
}
function sortByZ(a,b)
... {
return b.cz - a.cz;
}
]] ></ script >
< defs >
< circle id = " obj1 " r = ' 50 ' fill = ' url(#radial1) ' >
</ circle >
< radialGradient id = " radial1 " cx = " 75% " cy = " 75% " r = " 75% " >
< stop offset = " 15% " stop - color = " white " />
< stop offset = " 100% " stop - color = " red " />
</ radialGradient >
< radialGradient id = " radial2 " cx = " 25% " cy = " 25% " r = " 75% " >
< stop offset = " 15% " stop - color = " white " />
< stop offset = " 100% " stop - color = " blue " />
</ radialGradient >
< radialGradient id = " radial3 " >
< stop offset = " 15% " stop - color = " white " />
< stop offset = " 100% " stop - color = " green " />
</ radialGradient >
</ defs >
< rect id = " back " x = " 0 " y = " 0 " width = " 100% " height = " 100% " fill = " black " fill - opacity = " 1 " />
< g id = " g1 " >
</ g >
</ svg >
< svg id = " doc " viewbox = " 0 0 200 200 " zoomAndPan = " disable " onload = " init() " onresize = " sizeChange() " >
< desc > SVG test </ desc >
< script type = " text/javascript " ><! [CDATA[
var svgns = " http://www.w3.org/2000/svg " ;
var xlinkNS = " http://www.w3.org/1999/xlink " ;
var myNS = " http://www.svgfun.com/2006/others " ;
// Transform string
var t1;
var t2;
// Draw range and center point
var maxX,maxY,centerX,centerY;
var maxZ = 2000 ;
// Speed and accelerate
var maxSpeed = 100 ;
var maxAccelerate = 5 ;
// Refresh interval
var interval = 100 ;
// Object number
var count = 30 ;
var groupId = " g1 " ;
var objList = new Array();
// /
// / Bubble class
// /
function Bubble(id, cx, cy, cz, r, speed, accelerate, fill, transform)
... {
this.id = id;
this.cx = cx;
this.cy = cy;
this.cz = cz;
this.r = r;
this.speed = speed;
this.accelerate = accelerate;
this.fill = fill;
this.transform = transform;
this.draw = draw;
this.move = move;
}
function draw( groupId )
... {
var group = svgDocument.getElementById( groupId );
if(group == null)
...{
group = svgDocument.createElementNS(svgns, "g" )
group.setAttributeNS(null,"id",groupId);
svgDocument.documentElement.appendChild(group);
}
var elem = svgDocument.createElementNS(svgns, "circle" );
elem.setAttributeNS(null, "id", this.id);
elem.setAttributeNS(null, "cx", this.cx);
elem.setAttributeNS(null, "cy", this.cy);
elem.setAttributeNS(null, "r", this.r);
elem.setAttributeNS(myNS, "z", this.cz);
elem.setAttributeNS(myNS, "speed", this.speed);
elem.setAttributeNS(myNS, "accelerate", this.accelerate);
elem.setAttributeNS(null,"fill",this.fill);
elem.setAttributeNS(null,"transform",this.transform);
group.appendChild(elem);
}
function move()
... {
var newSpeed;
//Set new speed by accelerate
if( (this.speed + this.accelerate) < maxSpeed )
newSpeed = this.speed + this.accelerate;
else
newSpeed = maxSpeed;
//Set z distance
if( this.cz > this.speed )
...{
this.cz = this.cz - this.speed;
this.speed = newSpeed;
}
else
...{
//Set random position and object back to vinish point
this.cx = Math.random() * maxX;
this.cy = Math.random() * maxY;
this.cz = maxZ - 1;
this.speed = Math.random() * maxSpeed;
this.accelerate = Math.random() * (maxAccelerate);
}
//Set transform
var scale = (maxZ-this.cz)/maxZ;
var t3=" scale("+scale+") ";
this.transform = t1+t3+t2;
}
// /
// / Bubble class finish
// /
function setRange()
... {
maxX = window.innerWidth;
maxY = window.innerHeight;
centerX = maxX/2;
centerY = maxY/2;
t1 = "translate("+centerX+","+centerY+")";
t2 = "translate(-"+centerX+",-"+centerY+")";
//alert(t1+t2);
}
function sizeChange()
... {
setRange();
refresh();
}
function refresh()
... {
for (i in objList)
...{
objList[i].move();
}
objList.sort(sortByZ);
var group = document.getElementById( groupId );
document.documentElement.removeChild(group);
for (i in objList)
...{
objList[i].draw(groupId);
}
var group = document.getElementById( groupId );
//alert(printNode(group));
}
function init()
... {
setRange();
for(var i=0; i < count; i++)
...{
var id = "bubble"+i;
var cx = Math.random() * maxX;
var cy = Math.random() * maxY;
var cz = maxZ - 1;
var r = 50;
var speed = Math.random() * maxSpeed;
var accelerate = Math.random() * (maxAccelerate);
var fill = "url(#radial2)";
var transform = "scale(1)";
var bubble = new Bubble(id, cx, cy, cz, r, speed, accelerate, fill, transform);
objList[objList.length] = bubble;
}
//Set position and speed etc
refresh();
setInterval("refresh()",interval);
//alert(printNode(instance));
}
function sortByZ(a,b)
... {
return b.cz - a.cz;
}
]] ></ script >
< defs >
< circle id = " obj1 " r = ' 50 ' fill = ' url(#radial1) ' >
</ circle >
< radialGradient id = " radial1 " cx = " 75% " cy = " 75% " r = " 75% " >
< stop offset = " 15% " stop - color = " white " />
< stop offset = " 100% " stop - color = " red " />
</ radialGradient >
< radialGradient id = " radial2 " cx = " 25% " cy = " 25% " r = " 75% " >
< stop offset = " 15% " stop - color = " white " />
< stop offset = " 100% " stop - color = " blue " />
</ radialGradient >
< radialGradient id = " radial3 " >
< stop offset = " 15% " stop - color = " white " />
< stop offset = " 100% " stop - color = " green " />
</ radialGradient >
</ defs >
< rect id = " back " x = " 0 " y = " 0 " width = " 100% " height = " 100% " fill = " black " fill - opacity = " 1 " />
< g id = " g1 " >
</ g >
</ svg >
第二个是根据ADOBE DEMO中的化学元素例子改进,将元素文字改为球体,可以通过鼠标拖动旋转。
<
svg width
=
"
100%
"
height
=
"
100%
"
viewBox
=
"
0 0 350 300
"
xml:space
=
"
preserve
"
onload
=
"
Initialize(evt)
"
onmousedown
=
"
MouseDown(evt)
"
onmousemove
=
"
MouseMove(evt)
"
onmouseup
=
"
MouseUp(evt)
"
>
< title > 3D.svg </ title >
< desc > An interactive 3d model. </ desc >
< script language = " Javascript " ><! [CDATA[
var FontScalar = 8 // 10
// var ChargeScale = 0.8
var Dimensions = 200
var CameraDistance = Dimensions * 2
var HitherPlane = CameraDistance / 20
var MouseScalar = . 005
var MinMouseSpeed = . 01
var MouseMoveAverage = . 3
var MaxRotationalSpeed = . 05
var RotationsPerCycle = 4
var ApproachRange = 3 * RotationsPerCycle
var BondSpread = 3
var MinTurnIncrement = . 0001
var ShadowDistance = Dimensions / 4
var radio = 3 ;
var SVGDocument = null
var Rotating = true
var Scale = 1
var XOffset = 0
var YOffset = 0
var ZOffset = 0
var MinX = 0
var MaxX = 0
var MinY = 0
var MaxY = 0
var MinZ = 0
var MaxZ = 0
var Theta = 0 ;
var Phi = 0 ;
var ThrownTheta = 0 ;
var ThrownPhi = 0 ;
var Atoms = new Array();
var Bonds = new Array();
var debugFlag = true ; // My debug flag;
function Initialize(LoadEvent)
... {
SVGDocument = LoadEvent.getTarget().getOwnerDocument();
GenerateStructure();
/**//*
if(debugFlag == true)
{
s=printNode(SVGDocument.getElementById("atoms"));
alert(s);
debugFlag = false;
}
*/
}
function Atom(x, y, z, fillStyle)
... {
FixRange(x, y, z)
if (x != null) this.x = x;
if (y != null) this.y = y;
if (z != null) this.z = z;
this.SVG = SVGDocument.createElement("circle");
this.SVG.setAttributeNS(null,"fill", fillStyle);
//this.SVG.setAttributeNS(null,"filter","url(#dropShadow)");
SVGDocument.getElementById("atoms").appendChild(this.SVG);
Atoms[Atoms.length] = this; //Save to array
this.refresh();
return this;
}
Atom.prototype.x = 0 ;
Atom.prototype.y = 0 ;
Atom.prototype.z = 0 ;
Atom.prototype.bonds = new Array();
Atom.prototype.element = "" ;
Atom.prototype.displayPhi = 0 ;
Atom.prototype.displayTheta = 0 ;
Atom.prototype.distanceScalar = 1 ;
Atom.prototype.refresh = Atom_Refresh;
Atom.prototype.fixCoords = Atom_FixCoordinates;
Atom.prototype.calculateRotation = Atom_CalculateRotation
function Atom_FixCoordinates()
... {
this.x += XOffset;
this.y += YOffset;
this.z += ZOffset;
this.x = this.x * Scale;
this.y = this.y * Scale;
this.z = this.z * Scale;
this.refresh()
}
function Atom_Refresh()
... {
oldx = this.x
oldy = this.y
this.calculateRotation();
if (this.SVG != null)
...{
this.SVG.setAttribute("transform", "translate(" + this.displayX + ", " + this.displayY + ")");
this.SVG.setAttributeNS(null,"r", this.displayZ);
}
//Sort by displayZ
var elem = SVGDocument.getElementById("atoms");
var nodes = elem.getChildNodes;
var insertFlag = false;
//for(i = 1;i < ((nodes.length)-1);i += 2)
for(i = 0;i < ((nodes.length-1));i ++)
...{
//Compare by r attribute in SVG node
var nodeMap = nodes.item(i).attributes;
var r = nodeMap.getNamedItemNS(null, "r");
if(this.displayZ < r.nodeValue)
...{
elem.insertBefore(this.SVG, nodes.item(i));
insertFlag = true;
break;
}
}
//The nearest one
if(insertFlag == false)
elem.appendChild(this.SVG);
return ((oldx != this.x) || (oldy != this.y))
}
function Atom_CalculateRotation()
... {
var clipTheta = true;
var clipPhi = true;
for (var I = 1; I < RotationsPerCycle; I++)
...{
dTheta = Theta - this.displayTheta;
dPhi = Phi - this.displayPhi;
if ((dTheta > MaxRotationalSpeed * ApproachRange) || (dTheta < MaxRotationalSpeed * ApproachRange * -1))
dTheta = max(min(dTheta, MaxRotationalSpeed), MaxRotationalSpeed * -1)
else if ((dTheta < MinTurnIncrement) && (dTheta > MinTurnIncrement * -1))
dTheta = 0
else
...{
dTheta = dTheta / ApproachRange
clipTheta = false;
}
this.displayTheta += dTheta
if ((dPhi > MaxRotationalSpeed * ApproachRange) || (dPhi < MaxRotationalSpeed * ApproachRange * -1))
dPhi = max(min(dPhi, MaxRotationalSpeed), MaxRotationalSpeed * -1)
else if ((dPhi < MinTurnIncrement) && (dPhi > MinTurnIncrement * -1))
dPhi = 0
else
...{
dPhi = dPhi / ApproachRange
clipPhi = false;
}
this.displayPhi += dPhi
this.x += dTheta * this.z;
this.y += dPhi * this.z;
this.z -= (dTheta * this.x) + (dPhi * this.y);
}
if (clipTheta)
this.displayTheta = Theta - dTheta * (ApproachRange - 1)
if (clipPhi)
this.displayPhi = Phi - dPhi * (ApproachRange - 1)
z = this.z
if (z < HitherPlane - CameraDistance)
...{
z = HitherPlane - CameraDistance
}
DistanceScalar = CameraDistance / (z + CameraDistance)
this.distanceScalar = DistanceScalar
this.displayX = this.x * DistanceScalar
this.displayY = this.y * DistanceScalar
this.displayZ = DistanceScalar * FontScalar //The bigger z ,the smaller displayZ
}
// Create bond between atom, strength affect the storke width
function Bond(Atom1, Atom2, Strength)
... {
this.left = Atom1;
this.right = Atom2;
if (Strength != null) this.strength = Strength;
this.SVG = SVGDocument.createElement("line");
this.SVG.setAttributeNS(null,"stroke", "brown");
this.SVG.setAttributeNS(null,"stroke-width", this.strength);
SVGDocument.getElementById("bonds").appendChild(this.SVG)
Bonds[Bonds.length] = this;
this.refresh();
return this;
}
Bond.prototype.strength = 1 ;
Bond.prototype.refresh = Bond_Refresh;
// Update bond line according to atom positions
function Bond_Refresh()
... {
var x1 = this.right.displayX;
var y1 = this.right.displayY;
var x2 = this.left.displayX;
var y2 = this.left.displayY;
this.SVG.setAttribute("x1", x1);
this.SVG.setAttribute("y1", y1);
this.SVG.setAttribute("x2", x2);
this.SVG.setAttribute("y2", y2);
}
function RefreshScreen()
... {
for (var I = 0; I < Atoms.length; I++)
...{
Atoms[I].fixCoords()
}
for (var I = 0; I < Bonds.length; I++)
...{
Bonds[I].refresh();
}
}
function FixRange(x, y, z)
... {
if (x < MinX) MinX = x;
if (x > MaxX) MaxX = x;
if (y < MinY) MinY = y;
if (y > MaxY) MaxY = y;
if (z < MinZ) MinZ = z;
if (z > MaxZ) MaxZ = z;
XRange = MaxX - MinX
YRange = MaxY - MinY
ZRange = MaxZ - MinZ
Range = max(max(XRange, YRange), ZRange)
if (Range == 0)
Range = Dimensions
Scale = Dimensions / Range
XOffset = XRange / 2 - MaxX
YOffset = YRange / 2 - MaxY
ZOffset = ZRange / 2 - MaxZ
}
function max(a, b)
... {
if (a > b)
return a
else
return b
}
function min(a, b)
... {
if (a < b)
return a
else
return b
}
Mouse_LastX = null
Mouse_LastY = null
Mouse_dX = null
Mouse_dY = null
function MouseDown(MouseEvent)
... {
Mouse_LastX = MouseEvent.getScreenX()
Mouse_LastY = MouseEvent.getScreenY()
Mouse_dX = 0
Mouse_dY = 0
ThrownTheta = 0
ThrownPhi = 0
if (!(Rotating))
...{
Rotating = true
Rotate()
}
}
function MouseMove(MouseEvent)
... {
if ((Mouse_LastX != null) && (Mouse_LastY != null))
...{
Mouse_dX = ((1.0 - MouseMoveAverage) * Mouse_dX)
+ (MouseMoveAverage * ((MouseEvent.getScreenX() - Mouse_LastX) * MouseScalar))
Mouse_dY = ((1.0 - MouseMoveAverage) * Mouse_dY)
+ (MouseMoveAverage * ((MouseEvent.getScreenY() - Mouse_LastY) * MouseScalar))
Theta -= Mouse_dX
Phi -= Mouse_dY
Mouse_LastX = MouseEvent.getScreenX()
Mouse_LastY = MouseEvent.getScreenY()
}
}
function MouseUp(MouseEvent)
... {
if ((Mouse_LastX != null) && (Mouse_LastY != null))
...{
ThrownTheta = Mouse_dX
ThrownPhi = Mouse_dY
if ((ThrownTheta < MinMouseSpeed) && (ThrownTheta > (MinMouseSpeed * -1))
&& (ThrownPhi < MinMouseSpeed) && (ThrownPhi > (MinMouseSpeed * -1)))
...{
ThrownTheta = 0
ThrownPhi = 0
}
}
Mouse_LastX = null
Mouse_LastY = null
}
function Rotate()
... {
MovementMade = false
for (var I = 0; I < Atoms.length; I++)
...{
JustMoved = Atoms[I].refresh()
MovementMade = MovementMade || JustMoved
}
for (var I = 0; I < Bonds.length; I++)
...{
Bonds[I].refresh();
}
Theta -= ThrownTheta
Phi -= ThrownPhi
if ((MovementMade) || (ThrownTheta != 0) || (ThrownPhi != 0) || (Mouse_LastX != null))
setTimeout("window.rotate()", 50)
else
Rotating = false
}
window.rotate = Rotate
// Create molecule structure
function GenerateStructure()
... {
x = 10;
y = 10;
z = 10;
var point1 = new Atom(x, y, z, "url(#radial1)");
x = 15;
y = 10;
z = 10;
var point2 = new Atom(x, y, z, "url(#radial2)");
x = 10;
y = 15;
z = 10;
var point3 = new Atom(x, y, z, "url(#radial3)");
x = 10;
y = 10;
z = 15;
var point4 = new Atom(x, y, z, "url(#radial4)");
var strength = 2;
var line1 = new Bond(point1, point2, strength);
var line2 = new Bond(point1, point3, strength);
var line3 = new Bond(point1, point4, strength);
RefreshScreen();
Rotate();
}
]] ></ script >
< defs >
< radialGradient id = " radial1 " cx = " 75% " cy = " 75% " r = " 50% " >
< stop offset = " 15% " stop - color = " white " />
< stop offset = " 100% " stop - color = " red " />
</ radialGradient >
< radialGradient id = " radial2 " cx = " 75% " cy = " 75% " r = " 50% " >
< stop offset = " 15% " stop - color = " white " />
< stop offset = " 100% " stop - color = " green " />
</ radialGradient >
< radialGradient id = " radial3 " cx = " 75% " cy = " 75% " r = " 50% " >
< stop offset = " 15% " stop - color = " white " />
< stop offset = " 100% " stop - color = " blue " />
</ radialGradient >
< radialGradient id = " radial4 " cx = " 75% " cy = " 75% " r = " 50% " >
< stop offset = " 15% " stop - color = " white " />
< stop offset = " 100% " stop - color = " purple " />
</ radialGradient >
< filter id = " dropShadow " filterUnits = " userSpaceOnUse " >
< feOffset in = " SourceAlpha " dx = " 0 " dy = " 85 " result = " offset " />
< feGaussianBlur in = " offset " stdDeviation = " 5 " result = " blur " />
< feMerge >
< feMergeNode in = " blur " />
< feMergeNode in = " SourceGraphic " />
</ feMerge >
</ filter >
</ defs >
< g id = " backdrop " >
< rect x = " 0 " y = " 0 " width = " 350 " height = " 300 " style = " fill:white; " />
</ g >
< g transform = " translate(-30, -85) " >
< g id = " structure " >
< g id = " shadows " pointer - events = " none " transform = " translate(200, 350) " style = " fill:grey " />
< g id = " bonds " pointer - events = " none " transform = " translate(200, 215) " />
< g id = " atoms " pointer - events = " none " transform = " translate(200, 220) " style = " text-anchor:middle;alignment-baseline:middle " />
</ g >
</ g >
</ svg >
< title > 3D.svg </ title >
< desc > An interactive 3d model. </ desc >
< script language = " Javascript " ><! [CDATA[
var FontScalar = 8 // 10
// var ChargeScale = 0.8
var Dimensions = 200
var CameraDistance = Dimensions * 2
var HitherPlane = CameraDistance / 20
var MouseScalar = . 005
var MinMouseSpeed = . 01
var MouseMoveAverage = . 3
var MaxRotationalSpeed = . 05
var RotationsPerCycle = 4
var ApproachRange = 3 * RotationsPerCycle
var BondSpread = 3
var MinTurnIncrement = . 0001
var ShadowDistance = Dimensions / 4
var radio = 3 ;
var SVGDocument = null
var Rotating = true
var Scale = 1
var XOffset = 0
var YOffset = 0
var ZOffset = 0
var MinX = 0
var MaxX = 0
var MinY = 0
var MaxY = 0
var MinZ = 0
var MaxZ = 0
var Theta = 0 ;
var Phi = 0 ;
var ThrownTheta = 0 ;
var ThrownPhi = 0 ;
var Atoms = new Array();
var Bonds = new Array();
var debugFlag = true ; // My debug flag;
function Initialize(LoadEvent)
... {
SVGDocument = LoadEvent.getTarget().getOwnerDocument();
GenerateStructure();
/**//*
if(debugFlag == true)
{
s=printNode(SVGDocument.getElementById("atoms"));
alert(s);
debugFlag = false;
}
*/
}
function Atom(x, y, z, fillStyle)
... {
FixRange(x, y, z)
if (x != null) this.x = x;
if (y != null) this.y = y;
if (z != null) this.z = z;
this.SVG = SVGDocument.createElement("circle");
this.SVG.setAttributeNS(null,"fill", fillStyle);
//this.SVG.setAttributeNS(null,"filter","url(#dropShadow)");
SVGDocument.getElementById("atoms").appendChild(this.SVG);
Atoms[Atoms.length] = this; //Save to array
this.refresh();
return this;
}
Atom.prototype.x = 0 ;
Atom.prototype.y = 0 ;
Atom.prototype.z = 0 ;
Atom.prototype.bonds = new Array();
Atom.prototype.element = "" ;
Atom.prototype.displayPhi = 0 ;
Atom.prototype.displayTheta = 0 ;
Atom.prototype.distanceScalar = 1 ;
Atom.prototype.refresh = Atom_Refresh;
Atom.prototype.fixCoords = Atom_FixCoordinates;
Atom.prototype.calculateRotation = Atom_CalculateRotation
function Atom_FixCoordinates()
... {
this.x += XOffset;
this.y += YOffset;
this.z += ZOffset;
this.x = this.x * Scale;
this.y = this.y * Scale;
this.z = this.z * Scale;
this.refresh()
}
function Atom_Refresh()
... {
oldx = this.x
oldy = this.y
this.calculateRotation();
if (this.SVG != null)
...{
this.SVG.setAttribute("transform", "translate(" + this.displayX + ", " + this.displayY + ")");
this.SVG.setAttributeNS(null,"r", this.displayZ);
}
//Sort by displayZ
var elem = SVGDocument.getElementById("atoms");
var nodes = elem.getChildNodes;
var insertFlag = false;
//for(i = 1;i < ((nodes.length)-1);i += 2)
for(i = 0;i < ((nodes.length-1));i ++)
...{
//Compare by r attribute in SVG node
var nodeMap = nodes.item(i).attributes;
var r = nodeMap.getNamedItemNS(null, "r");
if(this.displayZ < r.nodeValue)
...{
elem.insertBefore(this.SVG, nodes.item(i));
insertFlag = true;
break;
}
}
//The nearest one
if(insertFlag == false)
elem.appendChild(this.SVG);
return ((oldx != this.x) || (oldy != this.y))
}
function Atom_CalculateRotation()
... {
var clipTheta = true;
var clipPhi = true;
for (var I = 1; I < RotationsPerCycle; I++)
...{
dTheta = Theta - this.displayTheta;
dPhi = Phi - this.displayPhi;
if ((dTheta > MaxRotationalSpeed * ApproachRange) || (dTheta < MaxRotationalSpeed * ApproachRange * -1))
dTheta = max(min(dTheta, MaxRotationalSpeed), MaxRotationalSpeed * -1)
else if ((dTheta < MinTurnIncrement) && (dTheta > MinTurnIncrement * -1))
dTheta = 0
else
...{
dTheta = dTheta / ApproachRange
clipTheta = false;
}
this.displayTheta += dTheta
if ((dPhi > MaxRotationalSpeed * ApproachRange) || (dPhi < MaxRotationalSpeed * ApproachRange * -1))
dPhi = max(min(dPhi, MaxRotationalSpeed), MaxRotationalSpeed * -1)
else if ((dPhi < MinTurnIncrement) && (dPhi > MinTurnIncrement * -1))
dPhi = 0
else
...{
dPhi = dPhi / ApproachRange
clipPhi = false;
}
this.displayPhi += dPhi
this.x += dTheta * this.z;
this.y += dPhi * this.z;
this.z -= (dTheta * this.x) + (dPhi * this.y);
}
if (clipTheta)
this.displayTheta = Theta - dTheta * (ApproachRange - 1)
if (clipPhi)
this.displayPhi = Phi - dPhi * (ApproachRange - 1)
z = this.z
if (z < HitherPlane - CameraDistance)
...{
z = HitherPlane - CameraDistance
}
DistanceScalar = CameraDistance / (z + CameraDistance)
this.distanceScalar = DistanceScalar
this.displayX = this.x * DistanceScalar
this.displayY = this.y * DistanceScalar
this.displayZ = DistanceScalar * FontScalar //The bigger z ,the smaller displayZ
}
// Create bond between atom, strength affect the storke width
function Bond(Atom1, Atom2, Strength)
... {
this.left = Atom1;
this.right = Atom2;
if (Strength != null) this.strength = Strength;
this.SVG = SVGDocument.createElement("line");
this.SVG.setAttributeNS(null,"stroke", "brown");
this.SVG.setAttributeNS(null,"stroke-width", this.strength);
SVGDocument.getElementById("bonds").appendChild(this.SVG)
Bonds[Bonds.length] = this;
this.refresh();
return this;
}
Bond.prototype.strength = 1 ;
Bond.prototype.refresh = Bond_Refresh;
// Update bond line according to atom positions
function Bond_Refresh()
... {
var x1 = this.right.displayX;
var y1 = this.right.displayY;
var x2 = this.left.displayX;
var y2 = this.left.displayY;
this.SVG.setAttribute("x1", x1);
this.SVG.setAttribute("y1", y1);
this.SVG.setAttribute("x2", x2);
this.SVG.setAttribute("y2", y2);
}
function RefreshScreen()
... {
for (var I = 0; I < Atoms.length; I++)
...{
Atoms[I].fixCoords()
}
for (var I = 0; I < Bonds.length; I++)
...{
Bonds[I].refresh();
}
}
function FixRange(x, y, z)
... {
if (x < MinX) MinX = x;
if (x > MaxX) MaxX = x;
if (y < MinY) MinY = y;
if (y > MaxY) MaxY = y;
if (z < MinZ) MinZ = z;
if (z > MaxZ) MaxZ = z;
XRange = MaxX - MinX
YRange = MaxY - MinY
ZRange = MaxZ - MinZ
Range = max(max(XRange, YRange), ZRange)
if (Range == 0)
Range = Dimensions
Scale = Dimensions / Range
XOffset = XRange / 2 - MaxX
YOffset = YRange / 2 - MaxY
ZOffset = ZRange / 2 - MaxZ
}
function max(a, b)
... {
if (a > b)
return a
else
return b
}
function min(a, b)
... {
if (a < b)
return a
else
return b
}
Mouse_LastX = null
Mouse_LastY = null
Mouse_dX = null
Mouse_dY = null
function MouseDown(MouseEvent)
... {
Mouse_LastX = MouseEvent.getScreenX()
Mouse_LastY = MouseEvent.getScreenY()
Mouse_dX = 0
Mouse_dY = 0
ThrownTheta = 0
ThrownPhi = 0
if (!(Rotating))
...{
Rotating = true
Rotate()
}
}
function MouseMove(MouseEvent)
... {
if ((Mouse_LastX != null) && (Mouse_LastY != null))
...{
Mouse_dX = ((1.0 - MouseMoveAverage) * Mouse_dX)
+ (MouseMoveAverage * ((MouseEvent.getScreenX() - Mouse_LastX) * MouseScalar))
Mouse_dY = ((1.0 - MouseMoveAverage) * Mouse_dY)
+ (MouseMoveAverage * ((MouseEvent.getScreenY() - Mouse_LastY) * MouseScalar))
Theta -= Mouse_dX
Phi -= Mouse_dY
Mouse_LastX = MouseEvent.getScreenX()
Mouse_LastY = MouseEvent.getScreenY()
}
}
function MouseUp(MouseEvent)
... {
if ((Mouse_LastX != null) && (Mouse_LastY != null))
...{
ThrownTheta = Mouse_dX
ThrownPhi = Mouse_dY
if ((ThrownTheta < MinMouseSpeed) && (ThrownTheta > (MinMouseSpeed * -1))
&& (ThrownPhi < MinMouseSpeed) && (ThrownPhi > (MinMouseSpeed * -1)))
...{
ThrownTheta = 0
ThrownPhi = 0
}
}
Mouse_LastX = null
Mouse_LastY = null
}
function Rotate()
... {
MovementMade = false
for (var I = 0; I < Atoms.length; I++)
...{
JustMoved = Atoms[I].refresh()
MovementMade = MovementMade || JustMoved
}
for (var I = 0; I < Bonds.length; I++)
...{
Bonds[I].refresh();
}
Theta -= ThrownTheta
Phi -= ThrownPhi
if ((MovementMade) || (ThrownTheta != 0) || (ThrownPhi != 0) || (Mouse_LastX != null))
setTimeout("window.rotate()", 50)
else
Rotating = false
}
window.rotate = Rotate
// Create molecule structure
function GenerateStructure()
... {
x = 10;
y = 10;
z = 10;
var point1 = new Atom(x, y, z, "url(#radial1)");
x = 15;
y = 10;
z = 10;
var point2 = new Atom(x, y, z, "url(#radial2)");
x = 10;
y = 15;
z = 10;
var point3 = new Atom(x, y, z, "url(#radial3)");
x = 10;
y = 10;
z = 15;
var point4 = new Atom(x, y, z, "url(#radial4)");
var strength = 2;
var line1 = new Bond(point1, point2, strength);
var line2 = new Bond(point1, point3, strength);
var line3 = new Bond(point1, point4, strength);
RefreshScreen();
Rotate();
}
]] ></ script >
< defs >
< radialGradient id = " radial1 " cx = " 75% " cy = " 75% " r = " 50% " >
< stop offset = " 15% " stop - color = " white " />
< stop offset = " 100% " stop - color = " red " />
</ radialGradient >
< radialGradient id = " radial2 " cx = " 75% " cy = " 75% " r = " 50% " >
< stop offset = " 15% " stop - color = " white " />
< stop offset = " 100% " stop - color = " green " />
</ radialGradient >
< radialGradient id = " radial3 " cx = " 75% " cy = " 75% " r = " 50% " >
< stop offset = " 15% " stop - color = " white " />
< stop offset = " 100% " stop - color = " blue " />
</ radialGradient >
< radialGradient id = " radial4 " cx = " 75% " cy = " 75% " r = " 50% " >
< stop offset = " 15% " stop - color = " white " />
< stop offset = " 100% " stop - color = " purple " />
</ radialGradient >
< filter id = " dropShadow " filterUnits = " userSpaceOnUse " >
< feOffset in = " SourceAlpha " dx = " 0 " dy = " 85 " result = " offset " />
< feGaussianBlur in = " offset " stdDeviation = " 5 " result = " blur " />
< feMerge >
< feMergeNode in = " blur " />
< feMergeNode in = " SourceGraphic " />
</ feMerge >
</ filter >
</ defs >
< g id = " backdrop " >
< rect x = " 0 " y = " 0 " width = " 350 " height = " 300 " style = " fill:white; " />
</ g >
< g transform = " translate(-30, -85) " >
< g id = " structure " >
< g id = " shadows " pointer - events = " none " transform = " translate(200, 350) " style = " fill:grey " />
< g id = " bonds " pointer - events = " none " transform = " translate(200, 215) " />
< g id = " atoms " pointer - events = " none " transform = " translate(200, 220) " style = " text-anchor:middle;alignment-baseline:middle " />
</ g >
</ g >
</ svg >
例子中都使用的是最简单的球体,而且缺省的处理只有一个固定的颜色渐变。
我认为SVG表现3D图形的问题主要有两个,一是SVG是否提供了必要的3D建模所需的一些工具,虽然SVG提供了相对丰富的坐标转换和渲染方法,例如滤镜效果,光照效果和高斯模糊,但这些操作的速度很慢,而且相对专门的3D工具,这些方法还远远不够,所以使用SVG建立3D图形意味着更多的代码和劳动。
二是效率问题,在例1中,圆形的数量到100个后,速度明显下降。在例二中,如果打开阴影特效,速度将慢的让人无法忍受。
因此,我认为在SVG中使用3D图形仅仅适用于以下情况:
1, 图形数量不多
2, 对图形效果的渲染要求不高或没有要求
3, 实时性,即对速度要求不高
期望使用SVG实现一个复杂的3D应用,例如3D射击游戏在现阶段是不现实的,但对一些特定应用,例如立体几何的教学软件等,SVG还是能够使用的。
对复杂的3D应用,除了OPENGL和DIRECTX等传统手段,可以期待X3D等其它XML应用的发展。