Skip to content

Instantly share code, notes, and snippets.

@code-matt
Last active January 26, 2023 04:26
Show Gist options
  • Save code-matt/fed2c5ae4db9da198d1a1d609e0ac083 to your computer and use it in GitHub Desktop.
Save code-matt/fed2c5ae4db9da198d1a1d609e0ac083 to your computer and use it in GitHub Desktop.
Outputs [interpolated transforms] from non-uniform keyframes in 3D space + 1D timeline for camera tracks and other uses. The output path can be sampled with dT or can be used to visualize the spline. Acceleration and rotation changes are very smooth with no tangents needed.. IN TORQUESCRIPT LOL. https://www.youtube.com/watch?v=YiY1A1fmiJw <-result
Thank you Boris :) Where-ever you are my friend 👨‍💻
//Copyright 2023 Matt Thompson
//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// I hope all games have better and simple to use camera paths for content creators someday. That need originally inspired
// this in a game called Legions Overdrive.
//*****************************//
// READ THIS IF YOU SERIOUSLY THINK ABOUT PORTING -- KEY FACT -- //
//
// "...when this baby hits 88 miles per hour you're going to see some serious shit." - Doc Brown
//
// You are going to see some shit indeed but it's not cool as that. In fact Torquescript is
// ENTIRELY strings on the script side. Once off to the engine, wether it's a string or number
// is finally decided. Enjoy that. Boris's Mathmatics here is incredible but obfuscated as he
// also battled around this lovely Torquescript when orignally created in 2015~ for MT.
//
// Keep this in mind about the strings as you read it and will help to some degreee. Bless
// the first person that turns this to the first language that has types.. not typed.. just,
// you know, has types 😂
//
// The other one is this is in TGEA/Legions Overdrive world space and axis setup. Keep in mind.
// There are some comments about that.
//
//
//
// Begin at makeBorisPath and inparticular line 103 is the beginning of the true chain..
//
//
//
//*****************************//
-------
function CameraMovesGroupClass::makeBorisPath()
{
// you must have 3 frames or more for a valid path
if (DCGroup.activemovegroup.getCount() < 3)
{
clientCmdCenterPrint("\c5******Error. You need at least 3 keyframes to recalculate.******\c5", 3000, 1);
return;
}
// the frames time must be in order low to high
if (!DCGroup.activemovegroup.verifyFrames())
{
clientCmdCenterPrint("\c5******Error. Your keyframes demoTime do not go in order.******\c5", 3000, 1);
return;
}
if (!isObject(DCGroup.activemovegroup.borisGroup))
{
echo("Creating scriptObject with the name borisSpline");
%myspl = new ScriptObject()
{
class = borisSpline;
};
%myspl.setupCamPath(%keyframes);
DCGroup.activeMoveGroup.borisGroup = %myspl;
DCGroup.activeMoveGroup.borisGroup.keyFrames = CreateBorisData();
DCGroup.activeMoveGroup.borisGroup.keyFrames.clearKeyFrames();
}
%count = DCGroup.activeMoveGroup.getcount();
for (%i=0; %i < %count; %i++)
{
DCGroup.activeMoveGroup.borisGroup.keyFrames.keyframe[%i] = DCGroup.activeMoveGroup.getObject(%i).cameraTransform TAB DCGroup.activeMoveGroup.getObject(%i).demoTime;
%done = false;
if (%i == 0)
{
%keyframes = DCGroup.activeMoveGroup.getObject(%i).cameraTransform TAB DCGroup.activeMoveGroup.getObject(%i).demoTime NL "";
%done = true;
}
if (%done == false)
%keyframes = %keyframes @ DCGroup.activeMoveGroup.getObject(%i).cameraTransform TAB DCGroup.activeMoveGroup.getObject(%i).demoTime NL "";
}
echo(%keyframes);
// This above just loaded in keyframes...
// {
// demoTime: 353,
// cameraTransform: {
// x,y,z,rX,rY,rZ....
// }
// }
// But you know, as tab deliminated strings 😂
// ^^^^^^ ***** ^^^^^^^ ***** ^^^^^^^ ***** ^^^^^^^ ***** ^^^^^^^
DCGroup.activeMoveGroup.borisGroup.setupCamPath(%keyframes);
// ^^^^^^ ***** ^^^^^^^ ***** ^^^^^^^ ****** ^^^^^^ ***** ^^^^^^^ *****
// POKE AROUND AND THEN READ ABOVE AND BEGIN HERE, PROBABALLY. MAYBE
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
// not important, visualize it in wharever engine/framework
// clientCmdCenterPrint("\c5Recalculation complete..\c5", 3000, 1);
// if (isObject(DCGroup.activemovegroup.pathPreviewGroup))
// %ret = DCGroup.activemovegroup.pathPreviewGroup.clearOrbs();
// DCGroup.activemovegroup.createPathPreviewGroup();
}
function CameraMovesGroupClass::sampleBorisPath(%this, %time)
{
}
//run between each set of keyframes.. 0 - 1, 1 - 2,2 - 3 etc.. and get the distance traveled that segment
//by doing a vectorDist between each tiny sample and adding them up.
//divide that by how many points you want
//run the same segment you just ran above again, this time every time it passes the distanceTraveled/<desiredNumberofpoints>
//above, save the sample that just put it over that threshhold and make a pathPreview point with it.
function borisSpline::updatePath(%this, %objectToFill)
{
%keyFrameCount = DCGroup.activemovegroup.borisGroup.keyframes.getDynamicFieldCount() - 1;
for (%i = 0; %i < %keyFrameCount; %i++)
{
%currentKeyFrame = %i;
%nextKeyFrame = %i + 1;
%currentKeyFrameTime = DCGroup.activemovegroup.getObject(%currentKeyFrame).demoTime;
%nextKeyFrameTime = DCGroup.activemovegroup.getObject(%nextKeyFrame).demoTime;
%durationOfMove = %nextKeyFrameTime - %currentKeyFrameTime;
%old_sample = DCGroup.activeMoveGroup.borisGroup.getCamPoint(%currentKeyFrameTime);
%sampleTime = %currentKeyFrameTime + 1;
%distanceTraveled = 0;
while (%sampleTime < ((%currentKeyFrameTime + 1) + %durationOfMove))
{
%sample = DCGroup.activeMoveGroup.borisGroup.getCamPoint(%sampleTime);
%position0 = getWord(%old_sample,0) SPC getWord(%old_sample,1) SPC getWord(%old_sample,2);
%position1 = getWord(%sample,0) SPC getWord(%sample,1) SPC getWord(%sample,2);
%distanceTraveled += vectorDist(%position0,%position1);
%sampleTime++;
%old_sample = %sample;
}
echo("distanceTraveled" SPC %distanceTraveled);
%segmentSpace = %distanceTraveled / 10; //modulate this 5 based on how long the segment was.
%distanceTraveled = 0;
//echo("segmentSpace" SPC %segmentSpace);
%old_sample = DCGroup.activeMoveGroup.borisGroup.getCamPoint(%currentKeyFrameTime);
%sampleTime = %currentKeyFrameTime + 1;
%segmentTime = 0;
%previousSpeed = 0;
while (%sampleTime < ((%currentKeyFrameTime + 1) + %durationOfMove))
{
%sample = DCGroup.activeMoveGroup.borisGroup.getCamPoint(%sampleTime);
%position0 = getWord(%old_sample,0) SPC getWord(%old_sample,1) SPC getWord(%old_sample,2);
%position1 = getWord(%sample,0) SPC getWord(%sample,1) SPC getWord(%sample,2);
%distanceTraveled += vectorDist(%position0,%position1);
%sampleTime++;
%old_sample = %sample;
%segmentTime++;
if (%distanceTraveled > %segmentSpace)
{
//echo("previousSpeed is" SPC %previousSpeed);
%speed = ((%segmentTime*20) / (%distanceTraveled*20))/100;
//echo("speed is" SPC %speed);
%color = 0 + (%speed * 10);
//echo("Color" SPC %color);
%distanceTraveled = 0;
%objectToFill.createPathPreviewObject(%sample, %color);
%segmentTime = 0;
%previousSpeed = %speed;
%speed = 0;
}
}
}
}
function CameraMovesGroupClass::playBorisPath(%this)
{
%startTime = DCGroup.activeMoveGroup.getObject(0).demoTime;
%finalTime = DCGroup.activeMoveGroup.getObject(DCGroup.activeMoveGroup.getCount() - 1).demoTime;
if ($demoTime < %startTime)
{
$demoCam.setTransform(DCGroup.activeMoveGroup.getObject(0).cameraTransform);
%this.schedule(1,playBorisPath);
return;
}
$demoCam.setTransform(DCGroup.activeMoveGroup.borisGroup.getCamPoint($demoTime));
if ($demoTime <= %finalTime)
%this.schedule(1,playBorisPath);
}
//$borisTime = 0;
function CameraMovesGroupClass::testBorisPath(%this)
{
%startTime = DCGroup.activeMoveGroup.getObject(0).demoTime;
%finalTime = DCGroup.activeMoveGroup.getObject(DCGroup.activeMoveGroup.getCount() - 1).demoTime;
$demoCam.setTransform(DCGroup.activeMoveGroup.borisGroup.getCamPoint($borisTime));
$borisTime++;
if ($borisTime >= %finalTime || $borisTime <= %startTime)
$boristime = DCGroup.activeMoveGroup.getObject(0).demoTime;
else
%this.schedule(1,testBorisPath);
}
//utility stuff
function CreateBorisGroup()
{
%BorisGroupObject = new ScriptGroup()
{
//superclass = DemoObject;
class = BorisGroup;
};
return %BorisGroupObject;
}
function CreateBorisData()
{
%BorisDataObject = new ScriptObject()
{
//superclass = DemoObject;
class = BorisData;
};
return %BorisDataObject;
}
function BorisData::clearKeyframes(%this)
{
%count = %this.getFieldCount();
for (%i = 0; %i < %count; %i++)
%this.keyframe[%i] = "";
}
Thank you Boris :) Where-ever you are my friend 👨‍💻
//Copyright 2023 Matt Thompson
//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------
// tridiagonal matrix functions
// import vector functionality
exec("./borisvector.cs");
// the tridiagonal linear system of equations is defined as:
// [ d1 c1 ] [x1] [b1]
// [ a1 d2 c2 ] [x2] = [b2]
// ... ... cn-1 ] [..] [..]
// [ an-1 dn ] [xn] [bn]
// each a,b,c,d,x is a vector of arbitrary dimensions
// usage is as simple as:
// %var = new ScriptObject(tridiagonal);
// %var.as = " .. ";
// %var.bs = " .. ";
// %var.cs = " .. ";
// %var.ds = " .. ";
// %var.solve(); // this will fill %var.xs column and return it
// debugging function; not in use
function tridiagonal::unittest()
{
%t = new ScriptObject(tridiagonal);
%t.ds = "2.0 4"NL"1.6 1";
%t.as = "0.6 3";
%t.cs = "0.6 3";
%t.bs = "0.5 5"NL"4 2";
//%t.ds = "2.0 3.0";
//%t.as = "4";
//%t.cs = "4.0";
//%t.bs = "5 6";
echo(%t.solve());
//%t.ddi(1);
}
//tridiagonal::unittest();
// simple shortcuts to retrieve i-th vector from an array of coefficients
// the layout is:
// as = a1 NL a2 NL a3 ... an-1
// bs = b1 NL b2 NL b3 ... bn
// cs = c1 NL c2 NL c3 ... an-1
// ds = d1 NL d2 NL d3 ... dn
function tridiagonal::ai(%this,%i) { return getRecord(%this.as,%i); }
function tridiagonal::bi(%this,%i) { return getRecord(%this.bs,%i); }
function tridiagonal::ci(%this,%i) { return getRecord(%this.cs,%i); }
function tridiagonal::di(%this,%i) { return getRecord(%this.ds,%i); }
// these are modified (during the solving stage) b and d coefficients: b' and d'
function tridiagonal::bbi(%this,%i) { return getRecord(%this.bbs,%i); }
function tridiagonal::ddi(%this,%i) { return getRecord(%this.dds,%i); }
// xs are available after the linear system is solved
// layout: xs = x1 x2 x3 ... xn
function tridiagonal::xi(%this,%i) { return getRecord(%this.xs,%i); }
// solves the system
// as, bs, cs, ds members must be initialized before calling solve()
// returns xs
function tridiagonal::solve(%this)
{
// order of the matrix
%n = getRecordCount(%this.bs);
// gaussian reduction: b -> b', d -> d'
%b = %this.bi(0); %this.bbs = %b;
%d = %this.di(0); %this.dds = %d;
for( %i = 1; %i < %n; %i ++ )
{
%a_d = vecDiv( %this.ai(%i-1), %d );
%b = vecSub( %this.bi(%i), vecMul(%a_d,%b) );
%d = vecSub( %this.di(%i), vecMul(%a_d,%this.ci(%i-1)) );
%this.bbs = %this.bbs NL %b;
%this.dds = %this.dds NL %d;
}
//echo("bbs" SPC %this.bbs);
//echo("dds" SPC %this.dds);
// backwards solution
%x = vecDiv( %this.bbi(%n-1), %this.ddi(%n-1) );
%this.xs = %x;
for( %i = %n - 2; %i >= 0; %i --)
{
%x = vecDiv( vecSub( %this.bbi(%i), vecMul(%this.ci(%i),%x) ), %this.ddi(%i) );
%this.xs = %x NL %this.xs;
}
//echo("xs" SPC %this.xs);
return %this.xs;
}
Thank you Boris :) Whereever you are my friend 👨‍💻
//Copyright 2023 Matt Thompson
//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------
// torque3d normal cubic spline calculation script
// import vector and matrix functionality
exec("./borismatrix.cs");
/*****************************************
* quick overview of functions:
// these calculate the spline parameters for further use
// keyPoints should have format: x y z x y z r TAB t NL x y z ... NL ...
function borisSpline::setupGeneric( %this, %keyPoints )
function borisSpline::setupCamPath( %this, %keyPoints )
// these calculate a point located at %snapTime on the spline
function borisSpline::getCamPoint( %this, %snapTime ) // for 7-D splines, converts from sane format into getTransform()-friendly
function borisSpline::getPointGeneric( %this, %snapTime ) // generic function for arbitrary dimension splines
// converts camera rotation transformations into plain unit vectors pointing at where it should look
function borisSpline::befriendCamera( %keyPoints )
// these calculate polynomial ax3 + bx2 + cx + d (vectors abcd, scalar x defined on [xbgn,xend])
function borisSpline::calcPolynomialV( %a, %b, %c, %d, %x, %xbgn, %xend )
function borisSpline::calcPolynomialV2( %p, %t, %tbgn, %tend ) // same but p is a^b^c^d
// convertors between "x y z a" rotation vector and "x y z" pointing unit vector
function borisSpline::xyz2rotation( %xyz )
function borisSpline::rotation2xyz( %rot )
* typical usage example:
%myspl = new ScriptObject(borisSpline);
%path = "-30 -30 40 1 0 0 0.1" TAB "0" NL
"40 -35 0 0 1 0 0.2" TAB "55" NL
"43 25 2 0 0 1 0.1" TAB "100";
%myspl.setupCamPath(%path);
for(%t = 0; %t <= 100; %t += 0.1)
myobject.setTransform( %myspl.getCamPoint(%t) );
* or with camera defined in world coordinates
%myspl = new ScriptObject(borisSpline);
// camera will always look at the (100,100,100) point
%path = "-30 -30 40 100 100 100 0" TAB "0" NL
"40 -35 0 100 100 100 0" TAB "55" NL
"43 25 2 100 100 100 0" TAB "100";
%myspl.setupCamPath(%path);
for(%t = 0; %t <= 100; %t += 0.1)
echo(%myspl.getWorldCamPoint(%t); // notice I use getWorldCamPoint() here
*****************************************/
// calculates a cubic polynomial: a(x-xbgn)^3 + b(xend-x)^3 + c(x-xbgn) + d(xend-x) (vectors abcd, scalar x)
function borisSpline::calcPolynomialV( %a, %b, %c, %d, %x, %xbgn, %xend )
{
// return vecAdd( vecScale( vecAdd( vecScale( vecAdd( vecScale(%a,%x), %b ), %x ), %c ), %x ), %d );
// better to avoid vector abstractions for optimization purpose (3-4 times speedup)
%ret = "";
while(true)
{
%aw = firstWord(%a); %a = restWords(%a);
%bw = firstWord(%b); %b = restWords(%b);
%cw = firstWord(%c); %c = restWords(%c);
%dw = firstWord(%d); %d = restWords(%d);
if(%aw $= "") break;
%xl = %x - %xbgn;
%xr = %xend - %x;
%xl3 = %xl * %xl * %xl;
%xr3 = %xr * %xr * %xr;
%rw = (%aw * %xl3 + %bw * %xr3) + (%cw * %xl + %dw * %xr);
//%rw = %dw + %x * (%cw + %x * (%bw + %x * %aw));
%ret = %ret @ %rw SPC "";
}
return %ret;
}
// calculates a cubic polynomial (vector form)
// takes a list %p: a TAB b TAB c TAB d
// a b c d are vectors
// and takes %t as time, %tbgn & %tend are the boundary points
// returns vector = a(t-tbgn)^3 + b(tend-t)^3 + c(t-tbgn) + d(tend-t)
function borisSpline::calcPolynomialV2( %p, %t, %tbgn, %tend )
{
return borisSpline::calcPolynomialV( getField(%p,0), getField(%p,1), getField(%p,2), getField(%p,3), %t, %tbgn, %tend );
}
// converts camera rotation transformations into plain unit vectors pointing at where it should look
// should be called before setupGeneric()
function borisSpline::befriendCamera( %keyPoints )
{
%ret = "";
%n = getRecordCount(%keyPoints);
for( %i = 0; %i < %n; %i ++ )
{
%pt = getRecord(%keyPoints,%i);
%t = getField(%pt,1);
%xyz = getWords(%pt,0,2);
%rot = getWords(%pt,3,6);
%cam = borisSpline::rotation2xyz(%rot);
%ret = %ret @ %xyz SPC %cam TAB %t NL "";
}
return %ret;
}
// same as setupGeneric, but converts camera into interpolation-friendly coordinate space
function borisSpline::setupCamPath( %this, %keyPoints )
{
%pts = borisSpline::befriendCamera(%keyPoints);
return %this.setupGeneric(%pts);
}
//
// sets up the spline parameters and returns them
//
// keyPoints should have format:
// x y z x y z r TAB t
// x y z x y z r TAB t
// ...
// r is the camera axial rotation in degrees
// t is the elapsed time in milliseconds
//
// returns a matrix of spline parameters (one line shorter than keyPoints):
// t1 TAB a TAB b TAB c TAB d TAB t2
// t1 TAB a TAB b TAB c TAB d TAB t2
// ...
// a b c d are polynomial constants (see calcPolynomialV())
// each t1 represents a time after which the polynomial defined in the same string becomes valid
// and t2 is the time after which it is no more
// t1 and t2 are used to obtain a time-coordinate relative to it's boundaries
// this allows to avoid losing precision in calcPolynomial() functions when moving across keyframes
function borisSpline::setupGeneric( %this, %keyPoints )
{
// we'll need a tridiagonal system here
%tri = new ScriptObject(tridiagonal);
// calculate the tridiagonal matrix constants
%tri.hs = ""; %tri.ds = ""; %tri.cs = ""; %tri.bs = "";
%n = getRecordCount(%keyPoints);
for( %i = -1; %i + 1 < %n; %i ++ )
{
%ptnxt = getRecord(%keyPoints,%i+1);
%xnxt = getField(%ptnxt,1);
%ynxt = getField(%ptnxt,0);
if (%i >= 0)
{
%dim = getWordCount(%yi);
%hi = %xnxt - %xi;
%bi = vecScale( vecSub(%ynxt,%yi), 1.0/%hi );
if (%i > 0)
{
%vi = (%hprv + %hi) * 2;
%ui = vecScale( vecSub(%bi,%bprv), 6 );
%tri.ds = %tri.ds @ vecFill(%dim,%vi) NL "";
%tri.cs = %tri.cs @ vecFill(%dim,%hi) NL "";
%tri.bs = %tri.bs @ %ui NL "";
}
%hprv = %hi;
%bprv = %bi;
%tri.hs = %tri.hs @ vecFill(%dim,%hi) NL "";
}
%xi = %xnxt;
%yi = %ynxt;
} // for %i
%tri.as = %tri.cs;
// solve the system
%zs = %tri.solve();
%zero = vecZero(%dim);
%zs = %zero NL %zs NL %zero;
%ret = "";
// prepare the polynomial coefficients
for( %i = -1; %i+1 < %n; %i ++ )
{
%ptnxt = getRecord(%keyPoints,%i+1);
%xnxt = getField(%ptnxt,1);
%ynxt = getField(%ptnxt,0);
%znxt = getRecord(%zs,%i+1);
%xnxt2 = %xnxt * %xnxt;
%xnxt3 = %xnxt2 * %xnxt;
if (%i >= 0)
{
%hi = %xnxt - %xi;
// a b c d are the final constants for calcPolynomialV() to use
%a = vecScale(%znxt,1.0/6/%hi);
%b = vecScale(%zi,1.0/6/%hi);
%c = vecSub( vecScale(%ynxt,1.0/%hi), vecScale(%znxt,%hi/6) );
%d = vecSub( vecScale(%yi ,1.0/%hi), vecScale(%zi,%hi/6) );
%ret = %ret @ %xi TAB %a TAB %b TAB %c TAB %d TAB %xnxt NL "";
}
%xi = %xnxt;
%xi2 = %xnxt2;
%xi3 = %xnxt3;
%yi = %ynxt;
%zi = %znxt;
} // for %i
// the matrix is no longer needed
%tri.delete();
// prepare the environment for getPoint() functions
%this.parms = %ret;
%this.last_index = -1;
%this.last_rec = "";
%this.last_time = -1;
%this.reccount = getRecordCount(%this.parms);
return %ret;
} // function borisSpline::setupGeneric( %this, %keyPoints )
// same as getPointGeneric, but fit for setupCamPath()'s splines
function borisSpline::getCamPoint( %this, %snapTime )
{
%ret = %this.getPointGeneric(%snapTime);
%cam = getWords(%ret,3,5);
%rot = borisSpline::xyz2rotation(%cam);
return getWords(%ret,0,2) SPC %rot;
}
//Poponfu addition for rewind since its scheduled and
//does not know the new $demoCamera yet.
function borisSpline::getSetCamPoint( %this, %snapTime )
{
%ret = %this.getPointGeneric(%snapTime);
%cam = getWords(%ret,3,5);
%rot = borisSpline::xyz2rotation(%cam);
$demoCam.setTransform(getWords(%ret,0,2) SPC %rot);
//return getWords(%ret,0,2) SPC %rot;
}
// calculates and returns a point on a spline of arbitrary dimensions
// snapTime is the time for which the calculation is performed
// one of setup() functions must be called before using this function
function borisSpline::getPointGeneric( %this, %snapTime )
{
// first try the cached record
if( (%this.last_index >= 0) && (%snapTime >= %this.last_time) )
{
%irec = %this.last_index;
%rec = %this.last_rec;
//echo("cached"SPC %snapTime);
}
else // not cached or time moved back
{
%irec = 0;
%rec = getRecord( %this.parms, %irec );
//echo("RESET"SPC %snapTime);
}
%t_bgn = getField( %rec, 0 );
%t_end = getField( %rec, 5 );
// if the polynomial is invalid for current %t, we should look for another
while( (%snapTime > %t_end) && (%irec + 1 < %this.reccount) )
{
//echo("SHIFT"SPC %snapTime);
%irec ++;
%rec = getRecord( %this.parms, %irec );
%t_bgn = getField( %rec, 0 );
%t_end = getField( %rec, 5 );
}
// now we have the polynomial vector we need
%polyv = getFields( %rec, 1, 4 );
// save this record for next use
%this.last_index = %irec;
%this.last_time = %snapTime;
%this.last_rec = %rec;
//echo(%polyv);
//echo(%polyv NL %snapTime NL %t_end NL %irec);
// calculate and return the result
return borisSpline::calcPolynomialV2( %polyv, %snapTime, %t_bgn, %t_end );
//return calcPolynomialV2( %polyv, %t ) TAB %snapTime;
}
// converts the x y z a rotation vector into a unit vector
function borisSpline::rotation2xyz( %rot )
{
%ux = getWord(%rot,0);
%uy = getWord(%rot,1);
%uz = getWord(%rot,2);
%th = getWord(%rot,3);
%si = msin(%th);
%co = mcos(%th);
%co1 = 1 - %co;
// these don't match wikipedia, but surprisingly they work:
%x = -%uz*%si - %ux*%uy*%co1;
%y = -%co - %uy*%uy*%co1;
%z = %ux*%si - %uz*%uy*%co1;
return %x SPC %y SPC %z;
}
// converts the unit vector into a x y z a rotation vector (with no regard to tilt)
// the logic is to rotate 180 deg around the sum of the default camera vector and %xyz
function borisSpline::xyz2rotation( %xyz )
{
%xyz = vectorNormalize(%xyz);
%y = getWord(%xyz,1);
%rot = setWord( %xyz, 1, %y - 1 );
return vectorNormalize(%rot) SPC 3.14159265358979;
}
/*
function test()
{
%myspl = new ScriptObject(borisSpline);
%path =
"330.642 509.276 278.967 -0.132647 -0.232508 0.963507 4.1468" TAB "26181" NL
"343.89 428.296 270.457 0.282095 0.165217 -0.945053 1.1096" TAB "28136" NL
"290.757 381.295 212.34 0.454349 0.34092 -0.823007 1.4785" TAB "30143" NL
"188.421 347.263 130.654 -0.0297017 -0.105913 0.993932 3.68526" TAB "31224" NL
"88.3822 276.627 205.949 0.0616986 0.132636 0.989243 4.00415" TAB "32314" NL
"-32.329 289.659 247.29 -0.0363 -0.0506524 -0.998056 1.89981" TAB "33058" NL
"-152.903 289.426 276.596 0.29403 0.12368 -0.94776 0.835409" TAB "33603" NL
"-202.958 178.154 187.014 -0.023778 -0.284452 0.958395 3.30148" TAB "35588" NL
"-348.243 7.27442 77.756 -0.0606393 -0.205792 0.976715 3.70205" TAB "37395" NL
"-402.377 -132.392 103.596 0.00887548 0.10993 0.9939 3.30174" TAB "38748" NL
"-328.199 -257.002 279.255 0.00446613 -0.0857139 0.99631 3.03786" TAB "41466" NL
"-227.92 -356.643 269.904 0.029313 0.0311558 -0.999085 1.63264" TAB "44202" NL
"-227.92 -356.643 269.904 0.697971 0.205405 -0.686036 0.810458" TAB "45943" NL
"-227.92 -356.643 269.904 0.942967 -0.207449 0.260344 1.40319" TAB "47695" NL
"-227.92 -356.643 269.904 -0.0990927 -0.236378 0.966595 3.91157" TAB "50498";
%sc = 1;
%tmp = "";
for(%i =0; %i < getRecordCount(%path);%i ++)
{
%ln = getRecord(%path,%i);
%t = getField(%ln,1) / %sc;
// %ln = getWords(%ln,0,2) SPC "0 0 0" SPC getWords(%ln,6);
%tmp = %tmp @ setField(%ln,1,%t) NL "";
}
%path = %tmp;
//echo(%path);
// %myspl.setupGeneric(%path);
%myspl.setupCamPath(%path);
// 37395 32314
for(%t = 32310; %t <= 32320; %t += 1)
// echo(%t @" -> "@ %myspl.getPointGeneric(%t/%sc));
echo(%t @" -> "@ %myspl.getCamPoint(%t/%sc));
// echo(%myspl.getPointGeneric(%t));
//echo(%spl.getPointGeneric(55.01));
}
test();
*/
Thank you Boris :) Where-ever you are my friend 👨‍💻
//Copyright 2023 Matt Thompson
//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------
// basic vector arithmetic for arbitrary number of dimensions
// generally a few times slower than vectorAdd() etc functions, but more general
// TODO: better to wrap this all into a package
// creates a zero vector of %dim dimensions
function vecZero(%dim) { return vecFill(%dim,0); }
// creates a vector of %dim equal values
function vecFill(%dim,%value)
{
%ret = %value;
for(%i = 1; %i < %dim; %i ++) %ret = %ret SPC %value;
return %ret;
}
// performs a scalar multiplication of vector %v and a scalar %s
function vecScale( %v, %s )
{
%ret = "";
while(true)
{
%w = firstWord(%v); %v = restWords(%v);
if(%w $= "") break;
%ret = %ret @ (%w * %s) SPC "";
}
return %ret;
}
// performs an element-wise operation %op on vectors %a and %b
function vecOp( %a, %b, %op )
{
%ret = "";
// using a chain of vectorOp() in one vecOp() yields little speedup (~10%), so I settled upon naive version
// eval(..%op..) however slows it down by 50% or more; better avoided
while(true)
{
%aw = firstWord(%a); %a = restWords(%a);
%bw = firstWord(%b); %b = restWords(%b);
if(%aw $= "") break;
//eval("%ret = %ret @ (%aw " @ %op @ " %bw) SPC \"\";");
switch$(%op) // switch turns to be 50% faster than eval(..%op..)
{
case "+": %rw = %aw + %bw;
case "-": %rw = %aw - %bw;
case "*": %rw = %aw * %bw;
case "/": %rw = %aw / %bw;
}
%ret = %ret @ %rw SPC "";
}
return %ret;
}
// actually used wrappers:
function vecAdd(%a,%b) { return vecOp(%a,%b,"+"); }
function vecSub(%a,%b) { return vecOp(%a,%b,"-"); }
function vecMul(%a,%b) { return vecOp(%a,%b,"*"); }
function vecDiv(%a,%b) { return vecOp(%a,%b,"/"); }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment