Merging geometries using a WebWorker? - merge

Anyone know if it's possible to merge a set of cube geometries in a web worker and pass it back to the main thread? Was thinking this could reduce the lag when merging large amounts of cubes.
Does Three.JS work okay in a web worker, and if it does, would it be possible (and faster) to do this? Not sure if passing the geometry back would take just as long as merging it normally.
At the moment I'm using a timed for loop to reduce the lag:
// This array is populated by the server and contains the chunk position and data (which I do nothing with yet).
var sectionData = data.secData;
var section = 0;
var tick = function() {
var start = new Date().getTime();
for (; section < sectionData.length && (new Date().getTime()) - start < 1; section++) {
var sectionXPos = sectionData[section][0] * 10;
var sectionZPos = sectionData[section][1] * 10;
var combinedGeometry = new THREE.Geometry();
for (var layer = 0; layer < 1; layer++) { // Only 1 layer because of the lag...
for (var x = 0; x < 10; x++) {
for (var z = 0; z < 10; z++) {
blockMesh.position.set(x-4.5, layer-.5, z-4.5);
blockMesh.updateMatrix();
THREE.GeometryUtils.merge(combinedGeometry, blockMesh);
}
}
}
var sectionMesh = new THREE.Mesh(combinedGeometry, grassBlockMat);
sectionMesh.position.set(sectionXPos, 0, sectionZPos);
sectionMesh.matrixAutoUpdate = false;
sectionMesh.updateMatrix();
scene.add(sectionMesh);
}
if (section < sectionData.length) {
setTimeout(tick, 25);
}
};
setTimeout(tick, 25);
Using Three.JS rev59-dev.
Merged cubes make up the terrain in chunks, and at the moment (due to the lag) each chunk only has 1 layer.
Any tips would be appreciated! Thanks.

THREE.JS will not work in a web worker, however you can copy those parts of the library that you need to work both in the main thread and in your web worker.
Your first problem will be that you cannot send the geometry object itself back to the main thread.
Since the web worker onmessage variable passing works only by sending copies of JSON (not javascript objects) or references to ArrayBuffers, you would have to decode the geometry down to each float, pack it in an ArrayBuffer, and send a reference back to the main thread.
Note those are called transferable objects and once sent, they are cleared in the webworker / main thread from which they came.
See here for more details:
http://www.html5rocks.com/en/tutorials/workers/basics/
https://developer.mozilla.org/en-US/docs/Web/Guide/Performance/Using_web_workers
Here is an example of packing position vertices into an array for a physics type system:
//length * 3 axes * 4 bytes per vertex
var posBuffer = new Float32Array(new ArrayBuffer(len * 3 * 4));
//in a loop
//... do hard work
posBuffer[i * 3] = pos.x; //pos is a threejs vector
posBuffer[i * 3 + 1] = pos.y;
posBuffer[i * 3 + 2] = pos.z;
//after loop send buffer to main thread
self.postMessage({posBuffer:posBuffer}, [posBuffer.buffer]);
I copied the THREE.JS vector class inside my web worker and cut out all the methods I didn't need to keep it nice and lean.
FYI it's not slow and for something like n-body collisions it works well.
The main thread sends a command to the web worker telling it to run the update and then listens for the response. Kind of like a producer consumer model in regular threading.

Related

I have trouble getting depth information from the DEPTH16 format with the Camera2 API using ToF on P30 pro

