Animate the PI = 4 experiment
+2
Vexman
Cr6
6 posters
Page 4 of 8
Page 4 of 8 • 1, 2, 3, 4, 5, 6, 7, 8
Re: Animate the PI = 4 experiment
.
Today’s initial output view showing the completed new pi switch track. All three spheres are at their final track positions, they will be back at the start in the next frame.
All the new track ball’s spin animations are correct. The sphere pointlight and the resized table are shown. The initial view is larger, I tried following your suggestion to make the straight track about 0.9x the view width as fas as I could without both circular loops busting the top and bottom of the frame. I’m such a wimp at times.
One minor problem, not all the pi switch track marker color animations work. It seems that changing back to black or lerping back causes a halt due to a material error when the ball crosses the three o’clock marker the second time, now past, a few frames before the image shown.
I’ll try saying it more clearly. The animation function contains the code for advancing each of the three spheres in alternate if branches, according to the rules for that branch corresponding to each track. Advance the sphere until the total distance allowed then go back to start. An error allows the ball in the new pi track gets past the max distance without going back to start, or I might need a larger marker set.
I’ll disconnect the pi swiches from our 8*r circular track.
I also need to come up with a convenient means to omit or include the new track in the scene as necessary. The tracks are added at the init level so a track configuration change cannot be a menu control item. Camera and table changes would be included. Plenty to do, I need a break from listing these changes.
.
Today’s initial output view showing the completed new pi switch track. All three spheres are at their final track positions, they will be back at the start in the next frame.
All the new track ball’s spin animations are correct. The sphere pointlight and the resized table are shown. The initial view is larger, I tried following your suggestion to make the straight track about 0.9x the view width as fas as I could without both circular loops busting the top and bottom of the frame. I’m such a wimp at times.
One minor problem, not all the pi switch track marker color animations work. It seems that changing back to black or lerping back causes a halt due to a material error when the ball crosses the three o’clock marker the second time, now past, a few frames before the image shown.
I’ll try saying it more clearly. The animation function contains the code for advancing each of the three spheres in alternate if branches, according to the rules for that branch corresponding to each track. Advance the sphere until the total distance allowed then go back to start. An error allows the ball in the new pi track gets past the max distance without going back to start, or I might need a larger marker set.
I’ll disconnect the pi swiches from our 8*r circular track.
I also need to come up with a convenient means to omit or include the new track in the scene as necessary. The tracks are added at the init level so a track configuration change cannot be a menu control item. Camera and table changes would be included. Plenty to do, I need a break from listing these changes.
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
I have another idea. When the balls reach a marker, they all stop moving for a few seconds, allowing the user to look at each track and see where they are.
Re: Animate the PI = 4 experiment
.
Orbital viewing about the three tracks.
We have a Reset button, pressing it repositions the spheres back to their starting locations. Any change to either of the two pi switches: twopivt or twopir includes repositioning all three spheres back to start. I don’t believe a reset is necessary for changing the cycloid roll selection. The tracks always remain in synch – the three spheres arrive at their maximum distances and return back to start in the next frame. The pi switch track markers still need attention.
No joy on solving your stop at each marker for a few seconds idea yet. I believe the solution involves using a setTimeout function. I created the new function called advance(), in which the next position for each sphere is calculated. If I comment out advance, the spheres do not leave their starting spots. I’ve buried the advance() call in what I thought timeout delays such as,
advance is clearly running but there are no delays. The continuous loops are not interrupted. I'll keep at it.
.
Orbital viewing about the three tracks.
We have a Reset button, pressing it repositions the spheres back to their starting locations. Any change to either of the two pi switches: twopivt or twopir includes repositioning all three spheres back to start. I don’t believe a reset is necessary for changing the cycloid roll selection. The tracks always remain in synch – the three spheres arrive at their maximum distances and return back to start in the next frame. The pi switch track markers still need attention.
No joy on solving your stop at each marker for a few seconds idea yet. I believe the solution involves using a setTimeout function. I created the new function called advance(), in which the next position for each sphere is calculated. If I comment out advance, the spheres do not leave their starting spots. I’ve buried the advance() call in what I thought timeout delays such as,
- Code:
setTimeout(advance(), 4*1000);
advance is clearly running but there are no delays. The continuous loops are not interrupted. I'll keep at it.
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
You can use setTimeout, or you could just have a wait variable that stops the motion calculations when it is above 0. If it is above 0, then it is decremented by the time since the last frame (it seems you don't have that data, look at various ThreeJS examples to see how to use a Clock object and the time values that it gives you) until it is 0 (or below and if so, set it to 0).
Notice that it should only effect the motion calculations, and not stop the rendering.
By the way, that call to setTimeout is wrong, or at least I strongly suggest that it is wrong given what you have written above. The setTimeout function takes 2 arguments: a function; and an amount of time to wait before executing that function. You are invoking the advance function, when (I believe) you should be just passing that function. The reason I say that it might be wrong is that the advance function could be returning a function which is what setTimeout will execute. But I doubt that.
It should be written like this:
Notice that advance does not have parentheses following it, which would invoke the function and pass whatever it returns to setTimeout.
In Javascript, a function name is really a variable name and that variable just happens to point to a function. So functions can be passed around like any other variable (which is extremely useful at times). It is only by following the function name with an argument list, encapsulated by ( and ), that the function will actually be executed.
Notice that it should only effect the motion calculations, and not stop the rendering.
By the way, that call to setTimeout is wrong, or at least I strongly suggest that it is wrong given what you have written above. The setTimeout function takes 2 arguments: a function; and an amount of time to wait before executing that function. You are invoking the advance function, when (I believe) you should be just passing that function. The reason I say that it might be wrong is that the advance function could be returning a function which is what setTimeout will execute. But I doubt that.
It should be written like this:
- Code:
setTimeout( advance, 4*1000 );
Notice that advance does not have parentheses following it, which would invoke the function and pass whatever it returns to setTimeout.
In Javascript, a function name is really a variable name and that variable just happens to point to a function. So functions can be passed around like any other variable (which is extremely useful at times). It is only by following the function name with an argument list, encapsulated by ( and ), that the function will actually be executed.
Re: Animate the PI = 4 experiment
The code needs to be cleaned up a lot. The animate function basically contains everything. I think a few more functions could be used to make things a lot clearer.
Re: Animate the PI = 4 experiment
.
Showing delta values and a new pauseAtMarkers option.
You indicated it seems I didn’t have time data, you must have a nice console. Over the last two days I've looked through many clock timing examples. I believe I created a proper clock – and associated data, by adding these two lines to the start of the animate function.
I had no luck finding anything resembling a countdown timer. In addition to setTimeout I tried sleep, which froze up the rendering; and asynchronous ‘await’ – which, as far as I could tell, was a no go. I was quite desperate when I finally came up with a simple solution using setTimeout.
Brief Joy, I hope you find it acceptable.
As per your direction I simplified the animation function as much as I could find breaking 6 or 7 functions out into their own functions, for a current total of 14 functions – not counting init() and animate().
.
Showing delta values and a new pauseAtMarkers option.
You indicated it seems I didn’t have time data, you must have a nice console. Over the last two days I've looked through many clock timing examples. I believe I created a proper clock – and associated data, by adding these two lines to the start of the animate function.
- Code:
var delta = clock.getDelta();
var time = clock.getElapsedTime() * 0.5;
I had no luck finding anything resembling a countdown timer. In addition to setTimeout I tried sleep, which froze up the rendering; and asynchronous ‘await’ – which, as far as I could tell, was a no go. I was quite desperate when I finally came up with a simple solution using setTimeout.
- Code:
function stopAtCrossing() {
if ( pauseMark == true ) {
paused = true;
var duration = pauseDuration*1000;
setTimeout(() => {
paused = false;
}, duration );
};
};
Brief Joy, I hope you find it acceptable.
As per your direction I simplified the animation function as much as I could find breaking 6 or 7 functions out into their own functions, for a current total of 14 functions – not counting init() and animate().
.
Last edited by LongtimeAirman on Thu Dec 12, 2019 9:53 pm; edited 1 time in total (Reason for editing : P.S. corrected three typos:1. 0.0166... ; 2. added }; 3. changed 'found' to 'came up with'.)
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
You don't need to find a countdown timer, you need to create one. Try to get out of the habit of finding things that others have done, and think about how you might go about it first. It is good to re-use code, even from others, but it is better to know how to do things yourself. Especially when they are trivial things. It is worth just thinking about how you might do it, before looking for what others have done. You might still go looking, because there might be other ways of doing it, but if what you come up with is simple enough, and works, then why waste the time.
All you need is a variable, and some code to make use of it. The stop-at-marker function is going to set it to some value, such as 4000(ms). The animate function is going to watch that variable and when it is above 0, then it will not execute the motion based code, and it will reduce the value by the time since the last frame (real time, not model time, hence why you need that Clock). When it is 0 or below, it will execute the motion code. That's it. The hardest part is setting up the if/else structure around the motion code, which will be a lot easier if that code was in its own function. That is why I wanted you to make some more functions. It makes the animate function a lot cleaner and you can see how to manipulate things in it. When it is 100's of lines long, you just can't see that sort of thing. It looks too hard, but it really isn't. Simplify, simplify, simplify! It really helps.
You will need to be careful of units, as the Clock uses seconds, but you really want milliseconds. You could use seconds if you wish, then the value will be set to 4, not 4000. Actually, that is probably the easier way to go. You only needed milliseconds because you were using setTimeout, but without that, you can use seconds. Play with the time too. 4 seconds seems a bit too much, but trial and error will find that out.
Get rid of the lerpHSL function. You don't need it. The THREE.Color class already does it, and it does it more efficiently than you are. Your implementation could be made more efficient, but why bother when it is already available?
There are a hell of a lot of global variables too. I would estimate about 150 to 200 of them. That needs to be fixed. I know the THREE examples do that a lot, but it is just lazy (of them, not you), and it can cause problems that are extremely difficult to find. One solution is to use a wrapper object, just like you have with the parameters object used for the DATGUI menu. There are other solutions, but they are a bit more advanced and I don't expect you to understand those ones. Don't feel bad about using global variables, we all start that way, but it is a habit best avoided rather than latched on to.
I know I'm getting picky, but it does 2 things. It educates you on better ways of going about things. It also makes the code more ready to be put into another page structure. As it is, that would be extremely difficult.
Also, I noticed that you copied the Clock code from a ThreeJS example, where it halves the time. Don't do that. You don't need to in this case, and it will effect how that 4s is treated (as it will make 4s into 8s).
All you need is a variable, and some code to make use of it. The stop-at-marker function is going to set it to some value, such as 4000(ms). The animate function is going to watch that variable and when it is above 0, then it will not execute the motion based code, and it will reduce the value by the time since the last frame (real time, not model time, hence why you need that Clock). When it is 0 or below, it will execute the motion code. That's it. The hardest part is setting up the if/else structure around the motion code, which will be a lot easier if that code was in its own function. That is why I wanted you to make some more functions. It makes the animate function a lot cleaner and you can see how to manipulate things in it. When it is 100's of lines long, you just can't see that sort of thing. It looks too hard, but it really isn't. Simplify, simplify, simplify! It really helps.
You will need to be careful of units, as the Clock uses seconds, but you really want milliseconds. You could use seconds if you wish, then the value will be set to 4, not 4000. Actually, that is probably the easier way to go. You only needed milliseconds because you were using setTimeout, but without that, you can use seconds. Play with the time too. 4 seconds seems a bit too much, but trial and error will find that out.
Get rid of the lerpHSL function. You don't need it. The THREE.Color class already does it, and it does it more efficiently than you are. Your implementation could be made more efficient, but why bother when it is already available?
There are a hell of a lot of global variables too. I would estimate about 150 to 200 of them. That needs to be fixed. I know the THREE examples do that a lot, but it is just lazy (of them, not you), and it can cause problems that are extremely difficult to find. One solution is to use a wrapper object, just like you have with the parameters object used for the DATGUI menu. There are other solutions, but they are a bit more advanced and I don't expect you to understand those ones. Don't feel bad about using global variables, we all start that way, but it is a habit best avoided rather than latched on to.
I know I'm getting picky, but it does 2 things. It educates you on better ways of going about things. It also makes the code more ready to be put into another page structure. As it is, that would be extremely difficult.
Also, I noticed that you copied the Clock code from a ThreeJS example, where it halves the time. Don't do that. You don't need to in this case, and it will effect how that 4s is treated (as it will make 4s into 8s).
Re: Animate the PI = 4 experiment
.
Creating new functions actually simplifies things eh? Takes some getting used to.
Once again, thanks Nevyn for your clear and expert instructions and explanations.
As per your directions I eliminated the lerpHSL function; that made subsequent ‘simplifications’ a lot easier to see. I created and removed 14 new functions away from the animate function. The animate function is now less than a hundred lines and easier to ‘read’. I think I see at least one more pi track function to break out. The pi track marker color corrections wil follow the simplification effort.
Please let me know if I made too many new functions. I haven’t begun to think about eliminating or otherwise dealing with the global variables. After today I’m reassured, I’ll need to lookup ‘wrappers’, but I’ll point out that the new functions I created today also pass variables, suggests another way to reduce the number of global variables.
For the record, I wrote the stopAtCrossing function, I posted ‘found’ but that didn’t mean I ‘found’ the solution outside myself. I posted a correction. I was extremely frustrated from many attempts over the last two days and still not having come up with a ‘countdown timer’. I was expecting the main criticism to be directed at my use of paused and the setTimeout function. As far as that solution goes, my particular aha moment was seeing someone using time as a variable to select different branches. A few seconds later I thought to use the paused variable with the setTimeout function. Since you didn’t seem to indicate any particular problem with stopAtCrossing() I’m greatly relieved and grateful for all the rest of your most excellent criticism. Believe it or not, I think its working.
.
Creating new functions actually simplifies things eh? Takes some getting used to.
Once again, thanks Nevyn for your clear and expert instructions and explanations.
As per your directions I eliminated the lerpHSL function; that made subsequent ‘simplifications’ a lot easier to see. I created and removed 14 new functions away from the animate function. The animate function is now less than a hundred lines and easier to ‘read’. I think I see at least one more pi track function to break out. The pi track marker color corrections wil follow the simplification effort.
Please let me know if I made too many new functions. I haven’t begun to think about eliminating or otherwise dealing with the global variables. After today I’m reassured, I’ll need to lookup ‘wrappers’, but I’ll point out that the new functions I created today also pass variables, suggests another way to reduce the number of global variables.
For the record, I wrote the stopAtCrossing function, I posted ‘found’ but that didn’t mean I ‘found’ the solution outside myself. I posted a correction. I was extremely frustrated from many attempts over the last two days and still not having come up with a ‘countdown timer’. I was expecting the main criticism to be directed at my use of paused and the setTimeout function. As far as that solution goes, my particular aha moment was seeing someone using time as a variable to select different branches. A few seconds later I thought to use the paused variable with the setTimeout function. Since you didn’t seem to indicate any particular problem with stopAtCrossing() I’m greatly relieved and grateful for all the rest of your most excellent criticism. Believe it or not, I think its working.
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
Using setTimeout was an acceptable solution, and in most other types of applications that is what you would do. However, in this app, and all 3D apps, we already have a loop that is running at all times. So it is better to take advantage of that and just create a simple count down variable. That actually runs better because JS only allows one thing to be running at a time. So to execute the setTimeout function (the one you pass to it) it has to stop the animation loop, which should be avoided if we want the 3D scene to render fluently.
In this particular case, the function you gave to setTimeout was not onerous in any way, so it would not have slowed things down in a noticeable way. However, I think it is better to remove it on principle and go with the better solution for this type of app.
Programming is about learning what sorts of things you can do, and finding ways to put them together to get some job done. Over time, as you get more experienced, you find other ways to do the same things. Eventually, you start to see when and where you should use particular solutions to specific problems.
I think the animate function can probably be reduced even more. I haven't looked at these recent changes yet, but I think that animate should be about 20 lines long, maybe even less. It should only contain the high level control structures and the code that actually does things should be in functions with clear names.
In essence, animate should look something like this:
Then executeMotion looks something like this:
The idea is that these higher level functions read nicely. You don't have to figure out what they are doing by looking through many lines of code. The function names tell you enough to get a rough idea of what is going on. Because they are so small and simple, you can see how to manipulate things a lot easier.
Let's say you remove track C. All you have to do is comment out the call to executeTackC and it is no longer being processed. Of course, you would also have to remove the code that adds it to the scene, but we aren't looking at that at the moment (but it can also use the same treatment as we are doing here and it gains the same benefits).
Don't worry too much about it. You're doing a good job. The animation itself looks great. Cleaning up the code has the main benefit of making the transition into another page much more easy, but also it will help if you come back to this app after not looking at it for some time. Sometimes, when you go back to code you wrote a long time ago, you find that you can't understand it anymore. Right now you are in the middle of it and understand what things are doing, but you will forget a lot of that within a few months of not working on it. Comments help that too. Even if the comment seems obvious now, it may not be later. Think about how daunting it is to look at my code, and you feel a bit lost and find it difficult to understand. That can happen with your own work too. It's a bit of a surprise when it first happens, but you get used to it after a while. In fact, if you go looking through your own old code and don't feel a bit ashamed of how you do some things, then you aren't learning.
In this particular case, the function you gave to setTimeout was not onerous in any way, so it would not have slowed things down in a noticeable way. However, I think it is better to remove it on principle and go with the better solution for this type of app.
Programming is about learning what sorts of things you can do, and finding ways to put them together to get some job done. Over time, as you get more experienced, you find other ways to do the same things. Eventually, you start to see when and where you should use particular solutions to specific problems.
I think the animate function can probably be reduced even more. I haven't looked at these recent changes yet, but I think that animate should be about 20 lines long, maybe even less. It should only contain the high level control structures and the code that actually does things should be in functions with clear names.
In essence, animate should look something like this:
- Code:
function animate( time )
{
requestAnimationFrame( animate );
// do any common pre-tasks here
if( waitAtMarker > 0 )
{
waitAtMarker = Math.max( 0, waitAtMarker - time.elapsedTime );
}
else
{
executeMotion( time );
}
// do any common post-tasks here
renderer.render( camera, scene );
// do any post rendering tasks here
}
Then executeMotion looks something like this:
- Code:
function executeMotion( time )
{
// do any common pre-tasks here
executeTrackA( time );
executeTrackB( time );
executeTrackC( time );
// do any common post-tasks here
}
}
The idea is that these higher level functions read nicely. You don't have to figure out what they are doing by looking through many lines of code. The function names tell you enough to get a rough idea of what is going on. Because they are so small and simple, you can see how to manipulate things a lot easier.
Let's say you remove track C. All you have to do is comment out the call to executeTackC and it is no longer being processed. Of course, you would also have to remove the code that adds it to the scene, but we aren't looking at that at the moment (but it can also use the same treatment as we are doing here and it gains the same benefits).
Don't worry too much about it. You're doing a good job. The animation itself looks great. Cleaning up the code has the main benefit of making the transition into another page much more easy, but also it will help if you come back to this app after not looking at it for some time. Sometimes, when you go back to code you wrote a long time ago, you find that you can't understand it anymore. Right now you are in the middle of it and understand what things are doing, but you will forget a lot of that within a few months of not working on it. Comments help that too. Even if the comment seems obvious now, it may not be later. Think about how daunting it is to look at my code, and you feel a bit lost and find it difficult to understand. That can happen with your own work too. It's a bit of a surprise when it first happens, but you get used to it after a while. In fact, if you go looking through your own old code and don't feel a bit ashamed of how you do some things, then you aren't learning.
Re: Animate the PI = 4 experiment
.
Nevyn, you gave me three tasks:
1. Reduce the size of the animate function to a small number of high level functions and small number of code lines. Since my last post, I was able to off-load the animate function of about four more functions, bringing the number of animate code lines down to 21 – amazed me. Unfortunately, there’s nothing high level about the six var declarations, curSectionS, nexSection, … , smack in the middle of the animate function. I have not been able to move or make them into their own independent function. I hope that isn’t a problem.
2. Global variable cleanup. Too many global variables is the result of poor practices. True, I removed about half of the global variables. A few were moved to the init function, but many were serving no purpose except as reminders of unsuccessful efforts, and were gotten rid of. I looked up wrapper, I’ll need to look it up again.
3. Create a waitAtMarker function without using the setTimeout function. I found this task to be quite difficult. Sure I’ve messed with or modified previously existing code which used time, but I’ve never coded with time or date objects before. I had no idea the setTimeout function added an additional loop separate from the animate function. Thanks for the directions, the simplified big picture – wait or animate (as in execute the animate function’s motion or not) with code! When I came up with a working solution my mind went skipping off and hasn’t returned yet - dang, the animate function is now about 35 lines.
A couple of last effort items cleaned up but not yet Pushed, I’ll do another walk through next.
.
Nevyn, you gave me three tasks:
1. Reduce the size of the animate function to a small number of high level functions and small number of code lines. Since my last post, I was able to off-load the animate function of about four more functions, bringing the number of animate code lines down to 21 – amazed me. Unfortunately, there’s nothing high level about the six var declarations, curSectionS, nexSection, … , smack in the middle of the animate function. I have not been able to move or make them into their own independent function. I hope that isn’t a problem.
2. Global variable cleanup. Too many global variables is the result of poor practices. True, I removed about half of the global variables. A few were moved to the init function, but many were serving no purpose except as reminders of unsuccessful efforts, and were gotten rid of. I looked up wrapper, I’ll need to look it up again.
3. Create a waitAtMarker function without using the setTimeout function. I found this task to be quite difficult. Sure I’ve messed with or modified previously existing code which used time, but I’ve never coded with time or date objects before. I had no idea the setTimeout function added an additional loop separate from the animate function. Thanks for the directions, the simplified big picture – wait or animate (as in execute the animate function’s motion or not) with code! When I came up with a working solution my mind went skipping off and hasn’t returned yet - dang, the animate function is now about 35 lines.
A couple of last effort items cleaned up but not yet Pushed, I’ll do another walk through next.
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
.
No radical changes, the sphere and tube radii are roughly doubled. The table is opaque and reflecting the sphere pointlights making them somewhat brighter and more defined. The latest pi switch test track is at the top showing the erroneous mainstream expected experimental outcome.
The pi switch test track allows the user to violate physics in four different ways in order to compare the expected and actual (to the best of my belief) pi geometric or pi kinematic behaviors – such as cycloid or non cycloid rolling. I’m glad you came up with the idea of a third track devoted to the pi switches. Still, I’d hate to see this application misused or misrepresented and hope it’s never used for evil. I’m thinking I could remove it from the scene by placing it elsewhere in worldspace, like I did with the table, and move it to the position shown when needed. Scratch that, the track and sphere is way more complicated.
The pi test track markers are now working correctly, changing from blue to red, or the other starting colors change to black as the spheres pass a diameter’s length away from them.
The track time selected is now close to the time it takes to complete the track – I’ve assumed a constant fps of 60.
I’ve tried removing the animate function vaiables multiple times without success, I don’t see how to make it any shorter except for motion execution restructuring or some such r – which has no problem that I’m aware of.
I did more variable clean up, everything looks ship shape to me.
I have no problems with any of the functions, you indicated your dislike for the keyboard spacebar operation. Space stops the action or allows crude step motion. I see little reason to change it. I believe it involves html code, which you indicated I should avoid. Not a big deal.
Awaiting further instructions.
.
No radical changes, the sphere and tube radii are roughly doubled. The table is opaque and reflecting the sphere pointlights making them somewhat brighter and more defined. The latest pi switch test track is at the top showing the erroneous mainstream expected experimental outcome.
The pi switch test track allows the user to violate physics in four different ways in order to compare the expected and actual (to the best of my belief) pi geometric or pi kinematic behaviors – such as cycloid or non cycloid rolling. I’m glad you came up with the idea of a third track devoted to the pi switches. Still, I’d hate to see this application misused or misrepresented and hope it’s never used for evil. I’m thinking I could remove it from the scene by placing it elsewhere in worldspace, like I did with the table, and move it to the position shown when needed. Scratch that, the track and sphere is way more complicated.
The pi test track markers are now working correctly, changing from blue to red, or the other starting colors change to black as the spheres pass a diameter’s length away from them.
The track time selected is now close to the time it takes to complete the track – I’ve assumed a constant fps of 60.
I’ve tried removing the animate function vaiables multiple times without success, I don’t see how to make it any shorter except for motion execution restructuring or some such r – which has no problem that I’m aware of.
I did more variable clean up, everything looks ship shape to me.
I have no problems with any of the functions, you indicated your dislike for the keyboard spacebar operation. Space stops the action or allows crude step motion. I see little reason to change it. I believe it involves html code, which you indicated I should avoid. Not a big deal.
Awaiting further instructions.
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
I don't like the table underneath everything. It washes out the colors.
You should never hide things by moving them off-screen. If they are not needed, then they should not exist. If they must be in the scene, then you can mark them as not visible. Every THREE.Object3D instance has a visible property that can be set to false. However, I would be adding something to the code to stop such entities even being created. This is complicated by the way you are processing motion. If you go back to the early posts on this thread, you will find me talking about having an array of tracks. If you follow that approach, then it is easy to remove a track without needing to remove processing code. Maybe if you read those posts now, you might understand them a bit better and see how to go about it like that.
What I don't like about the motion code is that it is specialized for each track. It should be generic and used for all tracks. The tracks define what they are and the motion code only executes the appropriate code for that particular track (actually, it would be a section of the track). So the motion code still knows how to handle a PI=4 curve, but it also knows how to handle a PI=3.14 curve, and it applies the right one for the right section of track that it is processing at the time.
I suggest you start a new HTML file and try to follow that approach. The tracks should define what they are, rather than the motion code knowing what they are. Does that make sense? It seems the current motion code determines how far along the ball is on a known type of track and performs certain motion based on that. It has to know which type of track it is. The motion code should just be a switch or if/else structure that says: if this type of section, do this, else if this type, do this, etc. It should be quite dumb as far as the track goes, but smart with respect to the types of sections that a track might have. This allows the motion code to handle any type of section, even if it never has to handle it (because you removed a certain track, for example). Basically, the motion needs to be separated from the track, and just be a structure of potential motions.
Can you see how that fixes the problem you have if you remove the PI=3.14 track? Where you currently have to remove motion code to do that, the other approach does not. The motion code can remain part of the app, but the track containing a PI=3.14 curve can be removed.
Similarly, the creation of the scene objects should be driven by the track array too. If everything works based on the track array, then you can just add and remove items from that array and everything works. This is what we call 'Data Driven' applications.
You should never hide things by moving them off-screen. If they are not needed, then they should not exist. If they must be in the scene, then you can mark them as not visible. Every THREE.Object3D instance has a visible property that can be set to false. However, I would be adding something to the code to stop such entities even being created. This is complicated by the way you are processing motion. If you go back to the early posts on this thread, you will find me talking about having an array of tracks. If you follow that approach, then it is easy to remove a track without needing to remove processing code. Maybe if you read those posts now, you might understand them a bit better and see how to go about it like that.
What I don't like about the motion code is that it is specialized for each track. It should be generic and used for all tracks. The tracks define what they are and the motion code only executes the appropriate code for that particular track (actually, it would be a section of the track). So the motion code still knows how to handle a PI=4 curve, but it also knows how to handle a PI=3.14 curve, and it applies the right one for the right section of track that it is processing at the time.
I suggest you start a new HTML file and try to follow that approach. The tracks should define what they are, rather than the motion code knowing what they are. Does that make sense? It seems the current motion code determines how far along the ball is on a known type of track and performs certain motion based on that. It has to know which type of track it is. The motion code should just be a switch or if/else structure that says: if this type of section, do this, else if this type, do this, etc. It should be quite dumb as far as the track goes, but smart with respect to the types of sections that a track might have. This allows the motion code to handle any type of section, even if it never has to handle it (because you removed a certain track, for example). Basically, the motion needs to be separated from the track, and just be a structure of potential motions.
Can you see how that fixes the problem you have if you remove the PI=3.14 track? Where you currently have to remove motion code to do that, the other approach does not. The motion code can remain part of the app, but the track containing a PI=3.14 curve can be removed.
Similarly, the creation of the scene objects should be driven by the track array too. If everything works based on the track array, then you can just add and remove items from that array and everything works. This is what we call 'Data Driven' applications.
Re: Animate the PI = 4 experiment
.
Thank you Sir, I think I understand, the program should be rewritten. I reviewed the thread and feel some shock and embarrassment at having diverged so far from your initial instructions. Sorry you must repeat yourself. On the positive side I can now appreciate your suggestion would lead to a better outcome and presents another excellent learning opportunity.
I wrote an extremely specialized ‘recipe’ application – akin to a painting - a curved path, a straight path, and an opposite curved path – in that order. The balls are moved around their particular paths during each frame - in that order. Alternatively, an array based structure introduces a level of abstraction and greater freedom. One might begin by just identifying the total number of items that needs to be accessed from a track array. As you indicated, each individual line from the track array could contain all information and directions necessary to create everything associated with that track as well as moving the ball along it. The track array provides a modular capability, allowing more freedom in selecting the number and types of tracks desired, easily modifiable for related applications. At best, my code might be compared to a special, one-of a kind item, where a track array based structure might be compared to an automated construction and operation set, far more efficient and versatile.
Well then, besides going back to start, how do I proceed? The array must act primarily as an index. How is it accessed? I suppose each track array line item could be thought of like some 3D object: track1 = new THREE.TrackObject(); with all the necessary item parameters. After giving it more thought I guess I’ll try defining and building the track array first.
Pardon me for mentioning, best wishes as always, but especially over these next next few weeks. Stay safe.
.
Thank you Sir, I think I understand, the program should be rewritten. I reviewed the thread and feel some shock and embarrassment at having diverged so far from your initial instructions. Sorry you must repeat yourself. On the positive side I can now appreciate your suggestion would lead to a better outcome and presents another excellent learning opportunity.
I wrote an extremely specialized ‘recipe’ application – akin to a painting - a curved path, a straight path, and an opposite curved path – in that order. The balls are moved around their particular paths during each frame - in that order. Alternatively, an array based structure introduces a level of abstraction and greater freedom. One might begin by just identifying the total number of items that needs to be accessed from a track array. As you indicated, each individual line from the track array could contain all information and directions necessary to create everything associated with that track as well as moving the ball along it. The track array provides a modular capability, allowing more freedom in selecting the number and types of tracks desired, easily modifiable for related applications. At best, my code might be compared to a special, one-of a kind item, where a track array based structure might be compared to an automated construction and operation set, far more efficient and versatile.
Well then, besides going back to start, how do I proceed? The array must act primarily as an index. How is it accessed? I suppose each track array line item could be thought of like some 3D object: track1 = new THREE.TrackObject(); with all the necessary item parameters. After giving it more thought I guess I’ll try defining and building the track array first.
Pardon me for mentioning, best wishes as always, but especially over these next next few weeks. Stay safe.
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
Don't feel bad about it. I didn't stop you because I saw that you needed to focus on the specifics before you could see the larger picture. I take a lot of that for granted. I can play with ideas because I trust that I can deal with the specifics when I get there. When ever I start something that I am unsure about, I often go straight into the specifics just to see how at least some part of it looks. That gives me a feel for how other things might end up, and I can start to think about the larger picture.
Let's also remember that you did solve the problem. You did get an animation up and running that showed what you wanted it to show. Sure, you didn't go about it the way I wanted, but so what? I probably wouldn't have even mentioned it now, except you presented a problem that would have been very easily solved in my method, but difficult in what you created. I saw that as a good opportunity to demonstrate a different design decision where the focus is on the data rather than the processing. I realised early on that I was talking about higher level ideas that you weren't able to see just yet. I was trying to lead you to things that you weren't ready for, so I let you find your own way through it.
We aren't so much concerned with how the track array is going to be accessed (which will just end up being a for loop iterating over the array), but with what data it might contain. The content of the array is critical and we need to make sure that it contains everything we need to do whatever it is we want to do with it. That sounds like a difficult task, but in reality, you just take it one step at a time.
First, you think about what sort of information you need to define a track. Break it down into smaller parts. Break those parts down into smaller parts, if you need to. You can, some-what, do this in conjunction with developing the motion code. Let's give it a quick try:
Looking at the tracks you already have, we ask ourselves "What does a track have?", and the answer is "It has sections".
Then we ask "What types of sections are there?" and we answer "There are straight sections and there are curved sections.". So we need a way to define that in our track definition.
But wait. That doesn't really cover what we already have. We don't just have a single type of curve, we have 2 different curves. What are we going to do about that? Well, there are a few things we could do to deal with that, but the simplest would be to create another type of section.
Note that I am not creating an actual track array here, even though it looks like it (and technically, it is). What I am doing is creating a declaration of what a track array can contain and how it contains it. We wouldn't have a curve4 and curve314 in the same track. Of course we could, but in this particular application, we don't need to. But I just want to show what types of sections are possible.
What else does a track need? How about something that moves along the track? We need a ball.
With this little bit of knowledge, we can now start to build the motion code a little bit. We now know what types of motion we require, and we can put that into some sort of structure.
See how the higher level functions are already looking like what we discussed a few days ago? Since we aren't thinking about the lower level code specific to these operations, the higher level code creates a nice, easy to read, structure. We are drilling down from the top, not piling things up from the bottom.
Also notice that we have disconnected the motion code from what tracks we need to handle. If there are no tracks defined with curve314 sections, then that particular code will not be used, but it doesn't need to be removed. The data determines what code gets executed. The data drives the code.
Basically, from here, you work the track definition and the motion code as best you can. Working one against the other. When you find that you need some sort of information in the motion code that you don't have, you go back to the track definition and see how (and if) it fits in there. First think about whether you can calculate it in some way, before adding it into the definition. We want the track definitions to be as lean as possible. You must convince yourself that what you are about to add to it absolutely must be added. Don't worry too much about that for now. I don't mind if you add more than it needs.
You will also need to do this for the 3D scene creation. Looping over the tracks to create the objects that each one needs, putting it together one section at a time. This might end up being more complicated than what you already have, but it is better in the long run.
Let's also remember that you did solve the problem. You did get an animation up and running that showed what you wanted it to show. Sure, you didn't go about it the way I wanted, but so what? I probably wouldn't have even mentioned it now, except you presented a problem that would have been very easily solved in my method, but difficult in what you created. I saw that as a good opportunity to demonstrate a different design decision where the focus is on the data rather than the processing. I realised early on that I was talking about higher level ideas that you weren't able to see just yet. I was trying to lead you to things that you weren't ready for, so I let you find your own way through it.
We aren't so much concerned with how the track array is going to be accessed (which will just end up being a for loop iterating over the array), but with what data it might contain. The content of the array is critical and we need to make sure that it contains everything we need to do whatever it is we want to do with it. That sounds like a difficult task, but in reality, you just take it one step at a time.
First, you think about what sort of information you need to define a track. Break it down into smaller parts. Break those parts down into smaller parts, if you need to. You can, some-what, do this in conjunction with developing the motion code. Let's give it a quick try:
Looking at the tracks you already have, we ask ourselves "What does a track have?", and the answer is "It has sections".
- Code:
var tracks = [
{
sections: []
}
];
Then we ask "What types of sections are there?" and we answer "There are straight sections and there are curved sections.". So we need a way to define that in our track definition.
- Code:
var tracks = [
{
sections: [
{
type: 'straight'
}
,{
type: 'curve'
}
]
}
];
But wait. That doesn't really cover what we already have. We don't just have a single type of curve, we have 2 different curves. What are we going to do about that? Well, there are a few things we could do to deal with that, but the simplest would be to create another type of section.
- Code:
var tracks = [
{
sections: [
{
type: 'straight'
}
,{
type: 'curve4'
}
,{
type: 'curve314'
}
]
}
];
Note that I am not creating an actual track array here, even though it looks like it (and technically, it is). What I am doing is creating a declaration of what a track array can contain and how it contains it. We wouldn't have a curve4 and curve314 in the same track. Of course we could, but in this particular application, we don't need to. But I just want to show what types of sections are possible.
What else does a track need? How about something that moves along the track? We need a ball.
- Code:
var tracks = [
{
ball: {
}
,sections: [
{
type: 'straight'
}
,{
type: 'curve4'
}
,{
type: 'curve314'
}
]
}
];
With this little bit of knowledge, we can now start to build the motion code a little bit. We now know what types of motion we require, and we can put that into some sort of structure.
- Code:
function processMotion( time )
{
for( var i=0; i<tracks.length; i++ )
{
processMotionForTrack( tracks[i], time );
}
}
function processMotionForTrack( track, time )
{
// TODO figure out what section the ball is in
var section = ...;
// process motion for section
switch( section.type )
{
case 'straight':
processStraightSection( track, section, time );
break;
case 'curve4':
processCurve4Section( track, section, time );
break;
case 'curve314':
processCurve314Section( track, section, time );
break;
}
}
See how the higher level functions are already looking like what we discussed a few days ago? Since we aren't thinking about the lower level code specific to these operations, the higher level code creates a nice, easy to read, structure. We are drilling down from the top, not piling things up from the bottom.
Also notice that we have disconnected the motion code from what tracks we need to handle. If there are no tracks defined with curve314 sections, then that particular code will not be used, but it doesn't need to be removed. The data determines what code gets executed. The data drives the code.
Basically, from here, you work the track definition and the motion code as best you can. Working one against the other. When you find that you need some sort of information in the motion code that you don't have, you go back to the track definition and see how (and if) it fits in there. First think about whether you can calculate it in some way, before adding it into the definition. We want the track definitions to be as lean as possible. You must convince yourself that what you are about to add to it absolutely must be added. Don't worry too much about that for now. I don't mind if you add more than it needs.
You will also need to do this for the 3D scene creation. Looping over the tracks to create the objects that each one needs, putting it together one section at a time. This might end up being more complicated than what you already have, but it is better in the long run.
Re: Animate the PI = 4 experiment
.
Holiday update. Events here have slowed my progress working on a "table array version" of the PI=4 app. I’m still in the initialization section, outputting what you see above, in festive colors. The spinning balls have yet to leave their starting lines and there are no lights. The curved and straight tubes are colored according to the direction of the surface normals, meshNormalMaterial. With global and table variable duplications and string names the table array is a 250 element monster that doesn’t seem to offer any efficiencies. I need to print it to the console so I can be sure my code is indexing the correct values. Can't wait to see how this process plays out.
.
Holiday update. Events here have slowed my progress working on a "table array version" of the PI=4 app. I’m still in the initialization section, outputting what you see above, in festive colors. The spinning balls have yet to leave their starting lines and there are no lights. The curved and straight tubes are colored according to the direction of the surface normals, meshNormalMaterial. With global and table variable duplications and string names the table array is a 250 element monster that doesn’t seem to offer any efficiencies. I need to print it to the console so I can be sure my code is indexing the correct values. Can't wait to see how this process plays out.
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
.
These are dangerous times, filled with uncertainty. All the more reason to wish everyone a Happy New Year and Decade.
As directed, I began working on the table array. Beyond that, I didn’t understand the difference between the table array structure you described and your switch case conditional method code. I recall a desperate moment stretching into days. Looking up - data driven versus object oriented coding – I found mostly discussions centered on minimizing processing time, not much help. Still, as I worked the table I did learn a few things. Starting with the fact that it is difficult to make small changes to a 250 element (give or take) array. After some effort, things began coming together, I found myself accessing the table Array’s three internal parallel data structures using two array pointers: a track starting index, and a number to add to that track starting index.
You indicated that in addition to the animation function I would also need to work on the initialization function. Over the last week I’ve just been working the table and the initialization function, but I think I’ve succeeded in reworking it as you requested. The track and all the items present are positioned by looping through the table.
I’ve begun to look at the animation function, staging the logic. The two curved track balls shown in the image above do not yet move along their curves, but instead maintain their side-by-side formation over the entire 10*r straight track length, no good reason to show a moment later. I'm happy to report I believe I understand your process logic. I couldn’t bring myself to split either the 10*r long straight or circular track into four or five sections during initialization, but I will certainly do so during the animation function processing. Data driven might be better described as an alternative methodology, approach or philosophy. It makes good sense.
.
These are dangerous times, filled with uncertainty. All the more reason to wish everyone a Happy New Year and Decade.
As directed, I began working on the table array. Beyond that, I didn’t understand the difference between the table array structure you described and your switch case conditional method code. I recall a desperate moment stretching into days. Looking up - data driven versus object oriented coding – I found mostly discussions centered on minimizing processing time, not much help. Still, as I worked the table I did learn a few things. Starting with the fact that it is difficult to make small changes to a 250 element (give or take) array. After some effort, things began coming together, I found myself accessing the table Array’s three internal parallel data structures using two array pointers: a track starting index, and a number to add to that track starting index.
You indicated that in addition to the animation function I would also need to work on the initialization function. Over the last week I’ve just been working the table and the initialization function, but I think I’ve succeeded in reworking it as you requested. The track and all the items present are positioned by looping through the table.
I’ve begun to look at the animation function, staging the logic. The two curved track balls shown in the image above do not yet move along their curves, but instead maintain their side-by-side formation over the entire 10*r straight track length, no good reason to show a moment later. I'm happy to report I believe I understand your process logic. I couldn’t bring myself to split either the 10*r long straight or circular track into four or five sections during initialization, but I will certainly do so during the animation function processing. Data driven might be better described as an alternative methodology, approach or philosophy. It makes good sense.
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
Airman wrote:Starting with the fact that it is difficult to make small changes to a 250 element (give or take) array.
Yes, that is difficult, and shows that the array is not structured correctly. The array should be helping you, not hindering. You need to make use of objects more. Objects allow you to create structure, with nice names for values instead of just chucking everything into some random array.
I was trying to avoid this because it would confuse things a bit more than I wanted just yet, but we are going to have to create some classes to keep things separated. Classes are awesome, but a little bit ham-handed in Javascript. When you work in a language like Java, you can't avoid classes because it is an Object-oriented language. However, Javascript is not, and is instead what is known as a prototyping language.
Before we get to that, I wanted to point out that Data-driven programming is not opposed to Object-oriented programming. We can use them both at the same time with no trouble, in fact, they can compliment each other nicely. Which is exactly what we are going to do now.
Object-oriented programming is based on the concept that everything is an object. Before that, we had Procedural programming, where everything was about the procedures (otherwise known as functions, although some languages do make a distinction between them, and to make things even more complicated, there is another paradigm called Functional programming that is different again, but forget about that). Up until now, you have been doing Procedural programming, and we are going to use a hybrid of the Procedural and Object-oriented paradigms.
Classes are the basis of Object-orientation. A Class defines what an Object is. To put it into architectural nomenclature, a Class is a blueprint, where-as an Object is the building made from it. A Class is an idea, where-as an Object is an actual thing. We can create many buildings from the same blueprint, and so it is with Classes. The Class defines what the Objects have, first by declaring their member variables, and secondly by declaring the methods that can operate on them.
In Javascript, we can just use Objects directly, without using Classes, however, a Class makes it a bit easier to keep things separated, as well as reducing potential mistakes. It does that by ensuring that all Objects created by that Class, contain the same variables in them. That may seem trivial, but it is rather important.
I'm out of time, so I'll continue on tomorrow.
Re: Animate the PI = 4 experiment
.
No change in the action/animation.
Message received, fine motivation. Sorry to interrupt, despite your promise of a great follow-up lesson, I felt I needed to correct - as soon as possible - some of the problems you identified by replacing all of my 'difficult' table array index numerals with nice-named variables. If you agree, whew, I’m still anxious to hear more. Maybe ‘class’ is what I think of as the tables' structure?
.
No change in the action/animation.
Message received, fine motivation. Sorry to interrupt, despite your promise of a great follow-up lesson, I felt I needed to correct - as soon as possible - some of the problems you identified by replacing all of my 'difficult' table array index numerals with nice-named variables. If you agree, whew, I’m still anxious to hear more. Maybe ‘class’ is what I think of as the tables' structure?
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
In general, you should not access items in an array using literal numerical indexes. Of course you can, and sometimes you will, but an array is generally meant to be iterated over. While an array can contain any type of items in it, generally, you want the same type of items in it so that you can treat them all the same without caring about what index they are in.
Let's say that you have an array and you are referencing everything from known indexes. You have setup the array such that the first 3 items represent one thing, the next 3 items represent the next thing, and the last 3 items represent the third thing. So you could access items like this:
Now, you realise that you need more information for each thing, so you add a fourth item for each of them. Every single piece of code that references that array now has to be found and changed to reflect the new size and structure of the array. That is not only a pain in the butt, but also an error prone process. So we make use of objects to fix that problem quickly and easily.
Instead of one array that contains all of the data, we create an array that contains 3 objects. Each object then contains whatever they need to, with nice variable names that are descriptive and easy to remember and use.
Now we can use them easily:
Notice how much more descriptive that code is compared to the direct access of an array containing everything? For starters, we can see that the second statement is changing the length value. In the previous version, we didn't know that just from looking at the code itself. We had to know the structure of the array.
So far, we have only used objects, and not classes. Using objects directly is fine and often all you need for simple cases, but when things get complicated, it is best to bring in the heavy hitters, and create some classes. I tend to go directly to classes because that is what I am use to from other languages. It also means things can grow naturally without me having to change the structure of things too much.
So let's change the above example into a class based structure:
But Nevyn, you created a function, not a class? What gives?
Well, this is how Javascript does classes. Strictly speaking, it doesn't do classes at all, it does prototypes (although I haven't actually used a prototype in this example because it doesn't need one yet). What makes it a class is not its declaration, but how you use it. In order to create an object using that function as its class, we use the new keyword, like this:
When Javascript encounters the new keyword, it expects a function to follow it. An object is created and given to the function using the variable name this. It's a bit ham-handed, but it gets the job done.
Even though we are now using a class to create the array items, the code that accesses it remains the same:
You may point out that I am still using literal numerical indexes to access this array, and that is totally correct. This is a trivial example, so I have not gone to any trouble to create something that is iterable. However, for this app, where we are going to create a class for each Track, it will be.
To be continued...
Let's say that you have an array and you are referencing everything from known indexes. You have setup the array such that the first 3 items represent one thing, the next 3 items represent the next thing, and the last 3 items represent the third thing. So you could access items like this:
- Code:
var array = [
1, '#ff00ff', 10,
3, '#f003dd', 4,
1, '#ff0000', 230
];
var color = array[3 + 1];
// or
array[6+2] += 10;
Now, you realise that you need more information for each thing, so you add a fourth item for each of them. Every single piece of code that references that array now has to be found and changed to reflect the new size and structure of the array. That is not only a pain in the butt, but also an error prone process. So we make use of objects to fix that problem quickly and easily.
Instead of one array that contains all of the data, we create an array that contains 3 objects. Each object then contains whatever they need to, with nice variable names that are descriptive and easy to remember and use.
- Code:
var array = [
{ type: 1, color: '#ff00ff', length: 10 },
{ type: 3, color: '#f003dd', length: 4 },
{ type: 1, color: '#ff0000', length: 230 }
];
Now we can use them easily:
- Code:
var color = array[1].color;
array[2].length += 10;
Notice how much more descriptive that code is compared to the direct access of an array containing everything? For starters, we can see that the second statement is changing the length value. In the previous version, we didn't know that just from looking at the code itself. We had to know the structure of the array.
So far, we have only used objects, and not classes. Using objects directly is fine and often all you need for simple cases, but when things get complicated, it is best to bring in the heavy hitters, and create some classes. I tend to go directly to classes because that is what I am use to from other languages. It also means things can grow naturally without me having to change the structure of things too much.
So let's change the above example into a class based structure:
- Code:
// define a class for each item in the array
function Item( type, color, length )
{
this.type = type;
this.color = color;
this.length = length;
}
But Nevyn, you created a function, not a class? What gives?
Well, this is how Javascript does classes. Strictly speaking, it doesn't do classes at all, it does prototypes (although I haven't actually used a prototype in this example because it doesn't need one yet). What makes it a class is not its declaration, but how you use it. In order to create an object using that function as its class, we use the new keyword, like this:
- Code:
var array = [
new Item( 1, '#ff00ff', 10 ),
new Item( 3, '#f003dd', 4),
new Item( 1, '#ff0000', 230)
];
When Javascript encounters the new keyword, it expects a function to follow it. An object is created and given to the function using the variable name this. It's a bit ham-handed, but it gets the job done.
Even though we are now using a class to create the array items, the code that accesses it remains the same:
- Code:
var color = array[1].color;
array[2].length += 10;
You may point out that I am still using literal numerical indexes to access this array, and that is totally correct. This is a trivial example, so I have not gone to any trouble to create something that is iterable. However, for this app, where we are going to create a class for each Track, it will be.
To be continued...
Re: Animate the PI = 4 experiment
.
Status update. As always, thanks for your patient guidance.
Arrays are collections of elements, only the length of the array needs to be defined in some way. The array elements may be objects, numbers, boolean, other arrays or left undefined. Using Arrays allows many associated methods. For example, the trackArrayData.indexOf(‘type’) method makes finding the specific numerical index of the S track data with respect to appropriate ‘string’ element a breeze. These string elements also serve to explain the table data, making the code much easier to understand. In summary, we want a well organized table with appropriate string elements.
I'm enjoying the array lesson. There's plenty more to the subject. I haven't been able to access one data section of the table array, a second colored array comprised of the title 'colorArray2' followed by nine color objects, { magenta: new THREE.Color("hsl(300,100%,50%)") } is the first. I need to learn more about accessing and or processing data, which must come through practice.
Reading your message however, I’m missing something. Why would we need a class (function) to create those track values? I suppose it would make mass producing tracks easier.
.
Status update. As always, thanks for your patient guidance.
Arrays are collections of elements, only the length of the array needs to be defined in some way. The array elements may be objects, numbers, boolean, other arrays or left undefined. Using Arrays allows many associated methods. For example, the trackArrayData.indexOf(‘type’) method makes finding the specific numerical index of the S track data with respect to appropriate ‘string’ element a breeze. These string elements also serve to explain the table data, making the code much easier to understand. In summary, we want a well organized table with appropriate string elements.
- Code:
'type', 'curve4',
'c0', 'strSect', posC.x, posC.y, posC.z,
'c1', 'c4Sect', posC.x + 2*tracks.r, posC.y, posC.z,
'c2', 'c4Sect', posC.x + 3*tracks.r, posC.y, posC.z - tracks.r,
'c3', 'c4Sect', posC.x + 2*tracks.r, posC.y, posC.z - 2*tracks.r,
'c4', 'c4Sect', posC.x + tracks.r, posC.y, posC.z-r,
'c5', 'c4Sect', posC.x + 2*tracks.r + tracks.r * Math.cos(tracks.piRotG), posC.y, posC.z - tracks.r - tracks.r * Math.sin(tracks.piRotG),
'c6', 'c4Sect', posC.x + 2*tracks.r, posC.y, posC.z,
I'm enjoying the array lesson. There's plenty more to the subject. I haven't been able to access one data section of the table array, a second colored array comprised of the title 'colorArray2' followed by nine color objects, { magenta: new THREE.Color("hsl(300,100%,50%)") } is the first. I need to learn more about accessing and or processing data, which must come through practice.
Reading your message however, I’m missing something. Why would we need a class (function) to create those track values? I suppose it would make mass producing tracks easier.
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
You can create functions, or just take advantage of the available methods on an array, to find parts of it and then work relative to that. However, classes (or even just objects without classes) provide a nice, clean way of defining everything related to a specific thing in one place. It keeps all of the relevant code for that thing together and easy to find and easy to manipulate. It also makes that code much easier to understand when you haven't looked at it for a while. Right now, you are comfortable with the structure that you have created with an array, but in a years time, when you haven't looked at this in months, it won't be, and you won't remember that the third element from the index of 'type' is a color, or a length, or some other piece of data.
I haven't gotten to this part about classes yet, but you can declare methods on a class (a method is just a function of a class), and those methods perform operations on an object of that class. Your example using the indexOf method of the Array class is a classic case. This allows the class to hide its internal structure (if you want to), and creates a nice interface for using an object of that class. This is also where prototypes come into it, but we'll get to that shortly.
For example, an array in Javascript is not really like arrays in other languages. It is, in fact, an object, and not an array. There is an Array class (defined by Javascript itself) and any array you actually create, is really an object of the class Array. This allows you to skip indexes in an array. So you could create an array and only add items to indexes 3, 50, and 3124565334234. In nearly all other languages, that would require an array that contains 3124565334234 items. But in Javascript, because it is an object, it only actually has 3 items in that array. Those items use the keys '3', '50' and '3124565334234', respectively. Yes, the numerical indexes you use are actually converted into strings internally. The Array.indexOf method knows this, and takes advantage of it, so a search for the value stored at index 3124565334234 performs very quickly, only really looking at the 3 items.
I know that classes require a bit of a conceptual leap. They require new syntax, and it is easy to get confused. After a while, all of that syntax becomes superfluous. You just see it at a glance, know that it means you have a class, and move on to the important things about that class. You do have to slog through it for a while, though, so I understand any reticence that you may feel. It is worth it, though. I remember learning about classes when I was starting, and it took a few months before I saw the real benefit of them. I initially just saw them as a collection of variables. Sure, they had nice names, and were easy to use, but it wasn't until I really understood how to use methods that I found classes to be a great thing. We may get to that in this app, and the classes I am trying to create for the next post are already hinting at it, so you may see it soon enough. Unfortunately, to see the greatest benefit from them, it requires use of an advanced concept of classes: Inheritance. Which we will get to shortly.
I haven't gotten to this part about classes yet, but you can declare methods on a class (a method is just a function of a class), and those methods perform operations on an object of that class. Your example using the indexOf method of the Array class is a classic case. This allows the class to hide its internal structure (if you want to), and creates a nice interface for using an object of that class. This is also where prototypes come into it, but we'll get to that shortly.
For example, an array in Javascript is not really like arrays in other languages. It is, in fact, an object, and not an array. There is an Array class (defined by Javascript itself) and any array you actually create, is really an object of the class Array. This allows you to skip indexes in an array. So you could create an array and only add items to indexes 3, 50, and 3124565334234. In nearly all other languages, that would require an array that contains 3124565334234 items. But in Javascript, because it is an object, it only actually has 3 items in that array. Those items use the keys '3', '50' and '3124565334234', respectively. Yes, the numerical indexes you use are actually converted into strings internally. The Array.indexOf method knows this, and takes advantage of it, so a search for the value stored at index 3124565334234 performs very quickly, only really looking at the 3 items.
I know that classes require a bit of a conceptual leap. They require new syntax, and it is easy to get confused. After a while, all of that syntax becomes superfluous. You just see it at a glance, know that it means you have a class, and move on to the important things about that class. You do have to slog through it for a while, though, so I understand any reticence that you may feel. It is worth it, though. I remember learning about classes when I was starting, and it took a few months before I saw the real benefit of them. I initially just saw them as a collection of variables. Sure, they had nice names, and were easy to use, but it wasn't until I really understood how to use methods that I found classes to be a great thing. We may get to that in this app, and the classes I am trying to create for the next post are already hinting at it, so you may see it soon enough. Unfortunately, to see the greatest benefit from them, it requires use of an advanced concept of classes: Inheritance. Which we will get to shortly.
Re: Animate the PI = 4 experiment
.
You mentioned trying to create new classes for your next post. Rather than wait for an ‘inheritance’ I’m doing my best at trying to keep up. I purchased a reference book, JavaScript, The Definitive Guide, sixth Edition, by David Flanagan. Close to the beginning - on page 8 of the 1080 page tome, I found some simple class code for 2D point objects that gave me something to play with. I’ve left the original comments but added the straight track marker locations.
I’m currently trying to come up with a generic Point function to determine the angular difference between two points on the unit circle. I’ll need to translate points, while distinguishing between both of the c4 and c314 type track locations, etc. I’m sure I would need to expand Points with additional properties. Doing a web search I see there’s also at least one so-called Point.js on github, where someone made their own Point class and functions available for use, review and development by others. https://github.com/moagrius/Point/blob/master/Point.js
There might even be an existing standard Point class that I’ve mistaken for the new ‘prototype’ above.
.
You mentioned trying to create new classes for your next post. Rather than wait for an ‘inheritance’ I’m doing my best at trying to keep up. I purchased a reference book, JavaScript, The Definitive Guide, sixth Edition, by David Flanagan. Close to the beginning - on page 8 of the 1080 page tome, I found some simple class code for 2D point objects that gave me something to play with. I’ve left the original comments but added the straight track marker locations.
- Code:
// Define a constructor function to initialize a new Point object
function Point(x,y,z) {
this.x = x;
this.y = y;
this.z = z;
}
// Use a constructor function with the keyword "new" to create instances
var s0 = new Point( -2*r, 0, 0 );
var s1 = new Point( 0, 0, 0 );
var s2 = new Point( 2*r, 0, 0 );
var s3 = new Point( 4*r, 0, 0 );
var s4 = new Point( 6*r, 0, 0 );
var s5 = new Point( 2*Math.PI*r, 0, 0 );
var s6 = new Point( 8*r, 0, 0 );
// Define methods for Point objects by assigning them to the prototype
// object associated with the constructor function.
Point.prototype.dist = function() {
return Math.sqrt( // Returns the distance to the origin
this.x * this.x +
this.y * this.y +
this.z * this.z
);
};
// Now the Point object (and all future Point objects) inherits the method dist().
I’m currently trying to come up with a generic Point function to determine the angular difference between two points on the unit circle. I’ll need to translate points, while distinguishing between both of the c4 and c314 type track locations, etc. I’m sure I would need to expand Points with additional properties. Doing a web search I see there’s also at least one so-called Point.js on github, where someone made their own Point class and functions available for use, review and development by others. https://github.com/moagrius/Point/blob/master/Point.js
There might even be an existing standard Point class that I’ve mistaken for the new ‘prototype’ above.
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
.
Update. I’ve delayed posting till I could include some ‘significant’ progress.
Version 2 of the PI=4 Experiment now includes proper ball animations: staying on track, cycloid rolling, and the single rotation the circular track balls make in rotating about their circular track centers. All three balls are hitting their markers ‘simultaneously’. That’s most of the animation function.
In doing so, I believe I've conformed to your top/down idea; at least I used the process motion animation function structure you identified – that is after converting the switch case to if statements. I should mention, other than dT*velocity, I haven’t used any explicit time variable yet. Another difference, instead of passing a variable identifying the current section, I pass the ball's next possible position.
I believe version two’s code has been simplified, but to be perfectly honest I have no idea if it’s improved or not. There’s no denying the able array is a fine addition – it simplifies writing and understanding code. Many thanks for bringing to my attention the simple object type, also known as property:value. That awareness will make a big difference. I've certainly done a lot of reading on these subjects and am almost overwhelmed by amount of things I don't know.
I seem to have lost all my objectivity.
And then I've been thwarted – yet again - in writing a dat.gui with pull-down choices, such as would allow easy selection of piG track motion violations which I’ve previously implemented using checkboxes. You may note the newby test gui, it controls the track time in seconds and a couple of dummy variables. I tried to control the ball materials with a pull-down - no joy. Only the pull-downs don’t work. A good faith, albeit unsuccessful, effort, after three strikes on my part you might consider giving it a look. I’ll clean it up before too long.
Next comes the color change animation, the marker makes just after the ball rolls through. That would change the marker colors in the above image to: the three final track markers would be red, all the rest would be black.
.
Update. I’ve delayed posting till I could include some ‘significant’ progress.
Version 2 of the PI=4 Experiment now includes proper ball animations: staying on track, cycloid rolling, and the single rotation the circular track balls make in rotating about their circular track centers. All three balls are hitting their markers ‘simultaneously’. That’s most of the animation function.
In doing so, I believe I've conformed to your top/down idea; at least I used the process motion animation function structure you identified – that is after converting the switch case to if statements. I should mention, other than dT*velocity, I haven’t used any explicit time variable yet. Another difference, instead of passing a variable identifying the current section, I pass the ball's next possible position.
I believe version two’s code has been simplified, but to be perfectly honest I have no idea if it’s improved or not. There’s no denying the able array is a fine addition – it simplifies writing and understanding code. Many thanks for bringing to my attention the simple object type, also known as property:value. That awareness will make a big difference. I've certainly done a lot of reading on these subjects and am almost overwhelmed by amount of things I don't know.
I seem to have lost all my objectivity.
And then I've been thwarted – yet again - in writing a dat.gui with pull-down choices, such as would allow easy selection of piG track motion violations which I’ve previously implemented using checkboxes. You may note the newby test gui, it controls the track time in seconds and a couple of dummy variables. I tried to control the ball materials with a pull-down - no joy. Only the pull-downs don’t work. A good faith, albeit unsuccessful, effort, after three strikes on my part you might consider giving it a look. I’ll clean it up before too long.
Next comes the color change animation, the marker makes just after the ball rolls through. That would change the marker colors in the above image to: the three final track markers would be red, all the rest would be black.
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
.
Rolling along, the track marker color animations are now working properly.
Progress report. Thanks Cr6. Javascript is – beyond a doubt - a great thing to know; but if you’re like me, it requires a great deal of time and effort to get any good at it. I hope my frustrations aren’t too obvious. Of course it’s worth the effort.
Actually most of the color changes are now opacity animations, maximum or minimum transparency. Nevyn mentioned that alternative with respect to my tying to hide a table below the tracks. Only the circular track blue/red start/stop changes are “color” animations. I know I’m learning.
For some unknown reason, orange is always white.
Still making changes to the color portions of the table array - by incorporating more objects.
I suppose I’ll try looking for a better spacebar pause control next.
.
Rolling along, the track marker color animations are now working properly.
Progress report. Thanks Cr6. Javascript is – beyond a doubt - a great thing to know; but if you’re like me, it requires a great deal of time and effort to get any good at it. I hope my frustrations aren’t too obvious. Of course it’s worth the effort.
Actually most of the color changes are now opacity animations, maximum or minimum transparency. Nevyn mentioned that alternative with respect to my tying to hide a table below the tracks. Only the circular track blue/red start/stop changes are “color” animations. I know I’m learning.
For some unknown reason, orange is always white.
Still making changes to the color portions of the table array - by incorporating more objects.
I suppose I’ll try looking for a better spacebar pause control next.
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
.
More progress with Pause.
Photo-finishes are again possible, PI=4 v2 now has new GUI controls: Pause, Next Frame, Reset and More. Besides using the top right button, one may also Pause using the spacebar. Last verson, the spacebar was very sloppy, pressing the spacebar ‘once’ would create a pause and a frame advance, or a manual advance by holding the spacebar down. Now, with a spacebar press and release, a single Pause action is generated. Each subsequent press advances the scene a single frame.
The animation function is much cleaner than the last version.
The time variable has become very important. I’m currently having some difficulty in implementing ‘Pause at markers’. I see that that function worked well in version 1. Hmmm.
.
More progress with Pause.
Photo-finishes are again possible, PI=4 v2 now has new GUI controls: Pause, Next Frame, Reset and More. Besides using the top right button, one may also Pause using the spacebar. Last verson, the spacebar was very sloppy, pressing the spacebar ‘once’ would create a pause and a frame advance, or a manual advance by holding the spacebar down. Now, with a spacebar press and release, a single Pause action is generated. Each subsequent press advances the scene a single frame.
The animation function is much cleaner than the last version.
The time variable has become very important. I’m currently having some difficulty in implementing ‘Pause at markers’. I see that that function worked well in version 1. Hmmm.
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
.
‘Pause at markers’ is working, the three balls shown here are pausing just after the starting lines. The previous version only paused at four locations: 0, 2*r, 4*r, and 6*r; the balls now also stop at the start( -2*r ), PIg( 2*r + 2*Math.PI*r), and end( total - distNext < dT*velocity ). When pausing, the balls don’t stop exactly at those marker locations, just wherever their frame motion step occurs within the frame distance interval (dT*velocity) just after each marker, or in the last interval before the end marker.
I got tired of messing with several colors and orange always appearing as white, so I changed to the marker colors as shown. The start and end markers are red, all others are green except for the non 2*r PIg marker. As the curved track balls pass the 0 mark switches to red, otherwise, as before, the other markers appear to turn black.
Note that the tracks now appear much nicer. I added a new directional light to the scene which makes a big difference. I think I’ll add point lights to the balls next. I’ll try making the balls and markers a little nicer too.
Objects certainly make more sense to me now, having replaced all threejs standard - new THREE.Vector3 objects - with Point objects. Exactly like Vector3, Point objects are used for positioning track pieces or the balls’ starting points.
Also ColorObjects, with currently unused properties [“position”] and [“color”], I used the [“hSLValue”]) for both initialization and animation.
Another object with several pieces - ballMesh - is copied to each track as needed.
I loaded a bunch of new property: value pairs into what I’ve referred to as my table array. These are used for both initialization and animation.
processMotion and processMotionForTrack work, the code is cleaner.
I’ve done some reading and can almost understand the difference between Parent and child, or the prototype and the object it creates (?). Or prototype chains. All well and good, except I don’t see where other class types should be created here.
Thanks for your constant attention and guidance. This project has clearly benefited by “rewriting the code”, as you suggested; but I’m not at all sure I accomplished what you’d hoped or even gotten the gist of the table array. Have I missed something important? You mentioned possible future classes, and inheritance?
.
‘Pause at markers’ is working, the three balls shown here are pausing just after the starting lines. The previous version only paused at four locations: 0, 2*r, 4*r, and 6*r; the balls now also stop at the start( -2*r ), PIg( 2*r + 2*Math.PI*r), and end( total - distNext < dT*velocity ). When pausing, the balls don’t stop exactly at those marker locations, just wherever their frame motion step occurs within the frame distance interval (dT*velocity) just after each marker, or in the last interval before the end marker.
I got tired of messing with several colors and orange always appearing as white, so I changed to the marker colors as shown. The start and end markers are red, all others are green except for the non 2*r PIg marker. As the curved track balls pass the 0 mark switches to red, otherwise, as before, the other markers appear to turn black.
Note that the tracks now appear much nicer. I added a new directional light to the scene which makes a big difference. I think I’ll add point lights to the balls next. I’ll try making the balls and markers a little nicer too.
Objects certainly make more sense to me now, having replaced all threejs standard - new THREE.Vector3 objects - with Point objects. Exactly like Vector3, Point objects are used for positioning track pieces or the balls’ starting points.
Also ColorObjects, with currently unused properties [“position”] and [“color”], I used the [“hSLValue”]) for both initialization and animation.
Another object with several pieces - ballMesh - is copied to each track as needed.
I loaded a bunch of new property: value pairs into what I’ve referred to as my table array. These are used for both initialization and animation.
processMotion and processMotionForTrack work, the code is cleaner.
I’ve done some reading and can almost understand the difference between Parent and child, or the prototype and the object it creates (?). Or prototype chains. All well and good, except I don’t see where other class types should be created here.
Thanks for your constant attention and guidance. This project has clearly benefited by “rewriting the code”, as you suggested; but I’m not at all sure I accomplished what you’d hoped or even gotten the gist of the table array. Have I missed something important? You mentioned possible future classes, and inheritance?
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
Creating the Point class was a good exercise. However, as you have pointed out, the THREE.Vector3 class already does all of that and more, with the advantage of also being the class that needs to be used by THREE. It doesn't help this application or clean up the code.
The classes that I think need to be developed are Track, Section, and Ball. A Track has an array of Sections (where each Section is between 2 markers, or it is straight vs curved, either way will work with the latter probably being a bit easier), and a Ball. The Section class then has 2 sub-classes: StraightSection and CurvedSection.
The Section class, which is abstract (i.e. it does not get instantiated, but is sub-classed), has a method such as applyMotion( ball, time ) which will return a number representing the amount of time remaining after the motion has been applied, which can be 0, but may also be >0. If >0, then the next Section should be used to apply more motion.
You could also sub-class CurvedSection with classes like Pi4Section and Pi314Section, so that each of those can apply the appropriate motion. So the code currently in the processMotionForTrack* functions should be put into those classes. This should be done in some way such that the majority of the code (common to both curved sections) is in the CurvedSection class, and the sub-classes just provide what is different for their respective curves.
A Track has no idea that it is straight or curved or Pi=4 or Pi=3.14 (although it may have methods to figure that out from the Sections that it has in it, if you need that). It only knows that it has Sections and a Ball. It will have an applyMotion( time ) method (which does not return a number since it has to use up the time given to it), and that method will take care of finding the current Section, applying its motion, and moving on to the next Section if it needs to (in the amount of time it is given for this frame).
The Ball class will contain the Object3D to represent that ball, and may have other properties to help with other calculations (such as the total length it has moved through all Sections which Track can use to find the current Section that the Ball is in).
The visual nodes for the tracks should be created in these classes too. The Section class will have some method such as createSceneObjects which creates the appropriate Object3D nodes to represent it. The Track class (which has a similar method) will take care of putting them together and placing them in the correct positions so that they connect to each other. That may be a bit tricky, and is why I think it is easier to use straight vs curved, rather than each Section being between 2 markers. If the complete circle is represented by one node, then all connections are on the straight line and you don't need to connect 2 parts of a curve where it gets messy.
I was starting to think that I had led you down a complicated path, but you have made some progress without me (which is great), so it is probably worth continuing. See if you can take my words and convert them into some classes. I recommend that each class be put into its own JS file, rather than in the main HTML file, as it will be easier to switch between them as you work. Then import them into the HTML file with a script tag.
The classes that I think need to be developed are Track, Section, and Ball. A Track has an array of Sections (where each Section is between 2 markers, or it is straight vs curved, either way will work with the latter probably being a bit easier), and a Ball. The Section class then has 2 sub-classes: StraightSection and CurvedSection.
The Section class, which is abstract (i.e. it does not get instantiated, but is sub-classed), has a method such as applyMotion( ball, time ) which will return a number representing the amount of time remaining after the motion has been applied, which can be 0, but may also be >0. If >0, then the next Section should be used to apply more motion.
You could also sub-class CurvedSection with classes like Pi4Section and Pi314Section, so that each of those can apply the appropriate motion. So the code currently in the processMotionForTrack* functions should be put into those classes. This should be done in some way such that the majority of the code (common to both curved sections) is in the CurvedSection class, and the sub-classes just provide what is different for their respective curves.
A Track has no idea that it is straight or curved or Pi=4 or Pi=3.14 (although it may have methods to figure that out from the Sections that it has in it, if you need that). It only knows that it has Sections and a Ball. It will have an applyMotion( time ) method (which does not return a number since it has to use up the time given to it), and that method will take care of finding the current Section, applying its motion, and moving on to the next Section if it needs to (in the amount of time it is given for this frame).
The Ball class will contain the Object3D to represent that ball, and may have other properties to help with other calculations (such as the total length it has moved through all Sections which Track can use to find the current Section that the Ball is in).
The visual nodes for the tracks should be created in these classes too. The Section class will have some method such as createSceneObjects which creates the appropriate Object3D nodes to represent it. The Track class (which has a similar method) will take care of putting them together and placing them in the correct positions so that they connect to each other. That may be a bit tricky, and is why I think it is easier to use straight vs curved, rather than each Section being between 2 markers. If the complete circle is represented by one node, then all connections are on the straight line and you don't need to connect 2 parts of a curve where it gets messy.
I was starting to think that I had led you down a complicated path, but you have made some progress without me (which is great), so it is probably worth continuing. See if you can take my words and convert them into some classes. I recommend that each class be put into its own JS file, rather than in the main HTML file, as it will be easier to switch between them as you work. Then import them into the HTML file with a script tag.
Re: Animate the PI = 4 experiment
.
Luckily, you'e provided several code examples. For example, I did a review of the many scripts included in CPIM Testbed. As a first step, please consider the following three objects: aTrackC, aSectionC0, aBallC. These are sample objects that should be created by prototype track, section and ball class functions.
Might these object arrays be acceptable? If so I’ll try creating their prototype next.
I've been under the impression table arrays should be read only. Or would it be appropriate including changing time/distance variables in these objects?
.
I was stunned when I first read that. Still am. I admire your patience Nevyn. You’ve been spending quite some time providing specific directions that I've yet to grasp. Separate scripts tabs are a whole nuther order of sophistication.I recommend that each class be put into its own JS file, rather than in the main HTML file, as it will be easier to switch between them as you work. Then import them into the HTML file with a script tag.
Luckily, you'e provided several code examples. For example, I did a review of the many scripts included in CPIM Testbed. As a first step, please consider the following three objects: aTrackC, aSectionC0, aBallC. These are sample objects that should be created by prototype track, section and ball class functions.
- Code:
var aTrackC = {
trackRadius: r,
tubeRadius: tRadius,
numTrkParts: 2,
trackCParts1: 'straightTwo', position: posC.x + tracks.r, posC.y, posC.z,
trackCParts2: 'circleTrack', position: posC.x + 2*tracks.r, posC.y, posC.z - tracks.r,
trackCSections: c0, c1, c2, c3, c4, c5, c6,
startTime: timeStart,
currentTime: timeCur,
};
var aSectionC0 = { // c0 for pi4 track. s0 for straight, g0 for pig
partOfTrack: piKTrack,
sectName: c0,
sectType: 'straight',
start: c0.x, c0.y, c0.z,
stop: c1.x, c1.y, c1.z,
markerAngle: Math.PI/2,
};
var aBallC = {
radius: bRadius,
trkTime: trTimeInSecs,
dT: 1/60,
velocity: 10*tracks.r/trTimeInSecs,
ballVelocity: 10*r/trTimeInSecs,
nextDist: dT * velocity,
paused: false,
startPosition: c0.x, c0.y, c0.z,
currentPosition: curPos.x, curPos.y, curPos.z,
nextPosition: nexPos.x, nexPos.y, nexPos.z,
distanceTraveled: sum,
rollAngleZ: angleZ,
trkAngleY: angleY,
};
Might these object arrays be acceptable? If so I’ll try creating their prototype next.
I've been under the impression table arrays should be read only. Or would it be appropriate including changing time/distance variables in these objects?
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
There is nothing inherently read-only about an array. It comes down to the specific array you are using and whether you want it to be read-only or not. In this app, the array of Tracks would be read-only (because there is no need to change them), the array of Sections in each Track would be read-only. However, plenty of other properties in these objects may be altered during runtime.
Those object definitions are a start. I don't think they look quite right yet, and some of them have data that they shouldn't. There is no need for a Track to know a radius (although it may use one, and I think it is more useful for a Section than a Track). The Section object has a property called partOfTrack, and this should not exist. A Section should not need to know what Track it is in. It should not know about Tracks at all. This is called 'Separation of Concerns', and it means that a class should never know what uses it (there are exceptions, but few), and should only know as little as possible about what it itself uses. Basically, you want a class to be as flexible as possible, so it should take care of itself as much as it can. When other classes know too much about it, it becomes very difficult to change any of those classes. They become too intertwined. Separate and Isolate is a good design philosophy, there are times when it isn't, but you will know them when you reach them because you can't make it work any other way.
What you are doing is trying to manipulate your existing code into classes, but this is not really a good way to go. You want to create the class structure first, create some methods (but with no code in them yet) that reflect what that class needs to do (which I have outlined above, a bit). When you can see how the classes fit together and what methods they are going to use from each other, then you fill in the methods with the code based on your existing code. You may find that you need slightly different code now, so think about the code generically. Think about what it is that that code is doing, and how you now want it to work based on the classes. Then re-implement it in those classes. Your mind will keep telling you that 'the existing code works, so we don't want to lose it or change it', but you need to shut that down. It is difficult, I know. Especially when you aren't comfortable with programming yet.
Those object definitions are a start. I don't think they look quite right yet, and some of them have data that they shouldn't. There is no need for a Track to know a radius (although it may use one, and I think it is more useful for a Section than a Track). The Section object has a property called partOfTrack, and this should not exist. A Section should not need to know what Track it is in. It should not know about Tracks at all. This is called 'Separation of Concerns', and it means that a class should never know what uses it (there are exceptions, but few), and should only know as little as possible about what it itself uses. Basically, you want a class to be as flexible as possible, so it should take care of itself as much as it can. When other classes know too much about it, it becomes very difficult to change any of those classes. They become too intertwined. Separate and Isolate is a good design philosophy, there are times when it isn't, but you will know them when you reach them because you can't make it work any other way.
What you are doing is trying to manipulate your existing code into classes, but this is not really a good way to go. You want to create the class structure first, create some methods (but with no code in them yet) that reflect what that class needs to do (which I have outlined above, a bit). When you can see how the classes fit together and what methods they are going to use from each other, then you fill in the methods with the code based on your existing code. You may find that you need slightly different code now, so think about the code generically. Think about what it is that that code is doing, and how you now want it to work based on the classes. Then re-implement it in those classes. Your mind will keep telling you that 'the existing code works, so we don't want to lose it or change it', but you need to shut that down. It is difficult, I know. Especially when you aren't comfortable with programming yet.
Re: Animate the PI = 4 experiment
.
Only the ball knows where it’s at, I think we agree. I’ve been adding dT*velocity distance increments to the balls’ current position, while that’s true it occurs to me that the distances traveled depend entirely on the ball radius and number of z-axis rotations (rolls) it experiences.
The balls’ paths are either left, straight, or right ahead, so I suppose I can work with the notion of a track section not necessarily pinned to any specific location in the larger track. It may be more difficult, but I would prefer stringing 6 sections together for ‘practical’ reasons. Two animations, 1.marker color changes as the balls pass, and 2. balls pausing at markers, make it necessary to monitor the balls’ position with respect to the six section endpoints. Of course I’d differ to your better judgement.
Here’s a second stab at defining the section, track and ball ‘classes’. I seem to be adding all the motion variables to the ball – is that ok? Does each ball then behave independently? It seems wrong to me.
Putting it mildly, I’m having great difficulty following your instructions, embarrassed by how much I’ve slowed down – wondering about brain degeneracy. Note, I’m a believer, utilizing arrays of grouped objects and their methods, also known as classes, provide the best coding efficiencies. JavaScript is, after all, a prototype based language – as if I knew what that meant. The big picture, ‘prototypes’ and extra scripts have given me catatonic fits. As you said, my main mental hangup is a lack of experience knowing the logical solutions are there. The prototype/inheritance structure will likely provide ample means for a solution.
Next on my list is to read and follow along doing the console excersizes, for:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
.
Only the ball knows where it’s at, I think we agree. I’ve been adding dT*velocity distance increments to the balls’ current position, while that’s true it occurs to me that the distances traveled depend entirely on the ball radius and number of z-axis rotations (rolls) it experiences.
The balls’ paths are either left, straight, or right ahead, so I suppose I can work with the notion of a track section not necessarily pinned to any specific location in the larger track. It may be more difficult, but I would prefer stringing 6 sections together for ‘practical’ reasons. Two animations, 1.marker color changes as the balls pass, and 2. balls pausing at markers, make it necessary to monitor the balls’ position with respect to the six section endpoints. Of course I’d differ to your better judgement.
Here’s a second stab at defining the section, track and ball ‘classes’. I seem to be adding all the motion variables to the ball – is that ok? Does each ball then behave independently? It seems wrong to me.
- Code:
function section() // Creating section c01
{
new Item( section.name: c01 ), // c00-c05, s00-s05, g00-g05
new Item( sectType: 'curvedPi4' ), // piKTrack, curvePiK, curvePiG
new Item( startPos: c0.x, c0.y, c0.z ), // c0-c5.x, s0-s5.y, g0-g5.z
new Item( startAngle: math.PI/2 ),
new Item( endPos: c1.x, c1.y, c1.z, ),
new Item( endAngle: 0 ),
new Item( gLength: 2*math.PI*r/4 ),
new Item( kDistance: 2*r ),
};
function track()
{
new Item( track.type: 'curvedPi4' ), // piKTrack, curvePiK, curvePiG
new Item( track.radius: r ),
new Item( track.tubeRadius: ballRadius ),
new Item( track.numTrkParts: 2 ),
new Item( track.trkPart1: 'straightTwo' ),
new Item( track.posTrkPart1: posC.x + tracks.r, posC.y, posC.z ),
new Item( track.trkPart2: 'circleTrack', position );
new Item( track.posTrkPart2: posC.x + 2*tracks.r, posC.y, posC.z - tracks.r ),
new Item( track.sect0: c00 ), // install markers using the section components.
new Item( track.sect1: c01 ),
new Item( track.sect2: c02 ),
new Item( track.sect3: c03 ),
new Item( track.sect4: c04 ),
new Item( track.sect5: c05 ),
new Item( track.sect6: c06 ),
);
function ball()
{
new Item( ball.trkTime: trTimeInSecs ),
new Item( ball.dT: 1/60 ),
new Item( ball.ballVelocity: 10*r/trTimeInSecs ),
new Item( ball.paused: false ),
new Item( ball.startPosition: c0.x, c0.y, c0.z ),
new Item( ball.radius: bRadius ),
new Item( ball.trackAngleZ: angleZ ),
new Item( ball.rollAngleY: angleY ),
new Item( ball.currentPosition: curPos.x, curPos.y, curPos.z ),
new Item( ball.distanceTraveled: distanceTraveled),
new Item( ball.nextDist: dT * velocity ),
new Item( ball.nextPosition: nexPos.x, nexPos.y, nexPos.z ),
};
Putting it mildly, I’m having great difficulty following your instructions, embarrassed by how much I’ve slowed down – wondering about brain degeneracy. Note, I’m a believer, utilizing arrays of grouped objects and their methods, also known as classes, provide the best coding efficiencies. JavaScript is, after all, a prototype based language – as if I knew what that meant. The big picture, ‘prototypes’ and extra scripts have given me catatonic fits. As you said, my main mental hangup is a lack of experience knowing the logical solutions are there. The prototype/inheritance structure will likely provide ample means for a solution.
Next on my list is to read and follow along doing the console excersizes, for:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
Yes, the balls are the only things that move, so it makes perfect sense that they are going to have motion based data. Each ball is independent of the others. They can't be linked because they can reach the end of their respective tracks before the others.
Even though the balls contain most of the motion data, a Section should (or at least it may) store its length, so that it can be used for quick calculations. For example, here is a method that could be on the Track class that finds the current Section given the Balls distance traveled.
That is backwards. The rotation of the Ball should be based on the distance it travels. The distance it travels in a given frame is totally dependent on the time that it has, and its velocity.
I think the markers should be separate from the Sections. They could be another array of the Track class. Create a Marker class that stores the distance along the track that it is at (along with its Object3D node). Don't worry about stopping at the markers for now. That can be added back in once the motions are working. If we want that at all.
While it is only a convention, and not necessary, we generally make classes use title-case names, and methods and variables use lower-case (camel-case to be more precise). It makes it easy to know what is a class and what is a variable or method/function.
There is no need for the Item class that you are using above. They should be properties of the class.
However, that code is creating a specific section, when it shouldn't be. Well, that is not strictly correct, it is creating a specific section, but you can't use literal values like you are here. Any data that is specific to this particular section needs to be passed in as a parameter or assigned after the object is created.
It also contains angular data which is not relevant to all Section classes, only CurvedSection classes. I don't see the need for a name, type or start/end positions either. The type is now handled by the class being used to create that section and we won't need to know it once all motion is handled in methods of these classes, because each class understands its own motion. Separation and isolation.
Notice how the KinematicSection and GeometricSection classes calculate different lengths to pass to the CurvedSection class, which then passes it on to the Section class so that it can be stored in the length property.
Don't worry about slowing down, that just means you are learning something.
Even though the balls contain most of the motion data, a Section should (or at least it may) store its length, so that it can be used for quick calculations. For example, here is a method that could be on the Track class that finds the current Section given the Balls distance traveled.
- Code:
Track.prototype.findCurrentSectionIndex = function()
{
var d = 0;
for( var i=0; i<this.sections.length; i++ )
{
d += this.sections[i].length;
if( d > this.ball.distance )
{
return i;
}
}
return 0;
}
Airman wrote:I’ve been adding dT*velocity distance increments to the balls’ current position, while that’s true it occurs to me that the distances traveled depend entirely on the ball radius and number of z-axis rotations (rolls) it experiences.
That is backwards. The rotation of the Ball should be based on the distance it travels. The distance it travels in a given frame is totally dependent on the time that it has, and its velocity.
I think the markers should be separate from the Sections. They could be another array of the Track class. Create a Marker class that stores the distance along the track that it is at (along with its Object3D node). Don't worry about stopping at the markers for now. That can be added back in once the motions are working. If we want that at all.
While it is only a convention, and not necessary, we generally make classes use title-case names, and methods and variables use lower-case (camel-case to be more precise). It makes it easy to know what is a class and what is a variable or method/function.
There is no need for the Item class that you are using above. They should be properties of the class.
- Code:
function Section() // Creating section c01
{
this.name = c01; // c00-c05, s00-s05, g00-g05
this.sectType = 'curvedPi4'; // piKTrack, curvePiK, curvePiG
this.startPos = c0.x, c0.y, c0.z; // c0-c5.x, s0-s5.y, g0-g5.z
this.startAngle = math.PI/2;
this.endPos = c1.x, c1.y, c1.z;
this.endAngle = 0;
this.gLength = 2*math.PI*r/4;
this.kDistance = 2*r;
};
However, that code is creating a specific section, when it shouldn't be. Well, that is not strictly correct, it is creating a specific section, but you can't use literal values like you are here. Any data that is specific to this particular section needs to be passed in as a parameter or assigned after the object is created.
It also contains angular data which is not relevant to all Section classes, only CurvedSection classes. I don't see the need for a name, type or start/end positions either. The type is now handled by the class being used to create that section and we won't need to know it once all motion is handled in methods of these classes, because each class understands its own motion. Separation and isolation.
- Code:
function Section( length ) // Creating section, this is an abstract class, so it won't be created directly
{
this.length = length;
};
// create the prototype to the Section class
Section.prototype = Object.create( {} );
function StraightSection( length ) // Creating straight section
{
// call the Section constructor so we get all of its properties
Section.call( this, length );
};
// create the prototype for the StraightSection class which extends Section
StraightSection.prototype = Object.create( Section.prototype );
function CurvedSection( radius, length ) // Creating curved section
{
// call the Section constructor so we get all of its properties
Section.call( this, length );
this.radius = radius;
};
// create the prototype for the CurvedSection class which extends Section
CurvedSection.prototype = Object.create( Section.prototype );
function KinematicSection( radius ) // Creating curved section based on kinematics
{
// call the CurvedSection constructor so we get all of its properties
CurvedSection.call( this, radius, 8*radius );
};
// create the prototype for the Pi4Section class which extends CurvedSection
KinematicSection.prototype = Object.create( CurvedSection.prototype );
function GeometricSection( radius ) // Creating curved section based on geometry
{
// call the CurvedSection constructor so we get all of its properties
CurvedSection.call( this, radius, 2*Math.PI*radius );
};
// create the prototype for the GeometricSection class which extends CurvedSection
GeometricSection.prototype = Object.create( CurvedSection.prototype );
Notice how the KinematicSection and GeometricSection classes calculate different lengths to pass to the CurvedSection class, which then passes it on to the Section class so that it can be stored in the length property.
Don't worry about slowing down, that just means you are learning something.
Re: Animate the PI = 4 experiment
.
The Section Object prototype chain.
Thanks for the Section code Nevyn. I even copied it with pencil and paper so as not to miss a thing, and then make a picture of it. Section as an object function and its prototype along with the descendants and their prototypes is a fantastic idea that would never have occurred to me; denizens in object space.
I’ve continued reviewing more references and a few youtube tutorial videos on js object oriented programming.
Mozilla’s “Inheritance and the prototype chain” https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain, included information on the Object.create method and syntax that seemed to be closest to your Section code. The videos certainly gave a flavor for the code versatility, but they are using simple examples without animation. I have a much better idea what I’m up against.
Still mostly just learning, I’ve stripped the code down to the main functions and a spinning ball for the start of version 3.
.
The Section Object prototype chain.
Thanks for the Section code Nevyn. I even copied it with pencil and paper so as not to miss a thing, and then make a picture of it. Section as an object function and its prototype along with the descendants and their prototypes is a fantastic idea that would never have occurred to me; denizens in object space.
I’ve continued reviewing more references and a few youtube tutorial videos on js object oriented programming.
Mozilla’s “Inheritance and the prototype chain” https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain, included information on the Object.create method and syntax that seemed to be closest to your Section code. The videos certainly gave a flavor for the code versatility, but they are using simple examples without animation. I have a much better idea what I’m up against.
Still mostly just learning, I’ve stripped the code down to the main functions and a spinning ball for the start of version 3.
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
I was thinking about creating a class diagram like that, glad you did it for yourself. That sort of stuff really helps you keep everything straight.
The code I wrote above is just the hierarchy, with a few obvious properties. The real fun starts when you write some methods for these classes. Then you begin to see how to break a solution down into smaller parts and distribute them among the classes. When this is done right, the amount of required code is usually smaller and more maintainable.
I mentioned some methods a few posts ago. Have a look at that again and see if you can understand how it fits into this class hierarchy. Add in those method signatures (which means just the function name and possible parameters, no actual code in them yet) and think about what that method means to each particular class.
Note that you will need to create some abstract methods, which just means that they have the signature, but no code in them and they never will have code in them. The sub-classes then override the methods to provide implementations that work for that particular class. Here's an example:
See how Animal is a generic idea, so we can't return a sound for it, since every different type of animal makes a different sound. However, Cat and Dog are specific animals and we know what they sound like, so we can return one. Cat and Dog override the makeSound method to provide the specific sound for that class. Because of the way the prototype hierarchy is searched for a method, the Cat and Dog implementations will be found first, so any object of those classes will use the correct one.
Now assume that we added another class for Fish, which don't make a sound (I don't know if that is strictly correct, but it is in this example). The Fish class would not override the makeSound method, but we can still call it, because it inherits it from Animal. The methods defined on the parent classes effectively define the interface to it. They define the contract for how to use it, while the concrete classes actually provide the implementations.
The parent classes can also contain methods that call other methods of that class, even though that parent class doesn't actually provide an implementation. Here's an example:
If we then create a Dog and a Cat object and call the describe method on each of them, they will return 'This animal makes the sound Woof.' and 'This animal makes the sound Meow.' respectively. When used this way, the makeSound method is called a Hook Method, because it creates something that sub-classes can hook into. This is one way to put common code into the parent classes while still providing differences for specific sub-classes.
In general, you should try to put as much code into the parent classes as possible, while that code still makes sense for that class. That is, you never do anything Cat specific in the Animal class, but if you have some code that applies to any animal, then it should be in the Animal class. Make use of hook methods and properties to get the code to work while allowing the sub-classes to provide their own special content for it.
The code I wrote above is just the hierarchy, with a few obvious properties. The real fun starts when you write some methods for these classes. Then you begin to see how to break a solution down into smaller parts and distribute them among the classes. When this is done right, the amount of required code is usually smaller and more maintainable.
I mentioned some methods a few posts ago. Have a look at that again and see if you can understand how it fits into this class hierarchy. Add in those method signatures (which means just the function name and possible parameters, no actual code in them yet) and think about what that method means to each particular class.
Note that you will need to create some abstract methods, which just means that they have the signature, but no code in them and they never will have code in them. The sub-classes then override the methods to provide implementations that work for that particular class. Here's an example:
- Code:
function Animal()
{
}
Animal.prototype = Object.create( {} );
// An abstract method.
// This allows us to always call the makeSound method on any Animal sub-class, even if it doesn't provide one of its own.
Animal.prototype.makeSound = function()
{
return null;
};
function Cat()
{
Animal.call( this );
}
Cat.prototype = Object.create( Animal.prototype );
Cat.prototype.makeSound = function()
{
return 'Meow';
};
function Dog()
{
Animal.call( this );
}
Dog.prototype = Object.create( Animal.prototype );
Dog.prototype.makeSound = function()
{
return 'Woof';
};
See how Animal is a generic idea, so we can't return a sound for it, since every different type of animal makes a different sound. However, Cat and Dog are specific animals and we know what they sound like, so we can return one. Cat and Dog override the makeSound method to provide the specific sound for that class. Because of the way the prototype hierarchy is searched for a method, the Cat and Dog implementations will be found first, so any object of those classes will use the correct one.
Now assume that we added another class for Fish, which don't make a sound (I don't know if that is strictly correct, but it is in this example). The Fish class would not override the makeSound method, but we can still call it, because it inherits it from Animal. The methods defined on the parent classes effectively define the interface to it. They define the contract for how to use it, while the concrete classes actually provide the implementations.
The parent classes can also contain methods that call other methods of that class, even though that parent class doesn't actually provide an implementation. Here's an example:
- Code:
...
Animal.prototype.describe = function()
{
var sound = this.makeSound();
if( sound != null )
{
return 'This animal makes the sound ' + sound + '.';
}
else
{
return 'This animal does not make a sound.';
}
}
...
If we then create a Dog and a Cat object and call the describe method on each of them, they will return 'This animal makes the sound Woof.' and 'This animal makes the sound Meow.' respectively. When used this way, the makeSound method is called a Hook Method, because it creates something that sub-classes can hook into. This is one way to put common code into the parent classes while still providing differences for specific sub-classes.
In general, you should try to put as much code into the parent classes as possible, while that code still makes sense for that class. That is, you never do anything Cat specific in the Animal class, but if you have some code that applies to any animal, then it should be in the Animal class. Make use of hook methods and properties to get the code to work while allowing the sub-classes to provide their own special content for it.
Re: Animate the PI = 4 experiment
.
Prototype class or not?
I think I’ve read and done enough console examples for the time being. Implementing a ball is turning out to be more difficult. You indicated that
Ok, here’s my latest attempt at creating the Ball Class.
The Ball class prototypal class/hierarchy seems to “work” properly – according to my console, with SBall and prototype properties. Unfortunately, including the function createAndPlaceBall within the class results in no visual output.
How does one create a visual node from within a class?
.
Prototype class or not?
I think I’ve read and done enough console examples for the time being. Implementing a ball is turning out to be more difficult. You indicated that
“the visual nodes for the tracks should be created in these classes too.”
Ok, here’s my latest attempt at creating the Ball Class.
- Code:
function Ball ( name, x, y, z ) // name: "straight", "kinematic" or "geometric", and starting position
{
this.name = name;
this.radius = tracksData.bRadius;
this.ballVelocity = 10*r/trTimeInSecs;
this.createAndPlaceBall = function()
{
ballMesh = new THREE.Mesh;
sphereGeom = new THREE.SphereGeometry( tracksData.bRadius, 12, 12 );
sphereMat = new THREE.MeshBasicMaterial( { color: 'yellow', wireframe: true } );
sphere = new THREE.Mesh( sphereGeom, sphereMat );
ballMesh.add( sphere );
ballMesh.position.set( x, y, z );
scene.add( ballMesh );
}
};
// And in the init function
Ball.prototype = Object.create( {} );
var SBall = new Ball("straight", tracksData.sTkStart.x, 0, 0 );
SBall.prototype = Ball.prototype;
console.log(SBall);
The Ball class prototypal class/hierarchy seems to “work” properly – according to my console, with SBall and prototype properties. Unfortunately, including the function createAndPlaceBall within the class results in no visual output.
How does one create a visual node from within a class?
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
You are referring to global variables a lot in this bit of code. Now, that isn't always a bad thing, but it should be avoided as much as possible. The main exception to that is constants. A constant is just a global variable that you choose not to alter at any point in the program, but it is conceptually different. A constant is meant to be shared in this way (and we name them with all upper-case letters to denote that in the program, although that is just a convention).
However, you are referring to data in tracksData and trTimeInSecs and r (even though you have the radius from another source). These should be parameters to the constructor. They actually should be slightly different values, too. Passing in the radius is good, but using trTimeInSecs is not. Just pass in the velocity and calculate what you want it to be before you call the constructor (which means it can also be shared amongst all balls, allowing 1 calculation instead of 3).
You have created a function, but have done it in the wrong way. It is legal, but not efficient, and does not use the prototype so we lose the benefits of that. By creating functions in the constructor, every time you call that constructor a new function gets created. Every object of that class now has its own version of that function, even though they are all the same. If it was created on the prototype, then only 1 instance of that function will ever exist, and all objects will use it.
That function is a bit of a mess, too. Firstly, you are referring to variables that have not been declared. Throw in a few var declarations. This may not seem like a big deal, because the code will still work, but it can cause headaches in some circumstances. This is because of an advanced concept called 'Closures', which I won't explain too much, but the JS compiler will look for those variables outside of this function. If it happens to find one, it will use it. This can cause variables to be changed when you don't want them to be. These can even be variables in other code, not your own. For example, suppose THREE had a global variable called ballMesh, then this code would find that and use it. I know THREE doesn't want you to use it, so this may cause some serious problems that are very difficult to track down.
It seems you are creating ballMesh, but it doesn't have parentheses, so I doubt that is going to work (the JS compiler might actually still do it because you have used the new keyword, so it knows your intent, but it is still bad practice to rely on that). I also don't think it actually wants to be a Mesh. It looks like it should be a Group instead, and it may not be needed at all.
The scene should be passed in as a parameter, not referenced as a global variable. It also doesn't need to be a Scene object, it could be a Group, and it is up to the calling code to decide what it wants it to be, not the Ball class. Both Scene and Group have an add method, so both will work in this circumstance.
The creation of the geometry is referring to tracksData.bRadius, when it should be using this.radius. The geometry is also a prime candidate for being a constant (which you define directly above the Ball class, and only use it for the Ball class).
I actually wouldn't even pass in the scene (or group), I would store the object as a property of this class. Track and Section are going to alter the position of this ball, so they need a way to find the node. In this instance, in spite of the fact that I explicitly said to do it with a function, I would just create the node in the constructor.
Lastly, this function/method should not be placing the ball. It has no idea about Tracks or Sections, so it has no idea of where it should be placed. The Track class should take care of that once the Ball is created.
However, you are referring to data in tracksData and trTimeInSecs and r (even though you have the radius from another source). These should be parameters to the constructor. They actually should be slightly different values, too. Passing in the radius is good, but using trTimeInSecs is not. Just pass in the velocity and calculate what you want it to be before you call the constructor (which means it can also be shared amongst all balls, allowing 1 calculation instead of 3).
You have created a function, but have done it in the wrong way. It is legal, but not efficient, and does not use the prototype so we lose the benefits of that. By creating functions in the constructor, every time you call that constructor a new function gets created. Every object of that class now has its own version of that function, even though they are all the same. If it was created on the prototype, then only 1 instance of that function will ever exist, and all objects will use it.
- Code:
function Ball( radius, velocity )
{
this.radius = radius;
this.velocity = velocity;
}
Ball.prototype = Object.create( {} );
Ball.prototype.createAndPlaceBall = function()
{
...
}
That function is a bit of a mess, too. Firstly, you are referring to variables that have not been declared. Throw in a few var declarations. This may not seem like a big deal, because the code will still work, but it can cause headaches in some circumstances. This is because of an advanced concept called 'Closures', which I won't explain too much, but the JS compiler will look for those variables outside of this function. If it happens to find one, it will use it. This can cause variables to be changed when you don't want them to be. These can even be variables in other code, not your own. For example, suppose THREE had a global variable called ballMesh, then this code would find that and use it. I know THREE doesn't want you to use it, so this may cause some serious problems that are very difficult to track down.
It seems you are creating ballMesh, but it doesn't have parentheses, so I doubt that is going to work (the JS compiler might actually still do it because you have used the new keyword, so it knows your intent, but it is still bad practice to rely on that). I also don't think it actually wants to be a Mesh. It looks like it should be a Group instead, and it may not be needed at all.
The scene should be passed in as a parameter, not referenced as a global variable. It also doesn't need to be a Scene object, it could be a Group, and it is up to the calling code to decide what it wants it to be, not the Ball class. Both Scene and Group have an add method, so both will work in this circumstance.
The creation of the geometry is referring to tracksData.bRadius, when it should be using this.radius. The geometry is also a prime candidate for being a constant (which you define directly above the Ball class, and only use it for the Ball class).
I actually wouldn't even pass in the scene (or group), I would store the object as a property of this class. Track and Section are going to alter the position of this ball, so they need a way to find the node. In this instance, in spite of the fact that I explicitly said to do it with a function, I would just create the node in the constructor.
Lastly, this function/method should not be placing the ball. It has no idea about Tracks or Sections, so it has no idea of where it should be placed. The Track class should take care of that once the Ball is created.
- Code:
function Ball( radius, velocity )
{
this.radius = radius;
this.velocity = velocity;
}
Ball.prototype = Object.create( {} );
Ball.prototype.createObject3D = function( parentNode )
{
// if all Balls can have different radii, then we need to create the geometry here
var g = new THREE.SphereGeometry( this.radius, 12, 12 );
// store the material as a property so that we can change it on a per Ball basis
this.material = new THREE.MeshBasicMaterial( { color: 'yellow', wireframe: true } );
// store the node as a property so we can use it later
this.object3D = new THREE.Mesh( g, this.material );
// add the node to the parent node
parentNode.add( this.object3D );
}
Re: Animate the PI = 4 experiment
.
Many thanks for the continuing guidance. I hope you don’t mind I’ve included several of your descriptions as comments in the code for convenient reference.
Your exposition on my ball class problems included in the createAndPlaceBall function was an excellent (humbling) lesson. I cleaned it up as you suggested and found that it can place balls at a desired location. However, as you indicated, the Track class will place and move the balls – not the Ball class. And so I replaced that function with the Ball.prototype.createObject3D function you provided. Unfortunately, when I try to call it I get “TypeError: Cannot read property “add” of undefined”, for the line.
While I haven’t made much progress with version 3, I’ve been trying to keep up by practicing object oriented programming (oop) ‘on the side’. I’m in the second of Mozilla’s oop practice excersizes “Bouncing Balls”, which includes setting up a new project folder containing .html, .css and .js files to modify. Another utube oop system project that looked promising but which I’ve failed to recreate - “Floating Particles” came to a halt by the following incomplete line.
By the way, I included tracksData because I assumed that PI=4 version 3 would include a tableArray. I must be mistaken, putting all the variables into separate classes seems the exact opposite. Object oriented programming strikes me as mostly modular, or functional, compared to procedural programming.
I haven’t forgotten the requirement to place each class within its own script code. I don't think that'll be a problem.
.
Many thanks for the continuing guidance. I hope you don’t mind I’ve included several of your descriptions as comments in the code for convenient reference.
Your exposition on my ball class problems included in the createAndPlaceBall function was an excellent (humbling) lesson. I cleaned it up as you suggested and found that it can place balls at a desired location. However, as you indicated, the Track class will place and move the balls – not the Ball class. And so I replaced that function with the Ball.prototype.createObject3D function you provided. Unfortunately, when I try to call it I get “TypeError: Cannot read property “add” of undefined”, for the line.
- Code:
parentNode.add( this.object3D );
While I haven’t made much progress with version 3, I’ve been trying to keep up by practicing object oriented programming (oop) ‘on the side’. I’m in the second of Mozilla’s oop practice excersizes “Bouncing Balls”, which includes setting up a new project folder containing .html, .css and .js files to modify. Another utube oop system project that looked promising but which I’ve failed to recreate - “Floating Particles” came to a halt by the following incomplete line.
- Code:
<meta name="viewport" content="width=device-width, initial height-scale
By the way, I included tracksData because I assumed that PI=4 version 3 would include a tableArray. I must be mistaken, putting all the variables into separate classes seems the exact opposite. Object oriented programming strikes me as mostly modular, or functional, compared to procedural programming.
I haven’t forgotten the requirement to place each class within its own script code. I don't think that'll be a problem.
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
You are getting that error because you are not passing in a scene or group object when you call that method.
I'm just calling it a node to differentiate it from objects. As-in a node in a hierarchy. They are basically interchangeable in this context.
Using a class hierarchy does not mean you can't use an array (I'm not exactly sure what you mean by table array). There will be an array, but it will end up being quite inconsequential. It will hold the Track objects, and we will iterate over it in the animate function, but that is about it. Using classes generally means the calling code is more simple, because the complexity is in the classes. It also does make it more modular, although there are ways to do that with other programming paradigms, too.
Using class inheritance allows us to use different implementations, without changing the interface to those classes. As long as there is a common interface, then the calling code treats all of them the same, but each different class does something different. This is called 'Polymorphism', literally meaning 'many changing forms'. In my animal example above, the Animal class provides the interface, with the makeSound method, and the Cat and Dog classes implement that in different ways, creating the Polymorphism. We can substitute any Cat object with any Dog object and the code will still work, although with different results.
That's what I'm trying to get at with the Section class hierarchy. We want to be able to treat all Sections the same, so the Track class can use them without needing to know what type of Section they are. This will simplify the Track code when dealing with Sections. It will literally just call a method, defined in the Section class, and each Section object does whatever it wants to, for its own needs. The Section classes will be given the Ball to operate on. They will make changes to the Ball by moving it along their path.
While the Ball class is totally capable of placing itself, we don't want it to be responsible for that because it doesn't have, and shouldn't have, that kind of information. A Ball is there to be moved, it does not supply the path that it should move on. That is supplied by the Sections that are in the Track that the Ball belongs to.
That meta tag is incomplete. You can find that in any ThreeJS example project, and probably any of mine that you have worked on, too.
I'm just calling it a node to differentiate it from objects. As-in a node in a hierarchy. They are basically interchangeable in this context.
Using a class hierarchy does not mean you can't use an array (I'm not exactly sure what you mean by table array). There will be an array, but it will end up being quite inconsequential. It will hold the Track objects, and we will iterate over it in the animate function, but that is about it. Using classes generally means the calling code is more simple, because the complexity is in the classes. It also does make it more modular, although there are ways to do that with other programming paradigms, too.
Using class inheritance allows us to use different implementations, without changing the interface to those classes. As long as there is a common interface, then the calling code treats all of them the same, but each different class does something different. This is called 'Polymorphism', literally meaning 'many changing forms'. In my animal example above, the Animal class provides the interface, with the makeSound method, and the Cat and Dog classes implement that in different ways, creating the Polymorphism. We can substitute any Cat object with any Dog object and the code will still work, although with different results.
That's what I'm trying to get at with the Section class hierarchy. We want to be able to treat all Sections the same, so the Track class can use them without needing to know what type of Section they are. This will simplify the Track code when dealing with Sections. It will literally just call a method, defined in the Section class, and each Section object does whatever it wants to, for its own needs. The Section classes will be given the Ball to operate on. They will make changes to the Ball by moving it along their path.
While the Ball class is totally capable of placing itself, we don't want it to be responsible for that because it doesn't have, and shouldn't have, that kind of information. A Ball is there to be moved, it does not supply the path that it should move on. That is supplied by the Sections that are in the Track that the Ball belongs to.
That meta tag is incomplete. You can find that in any ThreeJS example project, and probably any of mine that you have worked on, too.
Re: Animate the PI = 4 experiment
.
None of the above, balls or tracks, are positioned as they will be when PI=4 v3 is finished. I include them 'as is' for comfort and reassurance, that the object code appears to be working as I make changes.
I’ve immersed myself with plenty of object oriented reading and examples, but that doesn’t mean I understand it yet. Using the previous Ball creator function and Ball prototype as example, I was unable to put tracks on the screen. There’s only one ball type, as opposed to three section or track types. I modified the ball code to add a parent instead of the Ball prototype, then did the same with the section code. The track section types (StraightSection, KCurvedSection and PCurvedSection) all call (and inherit from) the same SectionExists function. The Ball creator function now also calls the creator function BallExist to inherit the this.exists variable, making BallExists the prototype parent instead of Ball prototype.
Dare I ask, is that correct?
.
None of the above, balls or tracks, are positioned as they will be when PI=4 v3 is finished. I include them 'as is' for comfort and reassurance, that the object code appears to be working as I make changes.
I’ve immersed myself with plenty of object oriented reading and examples, but that doesn’t mean I understand it yet. Using the previous Ball creator function and Ball prototype as example, I was unable to put tracks on the screen. There’s only one ball type, as opposed to three section or track types. I modified the ball code to add a parent instead of the Ball prototype, then did the same with the section code. The track section types (StraightSection, KCurvedSection and PCurvedSection) all call (and inherit from) the same SectionExists function. The Ball creator function now also calls the creator function BallExist to inherit the this.exists variable, making BallExists the prototype parent instead of Ball prototype.
Dare I ask, is that correct?
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
That doesn't sound right. You don't need a BallExist class just to add a property. Just add it straight to the Ball class. You only want a parent class if there are other classes that are going to inherit from it. Sometimes you might create a parent class just because it makes sense conceptually, and it allows you to separate some code into the 2 classes (parent and child).
There is no way StraightSection can have the same parent class as KCurvedSection and PCurvedSection, because the latter 2 should be inheriting from CurvedSection. Again, you don't need that *Exists class, just add a boolean property if you need it (which would be added to the Section class so all sub-classes get it). I just noticed that you got rid of the Section and CurvedSection classes? Why? You have lost the Section.length property and added it directly to each sub-class, defying one of the main uses of a class hierarchy.
You have also destroyed the polymorphism by doing so. There is no common method for all classes of type Section, so there is no polymorphism. You have instead declared methods like StraightSection.prototype.createSTrack and KCurvedSection.prototype.createKTrack and GCurvedSection.prototype.createGTrack. These should all have the exact same method name, and the Section class should also have a method with the same name. Also, why is a Section creating a Track? A Section is a part of a Track, so there is no way that it can create one.
I don't actually see why you want that exists property either. I imagine you are thinking about being able to disable a track, but you have added this to the Ball and Section classes, which have nothing to do with Track, so I am not sure what this is supposed to do.
Once these classes are setup correctly, I doubt you will need that exists property. If you want to disable a particular Track, then you just don't create it, or don't add it to the array of Tracks. Maybe I am not seeing what you have in mind for that property. When will it be true and when will it be false? What does it mean to be true and what does it mean to be false?
There is one question that you should ask yourself when thinking about adding something to a class or setting up a class hierarchy. In this example, you are adding a boolean exists property, so you should ask yourself: 'Does this class have an exist property, or is it an exist?'. I know, that doesn't make sense, which basically gives you the answer.
Let's do that for the animal example I used before: 'Does a Cat have an Animal, or is a Cat an Animal?'. Clearly, it is the latter, so we go with a parent-child class arrangement. Let's assume we wanted to represent a brain for these animals, we then ask: 'Does a Cat have a brain, or is a Cat a brain?'. Clearly, it has a brain, so we go with a property. Then we ask, 'Does a Cat have a brain, or does an Animal have a brain?'. Again, all animals have a brain, so we add that property to the Animal class. By doing so, we also gave a brain to the Dog class. I'm sure they will appreciate that.
In the above code, you are instantiating a new Ball object. However, immediately after that, you are assigning the prototype, which you should not be doing. The JS compiler has already done that for you when it created your object. Prototypes are for classes, not objects, although it is the objects that get all the advantages of them. By that, I mean if you are working with a prototype, then you are working on a class, never an object. When you get to the creation of objects, you should never be working with a prototype. It is all handled for you behind the scenes.
This stuff is tricky. Very difficult to get your head around at first, but once you do, it becomes very powerful. Don't worry too much about mixing things up, I've tortured my fair share of class hierarchies before, and I'll probably do it again, too.
There is no way StraightSection can have the same parent class as KCurvedSection and PCurvedSection, because the latter 2 should be inheriting from CurvedSection. Again, you don't need that *Exists class, just add a boolean property if you need it (which would be added to the Section class so all sub-classes get it). I just noticed that you got rid of the Section and CurvedSection classes? Why? You have lost the Section.length property and added it directly to each sub-class, defying one of the main uses of a class hierarchy.
You have also destroyed the polymorphism by doing so. There is no common method for all classes of type Section, so there is no polymorphism. You have instead declared methods like StraightSection.prototype.createSTrack and KCurvedSection.prototype.createKTrack and GCurvedSection.prototype.createGTrack. These should all have the exact same method name, and the Section class should also have a method with the same name. Also, why is a Section creating a Track? A Section is a part of a Track, so there is no way that it can create one.
I don't actually see why you want that exists property either. I imagine you are thinking about being able to disable a track, but you have added this to the Ball and Section classes, which have nothing to do with Track, so I am not sure what this is supposed to do.
Once these classes are setup correctly, I doubt you will need that exists property. If you want to disable a particular Track, then you just don't create it, or don't add it to the array of Tracks. Maybe I am not seeing what you have in mind for that property. When will it be true and when will it be false? What does it mean to be true and what does it mean to be false?
There is one question that you should ask yourself when thinking about adding something to a class or setting up a class hierarchy. In this example, you are adding a boolean exists property, so you should ask yourself: 'Does this class have an exist property, or is it an exist?'. I know, that doesn't make sense, which basically gives you the answer.
Let's do that for the animal example I used before: 'Does a Cat have an Animal, or is a Cat an Animal?'. Clearly, it is the latter, so we go with a parent-child class arrangement. Let's assume we wanted to represent a brain for these animals, we then ask: 'Does a Cat have a brain, or is a Cat a brain?'. Clearly, it has a brain, so we go with a property. Then we ask, 'Does a Cat have a brain, or does an Animal have a brain?'. Again, all animals have a brain, so we add that property to the Animal class. By doing so, we also gave a brain to the Dog class. I'm sure they will appreciate that.
- Code:
sBall = new Ball( true, 0.225*r, 1 );
sBall.prototype = Ball.prototype;
In the above code, you are instantiating a new Ball object. However, immediately after that, you are assigning the prototype, which you should not be doing. The JS compiler has already done that for you when it created your object. Prototypes are for classes, not objects, although it is the objects that get all the advantages of them. By that, I mean if you are working with a prototype, then you are working on a class, never an object. When you get to the creation of objects, you should never be working with a prototype. It is all handled for you behind the scenes.
This stuff is tricky. Very difficult to get your head around at first, but once you do, it becomes very powerful. Don't worry too much about mixing things up, I've tortured my fair share of class hierarchies before, and I'll probably do it again, too.
Re: Animate the PI = 4 experiment
For clarity, I mentioned those create*Track methods earlier, and suggested that they should have a common name (and that name should not have the word Track in it, either), and I wanted to point out that these names should actually exist as functions, not methods on a class. Also, they will actually create Track objects, not Sections.
This is where you do track specific code. The Track class is just a generic holder of Sections and a Ball, but here, when we instantiate some of them, we make them unique based on whatever it is we want them to be. It is the objects of the class Track that are straight or curved, kinematic or geometric, etc, not the class itself.
Then we create an array of Tracks like this:
Then we initialize the tracks like this:
There will be more to it than that, such as positioning the tracks based on their size, etc, but that is the gist of it.
Now, suppose we want to remove the geometric curved track, then all we have to do is comment it out like this:
Voila! No more P track.
- Code:
function createSTrack()
{
var track = new Track();
track.sections.push( new StraightSection( 5 ) );
return track;
}
function createKTrack( radius )
{
var track = new Track();
track.sections.push( new StraightSection( 1 ) );
track.sections.push( new KCurvedSection( radius ) );
return track;
}
function createPTrack( radius )
{
var track = new Track();
track.sections.push( new StraightSection( 1 ) );
track.sections.push( new PCurvedSection( radius ) );
return track;
}
This is where you do track specific code. The Track class is just a generic holder of Sections and a Ball, but here, when we instantiate some of them, we make them unique based on whatever it is we want them to be. It is the objects of the class Track that are straight or curved, kinematic or geometric, etc, not the class itself.
Then we create an array of Tracks like this:
- Code:
var tracks = [
createSTrack(),
createKTrack( radius ),
createPTrack( radius )
];
Then we initialize the tracks like this:
- Code:
var group = new THREE.Group();
for( var i=0; i<tracks.length; i++ )
{
tracks[i].createObject3D( group );
}
There will be more to it than that, such as positioning the tracks based on their size, etc, but that is the gist of it.
Now, suppose we want to remove the geometric curved track, then all we have to do is comment it out like this:
- Code:
var tracks = [
createSTrack(),
createKTrack( radius )
// createPTrack( radius )
];
Voila! No more P track.
Re: Animate the PI = 4 experiment
.
Object oriented programming. Oh well, I guess what I studied way back when is called ancient Computer Science.
Thank you Sir, I removed all the changes I made last time.
Ok, javaScript is an object oriented language. All things except a few primatives or null can be defined as objects by using the available ‘new’ constructors. One can choose not to use all the formal constructors, except for a few required things such as, new THREE.Vector3. I must say, after being sheltered by three.js for years, it’s good to see javaScript is way bigger. Using the constructors involve the creation of many different objects and their prototypes, each of which include many available built-in javaScript methods or one can customize their own. MDN, developer.mozilla.org, is a great resource.
For the record, weeks of flailing about is embarrassing, but also motivating. I’ve continued my efforts by watching/studying several youtube videos. As the presenters are quick to point out, in addition to simply running the code, the browser console allows real-time code interaction. After finding a decent video, I then find or recreate all its code (incl any necessary attendant files) in its own folder. Its best when I pause the video each and every line, making and/or saving the indicated changes as well as my own, to both files and/or console before continuing. My favorite videos so far, both about an hour long are:
Object Oriented JavaScript, by Derek Banas, https://www.youtube.com/watch?v=O8wwnhdkPE4 , and
Object-oriented Programming in JavaScript: Made Super Simple by Mosh, https://www.youtube.com/watch?v=PFmuCDHHpwk.
Reviewing them with non-stop pausing (not including the initial code copying), took me six to eight hours each. While I’ve yet to make any positive code changes to PI=4, v3 I know I’ve made all the oop changes indicated in those videos. ECMAScript 5 or 6, I’m at least passing familiar with new syntactical information.
I've gone back and re-read all your comments several times. I believe I even understand them. This mental hangup is fierce.
Not looking for a new career, what do you think of https://en.wikipedia.org/wiki/CS50 ?
P.S., I just now noticed your last post - can't wait to read it.
.
Object oriented programming. Oh well, I guess what I studied way back when is called ancient Computer Science.
Thank you Sir, I removed all the changes I made last time.
Ok, javaScript is an object oriented language. All things except a few primatives or null can be defined as objects by using the available ‘new’ constructors. One can choose not to use all the formal constructors, except for a few required things such as, new THREE.Vector3. I must say, after being sheltered by three.js for years, it’s good to see javaScript is way bigger. Using the constructors involve the creation of many different objects and their prototypes, each of which include many available built-in javaScript methods or one can customize their own. MDN, developer.mozilla.org, is a great resource.
For the record, weeks of flailing about is embarrassing, but also motivating. I’ve continued my efforts by watching/studying several youtube videos. As the presenters are quick to point out, in addition to simply running the code, the browser console allows real-time code interaction. After finding a decent video, I then find or recreate all its code (incl any necessary attendant files) in its own folder. Its best when I pause the video each and every line, making and/or saving the indicated changes as well as my own, to both files and/or console before continuing. My favorite videos so far, both about an hour long are:
Object Oriented JavaScript, by Derek Banas, https://www.youtube.com/watch?v=O8wwnhdkPE4 , and
Object-oriented Programming in JavaScript: Made Super Simple by Mosh, https://www.youtube.com/watch?v=PFmuCDHHpwk.
Reviewing them with non-stop pausing (not including the initial code copying), took me six to eight hours each. While I’ve yet to make any positive code changes to PI=4, v3 I know I’ve made all the oop changes indicated in those videos. ECMAScript 5 or 6, I’m at least passing familiar with new syntactical information.
I've gone back and re-read all your comments several times. I believe I even understand them. This mental hangup is fierce.
Not looking for a new career, what do you think of https://en.wikipedia.org/wiki/CS50 ?
P.S., I just now noticed your last post - can't wait to read it.
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
.
I’m finally able to study the code without jumping into the references; Post correction - Not.
Thus far, you’ve provided the code needed to create two classes, Ball and Section. The only class you haven’t formally defined is the Track class. It would be nice if I could figure out a Track constructor and prototype. Here’s my 'progress'.
You’ve described Track as an array of sections. Section is an abstract class that is not “created’ directly. The constructor requires a ( length ).
Your latest code installment includes track creations. Here’s a line from the createSTrack constructor function.
You also included both curved tracks. Here’s the core of the createKTrack constructor function.
I suppose the length being passed can be considered a kind of index or the array length.Passing the radius includes the implicit understanding that the radius conveys the entire circular track. radius might indicate length = 4. I believe you mentioned the code can go either way, I would prefer treating each circular track as four sections rather than 1.
Since Ball and Section are both function constructors, I guess Track should be too. But the argument passed is an object that’s used to build an array.
Here's a quote from JavaScript The Definitive Guide, section 9.6.7. Note the bold emphasis – we might want … constructor“ is mine.
I still feel lost. Am I making any valid comments?
.
I’m finally able to study the code without jumping into the references; Post correction - Not.
Thus far, you’ve provided the code needed to create two classes, Ball and Section. The only class you haven’t formally defined is the Track class. It would be nice if I could figure out a Track constructor and prototype. Here’s my 'progress'.
You’ve described Track as an array of sections. Section is an abstract class that is not “created’ directly. The constructor requires a ( length ).
- Code:
function Section( length )
Your latest code installment includes track creations. Here’s a line from the createSTrack constructor function.
- Code:
track.sections.push( new StraightSection( 5 ) );
You also included both curved tracks. Here’s the core of the createKTrack constructor function.
- Code:
track.sections.push( new StraightSection( 1 ) );
track.sections.push( new KCurvedSection( radius ) );
I suppose the length being passed can be considered a kind of index or the array length.Passing the radius includes the implicit understanding that the radius conveys the entire circular track. radius might indicate length = 4. I believe you mentioned the code can go either way, I would prefer treating each circular track as four sections rather than 1.
Since Ball and Section are both function constructors, I guess Track should be too. But the argument passed is an object that’s used to build an array.
- Code:
function Track( type, value )
{
}
Track.prototype = Object.create( {} );
Here's a quote from JavaScript The Definitive Guide, section 9.6.7. Note the bold emphasis – we might want … constructor“ is mine.
Constructor Overloading and Factory Methods
Sometimes we want to allow objects to be initialized in more than one way. We might want to create a Complex object initialized with a radius and an angle (polar coordinates) instead of real and imaginary components, for example, or we might want to create a Set whose members are the elements of an array rather than the arguments passed to the constructor.
One way to do this is to overload the constructor and have it perform different kinds of initialization depending on the arguments it is passed. Here is an overloaded version of the Set constructor, for example:
- Code:
function Set() {
this.values = {}; // The properties of this object hold the set
this.n = 0; // How many values are in the set
// If passed a single array-like object, add its elements to the set
// Otherwise, add all arguments to the set
if (arguments.length == 1 && isArrayLike(arguments[0]))
this.add.apply(this, arguments[0]);
else if (arguments.length > 0)
this.add.apply(this, arguments);
}
I still feel lost. Am I making any valid comments?
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
Airman wrote:For the straightTrack, length is 5. Five straight sections 2*r long. This clearly suggests ( length ) is the number of straight track sections. I should mention, up till now I wrongly interpreted ( length ) being equal to 2*r. That each track’s physical length equals 10*r. (2*r + 8*r = 10*r).
No, the length is meant to be the actual length of that single section, in meters or kilometers, or whatever units are in use. I couldn't be bothered to go back through all of the posts to find the right values, so I just chucked in something that seemed about right. I was more concerned with the structure of the code rather than the values it was being given.
Airman wrote:I suppose the length being passed can be considered a kind of index or the array length.
No, it absolutely can not be considered an index or length of any array. It is a distance. The complete distance of that particular section. The curved sections are given a radius because they will calculate different distances based on whether they use PI=3.14 or PI=4. The radius is only used in order to calculate that length (well, it may be needed for other things later, too).
Airman wrote:Passing the radius includes the implicit understanding that the radius conveys the entire circular track. radius might indicate length = 4. I believe you mentioned the code can go either way, I would prefer treating each circular track as four sections rather than 1.
I know it seems like using 4 sections to a circle is going to be easier, but it will not be when it comes time to join the sections together so that they line up and form a complete track. It will complicate that code quite a bit. It is do-able, but it may get a bit messy.
The Track class will look something like this:
- Code:
function Track()
{
this.sections = [];
this.ball = new Ball();
}
Track.prototype = Object.create( {} );
Track.prototype.createObject3D = function( parentNode )
{
this.object3D = new THREE.Group();
for( var i=0; i<this.sections.length; i++ )
{
var node = new THREE.Group();
this.sections[i].createObject3D( node );
// TODO position node so that it lines up with the last section (if there is one)
// retrieve the bounds of the node and use the X dimension to move each node over so that they line up with each other
}
}
Track.prototype.applyMotion = function( time )
{
var t = time.delta; // the amount of time we have to move the ball
// TODO find index of current section that this.ball is in
var index = ...;
while( index < this.sections.length && t > 0 )
{
// allow section to move ball, it will return the amount of time left over
t = this.sections[index].applyMotion( t, this.ball );
index++;
}
}
With respect to constructors, you can do some amazing things like you've shown above, but we don't need that. Don't over complicate things.
Re: Animate the PI = 4 experiment
.
The oop big picture eludes me.
The PI=4 Experiment has four circumferential sections. This is because, during circular motion, each quarter circle is being traversed in the same amount of time as an equivalent straight track, 4 diameters long.
Our tracks section lengths are based on the diameter. Why do we want to pass a radius? If we use a radius, then the tracks are ten sections long. The circular track should be eight. The total track length is 10 sections. Passing the radius instead of the diameter complicates things; wouldn't it simplify things to use one or the other?
Why do we need to pass the length parameter to the straight section and radius parameter to the curved? They are the same distance. Provide any length you like, the curved functions will make curves of four diameters or eight radii, and the straight function will make straight sections of four diameters or eight radii in length. One length works for both since they are both equal to the same kinematic distance.
.
The oop big picture eludes me.
The PI=4 Experiment has four circumferential sections. This is because, during circular motion, each quarter circle is being traversed in the same amount of time as an equivalent straight track, 4 diameters long.
Our tracks section lengths are based on the diameter. Why do we want to pass a radius? If we use a radius, then the tracks are ten sections long. The circular track should be eight. The total track length is 10 sections. Passing the radius instead of the diameter complicates things; wouldn't it simplify things to use one or the other?
Nevyn wrote:the length is meant to be the actual length of that single section, in meters or kilometers, or whatever units are in use
Why do we need to pass the length parameter to the straight section and radius parameter to the curved? They are the same distance. Provide any length you like, the curved functions will make curves of four diameters or eight radii, and the straight function will make straight sections of four diameters or eight radii in length. One length works for both since they are both equal to the same kinematic distance.
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
Airman wrote:The PI=4 Experiment has four circumferential sections. This is because, during circular motion, each quarter circle is being traversed in the same amount of time as an equivalent straight track, 4 diameters long.
Our tracks section lengths are based on the diameter. Why do we want to pass a radius? If we use a radius, then the tracks are ten sections long. The circular track should be eight. The total track length is 10 sections. Passing the radius instead of the diameter complicates things; wouldn't it simplify things to use one or the other?
I find it odd that you are arguing for using the diameter. I never think in diameters because the radius is the defining characteristic of a curve. A diameter only applies to a circle. So you are wanting to use a diameter, while also wanting to split the circles into 4 individual sections, thereby breaking the circles and losing the diameter.
What we want is for this app to rely on the concept of velocity, and not diameters or radii. Those properties are only there to give us a length, which can then be used in the velocity equation. So we don't care that it is 4 diameters long. Velocity will work for any length.
I also don't understand why you insist on spllitting up those circles into 4 curves. I think it is because you have an association between marker positions and the ends of sections. We don't need to combine those two concepts into the same thing. We can separate them and make things much easier. Also, if you do link those together, you end up with 5 sections per circle because the PI marker will split the last section again.
For the markers, I am thinking of adding some methods to the classes like this:
- Code:
function Marker( distance )
{
this.distance = distance; // this is the absolute distance along the full track
this.material = ...; // create a THREE.Material for the marker which we can manipulate later
this.object3D = ...; // create a node to represent the marker, using this.material
}
Marker.prototype = Object.create( {} );
...
Track.prototype.addMarker = function( marker )
{
var dist = 0;
var md = marker.distance;
for( var i=0; i<this.sections.length; i++ )
{
dist += this.sections[i].length;
if( marker.distance < dist )
{
// the marker is on this section
this.sections[i].addMarker( marker, md ); // position the marker based on the section it is in
this.markers.push( marker ); // add the marker to this track
break; // jump out of the for loop
}
md -= this.sections[i].length;
}
};
...
Section.prototype.addMarker = function( marker, distance )
{
// TODO each sub-class will implement this method to place a marker at the specific distance along its own length
// distance is relative to this section
// to put the marker at the start of the section, distance=0
// to put the marker at the end of the section, distance=this.length
};
...
Each section class then handles its own shape and places the marker at the correct position (and with the correct rotation). Then the Track class will iterate through the markers and it only needs the absolute distance that the ball has traveled to do that. It doesn't need to understand curves or straights, kinematic or geometric. It knows the distance that each marker is at, and compares that to the current distance of the ball to determine when the markers have been reached.
Airman wrote:Why do we need to pass the length parameter to the straight section and radius parameter to the curved? They are the same distance. Provide any length you like, the curved functions will make curves of four diameters or eight radii, and the straight function will make straight sections of four diameters or eight radii in length. One length works for both since they are both equal to the same kinematic distance.
For one thing, it makes no sense to describe a straight section with a radius. Straights don't have radii. Most importantly, though, that length doesn't work for a geometric distance. A kinematic curve and a geometric curve will have different lengths given the same radius. So we can't just pass in the length, since the geometric curve will end up with a smaller radius. We want them to have the exact same radius, so that is what we should supply to each type of curve, and then let it figure out how much length that equals.
If we were to pass in the length, then the calling code would need to know that it is creating a kinematic or geometric curve, and make the appropriate calculation. That breaks the idea of keeping related code together in an appropriate class. The curve knows what type it is, so it can make the right calculation. What we are trying to achieve is to make the calling code (of these classes) treat them all in the same way, except for a few parameters here or there which make them specific. Once created, we want every section to be treated the same. We don't want to care if the ball is currently in a straight or curve, kinematic or geometric section. We only care that it is a section and leave that class to deal with moving the ball.
Look at your previous code. It was full of if/else blocks and switch statements. Always determining what type of section it was currently handling. Further more, all of this code was duplicated for each track, with slight differences. The new way of doing it replaces those if/else and switch statements with classes. The calling code doesn't care what type of section it is, it just gives it the ball and lets it do its thing. Each class understands its own motion, so it can do that, without a care in the world for any other type of section.
Let's say you find a bug in the way straight sections are working. In the old code, you had to find the straight section code for each type of track and fix it in all of them. In the new code, you just go to the straight section class and fix it once.
As a bonus, although this won't be used in this particular app, the new way allows you to create a track containing any number and type of sections. We could create a track that has a straight, then a geometric curve, then a kinematic curve, etc, etc. Everything will still work. To do that in the old code would mean coding up that specific combination, and then, it only works for that particular track. So if we wanted to add another type of track, then we have to code that up too. That is a lot of work for no reason. Using classes allows you to put things together in new ways without everything falling apart or needing severe re-writes. Most of the code will still work because it doesn't need to understand the specifics. It only needs to understand the common interface to those classes.
Re: Animate the PI = 4 experiment
.
Sorry for the misunderstanding. I’m not arguing for “using the diameter” as the track section lengths. We were already using them as StevenO does in his experiment. Equivalent kinematic distances along straight and curved tracks. The tracks' physical dimensions are more important than the balls' velocity. Any ramp velocity works as long as the balls experience good rolling motion. Given the fact that PI=4 for circular motion, I believe it absolutely does make sense to think of a radius or a diameter as straight distances – given an equivalent curved path. The entire circular path as a single straight section is also valid. Looking at circular paths lately I keep trying to think in terms of equivalent curved or straight lengths.
Object oriented programming sounds great. I especially like the idea that the OOP methodology could easily allow the creation of customizable tracks. What I seem to be missing in the opp big picture is how to use it. Since I haven’t made much sense of PI=4 v3 yet, I’m still studying. Most recently, I reviewed a 27 minute, seven year old youtube video at http://www.objectplayground.com/. James Shore does a good job covering the prototype chain and inheritance and his site includes what he calls an object visualizer. Yes, it’s beginning to make sense and form patterns.
None of your code includes ECMAScript 6 reserved code words: class, extends or super. Instead, you’re using reserved words: Object, prototype, and new. That indicates the so called “classical” javaScript pattern for implementing classes, but I have no idea what’s next. Besides your own complex projects are there any oop sources you might suggest?
.
Sorry for the misunderstanding. I’m not arguing for “using the diameter” as the track section lengths. We were already using them as StevenO does in his experiment. Equivalent kinematic distances along straight and curved tracks. The tracks' physical dimensions are more important than the balls' velocity. Any ramp velocity works as long as the balls experience good rolling motion. Given the fact that PI=4 for circular motion, I believe it absolutely does make sense to think of a radius or a diameter as straight distances – given an equivalent curved path. The entire circular path as a single straight section is also valid. Looking at circular paths lately I keep trying to think in terms of equivalent curved or straight lengths.
Object oriented programming sounds great. I especially like the idea that the OOP methodology could easily allow the creation of customizable tracks. What I seem to be missing in the opp big picture is how to use it. Since I haven’t made much sense of PI=4 v3 yet, I’m still studying. Most recently, I reviewed a 27 minute, seven year old youtube video at http://www.objectplayground.com/. James Shore does a good job covering the prototype chain and inheritance and his site includes what he calls an object visualizer. Yes, it’s beginning to make sense and form patterns.
None of your code includes ECMAScript 6 reserved code words: class, extends or super. Instead, you’re using reserved words: Object, prototype, and new. That indicates the so called “classical” javaScript pattern for implementing classes, but I have no idea what’s next. Besides your own complex projects are there any oop sources you might suggest?
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
I don't think I explained myself very well. You can, and should, use the radius (or diameter) to calculate the lengths of all sections. However, while we want to pass in the radius to the curved sections, we don't want to do that for the straight sections, even though it could also calculate its own length based on it. The reason for that is that a radius, as a concept, does not apply to a straight. It just doesn't make sense. So the straight section class should just take in a length. This also allows it to take on any length and not be tied to this particular application which happens to rely on radii.
One of the great things about classes is that they are reusable. At least we should strive to make them so as much as possible. Sometimes a particular class will be tailored to the situation you design it for, but if there is a way to make it more generic, then this should be done (unless it is too onerous or makes the class difficult to work with). Then, when we start another project and come across a similar class, we can just take an existing one we have already developed and worked the bugs out of.
As you develop a class, try to think of it in isolation to the application that it is being designed for. Of course you need to make sure it does what that application needs, and in a way that it needs it to be, but if the class is something that could be used elsewhere, then try to make sense of it on its own.
The classes we are developing here are actually a good example. There is nothing about a track that is specific to the PI=4 concept. The idea of a track, and its sections and even the ball that travels on it, can be used in many other apps. So we should develop these classes with that in mind. Then, when we use them for this app, we find that this app uses them in a limited way, based on the PI=4 concept. The app is more limited than the classes. The PI=4 concept creates the need for lengths based on radii, not the track class, nor the section class.
I might be moving a bit fast. You need time for these concepts to sink in a bit before you can use them in more generic ways like I am pushing for. Being able to see when and where to place code to get the best advantages takes a lot of practice, and I take it for granted.
You mention not being able to see how to use classes effectively, and I've been wondering the same thing. That you need to see the other side of it in order to understand why we want the classes to be the way they are. I have tried to show a little bit of that in the last few posts, such as the Track.prototype.applyMotion method, which makes use of the Section.prototype.applyMotion method, and also where I showed how to use the create*Track functions which actually create Track objects.
So let's have a look at how these classes can make the app code a lot smaller, easier, understandable, and maintainable. If we break down what the app needs, then we can fill in the blanks with our classes.
The first thing we need is a way to define a track. This should be easy to do and manipulate. It should be flexible, even if we don't actually need it for this particular app. Where possible, it should also be extensible, so that we can use it in new ways later, in some other app, should we ever need it.
The second thing we need is a way to convert the track definition into 3D scene nodes. You see, the track is used to calculate motion as its primary goal, but it is also used to create a representation of that track so that the user can see that motion (and the track itself).
The third thing we need is a way to cause motion of the ball on the track. This needs to update the internal variables, but it also needs to update the 3D scene nodes so that the user can see it. Notice how I separate the idea of motion and visual representation. It is easy to conflate the two into one, but they don't need to be. It may end up that they do indeed become intertwined with each other, but we should not assume it from the start. There are good reasons to go either way, so we need to determine which reasons make sense for the particular app we are developing.
The fourth thing we need is a way for the user to exert some control over the app. The basics will be start, stop, and reset, but there could be others if we want.
Okay, that gives us our needs. Now we need to figure out how to accomplish them. I'm going to leave the definition for a bit, and work on the initialization and motion based areas first. These are the simplest, from an interface perspective.
We need to ask ourselves what the app should be interacting with. What classes will the app need to know about in order to function effectively? Does it need to know about the balls? Does it need to know about sections? Does it need to know about tracks? The answers to these questions will tell us where to put certain methods in order to accomplish our goals.
The quick answer is that the app only needs to know about tracks in order to initialize and create motion. The track definitions will also need to know about sections and balls, but we will ignore that for now. So the Track class is where we will want some methods to perform initialization and motion based calculations.
We create some placeholder method signatures.
These methods may change as we develop the app more, and figure out the things we actually want these methods to do, but these give us a general idea of what we want, for now.
We create some app code to use these methods.
Continuing on in next post...
One of the great things about classes is that they are reusable. At least we should strive to make them so as much as possible. Sometimes a particular class will be tailored to the situation you design it for, but if there is a way to make it more generic, then this should be done (unless it is too onerous or makes the class difficult to work with). Then, when we start another project and come across a similar class, we can just take an existing one we have already developed and worked the bugs out of.
As you develop a class, try to think of it in isolation to the application that it is being designed for. Of course you need to make sure it does what that application needs, and in a way that it needs it to be, but if the class is something that could be used elsewhere, then try to make sense of it on its own.
The classes we are developing here are actually a good example. There is nothing about a track that is specific to the PI=4 concept. The idea of a track, and its sections and even the ball that travels on it, can be used in many other apps. So we should develop these classes with that in mind. Then, when we use them for this app, we find that this app uses them in a limited way, based on the PI=4 concept. The app is more limited than the classes. The PI=4 concept creates the need for lengths based on radii, not the track class, nor the section class.
I might be moving a bit fast. You need time for these concepts to sink in a bit before you can use them in more generic ways like I am pushing for. Being able to see when and where to place code to get the best advantages takes a lot of practice, and I take it for granted.
You mention not being able to see how to use classes effectively, and I've been wondering the same thing. That you need to see the other side of it in order to understand why we want the classes to be the way they are. I have tried to show a little bit of that in the last few posts, such as the Track.prototype.applyMotion method, which makes use of the Section.prototype.applyMotion method, and also where I showed how to use the create*Track functions which actually create Track objects.
So let's have a look at how these classes can make the app code a lot smaller, easier, understandable, and maintainable. If we break down what the app needs, then we can fill in the blanks with our classes.
The first thing we need is a way to define a track. This should be easy to do and manipulate. It should be flexible, even if we don't actually need it for this particular app. Where possible, it should also be extensible, so that we can use it in new ways later, in some other app, should we ever need it.
The second thing we need is a way to convert the track definition into 3D scene nodes. You see, the track is used to calculate motion as its primary goal, but it is also used to create a representation of that track so that the user can see that motion (and the track itself).
The third thing we need is a way to cause motion of the ball on the track. This needs to update the internal variables, but it also needs to update the 3D scene nodes so that the user can see it. Notice how I separate the idea of motion and visual representation. It is easy to conflate the two into one, but they don't need to be. It may end up that they do indeed become intertwined with each other, but we should not assume it from the start. There are good reasons to go either way, so we need to determine which reasons make sense for the particular app we are developing.
The fourth thing we need is a way for the user to exert some control over the app. The basics will be start, stop, and reset, but there could be others if we want.
Okay, that gives us our needs. Now we need to figure out how to accomplish them. I'm going to leave the definition for a bit, and work on the initialization and motion based areas first. These are the simplest, from an interface perspective.
We need to ask ourselves what the app should be interacting with. What classes will the app need to know about in order to function effectively? Does it need to know about the balls? Does it need to know about sections? Does it need to know about tracks? The answers to these questions will tell us where to put certain methods in order to accomplish our goals.
The quick answer is that the app only needs to know about tracks in order to initialize and create motion. The track definitions will also need to know about sections and balls, but we will ignore that for now. So the Track class is where we will want some methods to perform initialization and motion based calculations.
We create some placeholder method signatures.
- Code:
Track.prototype.init = function()
{
// TODO initialize this track
};
Track.prototype.applyMotion = function( time )
{
// TODO move the ball along the sections given the time to do so
};
These methods may change as we develop the app more, and figure out the things we actually want these methods to do, but these give us a general idea of what we want, for now.
We create some app code to use these methods.
- Code:
// global variable to hold all Track objects in an array
var tracks = [];
function init()
{
// TODO define the tracks
...
// now initialize the Tracks
for( var i=0; i<tracks.length; i++ )
{
tracks[i].init();
}
...
}
// global variable to store the amount of seconds in a frame
var frameTime = 1/60;
function animate()
{
requestAnimationFrame( animate );
// apply motion to each track
for( var i=0; i<tracks.length; i++ )
{
tracks[i].applyMotion( frameTime );
}
renderer.render( scene, camera );
}
Continuing on in next post...
Last edited by Nevyn on Fri Feb 14, 2020 9:21 pm; edited 1 time in total
Re: Animate the PI = 4 experiment
Defining the tracks does get more complicated, because we now need to know about sections and balls. We could hide the balls inside of the Track class, but I don't want to do that because we may want some sort of individualization of them. For example, we might want to use different colored balls for each different track, or we might want to use different shapes for balls (and therefore they would not really be balls, but some more generic concept, such as an avatar). Instead of a ball, we might want to use a little car that drives around the track, or a slug that slips along it, the possibilities are endless. The main point is that the Track class doesn't need to know what it is that is moving around the track. It only needs to know how to tell it to move. Actually, it is the Section classes that need to know that, not the Track class.
So our Track class needs to store whatever it is that is moving around it, and the sections that define the path. Even with those sections, the Track class should not know much about them. For a fully fledged concept of a track, we would only need to know where each section starts and ends (and at what angle, etc). Allowing other sections to be connected to it. I have, so far, limited this a bit in order to reduce the complexity of connecting them together. That is why I have been pushing the full circle rather than split curves idea. It simplifies connecting them together such that we only need to care about 1 dimension because all sections will start and end on that dimension. To be more precise, they will only vary in that dimension, with the other dimensions being equal. Within each section there can be variance in those other dimensions (or we couldn't create curves), but the start and end points must be on the same dimensions (which a circle satisfies because it starts and ends at the same spot), and only vary in 1.
What I am going to do is create a Ball in the Track constructor, just so that it will always have something to move, but that can be replaced by the calling code that is creating the Track if it wants to. This is why we have an init method and don't do it in the constructor.
Now we can create some functions to create some tracks.
By defining the add method on the Track class, we hide how the Track stores the Sections. This would allow us to change how that happens should we ever need to. I don't think we will need to in this app, but it is a good design decision anyway. It also simplifies the calling code, as we see above. I have used function chaining on the add method too, so it makes it more readable.
In all honesty, I would not directly access that material object to set the color. I would hide that behind some method so that different classes can apply the color in different ways. I am just being lazy here so that I don't have to define all of these things, adding a few new classes that don't really help this discussion. We may get to that at some stage, though.
Now that we have these functions, we can edit our init function from above.
I hope that helps to see how classes make the calling code simpler and more readable, and therefore, more maintainable and flexible.
So our Track class needs to store whatever it is that is moving around it, and the sections that define the path. Even with those sections, the Track class should not know much about them. For a fully fledged concept of a track, we would only need to know where each section starts and ends (and at what angle, etc). Allowing other sections to be connected to it. I have, so far, limited this a bit in order to reduce the complexity of connecting them together. That is why I have been pushing the full circle rather than split curves idea. It simplifies connecting them together such that we only need to care about 1 dimension because all sections will start and end on that dimension. To be more precise, they will only vary in that dimension, with the other dimensions being equal. Within each section there can be variance in those other dimensions (or we couldn't create curves), but the start and end points must be on the same dimensions (which a circle satisfies because it starts and ends at the same spot), and only vary in 1.
What I am going to do is create a Ball in the Track constructor, just so that it will always have something to move, but that can be replaced by the calling code that is creating the Track if it wants to. This is why we have an init method and don't do it in the constructor.
- Code:
function Track()
{
this.sections = [];
this.moveable = new Ball();
}
Track.prototype = Object.create( {} );
Track.prototype.add = function( section )
{
this.sections.push( section );
return this; // by returning this, we allow function chaining
};
Now we can create some functions to create some tracks.
- Code:
// global variable to store the radius
var radius = 10;
function createSTrack()
{
var track = new Track()
.add( new StraightSection( 2 * radius * 5 ) )
;
track.moveable.material.color = '#ff0000';
return track;
}
function createKTrack()
{
var track = new Track()
.add( new StraightSection( 2 * radius ) )
.add( new KinematicSection( radius ) )
;
track.moveable.material.color = '#00ff00';
return track;
}
function createGTrack()
{
var track = new Track()
.add( new StraightSection( 2 * radius ) )
.add( new GeometricSection( radius ) )
;
track.moveable.material.color = '#0000ff';
return track;
}
By defining the add method on the Track class, we hide how the Track stores the Sections. This would allow us to change how that happens should we ever need to. I don't think we will need to in this app, but it is a good design decision anyway. It also simplifies the calling code, as we see above. I have used function chaining on the add method too, so it makes it more readable.
In all honesty, I would not directly access that material object to set the color. I would hide that behind some method so that different classes can apply the color in different ways. I am just being lazy here so that I don't have to define all of these things, adding a few new classes that don't really help this discussion. We may get to that at some stage, though.
Now that we have these functions, we can edit our init function from above.
- Code:
function init()
{
// define the tracks
tracks.push( createSTrack() );
tracks.push( createKTrack() );
tracks.push( createGTrack() );
// now initialize the Tracks
for( var i=0; i<tracks.length; i++ )
{
tracks[i].init();
}
...
}
I hope that helps to see how classes make the calling code simpler and more readable, and therefore, more maintainable and flexible.
Re: Animate the PI = 4 experiment
I've just realised that the first post has lost a lot of what I had written. I will add it in next.
Page 4 of 8 • 1, 2, 3, 4, 5, 6, 7, 8
Similar topics
» PI NINE - Pi experiment with two edge track
» How about an Experiment to Simulate Attraction?
» The Event Horizon Telescope experiment
» Photonics experiment resolves quantum paradox
» Revisiting the Pound-Rebka experiment in light of Miles' new Gravity Paper and the Charge Field
» How about an Experiment to Simulate Attraction?
» The Event Horizon Telescope experiment
» Photonics experiment resolves quantum paradox
» Revisiting the Pound-Rebka experiment in light of Miles' new Gravity Paper and the Charge Field
Page 4 of 8
Permissions in this forum:
You cannot reply to topics in this forum