Moving a line geometry with three.js using GLSL - three.js

I have a line geometry made with three.js, which I want to move as a spermatozoid (i.e. the head moves first, then all of the points along the tail move accordingly to the head) using GLSL.
Here is a visual representation of what I want:
So as you can see, point G eases to (follows) H, which in turn follows point E, which in turn follows the head.
I have a working examples, being animated on the CPU. Here is the code:
class Boid {
constructor (position) {
this.position = position
this.speed = 0.0009 + Math.random() * 0.0003
this.pointsNum = 12
this.points = []
this.line = null
this.angle = Math.random() * 360
for (let i = 0; i < this.pointsNum; i += 1) {
this.points.push(new THREE.Vector3(1, 1, 1))
}
this.angle = 0
}
update (target, time) {
if (time) {
this.line.geometry.verticesNeedUpdate = true
this.line.geometry.vertices.forEach((p, i) => {
let nextP = this.line.geometry.vertices[i + 1]
if (nextP) {
// if it's not the HEAD point, follow the next point in the geometry vertices
p.x += (nextP.x - p.x) * (time * 8.0)
p.y += (nextP.y - p.y) * (time * 8.0)
p.z += (nextP.z - p.z) * (time * 8.0)
} else {
// if the point is in fact the head, ease it according to some random moving point in our scene (target)
p.x += (target.x - p.x) * time
p.y += (target.y - p.y) * time
p.z += (target.z - p.z) * time
}
})
}
}
}
And here is a working example.
This technique is working, but would like to accomplish the same stuff with GLSL. My question is how should I approach it? Should I pass the next vertex position to the previous one and ease in my vertex shader? How should I keep track of the next's point position?
Any help is more then appreciated, I have been thinking about this a lot without any success.

Related

Continuously moving ThreeJS camera slows down other animations to a crawl?

I have what I believe is a fairly common setup for a ThreeJS scene that emulates a first person shooter world. I have this block of code in my animate loop, which came from one of the ThreeJS "controls" example. I can't remember which one since it's been a while.
const onObject = userIsLookingAtObjects.length > 0;
let delta = (currentTime - prevTime) / 1000;
// Limit long delay gaps, which indicate
// a background task interfered with us and
// we don't want the camera/user
const limitDelta = 0.2;
if (delta > limitDelta) {
delta = Math.min(limitDelta, delta);
console.warn(`${errPrefix}Capping delta time at: ${limitDelta}`);
}
velocity.x -= velocity.x * 10.0 * delta;
velocity.z -= velocity.z * 10.0 * delta;
velocity.y -= 9.8 * 100.0 * delta; // 100.0 = mass
// ROS: The Number() constructor is simply being used
// to convert the TRUE/FALSE move<direction> values
// to a number in the following set of values: [-1, 0, 1]
direction.z = Number(moveForward) - Number(moveBackward);
direction.x = Number(moveRight) - Number(moveLeft);
direction.normalize(); // this ensures consistent movements in all directions
if (moveForward || moveBackward)
velocity.z -= direction.z * 400.0 * delta;
if (moveLeft || moveRight)
velocity.x -= direction.x * 400.0 * delta;
if (onObject === true) {
// ROS: This appears to be part of a check to allow jumping or
// not. See the raycast intersection code above involving
// onObject.
velocity.y = Math.max(0, velocity.y);
canJump = true;
if (g_BreakHerePlease)
// This aids tracing using Chrome DevTools. See pointerlock.js
// for the keystroke that sets g_BreakHerePlease to TRUE.
console.info(`${errPrefix}Set DevTools breakpoint here.`);
}
g_ThreeJsControls.moveRight(-velocity.x * delta);
g_ThreeJsControls.moveForward(-velocity.z * delta);
g_ThreeJsControls.getObject().position.y += (velocity.y * delta); // new behavior
if (g_ThreeJsControls.getObject().position.y < 10) {
velocity.y = 0;
g_ThreeJsControls.getObject().position.y = 10;
canJump = true;
}
Everything works fine, but I noticed something today. If one of my animation models is LERP'ing and I move the camera continuously, like when you "strafe" around an object in an FPS game, the object that is LERP'ing slows way down. So slow, that at first I thought that moving the camera actually stopped other animations. It doesn't, it just brings them to a snail's crawl.
What could be causing this? I'm hoping it's not inherent to moving the camera around a lot because "players" in my world will be moving constantly. Can I fix thi?

How to calculate an orbit around the earth with respect to Mercator projection?