I am currently testing options for depth measurement with the smartphone and wanted to create a depth image initially for testing. I am using the Camera2Basic example as a basis for this. (https://github.com/android/camera-samples/tree/main/Camera2Basic) Using Depth16 I get a relatively sharp "depth image" back. But the millimetres are not correct. They are in a range around from 3600mm to 5000mm for an object like a wall that is about 500mm or 800mm away from the camera.
But what puzzles me the most is that the image does not transmit any information in the dark. If Android is really targeting the ToF sensor for DEPTH16, it shouldn't be affected in the dark, should it? Or do I have to use AR-Core or Huawei's HMS core to get a real ToF image?
I am using a Huawei P30 Pro and the code for extracting the depth information looks like this. And yes performance wise it is bullshit but it is only for testing purposes:)
private Map<String, PixelData> parseDepth16IntoDistanceMap(Image image) {
Map<String, PixelData> map = new HashMap();
Image.Plane plane = image.getPlanes()[0];
// using asShortBuffer() like in the documentation leads to a wrong format (for me) but does not help getting better values
ByteBuffer depthBuffer = plane.getBuffer().order(ByteOrder.nativeOrder());
int stride = plane.getRowStride();
int offset = 0;
int i = 0;
for (short y = 0; y < image.getHeight(); y++) {
for (short x = 0; x < image.getWidth(); x++) {
short depthSample = depthBuffer.getShort( (y / 2) * stride + x);
short depthSampleShort = (short) depthSample;
short depthRange = (short) (depthSampleShort & 0x1FFF);
short depthConfidence = (short) ((depthSampleShort >> 13) & 0x7);
float depthPercentage = depthConfidence == 0 ? 1.f : (depthConfidence - 1) / 7.f;
maxz = depthRange;
sum = sum + depthRange;
numPoints++;
listOfRanges.add((float) depthRange);
if (depthRange < minz && depthRange > 0) {
minz = depthRange;
}
map.put(x + "_" + y, new PixelData(x, y, depthRange, depthPercentage));
i++;
}
}
return map;
}
In any case, it would help a lot to know if you can get the data this way at all, so I know if I'm already doing something fundamentally wrong. Otherwise I will change to one of the ar systems. Either way, many thanks for your efforts
If you want to extract a depth map where you can see the distance to an object you might use ARCORE Depth API.
https://developers.google.com/ar/develop/java/depth/overview
Or you can follow the codelab where shows you how to get the data in millimeters.
https://codelabs.developers.google.com/codelabs/arcore-depth#0

formula to pick every pixel in a bitmap without repeating

I'm looking for an algorithm, I am programming in swift now but pseudocode or any reasonably similar "C family" syntax will do.
Imagine a large list of values, such as pixels in a bitmap. You want to pick each one in a visually random order, one at a time, and never pick the same one twice, and always end up picking them all.
I used it before in a Fractal generator so that it was not just rendering line by line, but built it up slowly in a stochastic way, but that was long ago, in a Java applet, and I no longer have the code.
I do not believe it used any pseudo-random number generator, and the main thing I liked about it is that it did not make the rendering time take longer than the just line by line approach. Any of the shuffling algorithms I looked at would make the rendering take longer with such a large number of values to deal with, unless I'm missing something.
EDIT: I used the shuffling an array approach. I shuffle once when the app loads, and it does not take that long anyway. Here is the code for my "Dealer" class.
import Foundation
import Cocoa
import Quartz
class Dealer: NSObject
{
//########################################################
var deck = [(CGFloat,CGFloat)]()
var count = 0
//########################################################
init(_ w:Int, _ h:Int)
{
super.init()
deck.reserveCapacity((w*h)+1)
for y in 0...h
{
for x in 0...w
{
deck.append((CGFloat(x),CGFloat(y)))
}
}
self.shuffle()
}
//########################################################
func shuffle()
{
var j:Int = 0
let total:Int = deck.count-1
for i:Int in 0...total
{
j = Int(arc4random_uniform(UInt32(total)))
deck.swapAt(i, j)
}
}
//########################################################
func deal() -> (CGFloat,CGFloat)
{
let result = deck[count]
let total:Int = deck.count-1
if(count<total) { count=count+1 } else { count=0 }
return(result)
}
//########################################################
}
The init is called once, and it calls shuffle, but if you want you can call shuffle again if needed.
Each time you need a "card" you call Deal. It loops to the beginning when the "deck" is done.
if you got enough memory space to store all the pixel positions you can shuffle them:
const int xs=640; // image resolution
const int ys=480;
color pixel[sz]; // image data
const int sz=xs*ys; // image size
int adr[sz],i,j;
for (i=0;i<sz;i++) adr[i]=i; // ordered positions
for (i=0;i<sz;i++) // shuffle them
{
j = random(sz); // pseudo-randomness with uniform distribution
swap(pixel[i],pixel[j]);
}
this way you got guaranteed that each pixel is used once and most likely all of them are shuffled ...
You need to implement a pseudo-random number generator with a theoretically known period, which is greater than but very close to the number of elements in your list. Suppose R() is a function that implements such a RNG.
Then:
for i = 1...N
do
idx = R()
while idx > N
output element(idx)
end
If the period of the RNG is greater than N, this algorithm is guaranteed to finish, and never output the same element twice
If the period of the RNG is close to N, this algorithm will be fast (i.e. the do-while loop will mostly do 1 iteration).
If the RNG has good quality, the visual output will look pleasant; here you have to do experiments and decide what is good enough for you
To find a RNG that has an exactly-known period, you should examine theory on RNGs, which is very extensive (maybe too extensive); Wikipedia has useful links.
Start with Linear congruential generators: they are very simple, and there is a chance they will be of good enough quality.
Here's a working example based on linear feedback shift registers. Since an n-bit LFSR has a maximal sequence length of 2n−1 steps, this will work best when the number of pixels is one less than a power of 2. For other sizes, the pseudo-random coordinates are discarded until one is obtained that lies within the specified range of coordinates. This is still reasonably efficient; in the worst case (where w×h is a power of 2), there will be an average of two LSFR iterations per coordinate pair.
The following code is in Javascript, but it should be easy enough to port this to Swift or any other language.
Note: For large canvas areas like 1920×1024, it would make more sense to use repeated tiles of a smaller size (e.g., 128×128). The tiling will be imperceptible.
var lsfr_register, lsfr_mask, lsfr_fill_width, lsfr_fill_height, lsfr_state, lsfr_timer;
var lsfr_canvas, lsfr_canvas_context, lsfr_blocks_per_frame, lsfr_frame_rate = 50;
function lsfr_setup(width, height, callback, duration) {
// Maximal length LSFR feedback terms
// (sourced from http://users.ece.cmu.edu/~koopman/lfsr/index.html)
var taps = [ -1, 0x1, 0x3, 0x5, 0x9, 0x12, 0x21, 0x41, 0x8E, 0x108, 0x204, 0x402,
0x829, 0x100D, 0x2015, 0x4001, 0x8016, 0x10004, 0x20013, 0x40013,
0x80004, 0x100002, 0x200001, 0x400010, 0x80000D, 0x1000004, 0x2000023,
0x4000013, 0x8000004, 0x10000002, 0x20000029, 0x40000004, 0x80000057 ];
nblocks = width * height;
lsfr_size = nblocks.toString(2).length;
if (lsfr_size > 32) {
// Anything longer than about 21 bits would be quite slow anyway
console.log("Unsupposrted LSFR size ("+lsfr_size+")");
return;
}
lsfr_register = 1;
lsfr_mask = taps[lsfr_size];
lsfr_state = nblocks;
lsfr_fill_width = width;
lsfr_fill_height = height;
lsfr_blocks_per_frame = Math.ceil(nblocks / (duration * lsfr_frame_rate));
lsfr_timer = setInterval(callback, Math.ceil(1000 / lsfr_frame_rate));
}
function lsfr_step() {
var x, y;
do {
// Generate x,y pairs until they are within the bounds of the canvas area
// Worst-case for an n-bit LSFR is n iterations in one call (2 on average)
// Best-case (where w*h is one less than a power of 2): 1 call per iteration
if (lsfr_register & 1) lsfr_register = (lsfr_register >> 1) ^ lsfr_mask;
else lsfr_register >>= 1;
y = Math.floor((lsfr_register-1) / lsfr_fill_width);
} while (y >= lsfr_fill_height);
x = (lsfr_register-1) % lsfr_fill_width;
return [x, y];
}
function lsfr_callback() {
var coords;
for (var i=0; i<lsfr_blocks_per_frame; i++) {
// Fetch pseudo-random coordinates and fill the corresponding pixels
coords = lsfr_step();
lsfr_canvas_context.fillRect(coords[0],coords[1],1,1);
if (--lsfr_state <= 0) {
clearInterval(lsfr_timer);
break;
}
}
}
function start_fade() {
var w = document.getElementById("w").value * 1;
var h = document.getElementById("h").value * 1;
var dur = document.getElementById("dur").value * 1;
lsfr_canvas = document.getElementById("cv");
lsfr_canvas.width = w;
lsfr_canvas.height = h;
lsfr_canvas_context = lsfr_canvas.getContext("2d");
lsfr_canvas_context.fillStyle = "#ffff00";
lsfr_canvas_context.fillRect(0,0,w,h);
lsfr_canvas_context.fillStyle = "#ff0000";
lsfr_setup(w, h, lsfr_callback, dur);
}
Size:
<input type="text" size="3" id="w" value="320"/>
×
<input type="text" size="3" id="h" value="240"/>
in
<input type="text" size="3" id="dur" value="3"/>
secs
<button onclick="start_fade(); return 0">Start</button>
<br />
<canvas id="cv" width="320" height="240" style="border:1px solid #ccc"/>