I'm trying to create a small interactive UFO-flying-around-the-earth scene in three.js.
I thought it would be good to control the UFO on a 2D projection of the map (like a minimap), convert the pixel coordinates to lat/lng coordinates and finally transform lat/lng to a Vector that I can use for my 3D scene.
As it turns out, it wasn't.
Works great if the UFO flies around the Equator (+/- some degrees), but for sure I forgot to apply the projection to my 2D controls:
Now I'm a little bit lost. My controls (WASD keys) basically sets the speed and the rotation of the UFO on the minimap, but I have to add some sort of "correction" to the player. That's how the values are set at the moment:
let left = parseFloat(this.minimapPlayer.style.left) + this.speed * Math.cos(Math.PI / 180 * this.rotation)
let top = parseFloat(this.minimapPlayer.style.top) + this.speed * Math.sin(Math.PI / 180 * this.rotation)
Is there a way to create a "real" orbit, so that my UFO doesn't always fly through both poles? Or may there be a better approach to handle the orbit of the UFO (maybe without the minimap)?
Here's the full animation code so far:
animatePlane(firstRender) {
requestAnimationFrame(this.animatePlane)
// set next position
let left = parseFloat(this.minimapPlayer.style.left) + this.speed * Math.cos(Math.PI / 180 * this.rotation)
let top = parseFloat(this.minimapPlayer.style.top) + this.speed * Math.sin(Math.PI / 180 * this.rotation)
// handle border collisions
if (left < 0) {
left = this.minimapBounds.width
}
if (left > this.minimapBounds.width) {
left = 0
}
if (top < 0 || top > this.minimapBounds.height) {
this.rotation = this.rotation * -1
if (this.rotation > 0) {
this.plane.up.set(0, 1, 0)
} else {
this.plane.up.set(0, -1, 0)
}
top = top + this.speed * Math.sin(Math.PI / 180 * this.rotation)
if (left < this.minimapBounds.width / 2) {
left = left + this.speed * Math.cos(Math.PI / 180 * this.rotation) + this.minimapBounds.width / 2
} else {
left = left + this.speed * Math.cos(Math.PI / 180 * this.rotation) - this.minimapBounds.width / 2
}
}
this.minimapPlayer.style.left = `${left}px`
this.minimapPlayer.style.top = `${top}px`
// convert to lat/lng
const lat = (180 / this.minimapBounds.height) * (top - this.minimapBounds.height / 2) * -1
const lng = (360 / this.minimapBounds.width) * (left - this.minimapBounds.width / 2)
// convert to vector
const p = this.latLongToVector3(lat, lng, this.radius, 200)
this.plane.position.set(p.x, p.y, p.z)
// bottom of the plane should always look the the middle of the earth
this.plane.lookAt(new THREE.Vector3(0,0,0))
}

how to figure out cursive paths for an enemy to follow