Promela system with unranged values

int rq_begin = 0, rq_end = 0;
int av_begin = 0, av_end = 0;
#define MAX_DUR 10
#define RQ_DUR 5
proctype Writer() {
do
:: (av_end < rq_end) -> av_end++;
if
:: (av_end - av_begin) > MAX_DUR -> av_begin = av_end - MAX_DUR;
:: else -> skip;
fi
printf("available span: [%d,%d]\n", av_begin, av_end);
od
}
proctype Reader() {
do
:: d_step {
rq_begin++;
rq_end = rq_begin + RQ_DUR;
}
printf("requested span: [%d,%d]\n", rq_begin, rq_end);
(rq_begin >= av_begin && rq_end <= av_end);
printf("got requested span\n");
od
}
init {
run Writer();
run Reader();
}
This system (only an example) should model a reader/writer queue where the reader requests a certain span of frames [rq_begin,rq_end], and the writer should then make at least this span available. [av_begin,av_end] is the span of available frames.
The 4 values are absolute frame indices, rq_begin gets incremented infinitley as the reader reads the next span of frames.
The system cannot be directly verified because the values are unranges (generating infinitely many states). Does Promela/Spin (or a similar software) has support to verify a system like this, and automatically transform it such that it becomes finite?
For example if all the 4 values were incremented by the same amount, the situation would still be the same. Or it could be reformulated into a model which instead has variables for the differences of these values, for example av_end - rq_end.
I'm using Promela/Spin to verify a more complex queuing system which uses absolute frame indices like this.

Unity3D: Simultaneous Mesh update