The Problem
I am making a game where enemies appear at some point on the screen then follow a smooth curvy path and disappear at some point. I can make them follow a straight path but can't figure out the way to make them follow the paths depicted in the image.
Attempts
I started with parabolic curve and implemented them successfully. I just used the equation of parabola to calculate the coordinates gradually. I have no clue what is the equation for desired paths supposed to be.
What I want
I am not asking for the code.I just want someone to explain me the general technique.If you still want to show some code then I don't have special preference for programming language for this particular question you can use C,Java or even pseudo-code.
First you need to represent each curve with a set of points over time, For example:
-At T(0) the object should be at (X0, Y0).
-At T(1) the object should be at (X1, Y1).
And the more points you have, the more smooth curve you will get.
Then you will use those set of points to generate two formulas-one for X, and another one for Y-, using any Interpolation method, like The La-grange's Interpolation Formula:
Note that you should replace 'y' with the time T, and replace 'x' with your X for X formula, and Y for Y formula.
I know you hoped for a simple equation, but unfortunately this is will take from you a huge effort to simplify each equation, and my advise DON'T do it unless it's worth it.
If you are seeking for a more simple equation to perform well in each frame in your game you should read about SPline method, In this method is about splitting your curve into a smaller segments, and make a simple equation for every segment, for example:
Linear Spline:
Every segment contains 2 points, this will draw a line between every two points.
The result will be some thing like this:
Or you could use quadratic spline, or cubic spline for more smooth curves, but it will slow your game performance. You can read more about those methods here.
I think linear spline will be great for you with reasonable set of points for each curve.
Please change the question title to be more generic.
If you want to generate a spiral path you need.
Total time
How many full rotations
Largest radius
So, total time T_f = 5sec, rotations R_f = 2.5 * 2 * PI, the final distance from the start D_f = 200px
function SpiralEnemy(spawnX, spawnY, time) {
this.startX = spawnX;
this.startY = spawnY;
this.startTime = time;
// these will change and be used for rendering
this.x = this.startX;
this.y = this.startY;
this.done = false;
// constants we figured out above
var TFinal = 5.0;
var RFinal = -2.6 * 2 * Math.PI;
var RStart = -Math.PI / 2;
var DFinal = 100;
// the update function called every animation tick with the current time
this.update = function(t) {
var delta = t - this.startTime;
if(delta > TFinal) {
this.done = true;
return;
}
// find out how far along you are in the animation
var percent = delta / TFinal;
// what is your current angle of rotation (in radians)
var angle = RStart + RFinal * percent;
// how far from your start point should you be
var dist = DFinal * percent;
// update your coordinates
this.x = this.startX + Math.cos(angle) * dist;
this.y = this.startY + Math.sin(angle) * dist;
};
}
EDIT Here's a jsfiddle to mess with http://jsfiddle.net/pxb3824z/
EDIT 2 Here's a loop (instead of spiral) version http://jsfiddle.net/dpbLxuz7/
The loop code splits the animation into 2 parts the beginning half and the end half.
Beginning half : angle = Math.tan(T_percent) * 2 and dist = Speed + Speed * (1 - T_percent)
End half : angle = -Math.tan(1 - T_percent) * 2 and dist = **Speed + Speed * T_percent
T_percent is normalized to (0, 1.0) for both halfs.
function LoopEnemy(spawnX, spawnY, time) {
this.startX = spawnX;
this.startY = spawnY;
this.startTime = time;
// these will change and be used for rendering
this.x = this.startX;
this.y = this.startY;
this.last = time;
this.done = false;
// constants we figured out above
var TFinal = 5.0;
var RFinal = -2 * Math.PI;
var RStart = 0;
var Speed = 50; // px per second
// the update function called every animation tick with the current time
this.update = function(t) {
var delta = t - this.startTime;
if(delta > TFinal) {
this.done = true;
return;
}
// find out how far along you are in the animation
var percent = delta / TFinal;
var localDelta = t - this.last;
// what is your current angle of rotation (in radians)
var angle = RStart;
var dist = Speed * localDelta;
if(percent <= 0.5) {
percent = percent / 0.5;
angle -= Math.tan(percent) * 2;
dist += dist * (1 - percent);
} else {
percent = (percent - 0.5) / 0.5;
angle -= -Math.tan(1 - percent) * 2;
dist += dist * percent;
}
// update your coordinates
this.last = t;
this.x = this.x + Math.cos(angle) * dist;
this.y = this.y + Math.sin(angle) * dist;
};
}
Deriving the exact distance traveled and the height of the loop for this one is a bit more work. I arbitrarily chose a Speed of 50px / sec, which give a final x offset of ~+145 and a loop height of ~+114 the distance and height will scale from those values linearly (ex: Speed=25 will have final x at ~73 and loop height of ~57)
I don't understand how you give a curve. If you need a curve depicted on the picture, you can find a curve is given analytically and use it. If you have not any curves you can send me here: hedgehogues#bk.ru and I will help find you. I leave e-mail here because I don't get any messages about answers of users from stackoverflow. I don't know why.
If you have some curves in parametric view in [A, B], you can write a code like this:
struct
{
double x, y;
}SPoint;
coord = A;
step = 0.001
eps = 1e-6;
while (coord + step - eps < B)
{
SPoint p1, p2;
p1.x = x(coord);
p1.y = y(coord);
coord += step;
p2.x = x(coord);
p2.y = y(coord);
drawline(p1, p2);
}

Binary Image "Lines-of-Sight" Edge Detection