I'm facing a problem regarding the update of several meshes materials.
I'm developing a projection mapping app, currently I have 3 planes on my surface, each has 4 points that define their position in space (each point, named DestinationPoint is located at the edge of the plane's corners representing the mesh). By moving (manually, using the OnMouseDown() and OnMouseDrag() functions) each point, the mesh is updated accordingly so that each corner follows each of the point (the goal is matches a real world surface.)
So that I don't have to do this calibration each time, I'm saving the values (local position property) of these 12 points (3 planes * 4 points) in a XML file. The saving/loading process of the XML file is working just fine, all points are automatically moved to the positions previously saved in the XML file. The problem arises when Unity updates each mesh material, it is only updating ONE mesh.
For example, this is the saved scene, saved into a XML file, with 3 planes, each having 4 points represented by the white spheres.
This is the scene loaded from the XML file. The points are moved into the proper position, yet the mesh isn't updated.
The function that loads the XML file is the following:
void LoadScenario(){
filePath = Application.dataPath + "/XmlData/"+scenario+".xml";
XmlReader reader = XmlReader.Create(filePath);
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(reader);
XmlNodeList Surfaces = xmlDoc.GetElementsByTagName("Surfaces");
/* XML file structure
*
* <Surfaces>
<ProjectionSurface1>
<DestinationPoints>
<DP0>
<Position>-8.28037,18.33852,10.06745</Position>
</DP0>
<DP1>
<Position>-31.55436,-3.485761,3.270439</Position>
</DP1>
<DP2>
<Position>38.00045,-1.380948,4.482903</Position>
</DP2>
<DP3>
<Position>30.65092,19.90506,10.96985</Position>
</DP3>
</DestinationPoints>
</ProjectionSurface1>
</Surfaces>
*/
for(int surface = 0; surface < Surfaces.Count; surface++){
// There is only 1 type of Surface, so only 1 node for now...
XmlNode NodeSurface = Surfaces.Item(surface);
// Get List of Projection Surfaces
XmlNodeList allProjSurfaces = NodeSurface.ChildNodes;
//Debug.Log("Number of Proj Surfaces = " + allProjSurfaces.Count);
for(int projSurf = 0 ; projSurf < allProjSurfaces.Count; projSurf++){
XmlNode nodeProjSurface = allProjSurfaces.Item(projSurf);
string activeProjSurfStr = nodeProjSurface.Name;
//Debug.Log("Projection Surface: " + nodeProjSurface.Name);
// Get the DestinationPoints node
XmlNode nodeDestPoints = nodeProjSurface.FirstChild;
// Get all DPs node list
XmlNodeList allDPs = nodeDestPoints.ChildNodes;
for(int dp = 0; dp < allDPs.Count; dp++){
XmlNode nodeDP = allDPs.Item(dp);
XmlNode nodePos = nodeDP.FirstChild;
string dpStr = nodeDP.Name;
Debug.Log("dpStr: " + dpStr);
string[] split_position = nodePos.InnerText.Split(',');
/*Debug.Log("ProjectionSurface : " + nodeProjSurface.Name +
" with " + nodeDP.Name +
" ,with PosX: " + float.Parse(split_position[0]) +
" ,with PosY: " + float.Parse(split_position[1]) +
" ,with PosZ: " + float.Parse(split_position[2]));*/
float xmlXPos = float.Parse(split_position[0]);
float xmlYPos = float.Parse(split_position[1]);
float xmlZPos = float.Parse(split_position[2]);
Vector3 xmlPosVec = new Vector3(xmlXPos, xmlYPos, xmlZPos);
List <GameObject> dpList = sleManager.listOfDestPoints;
int multiplier = destControl.ReturnModifier(projSurf+1);
dpList[dp+multiplier].transform.localPosition = xmlPosVec;
GameObject dpActiveObj = dpList[dp+multiplier].gameObject;
}
}
}
reader.Close();
// Test to force an update to all 3 projection surfaces meshes
GameObject dp0 = sleManager.listOfDestPoints[0];
GameObject dp4 = sleManager.listOfDestPoints[4];
GameObject dp8 = sleManager.listOfDestPoints[8];
destControl.Run(dp0);
Debug.Log("0");
destControl.Run(dp4);
destControl.Run(dp8);
}
}
The function Run(GameObj obj) that I'm using in the end has the main goal of updating the material properties of obj in the argument and is as follows:
public void Run(GameObject dpObj){
Debug.Log("Run com arg obj name: " + dpObj.gameObject.name);
manager.indexChanged = true;
string activeDPStr = dpObj.name;
string activeProjSurfStr = dpObj.transform.root.ToString();
manager.activeProjSurfObj = ReturnActiveProjSurf(dpObj);
manager.activePlaneObj = ReturnActivePlaneObj(manager.activeProjSurfObj);
string activePlaneStr = manager.activePlaneObj.name;
manager.activeProjSurf = int.Parse(activeProjSurfStr[17].ToString());
manager.modifier = ReturnModifier(manager.activeProjSurf);
manager.activeIndex = int.Parse(activeDPStr[2].ToString()) + manager.modifier;
manager.meshMaterial = manager.activePlaneObj.renderer.material;
}
The list "listOfDestPoints" contains all existing destination points. Keep in mind each plane has 4 points, so every 4 members in the list, the target plane changes.
Now, if I try to run do:
destControl.Run(dp0); // Updates Plane 1, works just fine.
But if I try:
destControl.Run(dp0);
destControl.Run(dp4); // Does not update Plane 1, only Plane 2.
p.s: Sorry about the wall of text!
Just solved it with:
yield return new WaitForSeconds(.25f);
Inside the LoadScenario() function. Seems For cycle was faster than the update function so only the last member of the For cycle was truly updated.

PID controller in C# Micro Framework issues