Consider this binary image:
A normal edge detection algorithm (Like Canny) takes the binary image as input and results into the contour shown in red. I need another algorithm that takes a point "P" as a second piece of input data. "P" is the black point in the previous image. This algorithm should result into the blue contour. The blue contours represents the point "P" lines-of-sight edge of the binary image.
I searched a lot of an image processing algorithm that achieve this, but didn't find any. I also tried to think about a new one, but I still have a lot of difficulties.
Since you've got a bitmap, you could use a bitmap algorithm.
Here's a working example (in JSFiddle or see below). (Firefox, Chrome, but not IE)
Pseudocode:
// part 1: occlusion
mark all pixels as 'outside'
for each pixel on the edge of the image
draw a line from the source pixel to the edge pixel and
for each pixel on the line starting from the source and ending with the edge
if the pixel is gray mark it as 'inside'
otherwise stop drawing this line
// part 2: edge finding
for each pixel in the image
if pixel is not marked 'inside' skip this pixel
if pixel has a neighbor that is outside mark this pixel 'edge'
// part 3: draw the edges
highlight all the edges
At first this sounds pretty terrible... But really, it's O(p) where p is the number of pixels in your image.
Full code here, works best full page:
var c = document.getElementById('c');
c.width = c.height = 500;
var x = c.getContext("2d");
//////////// Draw some "interesting" stuff ////////////
function DrawScene() {
x.beginPath();
x.rect(0, 0, c.width, c.height);
x.fillStyle = '#fff';
x.fill();
x.beginPath();
x.rect(c.width * 0.1, c.height * 0.1, c.width * 0.8, c.height * 0.8);
x.fillStyle = '#000';
x.fill();
x.beginPath();
x.rect(c.width * 0.25, c.height * 0.02 , c.width * 0.5, c.height * 0.05);
x.fillStyle = '#000';
x.fill();
x.beginPath();
x.rect(c.width * 0.3, c.height * 0.2, c.width * 0.03, c.height * 0.4);
x.fillStyle = '#fff';
x.fill();
x.beginPath();
var maxAng = 2.0;
function sc(t) { return t * 0.3 + 0.5; }
function sc2(t) { return t * 0.35 + 0.5; }
for (var i = 0; i < maxAng; i += 0.1)
x.lineTo(sc(Math.cos(i)) * c.width, sc(Math.sin(i)) * c.height);
for (var i = maxAng; i >= 0; i -= 0.1)
x.lineTo(sc2(Math.cos(i)) * c.width, sc2(Math.sin(i)) * c.height);
x.closePath();
x.fill();
x.beginPath();
x.moveTo(0.2 * c.width, 0.03 * c.height);
x.lineTo(c.width * 0.9, c.height * 0.8);
x.lineTo(c.width * 0.8, c.height * 0.8);
x.lineTo(c.width * 0.1, 0.03 * c.height);
x.closePath();
x.fillStyle = '#000';
x.fill();
}
//////////// Pick a point to start our operations: ////////////
var v_x = Math.round(c.width * 0.5);
var v_y = Math.round(c.height * 0.5);
function Update() {
if (navigator.appName == 'Microsoft Internet Explorer'
|| !!(navigator.userAgent.match(/Trident/)
|| navigator.userAgent.match(/rv 11/))
|| $.browser.msie == 1)
{
document.getElementById("d").innerHTML = "Does not work in IE.";
return;
}
DrawScene();
//////////// Make our image binary (white and gray) ////////////
var id = x.getImageData(0, 0, c.width, c.height);
for (var i = 0; i < id.width * id.height * 4; i += 4) {
id.data[i + 0] = id.data[i + 0] > 128 ? 255 : 64;
id.data[i + 1] = id.data[i + 1] > 128 ? 255 : 64;
id.data[i + 2] = id.data[i + 2] > 128 ? 255 : 64;
}
// Adapted from http://rosettacode.org/wiki/Bitmap/Bresenham's_line_algorithm#JavaScript
function line(x1, y1) {
var x0 = v_x;
var y0 = v_y;
var dx = Math.abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
var dy = Math.abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
var err = (dx>dy ? dx : -dy)/2;
while (true) {
var d = (y0 * c.height + x0) * 4;
if (id.data[d] === 255) break;
id.data[d] = 128;
id.data[d + 1] = 128;
id.data[d + 2] = 128;
if (x0 === x1 && y0 === y1) break;
var e2 = err;
if (e2 > -dx) { err -= dy; x0 += sx; }
if (e2 < dy) { err += dx; y0 += sy; }
}
}
for (var i = 0; i < c.width; i++) line(i, 0);
for (var i = 0; i < c.width; i++) line(i, c.height - 1);
for (var i = 0; i < c.height; i++) line(0, i);
for (var i = 0; i < c.height; i++) line(c.width - 1, i);
// Outline-finding algorithm
function gb(x, y) {
var v = id.data[(y * id.height + x) * 4];
return v !== 128 && v !== 0;
}
for (var y = 0; y < id.height; y++) {
var py = Math.max(y - 1, 0);
var ny = Math.min(y + 1, id.height - 1);
console.log(y);
for (var z = 0; z < id.width; z++) {
var d = (y * id.height + z) * 4;
if (id.data[d] !== 128) continue;
var pz = Math.max(z - 1, 0);
var nz = Math.min(z + 1, id.width - 1);
if (gb(pz, py) || gb(z, py) || gb(nz, py) ||
gb(pz, y) || gb(z, y) || gb(nz, y) ||
gb(pz, ny) || gb(z, ny) || gb(nz, ny)) {
id.data[d + 0] = 0;
id.data[d + 1] = 0;
id.data[d + 2] = 255;
}
}
}
x.putImageData(id, 0, 0);
// Draw the starting point
x.beginPath();
x.arc(v_x, v_y, c.width * 0.01, 0, 2 * Math.PI, false);
x.fillStyle = '#800';
x.fill();
}
Update();
c.addEventListener('click', function(evt) {
var x = evt.pageX - c.offsetLeft,
y = evt.pageY - c.offsetTop;
v_x = x;
v_y = y;
Update();
}, false);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.2.3/jquery.min.js"></script>
<center><div id="d">Click on image to change point</div>
<canvas id="c"></canvas></center>
I would just estimate P's line of sight contour with ray collisions.
RESOLUTION = PI / 720;
For rad = 0 To PI * 2 Step RESOLUTION
ray = CreateRay(P, rad)
hits = Intersect(ray, contours)
If Len(hits) > 0
Add(hits[0], lineOfSightContour)
https://en.wikipedia.org/wiki/Hidden_surface_determination with e.g. a Z-Buffer is relatively easy. Edge detection looks a lot trickier and probably needs a bit of tuning. Why not take an existing edge detection algorithm from a library that somebody else has tuned, and then stick in some Z-buffering code to compute the blue contour from the red?
First approach
Main idea
Run an edge detection algorithm (Canny should do it just fine).
For each contour point C compute the triplet (slope, dir, dist), where:
slope is the slope of the line that passes through P and C
dir is a bit which is set if C is to the right of P (on the x axis) and reset if it is to the left; it used in order to distinguish in between points having the same slope, but on opposite sides of P
dist is the distance in between P and C.
Classify the set of contour points such that a class contains the points with the same key (slope, dir) and keep the one point from each such class having the minimum dist. Let S be the set of these closest points.
Sort S in clockwise order.
Iterate once more through the sorted set and, whenever two consecutive points are too far apart, draw a segment in between them, otherwise just draw the points.
Notes
You do not really need to compute the real distance in between P and C since you only use dist to determine the closest point to P at step 3. Instead you can keep C.x - P.x in dist. This piece of information should also tell you which of two points with the same slope is closest to P. Also, C.x - P.x swallows the dir parameter (in the sign bit). So you do not really need dir either.
The classification in step 3 can ideally be done by hashing (thus, in linear number of steps), but since doubles/floats are subject to rounding, you might need to allow small errors to occur by rounding the values of the slopes.
Second approach
Main idea
You can perform a sort of BFS starting from P, like when trying to determine the country/zone that P resides in. For each pixel, look at the pixels around it that were already visited by BFS (called neighbors). Depending on the distribution of the neighbor pixels that are in the line of sight, determine if the currently visited pixel is in the line of sight too or not. You can probably apply a sort of convolution operator on the neighbor pixels (like with any other filter). Also, you do not really need to decide right away if a pixel is for sure in the line of sight. You could instead compute some probability of that to be true.
Notes
Due to the fact that your graph is a 2D image, BFS should be pretty fast (since the number of edges is linear in the number of vertices).
This second approach eliminates the need to run an edge detection algorithm. Also, if the country/zone P resides in is considerably smaller than the image the overall performance should be better than running an edge detection algorithm solely.

Rotation Automation

I've run into a few problems with this expression in Maya, basically anytime the radius is less than 1, the calculation is thrown off way too much.
float $radius = `getAttr prefix66_calculations_shape.rad`;
float $prevZval = `getAttr -time (frame -1) prefix66_driver.translateZ`;
float $prevXval = `getAttr -time (frame -1) prefix66_driver.translateX`;
float $Zval = prefix66_driver.translateZ - $prevZval;
float $Xval = prefix66_driver.translateX - $prevXval;
float $distance = ($Zval * $Zval) + ($Xval * $Xval);
float $direction;
$distance = sqrt($distance);
if ($prevZval > prefix66_driver.translateZ) {
$direction = 360;
}
else {
$direction = 360;
}
float $rotation = ($distance / (2 * 3.142 * $radius)) * $direction;
print $rotation;
pCube1.rotateX = pCube1.rotateX + $rotation;
Maybe my order of operations is wrong?
The rotation part of your code looks ok. However, you have an if/else block that returns the same thing in both cases, and as mentioned by #joojaa, you can avoid getAttr -time if you cache the translation values. In fact you should avoid getAttr and setAttr completely in expressions.
Instead, refer to the attributes you want directly and Maya will create connections for you. This is much faster and less prone to errors when you rename nodes and so on.
To cache the translation values, and calculate change in position you can add attributes to the node and use them in the expression.
Let's say you have a cylinder called wheel that rotates around its local X and is parented to a group node called control:
Add a vector attribute: control.lastTranslate
Add a vector attribute: control.deltaTranslate
Add a float attribute: control.distance
Here's an expression that will store the change in translation, then rotate the wheel based on the distance travelled.
// When deltaTranslate is calculated, lastTranslate still has its previous value.
control.deltaTranslateX = control.translateX - control.lastTranslateX;
control.deltaTranslateY = control.translateY - control.lastTranslateY;
control.deltaTranslateZ = control.translateZ - control.lastTranslateZ;
control.lastTranslateX = control.translateX;
control.lastTranslateY = control.translateY;
control.lastTranslateZ = control.translateZ;
control.distance = mag(<<control.deltaTranslateX,control.deltaTranslateY,control.deltaTranslateZ>>);
// Get radius from history node (or somewhere) and move the wheel's hub off the floor.
wheel.translateY = polyCylinder1.radius;
// add rotation to the wheel
float $tau = 6.283185307179586;
wheel.rotateX = wheel.rotateX + ( control.distance* -360.0) / (polyCylinder1.radius * $tau );
It's best to test this kind of thing by animating rather than dragging nodes around in the view.
If you wanted to make the wheel aim to the direction of travel, you could add a locator at translate + deltaTranslate and hook up an aim constraint.
e.g.
aimLocator.translateX = (control.deltaTranslateX / control.distance) + control.translateX;
aimLocator.translateY = (control.deltaTranslateY / control.distance) + control.translateY;
aimLocator.translateZ = (control.deltaTranslateZ / control.distance) + control.translateZ;
Dividing by distance will normalize the offset. You should probably check that distance is not zero.
I believe I have figured it out :)
Queering the old traslation average, with the new translation average will give me a true or false answer, which is what I needed to change direction.
Also added an if statement that if the ball is static and rotating, that the wheel doesn't turn automatically.
float $oldRotateAverage;
float $oldTransAverage;
float $direction;
nurbsCircle1.DeltaTranslateX = nurbsCircle1.translateX - nurbsCircle1.LastTranslateX;
nurbsCircle1.DeltaTranslateY = nurbsCircle1.translateY - nurbsCircle1.LastTranslateY;
nurbsCircle1.DeltaTranslateZ = nurbsCircle1.translateZ - nurbsCircle1.LastTranslateZ;
nurbsCircle1.LastTranslateX = nurbsCircle1.translateX;
nurbsCircle1.LastTranslateY = nurbsCircle1.translateY;
nurbsCircle1.LastTranslateZ = nurbsCircle1.translateZ;
nurbsCircle1.Distance = mag(<<nurbsCircle1.DeltaTranslateX,nurbsCircle1.DeltaTranslateY,nurbsCircle1.DeltaTranslateZ>>);
if ($oldTransAverage >= (nurbsCircle1.LastTranslateX + nurbsCircle1.LastTranslateY + nurbsCircle1.LastTranslateZ)){
$direction = -360.00;
} else {
$direction = 360.00;
};
if (Sh54_anim.auto == 1 )
{
Sh54_point_grp.rotateZ -= nurbsCircle1.Distance * $direction / 2 / 3.14 / 2;
};
if ((nurbsCircle1.rotateX + nurbsCircle1.rotateY + nurbsCircle1.rotateZ) != $oldRotateAverage && nurbsCircle1.Distance == $oldTransAverage){
Sh54_anim.auto = 0;
} else {
Sh54_anim.auto = 1;
};
Sh54_point_grp.back_up = Sh54_point_grp.translateX;
$oldRotateAverage = nurbsCircle1.rotateX + nurbsCircle1.rotateY + nurbsCircle1.rotateZ;
$oldTransAverage = nurbsCircle1.translateX + nurbsCircle1.translateY + nurbsCircle1.translateZ;

Resources