I have built a tricopter from scratch based on a .NET Micro Framework board from TinyCLR.com. I used the FEZ Mini which runs at 72 MHz. Read more about my project at: http://bit.ly/TriRot.
So after a pre-flight check where I initialise and test each component, like calibrating the IMU and spinning each motor, checking that I get receiver data, etc., it enters a permanent loop which then calls the flight controller method on each loop.
I'm trying to tune my PID controller now using the Ziegler-Nichols method, but I am always getting a progressively larger overshoot. I was eventually able to get a [mostly] stable oscillation using proportional control only (setting Ki and Kd = 0); timing the period K with a stopwatch averaged out to 3.198 seconds.
I came across the answer (by Rex Logan) on a similar question by chris12892.
I was initially using the "Duration" variable in milliseconds which made my copter highly aggressive, obviously because I was multiplying the running integrator error by thousands on each loop. I then divided it by another thousand to bring it to seconds, but I'm still battling...
What I don't understand from Rex's answer is:
Why does he ignore the time variable in the integral and differential parts of the equations? Is that right or is it a typo?
What he means by the remark
In a normal sampled system the delta term would be one...
One what? Should this be one second under normal circumstances? What
if this value fluctuates?
My flight controller method is below:
private static Single[] FlightController(Single[] imuData, Single[] ReceiverData)
{
Int64 TicksPerMillisecond = TimeSpan.TicksPerMillisecond;
Int64 CurrentTicks = DateTime.Now.Ticks;
Int64 TickCount = CurrentTicks - PreviousTicks;
PreviousTicks = CurrentTicks;
Single Duration = (TickCount / TicksPerMillisecond) / 1000F;
const Single Kp = 0.117F; //Proportional Gain (Instantaneou offset)
const Single Ki = 0.073170732F; //Integral Gain (Permanent offset)
const Single Kd = 0.001070122F; //Differential Gain (Change in offset)
Single RollE = 0;
Single RollPout = 0;
Single RollIout = 0;
Single RollDout = 0;
Single RollOut = 0;
Single PitchE = 0;
Single PitchPout = 0;
Single PitchIout = 0;
Single PitchDout = 0;
Single PitchOut = 0;
Single rxThrottle = ReceiverData[(int)Channel.Throttle];
Single rxRoll = ReceiverData[(int)Channel.Roll];
Single rxPitch = ReceiverData[(int)Channel.Pitch];
Single rxYaw = ReceiverData[(int)Channel.Yaw];
Single[] TargetMotorSpeed = new Single[] { rxThrottle, rxThrottle, rxThrottle };
Single ServoAngle = 0;
if (!FirstRun)
{
Single imuRoll = imuData[1] + 7;
Single imuPitch = imuData[0];
//Roll ----- Start
RollE = rxRoll - imuRoll;
//Proportional
RollPout = Kp * RollE;
//Integral
Single InstanceRollIntegrator = RollE * Duration;
RollIntegrator += InstanceRollIntegrator;
RollIout = RollIntegrator * Ki;
//Differential
RollDout = ((RollE - PreviousRollE) / Duration) * Kd;
//Sum
RollOut = RollPout + RollIout + RollDout;
//Roll ----- End
//Pitch ---- Start
PitchE = rxPitch - imuPitch;
//Proportional
PitchPout = Kp * PitchE;
//Integral
Single InstancePitchIntegrator = PitchE * Duration;
PitchIntegrator += InstancePitchIntegrator;
PitchIout = PitchIntegrator * Ki;
//Differential
PitchDout = ((PitchE - PreviousPitchE) / Duration) * Kd;
//Sum
PitchOut = PitchPout + PitchIout + PitchDout;
//Pitch ---- End
TargetMotorSpeed[(int)Motors.Motor.Left] += RollOut;
TargetMotorSpeed[(int)Motors.Motor.Right] -= RollOut;
TargetMotorSpeed[(int)Motors.Motor.Left] += PitchOut;// / 2;
TargetMotorSpeed[(int)Motors.Motor.Right] += PitchOut;// / 2;
TargetMotorSpeed[(int)Motors.Motor.Rear] -= PitchOut;
ServoAngle = rxYaw + 15;
PreviousRollE = imuRoll;
PreviousPitchE = imuPitch;
}
FirstRun = false;
return new Single[] {
(Single)TargetMotorSpeed[(int)TriRot.LeftMotor],
(Single)TargetMotorSpeed[(int)TriRot.RightMotor],
(Single)TargetMotorSpeed[(int)TriRot.RearMotor],
(Single)ServoAngle
};
}
Edit: I found that I had two bugs in my code above (fixed now). I was integrating and differentiating with the last IMU values as opposed to the last error values. That got rid of the runaway sitation completely. The only problem now is that it seems to be a bit slow. When I perturb the system, it responds very quickly and stop it from continuing, but it takes a long time to get back to the setpoint (0), about 10 seconds or more. Is this now just down to tuning the PID? I'll give the suggestions below a go, and let you know if any of them make a difference.
One question I have is:
being a .NET board, I don't want to bank on any kind of accurate timing, so instead of trying to work out at what frequency I am executing that method, surely if I calculate the actual time and factor that into the equations, it should be better, or am I misunderstanding something?