Animate the PI = 4 experiment
+2
Vexman
Cr6
6 posters
Page 5 of 8
Page 5 of 8 • 1, 2, 3, 4, 5, 6, 7, 8
Re: Animate the PI = 4 experiment
To implement user control, we only need to add 1 more method to the Track class which will reset everything back to the starting state.
Then we create a function to reset all tracks.
In order to start and stop the animation, we don't actually need to use the Track class, we just need to decide if we want to call the applyMotion method or not.
Looking at this code, we can see that the complexity is hidden behind these classes. The app code doesn't need to know much about them, it just needs to call the appropriate methods to make things happen. Each class then gets to decide how it wants to apply it, providing flexibility. We also see that this code does not care how many tracks there are. We can add and remove tracks all day long and this code will not change.
I think that was the gist of what was lost. I know better than to write long posts in the browser, but forgot, again.
- Code:
Track.prototype.reset = function()
{
// TODO reset the ball back to the start of the first section
}
Then we create a function to reset all tracks.
- Code:
function reset()
{
for( var i=0; i<tracks.length; i++ )
{
tracks[i].reset();
}
}
In order to start and stop the animation, we don't actually need to use the Track class, we just need to decide if we want to call the applyMotion method or not.
- Code:
// global variable to determine whether the animation should run or not
var running = true;
function animate()
{
requestAnimationFrame( animate );
if( running )
{
// apply motion to each track
for( var i=0; i<tracks.length; i++ )
{
tracks[i].applyMotion( frameTime );
}
}
renderer.render( scene, camera );
}
Looking at this code, we can see that the complexity is hidden behind these classes. The app code doesn't need to know much about them, it just needs to call the appropriate methods to make things happen. Each class then gets to decide how it wants to apply it, providing flexibility. We also see that this code does not care how many tracks there are. We can add and remove tracks all day long and this code will not change.
I think that was the gist of what was lost. I know better than to write long posts in the browser, but forgot, again.
Re: Animate the PI = 4 experiment
.
Thanks for the very clear, extensive description and code. Having gathered that and all your previous directions, it’s been a chore attempting to put it together in a correct order. I’ve got plenty to study and work with. Thanks for all your time, effort and confidence, I would never have gotten this far without you.
The classes and subclasses appear in the console. Most all the initialization code is up and running, but no track motion. There’s actually nothing visible in the scene yet. The spinning balls will be replaced with track controlled balls before it’s complete. As is, they simply indicate the code is running without error.
Last time you mentioned four requirements. I take that to mean directions?
Four requirements:
1. The first thing we need is a way to define a track. Flexible and extensible.
As I understand it, a track is a member of the Track class which is “defined” by the objects and functions that make up the Track class. Here’s the list of all the object and function, names and // brief descriptions you’ve provided for the Track class:
function Track()
// The Track constructor function is comprised of sections and a ball.
Track.prototype = Object.create( {} );
// Creates the Track class prototype parent.
Track.prototype.createObject3D = function( parentNode )
// Creates the 3D scene node.
Track.prototype.add = function( section )
// The prototype method builds a track by pushing sections.
Track.prototype.findCurrentSectionIndex = function()
// Find which track section the ball is in.
Track.prototype.applyMotion = function( time )
// The proto’s method, applying time and velocities to move the ball.
Track.prototype.init = function()
// Places the tracks in 3D scene node groups(?)
function createSTrack()
// Create straight track.
function createKTrack()
// Create kinematic track.
function createGTrack()
// Create geometric track.
Track.prototype.reset = function()
// The prototype’s function to place balls back at their starting locations
function reset()
// Each track resets its ball position
function Marker( distance )
// Each track resets its ball position
Track.prototype.addMarker = function( marker )
// The prototype method. Each sub-class section will place its own markers.
////////////////////
I guess you mean the track needs further definition before anything can be seen and then to behave properly.
I would say a track is a fixed path through space. Consisting of straight or curved sections, of specific lengths and betweeen specific locations. In addition to the track radius, we need locations and dimensions (including the ball’s radius) to properly position the tracks. That seems simple enough.
I was unreasonable equating the circular track’s radius/diameter or circle kinematic distance with straight sections’ remaining lengths used for calculating motion. You correctly point out that the PI=4 experiment constrains the overall track lengths, that Quote The app is more limited than the more generic or notion Track classes. Unquote. Yes. Curved and straight overall lengths must be scaled together.
2. The second thing we need is a way to convert the track definition into 3D scene nodes.
Agreed, one question. What is a scene node? Geometries and materials create meshes. Meshes are objects that can be added to the scene or added to a 3D group which is then added … to the scene. Is the 3D group the scene node?
One thing not running at the moment (// commented out) is “track.moveable.material.color = '#0000ff'. Should be another easy enough task.
3. The third thing we need is a way to cause motion of the ball on the track. Update: internal variables, the 3D scene nodes.
Affirmative.
4. The fourth thing we need is user control of start, stop, reset, and possibly others.
Copy, Good. Your code included running and reset. Acceleration (gas pedal) might be nice.
////////////////////
I’ve tried separating the piEq4.js file into separate js files, using functions, not classes, without luck. I included the name of the new file in the html source list. No joy yet. Do I need to do some some sort of special DOM declaration?
I’ve got my marching orders, I’m happy. Feel free to add.
.
Thanks for the very clear, extensive description and code. Having gathered that and all your previous directions, it’s been a chore attempting to put it together in a correct order. I’ve got plenty to study and work with. Thanks for all your time, effort and confidence, I would never have gotten this far without you.
The classes and subclasses appear in the console. Most all the initialization code is up and running, but no track motion. There’s actually nothing visible in the scene yet. The spinning balls will be replaced with track controlled balls before it’s complete. As is, they simply indicate the code is running without error.
Last time you mentioned four requirements. I take that to mean directions?
Four requirements:
1. The first thing we need is a way to define a track. Flexible and extensible.
As I understand it, a track is a member of the Track class which is “defined” by the objects and functions that make up the Track class. Here’s the list of all the object and function, names and // brief descriptions you’ve provided for the Track class:
function Track()
// The Track constructor function is comprised of sections and a ball.
Track.prototype = Object.create( {} );
// Creates the Track class prototype parent.
Track.prototype.createObject3D = function( parentNode )
// Creates the 3D scene node.
Track.prototype.add = function( section )
// The prototype method builds a track by pushing sections.
Track.prototype.findCurrentSectionIndex = function()
// Find which track section the ball is in.
Track.prototype.applyMotion = function( time )
// The proto’s method, applying time and velocities to move the ball.
Track.prototype.init = function()
// Places the tracks in 3D scene node groups(?)
function createSTrack()
// Create straight track.
function createKTrack()
// Create kinematic track.
function createGTrack()
// Create geometric track.
Track.prototype.reset = function()
// The prototype’s function to place balls back at their starting locations
function reset()
// Each track resets its ball position
function Marker( distance )
// Each track resets its ball position
Track.prototype.addMarker = function( marker )
// The prototype method. Each sub-class section will place its own markers.
////////////////////
I guess you mean the track needs further definition before anything can be seen and then to behave properly.
I would say a track is a fixed path through space. Consisting of straight or curved sections, of specific lengths and betweeen specific locations. In addition to the track radius, we need locations and dimensions (including the ball’s radius) to properly position the tracks. That seems simple enough.
I was unreasonable equating the circular track’s radius/diameter or circle kinematic distance with straight sections’ remaining lengths used for calculating motion. You correctly point out that the PI=4 experiment constrains the overall track lengths, that Quote The app is more limited than the more generic or notion Track classes. Unquote. Yes. Curved and straight overall lengths must be scaled together.
2. The second thing we need is a way to convert the track definition into 3D scene nodes.
Agreed, one question. What is a scene node? Geometries and materials create meshes. Meshes are objects that can be added to the scene or added to a 3D group which is then added … to the scene. Is the 3D group the scene node?
One thing not running at the moment (// commented out) is “track.moveable.material.color = '#0000ff'. Should be another easy enough task.
3. The third thing we need is a way to cause motion of the ball on the track. Update: internal variables, the 3D scene nodes.
Affirmative.
4. The fourth thing we need is user control of start, stop, reset, and possibly others.
Copy, Good. Your code included running and reset. Acceleration (gas pedal) might be nice.
////////////////////
I’ve tried separating the piEq4.js file into separate js files, using functions, not classes, without luck. I included the name of the new file in the html source list. No joy yet. Do I need to do some some sort of special DOM declaration?
I’ve got my marching orders, I’m happy. Feel free to add.
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
In those last few posts, I was just trying to show the thought processes in designing these classes. Trying to show why we want certain methods, from the perspective of the application (which is quite separate to the perspective of the class). Trying to show how things fit together so that the app and the classes work in conjunction with each other. They weren't really tasks, although they sort of are, too. I defined the interface, but you need to fill in the methods so that they do the things that I identified.
While I think what you are saying here is correct, it actually reads incorrectly to a developer. That is because the term 'member' means something in relation to a class. A member is a variable (or property) defined in a class. It is owned by that class. So your statement actually says that a track is a variable in the Track class, which is obviously wrong. However, I think what you mean by 'member' is really 'group'. Maybe 'instance' is a better word.
A scene node is anything that is in the scene. The scene is a hierarchy of nodes. So a Mesh is a node, but the Geometry and Material that create the Mesh are not. A Group is a node, and is a special kind of node that creates a branch.
When I use the term node, I am talking very generically. What that node is actually represented by is up to the code and what makes sense for it to do its job. It may be a Mesh sometimes, but other times it will be a Group, yet other times it may be some other type of node (there are a lot of different types of nodes, such as lights, backgrounds, cameras, sprites). In ThreeJS, basically anything that extends Object3D is a node, although there may be other classes that do not extend from that that are still nodes (I can't remember the class structure enough to say one way or the other).
If you go back a few posts I described the Ball class somewhere, and in that I defined a material property, which is being used here. I was just trying to show how the create*Track methods are where you individualize the tracks and balls.
That can be implemented by manipulating the frameTime global variable that I declared above.
By the way, with respect to those global variables that I defined, I only made them global for ease (and laziness). They still need to be global in some way, but they could be stored inside of an object that is itself global. This would make it easier to reference them with DATGUI controls.
Yes, you need to reference them in the HTML like this:
Replace 'js/three.min.js' with the name of your file. Repeat this for each file you want to import.
You need to make sure that they are imported in the correct order. If file A needs something in file B, then file B needs to be imported first. This can get complicated, but if you run into problems just let me know because sometimes the order matters and sometime it doesn't. It completely depends on the code.
Please note that I have renamed some methods that I referred to in earlier posts. The one I can think of at the moment is I referred to a createObjectNodes method once (or something like that), but that is now called init. They do the same things.
There is a lot to get done before the tracks will be visible. I would probably start work on that once you have the files organized and imported correctly. It is always good to be able to see something. However, that code is going to be split up between the Track, Section (or its sub-classes), and Ball classes. We want each class to initialize its own nodes. The Track class will just group the nodes created by the Section classes, and adding in the Ball node too. That is going to be tricky, but have a think about it and see where you think things should be done before you start writing code.
Airman wrote:As I understand it, a track is a member of the Track class which is “defined” by the objects and functions that make up the Track class.
While I think what you are saying here is correct, it actually reads incorrectly to a developer. That is because the term 'member' means something in relation to a class. A member is a variable (or property) defined in a class. It is owned by that class. So your statement actually says that a track is a variable in the Track class, which is obviously wrong. However, I think what you mean by 'member' is really 'group'. Maybe 'instance' is a better word.
Airman wrote:What is a scene node? Geometries and materials create meshes. Meshes are objects that can be added to the scene or added to a 3D group which is then added … to the scene. Is the 3D group the scene node?
A scene node is anything that is in the scene. The scene is a hierarchy of nodes. So a Mesh is a node, but the Geometry and Material that create the Mesh are not. A Group is a node, and is a special kind of node that creates a branch.
When I use the term node, I am talking very generically. What that node is actually represented by is up to the code and what makes sense for it to do its job. It may be a Mesh sometimes, but other times it will be a Group, yet other times it may be some other type of node (there are a lot of different types of nodes, such as lights, backgrounds, cameras, sprites). In ThreeJS, basically anything that extends Object3D is a node, although there may be other classes that do not extend from that that are still nodes (I can't remember the class structure enough to say one way or the other).
Airman wrote:One thing not running at the moment (// commented out) is “track.moveable.material.color = '#0000ff'.
If you go back a few posts I described the Ball class somewhere, and in that I defined a material property, which is being used here. I was just trying to show how the create*Track methods are where you individualize the tracks and balls.
Airman wrote:Acceleration (gas pedal) might be nice.
That can be implemented by manipulating the frameTime global variable that I declared above.
By the way, with respect to those global variables that I defined, I only made them global for ease (and laziness). They still need to be global in some way, but they could be stored inside of an object that is itself global. This would make it easier to reference them with DATGUI controls.
Airman wrote:I’ve tried separating the piEq4.js file into separate js files, using functions, not classes, without luck. I included the name of the new file in the html source list. No joy yet. Do I need to do some some sort of special DOM declaration?
Yes, you need to reference them in the HTML like this:
- Code:
<html>
<head>
<script type="javascript" src="js/three.min.js"></script>
</head>
</html>
Replace 'js/three.min.js' with the name of your file. Repeat this for each file you want to import.
You need to make sure that they are imported in the correct order. If file A needs something in file B, then file B needs to be imported first. This can get complicated, but if you run into problems just let me know because sometimes the order matters and sometime it doesn't. It completely depends on the code.
Please note that I have renamed some methods that I referred to in earlier posts. The one I can think of at the moment is I referred to a createObjectNodes method once (or something like that), but that is now called init. They do the same things.
There is a lot to get done before the tracks will be visible. I would probably start work on that once you have the files organized and imported correctly. It is always good to be able to see something. However, that code is going to be split up between the Track, Section (or its sub-classes), and Ball classes. We want each class to initialize its own nodes. The Track class will just group the nodes created by the Section classes, and adding in the Ball node too. That is going to be tricky, but have a think about it and see where you think things should be done before you start writing code.
Re: Animate the PI = 4 experiment
.
1. Organize and import the files correctly.
I can report some progress. The single piEq4.js file is now two js files which I’ve had to reorganize a couple of times. My main initial difficulties were file order error and confusion; no brag, I never do anything right the first time.
piEq4.js contains all global variable declarations as well as ‘routine’ application functions such as: init(), animate(), displaygui(), and processMotion() - for spinning balls.
piEq4Functions.js contains all functions associated with the Ball, Section and Track classes, their constructors, prototypes, methods and most of the crib notes.
I believe you originally asked for separate .js files for each class (?) but the Track class controls everything, why break them apart? I must ask, are the current files “Organized and imported correctly”?
Anyway, moving and thinking very slowly, I’ve started three new init functions.
StraightSection.prototype.init = function() {};
KinematicSection.prototype.init = function() {};
GeometricSection.prototype.init = function() {};
.
Good, new tasks. The simpler the better.Nevyn wrote: There is a lot to get done before the tracks will be visible. I would probably start work on that once you have the files organized and imported correctly.
1. Organize and import the files correctly.
I can report some progress. The single piEq4.js file is now two js files which I’ve had to reorganize a couple of times. My main initial difficulties were file order error and confusion; no brag, I never do anything right the first time.
piEq4.js contains all global variable declarations as well as ‘routine’ application functions such as: init(), animate(), displaygui(), and processMotion() - for spinning balls.
piEq4Functions.js contains all functions associated with the Ball, Section and Track classes, their constructors, prototypes, methods and most of the crib notes.
I believe you originally asked for separate .js files for each class (?) but the Track class controls everything, why break them apart? I must ask, are the current files “Organized and imported correctly”?
Thanks for ending some confusion here too.Nevyn wrote: Please note that I have renamed some methods that I referred to in earlier posts. The one I can think of at the moment is I referred to a createObjectNodes method once (or something like that), but that is now called init. They do the same things.
Ok, that’s where I’m at, thinking and moving very slowly, I’m not able to give you a good answer yet. Sounds like you changed the name from Track.prototype = Object.create to Track.prototype.init because initializing each track will involve two or three different parts? Different track sections and balls. The actual object creation needs be delegated to the subclass functions?Nevyn wrote: The Track class will just group the nodes created by the Section classes, and adding in the Ball node too. That is going to be tricky, but have a think about it and see where you think things should be done before you start writing code.
Anyway, moving and thinking very slowly, I’ve started three new init functions.
StraightSection.prototype.init = function() {};
KinematicSection.prototype.init = function() {};
GeometricSection.prototype.init = function() {};
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
It's ok to put the classes in a single file, but if they start to get large, then split them into their own. Personally, I find it much easier to have each class in their own file, because I can have them all open in my editor and switch between them without losing the last place I was working on each one. Otherwise, you keep scrolling up and down the same file, always trying to find the right place for your next edit. However, I do sometimes still have multiple classes in the one file, especially if I am creating a module, and not just some classes.
Organize the classes so that all code for a given class is together. Don't put all constructors together, and then all prototype declarations together, and then method declarations, etc. You want each class to have all of its own things together. It allows you to see how the parts work together and make sure that you haven't missed anything.
No, Track.prototype = Object.create( {} ); must always exist for a class (when it has methods, anyway). It has nothing to do with the init method, or any other.
Yes, not for Track, because that won't need sub-classes, but we don't want the Track class to be creating nodes to represent the Sections. We want each different type of Section to create its own nodes, because it is the only thing that knows what it needs. A StraightSection doesn't need to know anything about curves. A CurvedSection doesn't need to know anything about straights. A KinematicSection doesn't want to use PI=3.14, just as much as a GeometricSection doesn't want to use PI=4.
However, when it comes to the CurvedSection sub-classes, there is a way to actually create the nodes (and apply motion) in the CurvedSection class, and not in the Kinematic and Geometric sections. We can use the radius given to the constructor to create that circle. PI is only needed to calculate the length and is used for motion calculations (and we can get around it there too). So you won't need KinematicSection.init or GeometricSection.init, only CurvedSection.init. Same with CurvedSection.applyMotion. Note that I am omitting the prototype part of these names because that is an implementation detail and doesn't need to be mentioned, it is implied (when using Javascript). You will definitely need to add these methods to the prototype though.
You want to separate class code from application code. You can use a JS file for the application code (and this is encouraged) or you can leave it in the HTML file. However, the functions such as create*Track and reset are application code that uses the classes. It is not defining those classes, so it should be separate. In the end, you should be able to copy the class files into another project and start working. You don't want code specific to this application in there. It is tricky to figure out which is which, but just ask yourself if you are writing this function for the class, or for the app? Does it belong to the class, or to the app? You'll get the hang of it.
Organize the classes so that all code for a given class is together. Don't put all constructors together, and then all prototype declarations together, and then method declarations, etc. You want each class to have all of its own things together. It allows you to see how the parts work together and make sure that you haven't missed anything.
Airman wrote:Sounds like you changed the name from Track.prototype = Object.create to Track.prototype.init because initializing each track will involve two or three different parts?
No, Track.prototype = Object.create( {} ); must always exist for a class (when it has methods, anyway). It has nothing to do with the init method, or any other.
Airman wrote:The actual object creation needs be delegated to the subclass functions?
Yes, not for Track, because that won't need sub-classes, but we don't want the Track class to be creating nodes to represent the Sections. We want each different type of Section to create its own nodes, because it is the only thing that knows what it needs. A StraightSection doesn't need to know anything about curves. A CurvedSection doesn't need to know anything about straights. A KinematicSection doesn't want to use PI=3.14, just as much as a GeometricSection doesn't want to use PI=4.
However, when it comes to the CurvedSection sub-classes, there is a way to actually create the nodes (and apply motion) in the CurvedSection class, and not in the Kinematic and Geometric sections. We can use the radius given to the constructor to create that circle. PI is only needed to calculate the length and is used for motion calculations (and we can get around it there too). So you won't need KinematicSection.init or GeometricSection.init, only CurvedSection.init. Same with CurvedSection.applyMotion. Note that I am omitting the prototype part of these names because that is an implementation detail and doesn't need to be mentioned, it is implied (when using Javascript). You will definitely need to add these methods to the prototype though.
You want to separate class code from application code. You can use a JS file for the application code (and this is encouraged) or you can leave it in the HTML file. However, the functions such as create*Track and reset are application code that uses the classes. It is not defining those classes, so it should be separate. In the end, you should be able to copy the class files into another project and start working. You don't want code specific to this application in there. It is tricky to figure out which is which, but just ask yourself if you are writing this function for the class, or for the app? Does it belong to the class, or to the app? You'll get the hang of it.
Re: Animate the PI = 4 experiment
.
Update. Slow going, pardon the exaggeration.
The Current Task. Work on the initialization and motion based areas first. These are the simplest, from an interface perspective.
As you explained, the scene’s visible object scene nodes: Balls, Sections and Markers; are created ‘elsewhere’, Track.init will then assemble the those nodes. It seems you reserve the method title createObject3D for the actual scene node creation function.
If that’s the case, I suppose it would it be preferable to rename the two Section functions: StraightSection.init and CurvedSection.init to StraightSection.createObject3D and CurvedSection.createObject3D. Actually on the prototypes. There’s a Ball.createObject3D function. Unfortunately, Track.addMarker has a geometry, material and object3D, its name doesn’t follow that rule.
As you described last time, StraightSection.init and CurvedSection.init should be used for creating scene nodes (noting that the two objects can also handle motion). Should I change those two Section function names from init to StraightSection.createObject3D and CurvedSection.createObject3D. I have two primary examples to draw on, Ball.createObject3D, which may or may not be a good oop example, and function Marker( distance ) although it has a geometry, this.material and object3D, the function name doesn't seem to follow that rule or reasoning.
Once the StraightSection and CurvedSection scene nodes are complete, I'll need to go back to Track.init in order to initialize the track. Here’s what StraightSection looks like so far.
Update. Slow going, pardon the exaggeration.
The Current Task. Work on the initialization and motion based areas first. These are the simplest, from an interface perspective.
Airman wrote: Sounds like you changed the name from Track.prototype = Object.create to Track.prototype.init because initializing each track will involve two or three different parts?
Thanks. It seems safe to say that Object.create( {} ); or the prototype object creation, is the most important function in object oriented programming. You probably realized I screwed up my question, having pointed out you'd renamed a function from something like Track.createObject3D to Track.init, making sure I knew they were intended to be the same function and saving me confusion. What I meant to say was “Sounds like you changed the name from Track.prototype.createObject3D to Track.prototype.init because initializing each track will involve two or three different parts?Nevyn wrote. No, Track.prototype = Object.create( {} ); must always exist for a class (when it has methods, anyway). It has nothing to do with the init method, or any other.
As you explained, the scene’s visible object scene nodes: Balls, Sections and Markers; are created ‘elsewhere’, Track.init will then assemble the those nodes. It seems you reserve the method title createObject3D for the actual scene node creation function.
If that’s the case, I suppose it would it be preferable to rename the two Section functions: StraightSection.init and CurvedSection.init to StraightSection.createObject3D and CurvedSection.createObject3D. Actually on the prototypes. There’s a Ball.createObject3D function. Unfortunately, Track.addMarker has a geometry, material and object3D, its name doesn’t follow that rule.
As you described last time, StraightSection.init and CurvedSection.init should be used for creating scene nodes (noting that the two objects can also handle motion). Should I change those two Section function names from init to StraightSection.createObject3D and CurvedSection.createObject3D. I have two primary examples to draw on, Ball.createObject3D, which may or may not be a good oop example, and function Marker( distance ) although it has a geometry, this.material and object3D, the function name doesn't seem to follow that rule or reasoning.
Once the StraightSection and CurvedSection scene nodes are complete, I'll need to go back to Track.init in order to initialize the track. Here’s what StraightSection looks like so far.
- Code:
StraightSection.prototype.createObject3D = function(length)
{
// the first (or only) section to any track is a straight section either 2*r or 10*r long.
this.object3D = new THREE.Group();
for( var i=0; i<this.sections.length; i++ )
{
var straightSectionGeometry = new THREE.CylinderBufferGeometry( 0.225*r, 0.225*r, this.sections[i].length, 20, 12, true );
this.material = new THREE.MeshStandardMaterial(
{ color: 0x0c0c0c, transparent: true, opacity: 0.4, blending: THREE.AdditiveBlending, side: THREE.DoubleSide } );
this.object3D = new THREE.Mesh(straightSectionGeometry, this.material );
var node = new THREE.Group();
this.sections[i].createObject3D( node );
parentNode.add( this.object3D );
// 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.
// either 2*r or 10*r long.
if (this.sections[i].length === 2*r)
{
this.object3D.position.set(-4*r, 0, r ); //
}
else // 10*r long
{
this.object3D.position.set(0, 0, r ); //
}
parentNode.add( this.object3D ); // this line is from Ball.createObject3D
}
};
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
Airman wrote:The Current Task. Work on the initialization and motion based areas first. These are the simplest, from an interface perspective.
That wasn't a directive. I only meant that I was going to start with those, because they are the simplest from an interface perspective. They are not the simplest from an implementation perspective. Although they need to be done. It doesn't matter if that is what you want to work on.
Airman wrote:Sounds like you changed the name from Track.prototype.createObject3D to Track.prototype.init because initializing each track will involve two or three different parts?
I changed that method name because init is more generic than createObject3D. A method with the name createObject3D should only be creating an Object3D object. Nothing else. However, init is responsible for initializing whatever owns that method. These subtle differences actually matter. A method/function name should be descriptive of what it is doing, or generic enough to cover what it needs to.
Airman wrote:It seems you reserve the method title createObject3D for the actual scene node creation function.
No, I haven't reserved that method name, I just got rid of it completely. What that method was doing, should now be done in the init method(s). If you find that the init method is getting to large, and you want to split it up a bit, then you can introduce such a method, but it shouldn't be necessary.
Airman wrote:If that’s the case, I suppose it would it be preferable to rename the two Section functions: StraightSection.init and CurvedSection.init to StraightSection.createObject3D and CurvedSection.createObject3D. Actually on the prototypes. There’s a Ball.createObject3D function. Unfortunately, Track.addMarker has a geometry, material and object3D, its name doesn’t follow that rule.
No, don't do that. Leave them as init.
Track.addMarker is a completely different type of method. It is not trying to do the same things as the init methods. They don't belong in the same discussion. It doesn't need to follow any naming rule.
Here is the naming rule:
If a class declares a method that is abstract, then any sub-class (that is not abstract itself) must implement that method (if some other parent class hasn't), and that method must have the same signature (name, parameter list, and return type).
Essentially, any concrete sub-class must have that method implemented, by either itself, or some class in its parent hierarchy.
Javascript doesn't have a way to declare an abstract method. Other languages, such as Java, allow you to declare abstract methods and sub-classes must implement it or the compiler will complain and fail the compilation. So in Javascript, you declare a default implementation and document that it must be overridden by sub-classes (or just rely on memory, which is not ideal, but often done anyway).
Airman wrote:StraightSection.prototype.createObject3D = function(length)
That method should be changed to init, instead of createObject3D, and it should not take in the length parameter. The length was given to the constructor, so it should be referenced as this.length.
This method is implementing the abstract method from Section, so it can't just add new parameters to the list. While that will actually work in Javascript, because it doesn't care about those parameters and relies on the name to find a method, other languages are not like that. In a more formal language, like Java, you can use Method Overloading, which just means you can have multiple methods with the same name, on the same class, but with different parameter lists and they will be considered different methods. This is actually a great thing, and very useful, but isn't available in Javascript. You can accomplish the same thing in JS, but it isn't as nice.
A lot of your code refers to an r variable, but it is never defined. I assume it is a global variable, but it should not be used in the classes. You can use it in app functions, but not the classes, because the classes are not supposed to be tied to this app. If you were to use these classes in another project, then they would fail because they can't find that r variable. StraightSection should not need to use it anyway. It is given its length in the constructor, so use it.
The StraightSection class should not have a this.sections property. That looks like Track code, not any type of Section class. A Section is only responsible for creating its own Object3D node. There should not be a loop in there, since there is nothing to loop over.
Airman wrote:
- Code:
// either 2*r or 10*r long.
if (this.sections[i].length === 2*r)
{
this.object3D.position.set(-4*r, 0, r ); //
}
else // 10*r long
{
this.object3D.position.set(0, 0, r ); //
}
This is very bad. That code relies on knowing that a StraightSection is going to be 2r or 10r long, but the class has no way of knowing that, and even if it did, it absolutely should not use it. A StraightSection can be any length, not 2r, not 10r, not any r. You should not be setting the position in this method. The StraightSection class has no way of knowing where it should position itself. It is not responsible for that. Whoever is using it must do it, which in this case is the Track class. Only the Track knows where it needs to place a Section.
The way to do this is to define where a Section should have its start and end positions. For convenience, we will use the X dimension for this, so all Sections must start and end on Y=0 and Z=0, but can vary along the X dimension. The Sections Object3D node will always start at X=0, and increase to the positive side. A curve does make this tricky, and I have only just realised this, so we need another way to determine where the start and end offsets are for a given Section.
To do that, we will create 2 new methods on the Section class that sub-classes must provide.
- Code:
Section.prototype.getStartOffset = function()
{
return 0;
};
Section.prototype.getEndOffset = function()
{
return 0;
};
The StraightSection class can implement these easily, and only actually needs to override one of them to do so:
- Code:
StraightSection.prototype.getEndOffset = function()
{
return this.length;
};
The CurvedSection class can implement both of them, since the start and end are actually in the middle of the space that the section takes up.
- Code:
CurvedSection.prototype.getStartOffset = function()
{
return this.radius;
};
CurvedSection.prototype.getEndOffset = function()
{
return this.radius;
};
But that means that the circle must be centered at (this.radius, this.radius, 0), so when you create the mesh, make sure to set its position as such. Also, we don't want the user of the section to be able to change that position, so make this.object3D a THREE.Group, and add the Mesh to that.
The Track class will now need to use those new methods to determine where to place a given Section with respect to the previous Section.
Airman wrote:parentNode.add( this.object3D ); // this line is from Ball.createObject3D
I don't think we will need that line. We won't be passing in the parent node, the code that uses this class will know that it has an object3D property, and use it directly.
Re: Animate the PI = 4 experiment
I should add a little bit about coordinate systems here, so that some of my above post makes a bit more sense. I talked about the sections varying along the X dimension, and always having Y and Z = 0. When I talked about that, I meant in the Track coordinate system. This is different from the Sections own coordinate system. You have to think about them from the inside-out.
Every scene node has its own coordinate system. When you alter its position vector, for example, you are doing so in that coordinate system. You are saying: "Relative to your origin, your position is (X,Y,Z)".
The new methods I defined for Section, getStartOffset and getEndOffset, are in that Sections own coordinate system. So if the CurvedSection class returns its own radius as its starting offset, that means that relative to its left most edge (since we are working in the X dimension), the starting position (i.e. what we want to connect to the last sections end position) will be 1 radii to the right.
To keep things simple, which is always a good thing, we define a Section as starting at 0 and increasing in size to the right (+X), in its own coordinate system. That means that any nodes you create inside of that Section need to honor that. StraightSection is simple, is just starts at 0 and creates a tube that is this.length long in the X dimension (although you may need to rotate the tube to get it into the X dimension if it is not already there). CurvedSection is a bit more of a problem, because the connection point is halfway along the space that the curve takes up. Half of the circle is before the connection point, and half is after it. This means that the circle actually overlaps the last and next sections, in the X dimension.
The Track class needs to adjust the positions of each Section in order to place them in the correct positions to create the track. To do that, it will calculate the bounds of each Section (see the THREE.Box3 class for how to do this) and use the X value of those bounds to place them. Track will need to subtract the value returned from Section.getStartOffset() from that position to place curved sections correctly. Similarly, it will need to add the value returned from Section.getEndOffset() as well when it places the next Section. Which will look something like this:
Turns out, we don't actually need to use the bounds to place the sections. I've been thinking all along that we would, but now that we get there, we don't need it. The offsets take care of it.
See how that method is just moving each section along the X dimension, given the start and end positions of the last section? All Sections will line up, connecting at the correct places to form the complete track.
Also notice that this is where we add the section's node to the track node, rather than passing in a parentNode parameter. In turn, the Tracks object3D node will need to be added to the scene at some point, but not in this method. That is outside of the scope for this method which only deals with initializing the Track.
Every scene node has its own coordinate system. When you alter its position vector, for example, you are doing so in that coordinate system. You are saying: "Relative to your origin, your position is (X,Y,Z)".
The new methods I defined for Section, getStartOffset and getEndOffset, are in that Sections own coordinate system. So if the CurvedSection class returns its own radius as its starting offset, that means that relative to its left most edge (since we are working in the X dimension), the starting position (i.e. what we want to connect to the last sections end position) will be 1 radii to the right.
To keep things simple, which is always a good thing, we define a Section as starting at 0 and increasing in size to the right (+X), in its own coordinate system. That means that any nodes you create inside of that Section need to honor that. StraightSection is simple, is just starts at 0 and creates a tube that is this.length long in the X dimension (although you may need to rotate the tube to get it into the X dimension if it is not already there). CurvedSection is a bit more of a problem, because the connection point is halfway along the space that the curve takes up. Half of the circle is before the connection point, and half is after it. This means that the circle actually overlaps the last and next sections, in the X dimension.
The Track class needs to adjust the positions of each Section in order to place them in the correct positions to create the track. To do that, it will calculate the bounds of each Section (see the THREE.Box3 class for how to do this) and use the X value of those bounds to place them. Track will need to subtract the value returned from Section.getStartOffset() from that position to place curved sections correctly. Similarly, it will need to add the value returned from Section.getEndOffset() as well when it places the next Section. Which will look something like this:
- Code:
Track.prototype.init = function()
{
...
this.object3D = new THREE.Group();
var pos = new THREE.Vector3();
for( var i=0; i<this.sections.length; i++ )
{
var s = this.sections[i];
pos.x -= s.getStartOffset();
s.object3D.position.copy( pos );
pos.x += s.getEndOffset(); // move it along for the next section
this.object3D.add( s.object3D );
}
...
};
Turns out, we don't actually need to use the bounds to place the sections. I've been thinking all along that we would, but now that we get there, we don't need it. The offsets take care of it.
See how that method is just moving each section along the X dimension, given the start and end positions of the last section? All Sections will line up, connecting at the correct places to form the complete track.
Also notice that this is where we add the section's node to the track node, rather than passing in a parentNode parameter. In turn, the Tracks object3D node will need to be added to the scene at some point, but not in this method. That is outside of the scope for this method which only deals with initializing the Track.
Re: Animate the PI = 4 experiment
I missed a very important thing in that Track.init method. We have to initialize the Sections.
We call Section.init before we use getStartOffset and getEndOffset. In most cases that won't actually matter, it doesn't in the implementations that I wrote above, but we should allow for a situation where the get*Offset methods need things performed in the init method and calculate values based on that.
- Code:
Track.prototype.init = function()
{
...
this.object3D = new THREE.Group();
var pos = new THREE.Vector3();
for( var i=0; i<this.sections.length; i++ )
{
var s = this.sections[i];
s.init();
pos.x -= s.getStartOffset();
s.object3D.position.copy( pos );
pos.x += s.getEndOffset(); // move it along for the next section
this.object3D.add( s.object3D );
}
...
};
We call Section.init before we use getStartOffset and getEndOffset. In most cases that won't actually matter, it doesn't in the implementations that I wrote above, but we should allow for a situation where the get*Offset methods need things performed in the init method and calculate values based on that.
Re: Animate the PI = 4 experiment
.
Thanks for the education, Nevyn. I’m most grateful for your explanations; at the same time I feel like a most un-gifted object oriented coder, embarrassed for the frustrations I must be causing you.
No directives taken, that’s way too advanced. My only ‘directives’, if you want to call them that, are to not be afraid to try things and to not worry about breaking them. I’m not real familiar with with app development team etiquette. I hope I’m not stepping on your toes.
Tasks, on the other hand, I’m perfectly good with, even when I don't succeed. Tasks are specific items, details that need to be accomplished in due course and good order so the project can progress toward completion. Were it not for the tasks you’ve provided, TODOs in your posted code, clear guidance in your comments and explicit code blocks, I would have gotten nowhere. Thank you very much for organizing/breaking this project down into workable tasks so that I might learn a few things.
I believe I’ve followed most of the instructions you provided in your latest series of posts. Unfortunately, not without errors.
I removed all instances of var r, replacing them with var radius. No problems.
I added the five new getStartOffset and getEndOffset functions without any difficulty.
StraightSection.prototype.createObject3D() and CurvedSection.prototype.createObject3D() are now StraightSection.prototype.init() and CurvedSection.prototype.init(). Having struggled with the names, and the questions, I better appreciate some of the naming considerations.
I removed the this.sections ‘property’ from both of those init functions, the CurvedSection.init change will be included in my next BitBucket Commit.
I have a problem with both those functions. I was unable to change the function parameters from length to this.length, or from radius to this.radius without receiving two errors: Ball is not defined and Track is not defined.
There are also three problems I encountered when making your recommended additions to Track.init()
2. s.object3D.position.copy( pos ); causes the error - does not recognize position, and is commented out.
3. this.object3D.add( s.object3D ); // THREE.Object3D.add: object not an instance of this.object3D. undefined.
I'll keep at it.
.
Thanks for the education, Nevyn. I’m most grateful for your explanations; at the same time I feel like a most un-gifted object oriented coder, embarrassed for the frustrations I must be causing you.
Nevyn wrote:That wasn't a directive. I only meant that I was going to start with those, because they are the simplest from an interface perspective. They are not the simplest from an implementation perspective. Although they need to be done. It doesn't matter if that is what you want to work on.
No directives taken, that’s way too advanced. My only ‘directives’, if you want to call them that, are to not be afraid to try things and to not worry about breaking them. I’m not real familiar with with app development team etiquette. I hope I’m not stepping on your toes.
Tasks, on the other hand, I’m perfectly good with, even when I don't succeed. Tasks are specific items, details that need to be accomplished in due course and good order so the project can progress toward completion. Were it not for the tasks you’ve provided, TODOs in your posted code, clear guidance in your comments and explicit code blocks, I would have gotten nowhere. Thank you very much for organizing/breaking this project down into workable tasks so that I might learn a few things.
I believe I’ve followed most of the instructions you provided in your latest series of posts. Unfortunately, not without errors.
I removed all instances of var r, replacing them with var radius. No problems.
I added the five new getStartOffset and getEndOffset functions without any difficulty.
StraightSection.prototype.createObject3D() and CurvedSection.prototype.createObject3D() are now StraightSection.prototype.init() and CurvedSection.prototype.init(). Having struggled with the names, and the questions, I better appreciate some of the naming considerations.
I removed the this.sections ‘property’ from both of those init functions, the CurvedSection.init change will be included in my next BitBucket Commit.
I have a problem with both those functions. I was unable to change the function parameters from length to this.length, or from radius to this.radius without receiving two errors: Ball is not defined and Track is not defined.
There are also three problems I encountered when making your recommended additions to Track.init()
- Code:
Track.prototype.init = function()
{
// TODO initialize this track
// Each track type needs its own initialization.
this.object3D = new THREE.Group();
var pos = new THREE.Vector3();
for( var i=0; i<this.sections.length; i++ )
{
var s = this.sections[i];
// s.init();
// error - sections is not defined
pos.x -= s.getStartOffset();
// s.object3D.position.copy( pos );
// error - does not recognize position //
pos.x += s.getEndOffset(); // move it along for the next section
// this.object3D.add( s.object3D );
// error // THREE.Object3D.add: object not an instance of this.object3D. undefined
};
};
2. s.object3D.position.copy( pos ); causes the error - does not recognize position, and is commented out.
3. this.object3D.add( s.object3D ); // THREE.Object3D.add: object not an instance of this.object3D. undefined.
I'll keep at it.
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
Airman wrote:1. s.init() causes a - sections is not defined error, and so the line is commented out.
2. s.object3D.position.copy( pos ); causes the error - does not recognize position, and is commented out.
3. this.object3D.add( s.object3D ); // THREE.Object3D.add: object not an instance of this.object3D. undefined.
All of these errors are probably caused by the Section.init methods which are not setup correctly. The first one could be caused by the Track class not having a sections property, so the line before s.init() is where the actual error occurs, but I don't think it is because the next error suggests that s has a value, and that value has a property called object3D, but that property is null or undefined. But then the last error suggests that s.object3D is not defined, so these errors are a bit conflicting. Maybe you saw them at different times. If you see them all at the same time (same execution of the app), then ... actually, no, you can't see all of those errors at the same time.
Things are not going to work until everything is in place, or at least in a runnable state. I believe, without looking at the code, that these are all caused in the Section sub-classes. Make sure that they are in a good state, or at least follow the errors down into that code and see where the problems are. The Sections must be initialized, so don't comment it out hoping for the best. That won't help. You have to mine down into the code to find what is wrong. Just follow the methods. If s.init() causes a problem, then figure out which Section class is being used at the time of the error and look at its init method.
After looking at the code, here is the first problem (actually, it causes all of the problems):
- Code:
CurvedSection.prototype.init = function(radius)
{
if(sections[1].radius) //Only this.sections[1].radius is used, 4 or pi, in determining the circular track's position.
{
this.object3D = new THREE.Group();
var curvedGeometry = new THREE.TorusBufferGeometry( radius, Track.Ball.ballRadius, 20, 48 );
this.material = new THREE.MeshStandardMaterial( { color: 0x0c0c0c, transparent: true, opacity: 0.4, blending: THREE.AdditiveBlending, side: THREE.DoubleSide } ); //
this.object3D = new THREE.Mesh( curvedGeometry, this.material );
this.object3D.rotation.x = Math.PI/2; // align the track about the y axis
// 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.
if (this.sections[1].radius === 4)
{
this.object3D.position.set(-4*radius, 0, -2*radius );
}
else
{
this.object3D.position.set(-4*radius, 0, 2*radius );
};
parentNode.add( this.object3D );
};
};
That is referring to sections[1].radius, which is not defined, and should not be. I don't know what that code is supposed to be doing, but it should not be referring to any array of sections. It should also not be taking in a radius parameter. It should refer to this.radius.
It also refers to Track.Ball.ballRadius which is not valid. That does present a problem that needs to be solved though. We need to know the radius of the tubes. This is a bit of a pain, really, but not insurmountable. I don't want to store that tube radius on each Section object (which would also require the Track class to store it too). So I am going to introduce a parameter to the init method that will be an object that can have special properties like this.
So we need to change all Section.init methods, and Track.init, to accept a new parameter:
- Code:
Track.prototype.init = function( params )
{
// make sure we have a value for params so we don't need to check it in all subsequent code
if( typeof params !== 'object' )
{
params = {};
}
// make sure we have values for required parameters
if( typeof params.tubeRadius !== 'number' )
{
params.tubeRadius = 1; // set some default tube radius
}
...
// TODO pass params to Section.init
...
};
Section.prototype.init = function( params )
{
...
};
// and so on for all Section sub-classes
You will also need to set that up in the app code where you initialize the track objects and pass it in to the Track.init method call.
Back to CurvedSection.prototype.init.
The this.object3D property is being set twice. The first one creates a Group, but is then overridden by a Mesh. It should be a Group that then contains the Mesh.
Then get rid of that positioning code.
The Track.prototype.addMarker method has an extra } at the end (the second last one, not the last). That will cause problems.
Re: Animate the PI = 4 experiment
.
Once again, thanks for your cooperation, I corrected the problems you identified but came up with new ones. I still need to Commit my changes.
StraightSection.init() and Curved.Section.init() are now behaving correctly, using this.length and this.radius. I didn’t remove any other radius parameters.
Track.init(). The three problems I mentioned yesterday involving the need to comment out 1. s.init() 2. s.object3D.position.copy( pos ); and 3. this.object3D.add( s.object3D) are gone. Track.init() is now running properly.
Two problems remain.
Yesterday you saw the need for a new params{} list, including params.tubeRadius.
So I added the new params code to Track, Section, StriaghtSection and Curved Section; including the default tubeRadius size. No problemo.
There were no existing KinematicSection.init(), or GeometricSection.init() functions. When I try to create those two init functions – either empty or containing only the new params code, I get the errors.
.
Once again, thanks for your cooperation, I corrected the problems you identified but came up with new ones. I still need to Commit my changes.
Thanks, acknowledged, commented out.The this.object3D property is being set twice. The first one creates a Group, but is then overridden by a Mesh. It should be a Group that then contains the Mesh.
Done.Then get rid of that positioning code.
I believe the brackets just needed proper indentation, they look good to me.The Track.prototype.addMarker method has an extra } at the end (the second last one, not the last). That will cause problems.
StraightSection.init() and Curved.Section.init() are now behaving correctly, using this.length and this.radius. I didn’t remove any other radius parameters.
Track.init(). The three problems I mentioned yesterday involving the need to comment out 1. s.init() 2. s.object3D.position.copy( pos ); and 3. this.object3D.add( s.object3D) are gone. Track.init() is now running properly.
Two problems remain.
Yesterday you saw the need for a new params{} list, including params.tubeRadius.
...
So we need to change all Section.init methods, and Track.init, to accept a new parameter:
- Code:
...
// and so on for all Section sub-classes.
So I added the new params code to Track, Section, StriaghtSection and Curved Section; including the default tubeRadius size. No problemo.
There were no existing KinematicSection.init(), or GeometricSection.init() functions. When I try to create those two init functions – either empty or containing only the new params code, I get the errors.
- Code:
//KinematicSection.prototype.init() = function( radius ) // ReferenceError: Invalid left-hand side in assignment
// GeometricSection.prototype.init() = function( radius ) // ReferenceError: Invalid left-hand side in assignment
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
Airman wrote:I believe the brackets just needed proper indentation, they look good to me.
Yes, that is correct. Indentation is so important to writing good code, and more importantly, to reading that code, that I assumed it was correct before reading the actual code. My bad. Glad you didn't just accept what I said.
Airman wrote:There were no existing KinematicSection.init(), or GeometricSection.init() functions. When I try to create those two init functions – either empty or containing only the new params code, I get the errors.Do they really need the tubeRadius?
- Code:
http://KinematicSection.prototype.init() = function( radius ) // ReferenceError: Invalid left-hand side in assignment
// GeometricSection.prototype.init() = function( radius ) // ReferenceError: Invalid left-hand side in assignment
No, you shouldn't need an init method on the Kinematic or Geometric curve classes. We can initialize in CurvedSection because it already knows the radius.
However, just for clarity, the problem is the () after init.
The radius variable is still being used everywhere that it shouldn't.
- Code:
function Ball()
{
this.ballRadius = radius*0.225;
this.ballVelocity = velocity;
};
It is used here in the Ball constructor, but what should happen is that it is passed in as a parameter. Velocity is also not defined, and should be a parameter.
- Code:
function Ball( radius, velocity )
{
this.ballRadius = radius;
this.ballVelocity = velocity;
};
I also don't like those property names. This is a constructor for the Ball class, so we don't need the ball prefix on its own properties. Just call them radius and velocity.
You should also declare all public properties in the constructor so that users know what is available. This is probably my fault, as I may have forgotten to do that in my examples.
- Code:
function Ball( radius, velocity )
{
this.ballRadius = radius;
this.ballVelocity = velocity;
this.material = null;
this.object3D = null;
};
Similar problems with Section:
- Code:
Section.prototype.init = function( params )
{
// make sure we have a value for params so we don't need to check it in all subsequent code
if( typeof params !== 'object' )
{
params = {};
}
// make sure we have values for required parameters
if( typeof params.tubeRadius !== 'number' )
{
params.tubeRadius = 0.225*radius;; // set some default tube radius
};
};
It refers to radius when it shouldn't. It also shouldn't need to make sure that params is a valid object and contains the tubeRadius value. That is done in the Track class. Although checking is a good thing, we can define the method as requiring those things so that we don't have to. While we could do that for Track too, I didn't because Track is going to be used by the app, where-as Section is used by Track, so we have more control over it and can make such assumptions.
- Code:
StraightSection.prototype.init = function()
// probably need params to the parameters of all section init functions
{
// make sure we have a value for params so we don't need to check it in all subsequent code
if( typeof params !== 'object' )
{
params = {};
}
// make sure we have values for required parameters
if( typeof params.tubeRadius !== 'number' )
{
params.tubeRadius = 0.225*this.radius; // set some default tube radius
};
this.object3D = new THREE.Group();
var straightGeometry = new THREE.CylinderBufferGeometry( params.tubeRadius, params.tubeRadius, this.length, 20, 12, true );
this.material = new THREE.MeshStandardMaterial( { color: 0x0c0c0c, transparent: true, opacity: 0.4, blending: THREE.AdditiveBlending, side: THREE.DoubleSide } ); //
this.object3D = new THREE.Mesh( straightGeometry, this.material );
this.object3D.rotation.z = -Math.PI/2; // orient the track along the x axis
// 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.
// All tracks have either a straight section, 10*radius long, or an initial straight 2*radius long.
};
That doesn't declare the params parameters, even though the code uses it. It also sets the tubeRadius to 0.225 * this.radius, which would be the wrong radius to use. Again, it doesn't need to set that value, just assume that it exists.
- Code:
Track.prototype.init = function()
{
// make sure we have a value for params so we don't need to check it in all subsequent code
if( typeof params !== 'object' )
{
params = {};
}
// make sure we have values for required parameters
if( typeof params.tubeRadius !== 'number' )
{
params.tubeRadius = 0.225*radius; // set some default tube radius
}
//...
// TODO pass params to Section.init
//...
// TODO initialize this track
// Each track type needs its own initialization.
this.object3D = new THREE.Group();
var pos = new THREE.Vector3();
for( var i=0; i<this.sections.length; i++ )
{
var s = this.sections[i];
s.init();
pos.x -= s.getStartOffset();
s.object3D.position.copy( pos );
pos.x += s.getEndOffset(); // move it along for the next section
this.object3D.add( s.object3D );
};
};
This also needs the params parameter defined. It also needs to pass it along the s.init.
I haven't mentioned this before, but Track.init must also initialize the Ball, and add the ball.object3D property to this.object3D.
This also uses that radius variable when it shouldn't. You can use that radius in the app code, which will create the object that becomes params in this method. Setting the default value for tubeRadius is a fallback for when the user doesn't supply it. It can not know what the user wanted, so it can't use that radius. That is why I set it to 1 in my example. It is not the value that we want, but it isn't the place to know what that value is. This might seem pedantic, but it has its purposes, and reduces potential problems in the future.
This is all coming along pretty well. It probably doesn't feel like it to you, but don't worry about it. This is complicated stuff.
Re: Animate the PI = 4 experiment
Actually, Section.init should not have any code in the method body. It should be empty, because it has nothing to initialize, yet it needs to define the method. It is the sub-classes that need to fill in the method body.
- Code:
Section.prototype.init = function( params )
{
};
Re: Animate the PI = 4 experiment
.
I thought I was doing ok until I lost track in your long list of my errors.
Coding sure requires a high degree of neatness and accuracy, but more important, clear thinking.
It’s probably clear to you that if I do not follow your instructions, or do the exact opposite of what you say, it’s likely because I didn’t properly understand what you said in the first place.
I’ve corrected a few of the errors, I think. I’ll need a couple of days to work on some more, for example, I need to start looking at declaring all public properties in the constructors. And I should try and ask better questions too.
.
I thought I was doing ok until I lost track in your long list of my errors.
Coding sure requires a high degree of neatness and accuracy, but more important, clear thinking.
It’s probably clear to you that if I do not follow your instructions, or do the exact opposite of what you say, it’s likely because I didn’t properly understand what you said in the first place.
I’ve corrected a few of the errors, I think. I’ll need a couple of days to work on some more, for example, I need to start looking at declaring all public properties in the constructors. And I should try and ask better questions too.
Thank you Sir, I needed that.This is all coming along pretty well. It probably doesn't feel like it to you, but don't worry about it. This is complicated stuff.
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
.
I believe this image shows the object oriented PI=4 constructor class hierarchy containing all public properties, so far anyway. The Marker constructor might still be preliminary(?).
I should have explained the blue and yellow balls image last time. Track.init() is creating the yellow ball, actually three coincident yellow wire frame balls.. The blue ball is created with its own initialization function as previously. I don’t know why they are different sizes, and an error.
Got it. Declare all public properties. Thanks for guidance. I see your Ball constructor includes this.material and this.object3D even though those properties are defined in another Ball function, Ball.createObject3D. Including those properties in the constructor function allows easy reference. Recommending that I do the same for the remaining constructors is a great idea, in fact a highly recommended, best practice standard which makes code much easier to read, write or understand. Just what I happen to need at the moment.
I tried including all the public properties currently in the PI=4 Application Constructor Class Hierachy image at the top. Feel free to mark up or correct. I’m not sure whether params should be sent to Track.init(), etc.
Airman wrote: Some StraightSection.prototype.init = function() nonsense.
While listing the public properties I soon realized how params should be defined (I hope) in the Section constructor, as shown in the image, so that the params.tubeRadius property is available to both the Section subclass init functions.
Another several items calls to question my use/misuse of radius. The only param at present is tubeRadius. For convenience I set the tube radius equal to the balls’ radius equal to 0.225*radius. radius is supposed to be the global variable equal to the track radius. Currently, the ball radius is specified by Track.init() and is passed, along with the velocity, as parameters to the Ball() constructor function. I still need to understand how you use radius. Why not include the ball radius as a second params property?
Currently I include it, should I include (params) in the Track.init(params) function?
A couple of small changes, like adding this.material = null; and this.object3D = null; to the CurvedSection constructor.
Note: I’m still punch drunk from last time, thanks for letting me know me I missed something, again.
.
I believe this image shows the object oriented PI=4 constructor class hierarchy containing all public properties, so far anyway. The Marker constructor might still be preliminary(?).
I should have explained the blue and yellow balls image last time. Track.init() is creating the yellow ball, actually three coincident yellow wire frame balls.. The blue ball is created with its own initialization function as previously. I don’t know why they are different sizes, and an error.
andNevyn wrote: You should also declare all public properties in the constructor so that users know what is available.
- Code:
function Ball( radius, velocity )
{
this.radius = radius;
this.velocity = velocity;
this.material = null;
this.object3D = null;
};
Got it. Declare all public properties. Thanks for guidance. I see your Ball constructor includes this.material and this.object3D even though those properties are defined in another Ball function, Ball.createObject3D. Including those properties in the constructor function allows easy reference. Recommending that I do the same for the remaining constructors is a great idea, in fact a highly recommended, best practice standard which makes code much easier to read, write or understand. Just what I happen to need at the moment.
I tried including all the public properties currently in the PI=4 Application Constructor Class Hierachy image at the top. Feel free to mark up or correct. I’m not sure whether params should be sent to Track.init(), etc.
Airman wrote: Some StraightSection.prototype.init = function() nonsense.
.Nevyn wrote. That doesn't declare the params parameters, even though the code uses it. It also sets the tubeRadius to 0.225 * this.radius,
While listing the public properties I soon realized how params should be defined (I hope) in the Section constructor, as shown in the image, so that the params.tubeRadius property is available to both the Section subclass init functions.
Another several items calls to question my use/misuse of radius. The only param at present is tubeRadius. For convenience I set the tube radius equal to the balls’ radius equal to 0.225*radius. radius is supposed to be the global variable equal to the track radius. Currently, the ball radius is specified by Track.init() and is passed, along with the velocity, as parameters to the Ball() constructor function. I still need to understand how you use radius. Why not include the ball radius as a second params property?
- Code:
Section.prototype.init = function( params ) // I can omit params with no error.
{
};
Currently I include it, should I include (params) in the Track.init(params) function?
A couple of small changes, like adding this.material = null; and this.object3D = null; to the CurvedSection constructor.
Note: I’m still punch drunk from last time, thanks for letting me know me I missed something, again.
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
Hey Guys, just wanted to introduce a conference that we missed last year. If this happens again soon...might be worth submitting the PI=4 app as proposal?
https://cs2018.sciencesconf.org/
Latest workshop next week: https://sigma2020.projet.liris.cnrs.fr/program.html
Welcome to Curves and Surfaces
Welcome to 9th International Conference on Curves and Surfaces, organised by SMAI-SIGMA.
The conference will take place from June 28 to July 4, 2018 at the Palais des Congrès in Arcachon, France.
Registration and accomodation are open.
Conference topics
Overall theme: “Representation and Approximation of Curves and Surfaces and Applications” with subtopics (in alphabetical order):
Approximation theory
Computer-aided geometric design
Computer graphics and visualisation
Computational geometry and topology
Geometry processing
Image and signal processing
Interpolation and smoothing
Mesh generation, finite elements and splines
Scattered data processing and learning theory
Sparse and high-dimensional approximation
Subdivision, wavelets and multi-resolution methods
as well as related applications in manufacturing, mechanics, solid modelling, terrain modelling, oceanography, geosciences, life sciences ...
Invited speakers
Alexander Bobenko (Technische Universität Berlin)
Emmanuel Candes (Stanford University)
Maria Charina (Universität Wien)
Elaine Cohen (University of Utah at Salt Lake City)
Philipp Grohs (Universität Wien)
Frances Kuo (University of South Wales)
Mauro Maggioni (Johns Hopkins University)
Jorg Peters (University of Florida at Gainsville)
Amit Singer (Princeton University)
Max Wardetzky (Georg-August Universität Goettingen)
Mini-symposia
Constrained approximation (Dany Leviatan, Tel Aviv University)
High dimensional approximation (Vladimir Temlyakov, University of South Carolina)
Applications in energy industry (Christian Gout, INSA Rouen)
Isogeometric Analysis (Giancarlo Sangalli, Universita di Pavia ; Mario Kapl, RICAM)
Mathematical aspects of 3D printing (Georg Muntingh, Sintef Oslo)
Advances in radial basis approximation (Thomas Hangelbroek, University of Hawaii at Manoa)
Topological data analysis and learning (Steve Oudot, INRIA Saclay)
Shape processing (Martin Rumpf, Universität Bonn)
PDE and variational methods for geometry processing for images (Carola-Bibiane Schoenlieb, Cambridge University ; Simon Masnou, Université de Lyon)
Advances on Prony’s methods (Stefan Kunis, Universität Osnabrueck)
https://cs2018.sciencesconf.org/
Latest workshop next week: https://sigma2020.projet.liris.cnrs.fr/program.html
Welcome to Curves and Surfaces
Welcome to 9th International Conference on Curves and Surfaces, organised by SMAI-SIGMA.
The conference will take place from June 28 to July 4, 2018 at the Palais des Congrès in Arcachon, France.
Registration and accomodation are open.
Conference topics
Overall theme: “Representation and Approximation of Curves and Surfaces and Applications” with subtopics (in alphabetical order):
Approximation theory
Computer-aided geometric design
Computer graphics and visualisation
Computational geometry and topology
Geometry processing
Image and signal processing
Interpolation and smoothing
Mesh generation, finite elements and splines
Scattered data processing and learning theory
Sparse and high-dimensional approximation
Subdivision, wavelets and multi-resolution methods
as well as related applications in manufacturing, mechanics, solid modelling, terrain modelling, oceanography, geosciences, life sciences ...
Invited speakers
Alexander Bobenko (Technische Universität Berlin)
Emmanuel Candes (Stanford University)
Maria Charina (Universität Wien)
Elaine Cohen (University of Utah at Salt Lake City)
Philipp Grohs (Universität Wien)
Frances Kuo (University of South Wales)
Mauro Maggioni (Johns Hopkins University)
Jorg Peters (University of Florida at Gainsville)
Amit Singer (Princeton University)
Max Wardetzky (Georg-August Universität Goettingen)
Mini-symposia
Constrained approximation (Dany Leviatan, Tel Aviv University)
High dimensional approximation (Vladimir Temlyakov, University of South Carolina)
Applications in energy industry (Christian Gout, INSA Rouen)
Isogeometric Analysis (Giancarlo Sangalli, Universita di Pavia ; Mario Kapl, RICAM)
Mathematical aspects of 3D printing (Georg Muntingh, Sintef Oslo)
Advances in radial basis approximation (Thomas Hangelbroek, University of Hawaii at Manoa)
Topological data analysis and learning (Steve Oudot, INRIA Saclay)
Shape processing (Martin Rumpf, Universität Bonn)
PDE and variational methods for geometry processing for images (Carola-Bibiane Schoenlieb, Cambridge University ; Simon Masnou, Université de Lyon)
Advances on Prony’s methods (Stefan Kunis, Universität Osnabrueck)
Chromium6- Posts : 818
Join date : 2019-11-29
Re: Animate the PI = 4 experiment
.
Hey Chromium6, Curves and Surfaces sounds good. A few of the topics might be interesting. The sad truth is, I can’t imagine going to France to attend the conference. I’ll tell you this though, I didn’t go last time, but I would certainly consider going to the next Charge Field conference, but not if it were in France.
Sorry Boss, still slow going. Although this probably doesn’t count as ‘progress’. Both the Track initiated (yellow) and function generated (blue) spheres are the same size. Their divisions are slightly different, but both their radii now equal 0.225*tubeRadius, the yellow sphere’s radius no longer defaults to 1, as the default params.tubeRadius is defined in Track.init. That occurred after I made params a global variable.
But that cannot be the solution. Your previous code included params in the init(params) calls, making params a global means I don’t need to pass (params) anywhere.
I guess I’m still trying to make sense of that last sentence.
.
Hey Chromium6, Curves and Surfaces sounds good. A few of the topics might be interesting. The sad truth is, I can’t imagine going to France to attend the conference. I’ll tell you this though, I didn’t go last time, but I would certainly consider going to the next Charge Field conference, but not if it were in France.
Sorry Boss, still slow going. Although this probably doesn’t count as ‘progress’. Both the Track initiated (yellow) and function generated (blue) spheres are the same size. Their divisions are slightly different, but both their radii now equal 0.225*tubeRadius, the yellow sphere’s radius no longer defaults to 1, as the default params.tubeRadius is defined in Track.init. That occurred after I made params a global variable.
But that cannot be the solution. Your previous code included params in the init(params) calls, making params a global means I don’t need to pass (params) anywhere.
Monday 24 Feb 2020 at 7:36 pm Nevyn wrote. So we need to change all Section.init methods, and Track.init, to accept a new parameter:
- Code:
Track.prototype.init = function( params )
'''
You will also need to set that up in the app code where you initialize the track objects and pass it in to the Track.init method call.
I guess I’m still trying to make sense of that last sentence.
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
You have to stop relying on global variables. Yes, they make everything easy to access, but they make the code so reliant on external things that it becomes useless for any other purpose. It becomes what we call 'Spaghetti code', because there are connections all over the place that if you drew it in a diagram, it would look like spaghetti. Whenever you think about creating a global variable, you have to justify it to yourself. Ask if there is any other way to do what you need. 99% of the time, there is always a better way. Some things do belong as globals, but rarely.
None of the init methods declare the params parameter.
They should all look like this:
You are now passing params to the Sections in Track.init, but it is not passed in to Track.init as it should be. Even though it is passed in to the Section classes when the init method is called, it isn't actually being used because it is not declared as a parameter to those methods. So it is using the global variable (which is probably going to be the same object, but it works by accident, rather than by design).
If the params are not working, then it is because you are not passing it in when you call Track.init.
This is the problem right here. You are passing in a THREE.Group, instead of the params value.
That does not look good. The function that contains that code above should create the params object, give it the correct values, and pass it along to Track.init method calls (same object for all tracks). It doesn't need to be global, just a local variable in that function.
None of the init methods declare the params parameter.
- Code:
Track.prototype.init = function()
Section.prototype.init = function( )
CurvedSection.prototype.init = function( )
StraightSection.prototype.init = function( )
They should all look like this:
- Code:
Track.prototype.init = function( params )
Section.prototype.init = function( params )
CurvedSection.prototype.init = function( params )
StraightSection.prototype.init = function( params )
You are now passing params to the Sections in Track.init, but it is not passed in to Track.init as it should be. Even though it is passed in to the Section classes when the init method is called, it isn't actually being used because it is not declared as a parameter to those methods. So it is using the global variable (which is probably going to be the same object, but it works by accident, rather than by design).
If the params are not working, then it is because you are not passing it in when you call Track.init.
- Code:
// Initialize the tracks like this:
var group = new THREE.Group(); //
for( var i=0; i<tracks.length; i++ )
{
//tracks[i].createObject3D( group ); // error - this.sections[i].createObject3D( node ) is not a function
tracks[i].init( group ); // Looks good
};
This is the problem right here. You are passing in a THREE.Group, instead of the params value.
- Code:
tracks[i].init( group ); // Looks good
That does not look good. The function that contains that code above should create the params object, give it the correct values, and pass it along to Track.init method calls (same object for all tracks). It doesn't need to be global, just a local variable in that function.
- Code:
// Initialize the tracks like this:
var params = { tubeRadius: 0.255 * radius };
for( var i=0; i<tracks.length; i++ )
{
tracks[i].init( params ); // Now, that looks good!
};
Re: Animate the PI = 4 experiment
Here is the thought process for figuring out why a parameter is not working as you want it to.
1) Is it declared as a parameter to this method/function?
2) If yes, then find where this method/function is being called.
3) Is it being given to the method/function call?
4) Is it the correct object or value?
5) Is it a parameter to this method/function?
6) If yes, then go to 1) for this method/function.
You just have to follow the method/function calls until you find the problem. There is more to that list, but that is the guts of it. Just follow the calls.
1) Is it declared as a parameter to this method/function?
2) If yes, then find where this method/function is being called.
3) Is it being given to the method/function call?
4) Is it the correct object or value?
5) Is it a parameter to this method/function?
6) If yes, then go to 1) for this method/function.
You just have to follow the method/function calls until you find the problem. There is more to that list, but that is the guts of it. Just follow the calls.
Re: Animate the PI = 4 experiment
.
I hope you don’t find these continuing, repetitive images too tedious, I don’t. They are in no way intended to diminish the PI=4 Experiment. In my opinion, even without tracks they make fine charge field, circular motion diagrams.
While working with the code, they are spinning in my browser confirming the application is still running after my latest change. My routine is to make a change, reload the browser page, give the mouse wheel a few pushes, look, then swing the orbit control view. If it’s something worth sharing I’ll adjust the scene viewpoint and slow the motion down. Going to an extreme I changed the spheres’ division numbers for the image above. I’ll take the best out of maybe three screen captures. … They should be in focus. Saving these snapshots every now and then makes things more interesting, a unique way of journaling the project.
///////\\\\\\\///////\\\\\\\
Thanks Nevyn, a “thought process for figuring out why a parameter is not working” is without a doubt, something good to know. I can almost follow it, I need to advance a bit before I can readily agree.
Nevyn wrote:
Unfortunately, even with those changes an error remains. Line 338, containing, this.object3D.add( this.ball). When adding each of the three coincident yellow spheres.
The large number of errors (here 152) occur as I move the mouse wheel, dollying the scene view.
Here is how Track.init() currently adds a track ball after the sections are assembled.
///////\\\\\\\///////\\\\\\\
Here’s how I might define a track.
a. A track is a single path through space consisting of a series (one or two) of sections lined end-to-end.
b. A track section can be straight or curved.
c. Each track contains a ball.
d. A single function is used to create each track.
I don’t quite understand. Why does Section.getStartOffset() and Section.getEndOffset() return 0? Oh, they are prototype placeholder functions. It’s taking me this long to figure out I might should be working on making the StraightSection.getStartOffset() and CurvedSection.getEndOffset() functions. I find it difficult to work without seeing track sections on the screen.
.
I hope you don’t find these continuing, repetitive images too tedious, I don’t. They are in no way intended to diminish the PI=4 Experiment. In my opinion, even without tracks they make fine charge field, circular motion diagrams.
While working with the code, they are spinning in my browser confirming the application is still running after my latest change. My routine is to make a change, reload the browser page, give the mouse wheel a few pushes, look, then swing the orbit control view. If it’s something worth sharing I’ll adjust the scene viewpoint and slow the motion down. Going to an extreme I changed the spheres’ division numbers for the image above. I’ll take the best out of maybe three screen captures. … They should be in focus. Saving these snapshots every now and then makes things more interesting, a unique way of journaling the project.
///////\\\\\\\///////\\\\\\\
Thanks Nevyn, a “thought process for figuring out why a parameter is not working” is without a doubt, something good to know. I can almost follow it, I need to advance a bit before I can readily agree.
Nevyn wrote:
- Code:
var params = { tubeRadius: 0.255 * radius };
for( var i=0; i<tracks.length; i++ )
{
tracks[i].init( params ); // Now, that looks good!
};
Unfortunately, even with those changes an error remains. Line 338, containing, this.object3D.add( this.ball). When adding each of the three coincident yellow spheres.
The large number of errors (here 152) occur as I move the mouse wheel, dollying the scene view.
Here is how Track.init() currently adds a track ball after the sections are assembled.
- Code:
this.ball = new Ball(params.tubeRadius,1);
this.ball.createObject3D(grpBS);
grpBS.position.set(0, 0, 0); //
this.object3D.add( this.ball ); // line 338.
// line 338 error. three.min.js:434 THREE.Object3D.add: object not an instance of THREE.Object3D.
///////\\\\\\\///////\\\\\\\
Airman. Unless I’m sadly mistaken, I believe that requirement has been met.Nevyn wrote.
Four requirements:
1. The first thing we need is a way to define a track.
… Flexible and extensible … .
Here’s how I might define a track.
a. A track is a single path through space consisting of a series (one or two) of sections lined end-to-end.
b. A track section can be straight or curved.
c. Each track contains a ball.
d. A single function is used to create each track.
Airman. That's where I’m at. StraightSection and CurvedSection.init()Nevyn wrote.
2. The second thing we need is a way to convert the track
definition into 3D scene nodes.
I don’t quite understand. Why does Section.getStartOffset() and Section.getEndOffset() return 0? Oh, they are prototype placeholder functions. It’s taking me this long to figure out I might should be working on making the StraightSection.getStartOffset() and CurvedSection.getEndOffset() functions. I find it difficult to work without seeing track sections on the screen.
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
You can't add this.ball to the Object3D object, because it is not an instance of THREE.Object3D. You just need to access the property of this.ball that is the Object3D: this.ball.object3D. So it becomes:
In essence, we have 2 different, but related, hierarchies here. We have the class hierarchy, which is made up of Tracks and Sections and Balls. Then we have the scene hierarchy, which is made up of THREE.Object3D objects (and its many sub-classes). The Tracks and Sections and Balls all have their own Object3D to represent themselves, but we need to add them to the scene hierarchy. The init methods are there to create and add them to that scene.
That wasn't what I meant, sorry, I could have been clearer. I meant that we need a way to define a track programmatically. We need a way to write code that defines the tracks, etc. We are a long way down that path, but not quite there yet. Once the init methods are sorted out, that all should fall into place.
Yes, that is correct. They are just there to define the method signature so that users of the class know what methods to call, and so that sub-classes know what methods to implement for their own needs. It's great that you saw that yourself. That's progress.
I should also add that while these do provide the signatures, they also provide default implementations. So not every sub-class will need to override them. Which leads me to...
StraightSection.getStartOffset() does not need to exist. The default implementation (Section.getStartOffset) is already returning 0, which is what StraightSection wants to do, too. So it does not need to be implemented by StraightSection.
CurvedSection.getEndOffset() does need to exist, and the implementation I supplied is all that is needed. Here it is again so you don't have to find it in the posts above:
I think the Tracks and Sections are pretty close to being operational from a scene perspective. There won't be any motion of the ball just yet, but you should be seeing tracks pretty soon.
- Code:
this.object3D.add( this.ball.object3D );
In essence, we have 2 different, but related, hierarchies here. We have the class hierarchy, which is made up of Tracks and Sections and Balls. Then we have the scene hierarchy, which is made up of THREE.Object3D objects (and its many sub-classes). The Tracks and Sections and Balls all have their own Object3D to represent themselves, but we need to add them to the scene hierarchy. The init methods are there to create and add them to that scene.
Airman wrote:Unless I’m sadly mistaken, I believe that requirement has been met.
That wasn't what I meant, sorry, I could have been clearer. I meant that we need a way to define a track programmatically. We need a way to write code that defines the tracks, etc. We are a long way down that path, but not quite there yet. Once the init methods are sorted out, that all should fall into place.
Airman wrote:I don’t quite understand. Why does Section.getStartOffset() and Section.getEndOffset() return 0? Oh, they are prototype placeholder functions.
Yes, that is correct. They are just there to define the method signature so that users of the class know what methods to call, and so that sub-classes know what methods to implement for their own needs. It's great that you saw that yourself. That's progress.
I should also add that while these do provide the signatures, they also provide default implementations. So not every sub-class will need to override them. Which leads me to...
Airman wrote:It’s taking me this long to figure out I might should be working on making the StraightSection.getStartOffset() and CurvedSection.getEndOffset() functions. I find it difficult to work without seeing track sections on the screen.
StraightSection.getStartOffset() does not need to exist. The default implementation (Section.getStartOffset) is already returning 0, which is what StraightSection wants to do, too. So it does not need to be implemented by StraightSection.
CurvedSection.getEndOffset() does need to exist, and the implementation I supplied is all that is needed. Here it is again so you don't have to find it in the posts above:
- Code:
CurvedSection.prototype.getEndOffset = function()
{
return this.radius;
}
I think the Tracks and Sections are pretty close to being operational from a scene perspective. There won't be any motion of the ball just yet, but you should be seeing tracks pretty soon.
Re: Animate the PI = 4 experiment
It is probably a good time to go over how Javascript implements classes, since it really isn't an Object-Oriented language. I think I've gone over this before, but you probably weren't ready for the info then.
Javascript is a Prototyping language, rather than an Object-Oriented one. The main reason for that is that functions in Javascript are just objects. Most languages do not treat them as such. In a truelly Object-Oriented language, such as Java, a function (really a method, since everything is a class in Java) is something that a class has. It is part of the class. Javascript is more flexible (in some ways), and treating functions/methods as objects has some pretty cool advantages. But that isn't what I want to cover here. What we need to understand at the moment is how the prototype works.
To start writing a class, we actually create a function. What makes it a class, and not just any function, is how you call it. When you use the new operator, Javascript will create a new object and then call the function giving it that object (which becomes this inside of that function). However, we must back up a step, that function (which we call a Constructor, because it constructs an object of that class) is still an object in its own right. As an object, we can add properties to it, just like any object. There are times when you actually want to do that for your own needs, but I don't want to discuss that now either. The only one that matters right now is specifying the prototype property.
That is a constructor for the MyClass class, and creates a prototype for it. Notice that the Object.create method takes in an object as its parameter. That object becomes the parent prototype for MyClass. In this instance, we don't actually have a parent class, so it is just given an empty object. Let's have a look at what that does.
There is now a global object called MyClass, and that object is a function. It has a property called prototype which is an object:
Now, the prototype object also has a prototype, which is the object that we gave to the Object.create method above:
When you create an instance of the MyClass class, the MyClass.prototype object is given to it as its own prototype, so it looks like this:
See how the object has a property called name, which was created in the MyClass constructor, and it also has the same prototype as the MyClass function.
Let's create a class hierarchy:
In this example, we have created a new class called MySubClass, which extends MyClass. We have given the MyClass.prototype object to the Object.create function so that they all link up correctly. So it will look like this:
If we create an instance of MySubClass, it will look like this:
Now let's add some methods to make things interesting:
The classes will look like this:
An instance of MySubClass will look like this:
Let's make it even more interesting by overriding a parent method:
The classes will look like this:
An instance of MySubClass will look like this:
Notice that both toString methods are in the ptototype chain. So if we call the toString method, what happens?
The Javascript engine that is running your code will start with the object itself, and it will see if it has a property called toString. But it doesn't, so it will look for a prototype property. It finds one, so it looks for a toString property on that object, which it does find, so it executes it. The toString method actually found belongs to the MySubClass prototype, so that is the method that gets executed.
Let's say we call the setName method, what happens then? Javascript looks on the object, but doesn't find it. Then it looks at the prototype, but still doesn't find it. So it looks for a prototype property in the prototype, which it finds, and it looks in there. Finally, it finds a setName method, and it executes it. So in this case, it is the MyClass.setName method that gets executed, but on a MySubClass object.
Javascript will keep following the prototypes all the way down until it finds what it is looking for, or it doesn't find another prototype and throws an error.
The prototypes do not need to only contains functions/methods. They can contain any value you want. It can get tricky when you start putting values in there though, and you need to be careful that it is doing what you want it to do. Essentially, anything placed into the prototype is going to be the exact same property for all objects of that class. This is what we call a static variable, because it doesn't change per object, but rather, per class. This can be very useful at times. You do have to be very careful about how you access them. Retrieving the value is easy enough, and just done like any other property (object.property), but setting them is a different matter.
If you were to use the object.property syntax, then it is going to store it on the object, not in the prototype. So you need to actually dig down into the prototype chain yourself, like this: object.prototype.property = value, or object.prototype.prototype.property = value, etc. There are some tricks that I do to make that a lot more usable and readable and maintainable, but I have probably already given you too much information, so I won't go into that here.
Javascript is a Prototyping language, rather than an Object-Oriented one. The main reason for that is that functions in Javascript are just objects. Most languages do not treat them as such. In a truelly Object-Oriented language, such as Java, a function (really a method, since everything is a class in Java) is something that a class has. It is part of the class. Javascript is more flexible (in some ways), and treating functions/methods as objects has some pretty cool advantages. But that isn't what I want to cover here. What we need to understand at the moment is how the prototype works.
To start writing a class, we actually create a function. What makes it a class, and not just any function, is how you call it. When you use the new operator, Javascript will create a new object and then call the function giving it that object (which becomes this inside of that function). However, we must back up a step, that function (which we call a Constructor, because it constructs an object of that class) is still an object in its own right. As an object, we can add properties to it, just like any object. There are times when you actually want to do that for your own needs, but I don't want to discuss that now either. The only one that matters right now is specifying the prototype property.
- Code:
function MyClass()
{
this.name = 'unknown';
}
MyClass.prototype = Object.create( {} );
That is a constructor for the MyClass class, and creates a prototype for it. Notice that the Object.create method takes in an object as its parameter. That object becomes the parent prototype for MyClass. In this instance, we don't actually have a parent class, so it is just given an empty object. Let's have a look at what that does.
There is now a global object called MyClass, and that object is a function. It has a property called prototype which is an object:
- Code:
MyClass
- prototype
Now, the prototype object also has a prototype, which is the object that we gave to the Object.create method above:
- Code:
MyClass
- prototype
- prototype
When you create an instance of the MyClass class, the MyClass.prototype object is given to it as its own prototype, so it looks like this:
- Code:
object (of type MyClass)
- name
- prototype
- prototype
See how the object has a property called name, which was created in the MyClass constructor, and it also has the same prototype as the MyClass function.
Let's create a class hierarchy:
- Code:
function MyClass()
{
this.name = 'unknown';
}
MyClass.prototype = Object.create( {} );
function MySubClass()
{
MyClass.call( this );
this.type = 'child';
}
MySubClass.prototype = Object.create( MyClass.prototype );
In this example, we have created a new class called MySubClass, which extends MyClass. We have given the MyClass.prototype object to the Object.create function so that they all link up correctly. So it will look like this:
- Code:
MySubClass
- prototype
- prototype (from MyClass)
- prototype
If we create an instance of MySubClass, it will look like this:
- Code:
object (of type MySubClass)
- name
- type
- prototype (from MySubClass)
- prototype (from MyClass)
- prototype
Now let's add some methods to make things interesting:
- Code:
function MyClass()
{
this.name = 'unknown';
}
MyClass.prototype = Object.create( {} );
MyClass.prototype.setName = function( name )
{
this.name = name;
};
function MySubClass()
{
MyClass.call( this );
this.type = 'child';
}
MySubClass.prototype = Object.create( MyClass.prototype );
MySubClass.prototype.setType = function( type )
{
this.type = type;
};
The classes will look like this:
- Code:
MyClass
- prototype
- setName
- prototype
MySubClass
- prototype
- setType
- prototype (from MyClass)
- setName
- prototype
An instance of MySubClass will look like this:
- Code:
object (of type MySubClass)
- name
- type
- prototype (from MySubClass)
- setType
- prototype (from MyClass)
- setName
- prototype
Let's make it even more interesting by overriding a parent method:
- Code:
function MyClass()
{
this.name = 'unknown';
}
MyClass.prototype = Object.create( {} );
MyClass.prototype.setName = function( name )
{
this.name = name;
};
MyClass.prototype.toString = function()
{
return this.name;
};
function MySubClass()
{
MyClass.call( this );
this.type = 'child';
}
MySubClass.prototype = Object.create( MyClass.prototype );
MySubClass.prototype.setType = function( type )
{
this.type = type;
};
MySubClass.prototype.toString = function()
{
return this.name + '[' + this.type + ']';
};
The classes will look like this:
- Code:
MyClass
- prototype
- setName
- toString
- prototype
MySubClass
- prototype
- setType
- toString
- prototype (from MyClass)
- setName
- toString
- prototype
An instance of MySubClass will look like this:
- Code:
object (of type MySubClass)
- name
- type
- prototype (from MySubClass)
- setType
- toString
- prototype (from MyClass)
- setName
- toString
- prototype
Notice that both toString methods are in the ptototype chain. So if we call the toString method, what happens?
The Javascript engine that is running your code will start with the object itself, and it will see if it has a property called toString. But it doesn't, so it will look for a prototype property. It finds one, so it looks for a toString property on that object, which it does find, so it executes it. The toString method actually found belongs to the MySubClass prototype, so that is the method that gets executed.
Let's say we call the setName method, what happens then? Javascript looks on the object, but doesn't find it. Then it looks at the prototype, but still doesn't find it. So it looks for a prototype property in the prototype, which it finds, and it looks in there. Finally, it finds a setName method, and it executes it. So in this case, it is the MyClass.setName method that gets executed, but on a MySubClass object.
Javascript will keep following the prototypes all the way down until it finds what it is looking for, or it doesn't find another prototype and throws an error.
The prototypes do not need to only contains functions/methods. They can contain any value you want. It can get tricky when you start putting values in there though, and you need to be careful that it is doing what you want it to do. Essentially, anything placed into the prototype is going to be the exact same property for all objects of that class. This is what we call a static variable, because it doesn't change per object, but rather, per class. This can be very useful at times. You do have to be very careful about how you access them. Retrieving the value is easy enough, and just done like any other property (object.property), but setting them is a different matter.
If you were to use the object.property syntax, then it is going to store it on the object, not in the prototype. So you need to actually dig down into the prototype chain yourself, like this: object.prototype.property = value, or object.prototype.prototype.property = value, etc. There are some tricks that I do to make that a lot more usable and readable and maintainable, but I have probably already given you too much information, so I won't go into that here.
Re: Animate the PI = 4 experiment
A few issues I found in the latest source code:
When calling the super-class (what we usually call the parent class) constructor, you should be passing along the length parameter, not the this.length member variable, which wouldn't even have a value at that point in the code (because it gets set in the Section constructor). It should be:
Saw the same thing in CurvedSection and KinematicSection (which did it with the radius)
The init method should not return a value.
The Section.init method wants the whole params object, not just the tubeRadius value.
We aren't working with markers at the moment, but this constructor should not be taking in a parentNode. The Section classes will handle adding the marker's node to its own node via the Track.addMarker method, which calls the Section.addMarker method for the appropriate section for that marker (using its distance along the track).
In piEq4.js
After initializing each Track, you need to add tracks[ i ].object3D (I added space around the i so it doesn't make the text italic, space not needed in the code) to the scene somewhere. You have a few THREE.Groups setup, I'm not sure what they are for, or if they will stay (since some of them look like they are for balls, but they won't be needed anymore). You just need a group for the tracks. That should get very close to making the track visible.
- Code:
function StraightSection( length ) // Creating straight section
{
// call the Section constructor so we get all of its properties
Section.call( this, this.length );
this.material = null;
this.object3D = null;
};
When calling the super-class (what we usually call the parent class) constructor, you should be passing along the length parameter, not the this.length member variable, which wouldn't even have a value at that point in the code (because it gets set in the Section constructor). It should be:
- Code:
Section.init.call( this, length );
Saw the same thing in CurvedSection and KinematicSection (which did it with the radius)
- Code:
StraightSection.prototype.init = function( params )
{
this.object3D = new THREE.Group();
var straightGeometry = new THREE.CylinderBufferGeometry( params.tubeRadius, params.tubeRadius, this.length, 20, 12, true );
this.material = new THREE.MeshStandardMaterial( { color: 0x0c0c0c, transparent: true, opacity: 0.4, blending: THREE.AdditiveBlending, side: THREE.DoubleSide } ); //
var mesh = new THREE.Mesh( straightGeometry, this.material );
mesh.rotation.z = -Math.PI/2; // orient the track along the x axis
this.object3D.add( mesh );
//grpSS.add(this.object3D);
return this.object3D;
};
The init method should not return a value.
- Code:
Track.prototype.init = function( params )
{
...
var s = this.sections[i];
// TODO pass params to Section.init
s.init( params.tubeRadius ); // This line points to StraightSection.init which consists of:
...
};
The Section.init method wants the whole params object, not just the tubeRadius value.
- Code:
function Marker( distance, parentNode )
{
this.distance = distance; // this is the absolute distance along the full track /////////////////////////////
// create a THREE.Material for the marker which we can manipulate later
var markerGeo = new THREE.TorusBufferGeometry( params.tubeRadius*1.15, params.tubeRadius*0.075, 10, 48 );
// store the material as a property so that we can change it
this.markerMaterial = new THREE.MeshStandardMaterial( { color: 0x0c0c0c, transparent: true, opacity: 0.95, blending: THREE.AdditiveBlending, side: THREE.DoubleSide } ); //
// create a node to represent the marker, using this.material
this.object3D = new THREE.Mesh( markerGeo, markerMaterial );
// add the node to the parent node
parentNode.add( this.object3D );
};
We aren't working with markers at the moment, but this constructor should not be taking in a parentNode. The Section classes will handle adding the marker's node to its own node via the Track.addMarker method, which calls the Section.addMarker method for the appropriate section for that marker (using its distance along the track).
In piEq4.js
- Code:
function init()
{
scene = new THREE.Scene();
...
// Create an array of Tracks like this: // Prior Notes
var tracks = [
createSTrack(),
createKTrack( radius ),
createGTrack( radius )
];
// ...
// Initialize the tracks like this:
var params = { tubeRadius: 0.2225 * radius }; //
for( var i=0; i<tracks.length; i++ )
{
tracks[i].init( params ); // Now, that looks good!
// Begging your pardon, tracks have yet to be seen. Worse, I failed to mention an error
// I've been receiving with respect to adding bBall to the scene. Today params is
// no doubt looking good, and bBall has been changed to this.ball, the error remains.
// this.object3D.add( this.ball );
};
...
};
After initializing each Track, you need to add tracks[ i ].object3D (I added space around the i so it doesn't make the text italic, space not needed in the code) to the scene somewhere. You have a few THREE.Groups setup, I'm not sure what they are for, or if they will stay (since some of them look like they are for balls, but they won't be needed anymore). You just need a group for the tracks. That should get very close to making the track visible.
Re: Animate the PI = 4 experiment
.
Status. We have sections and balls that need to be repositioned. So far, only the circular tracks are in their ‘final’ positions. The two short straight sections have been moved in the negative x direction and they still need an additional change in their z positions.
Here’s the play by play.
In CurvedSection.init() I’ve moved both circular sections, in the z direction (up and down in the image), +/-3*Math.PI/4 depending on whether this.length equals piG or piK.
if this.length == 2 * Math.PI * radius, move the circular mesh, 3*Math.PI/4 below the long straight section; and
if this.length == 2 * 4 * radius, move the circular mesh, 3*Math.PI/4 above the long straight section.
Both curved tracks are moved in the negative x direction, (to the left) -4*this.radius. I think I need to tie this position to the getEndOffset associated with the short straight section.
In StraightSection.init(), the 10*r straight section is well positioned and doesn’t need to be moved.
if this.length == 2 * radius. The mesh is moved 2*this.length to the left. The two 2*radius length straight sections are aligned and superimposed at the left side of the 10*radius straight section.
//////\\\\\\//////\\\\\\//////\\\\\\
The technical difficulties follow.
\\\\\\//////\\\\\\//////\\\\\\//////
1. How do we assign the short straight section to either the piG or piK track?
In StraightSection.init, this.length equals either 2*radius or 10*radius. How does the app know to move the short straight section in the proper up or down z position? this.length does not equal piG or piK, those two this.length values are found in the CurvedSection.init function. Also, in StraightSection.init(), this.radius is undefined.
I considered adding a new passed variable called type to short straight sections. this.type = piK or piG, with no good ideas.
One possible solution. Differentiate the 2*radius long initial StraightSection into two new subclasses, say: StraightSectionKBegin or StraightSectionGBegin. The create circular track functions, createGTrack() or createKTrack() will call the appropriate initial short straight section. Creating new subclasses to choose between two different z positions seems like overkill.
//////\\\\\\//////\\\\\\//////\\\\\\
2. You mentioned We need a way to write code that defines the tracks.
Why is properly aligning the short straight track an issue? It’s an issue if we want the three track configuration, top to bottom, KTrack, StraightTrack, and GTrack. If so new short straight sub-classes might not be overkill.
Alternatively, we might want to be able start the track in an arbitrary z position. So long as the following curved track meets it at the proper x, y and z position and the sections and correct length, we don’t necessarily need a fixed +/- z position starting point.
\\\\\\//////\\\\\\//////\\\\\\//////
3. Not quite understanding your explanation of why it isn’t necessary, I would swear I need to use getStartOffset() to properly position the short straight section in the x direction.
//////\\\\\\//////\\\\\\//////\\\\\\
4. Again, I need to give it more thought, I have every intention to use getEndOffset() to properly position the circular track in the x direction. It seems I would need another offset in the z dimension as well.
.
Status. We have sections and balls that need to be repositioned. So far, only the circular tracks are in their ‘final’ positions. The two short straight sections have been moved in the negative x direction and they still need an additional change in their z positions.
Here’s the play by play.
In CurvedSection.init() I’ve moved both circular sections, in the z direction (up and down in the image), +/-3*Math.PI/4 depending on whether this.length equals piG or piK.
if this.length == 2 * Math.PI * radius, move the circular mesh, 3*Math.PI/4 below the long straight section; and
if this.length == 2 * 4 * radius, move the circular mesh, 3*Math.PI/4 above the long straight section.
Both curved tracks are moved in the negative x direction, (to the left) -4*this.radius. I think I need to tie this position to the getEndOffset associated with the short straight section.
In StraightSection.init(), the 10*r straight section is well positioned and doesn’t need to be moved.
if this.length == 2 * radius. The mesh is moved 2*this.length to the left. The two 2*radius length straight sections are aligned and superimposed at the left side of the 10*radius straight section.
//////\\\\\\//////\\\\\\//////\\\\\\
The technical difficulties follow.
\\\\\\//////\\\\\\//////\\\\\\//////
1. How do we assign the short straight section to either the piG or piK track?
In StraightSection.init, this.length equals either 2*radius or 10*radius. How does the app know to move the short straight section in the proper up or down z position? this.length does not equal piG or piK, those two this.length values are found in the CurvedSection.init function. Also, in StraightSection.init(), this.radius is undefined.
I considered adding a new passed variable called type to short straight sections. this.type = piK or piG, with no good ideas.
One possible solution. Differentiate the 2*radius long initial StraightSection into two new subclasses, say: StraightSectionKBegin or StraightSectionGBegin. The create circular track functions, createGTrack() or createKTrack() will call the appropriate initial short straight section. Creating new subclasses to choose between two different z positions seems like overkill.
//////\\\\\\//////\\\\\\//////\\\\\\
2. You mentioned We need a way to write code that defines the tracks.
Why is properly aligning the short straight track an issue? It’s an issue if we want the three track configuration, top to bottom, KTrack, StraightTrack, and GTrack. If so new short straight sub-classes might not be overkill.
Alternatively, we might want to be able start the track in an arbitrary z position. So long as the following curved track meets it at the proper x, y and z position and the sections and correct length, we don’t necessarily need a fixed +/- z position starting point.
\\\\\\//////\\\\\\//////\\\\\\//////
3. Not quite understanding your explanation of why it isn’t necessary, I would swear I need to use getStartOffset() to properly position the short straight section in the x direction.
//////\\\\\\//////\\\\\\//////\\\\\\
4. Again, I need to give it more thought, I have every intention to use getEndOffset() to properly position the circular track in the x direction. It seems I would need another offset in the z dimension as well.
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
I created a new branch because the changes I was making were a bit drastic. Just switch to the track-alignment branch to see those changes.
I was trying to get things to line up, as you have been doing too. There are some issues around that that are proving difficult to fix.
Firstly, I removed the if statements based on known lengths and radii. You just can't do that. It must be avoided at all cost. I replaced them with a new property on the CurvedSection class called isPositiveRotation, which controls which way the curve bends. While that did work as expected, it highlighted a problem with track alignment.
I changed the track initialization code (in the app, not the Track.init method) to place the tracks below each other, accounting for their sizes. This is not working very well at the moment, and it is because of the way the curves bend in different directions. It does get them to line up on the left edge. I did actually alter Track.init so that it would center the track in its own coordinate system (except on the X dimension). That isn't working as expected either. There is something funky going on with the way the parts are placed for each section. The rotation applied to the torus is messing something up. I just can't figure it out at the moment. I'm missing something.
I was trying to get things to line up, as you have been doing too. There are some issues around that that are proving difficult to fix.
Firstly, I removed the if statements based on known lengths and radii. You just can't do that. It must be avoided at all cost. I replaced them with a new property on the CurvedSection class called isPositiveRotation, which controls which way the curve bends. While that did work as expected, it highlighted a problem with track alignment.
I changed the track initialization code (in the app, not the Track.init method) to place the tracks below each other, accounting for their sizes. This is not working very well at the moment, and it is because of the way the curves bend in different directions. It does get them to line up on the left edge. I did actually alter Track.init so that it would center the track in its own coordinate system (except on the X dimension). That isn't working as expected either. There is something funky going on with the way the parts are placed for each section. The rotation applied to the torus is messing something up. I just can't figure it out at the moment. I'm missing something.
Re: Animate the PI = 4 experiment
.
Working on track alignments.
Seeing you work the problem of aligning track sections by using 3D Boxes is a good thing. Now I understand what you meant by starting every track at 0,0,0 in its own coordinate system. Using a new curved section property, isPositiveRotation seems like a sensible addition. Not to mention several corrections. You’re making significant changes that I would try to recreate. Too bad it’s giving you difficulties. I’ll mess with the branch if you don't mind.
.
Working on track alignments.
Making working backup files for a new branch makes it seem like an entire new project. I made an image of your changes for convenient review. Many changes (pink), mostly additions(green). Below is a thumbnail copy to get the idea.Nevyn wrote:
I created a new branch because the changes I was making were a bit drastic.
Seeing you work the problem of aligning track sections by using 3D Boxes is a good thing. Now I understand what you meant by starting every track at 0,0,0 in its own coordinate system. Using a new curved section property, isPositiveRotation seems like a sensible addition. Not to mention several corrections. You’re making significant changes that I would try to recreate. Too bad it’s giving you difficulties. I’ll mess with the branch if you don't mind.
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
.
The initial app view. Looking down the y axis. An axes helper and the yellow and blue wire frame spheres are at (0,0,0) where all three tracks begin, sharing the same x axis. Two axes helpers at (this.radius, 0, 0) identify the two circular tracks.
I didn’t mess with the new branch at all, that would have been wrong. The image resulted after I transcribed most of your new branch changes into the main. That included corrections and adding the new curved section property – isPositiveRotation. Nothing drastic, everything except for the piEq4.js Hunk 1, in the app track initialization where you are using 3D Boxes to align the tracks. The two largest green additions in the left most column of that mini code image I showed yesterday.
Transcribing your code allowed a good review, it just needs some no longer appropriate (i.e. “this is bad”) comments removed that can wait till the branch joins the main or the tracks are properly positioned.
At this point, as you might suspect, drastic or not, I’ll try creating and messing with 3D Boxes too.
.
The initial app view. Looking down the y axis. An axes helper and the yellow and blue wire frame spheres are at (0,0,0) where all three tracks begin, sharing the same x axis. Two axes helpers at (this.radius, 0, 0) identify the two circular tracks.
I didn’t mess with the new branch at all, that would have been wrong. The image resulted after I transcribed most of your new branch changes into the main. That included corrections and adding the new curved section property – isPositiveRotation. Nothing drastic, everything except for the piEq4.js Hunk 1, in the app track initialization where you are using 3D Boxes to align the tracks. The two largest green additions in the left most column of that mini code image I showed yesterday.
Transcribing your code allowed a good review, it just needs some no longer appropriate (i.e. “this is bad”) comments removed that can wait till the branch joins the main or the tracks are properly positioned.
At this point, as you might suspect, drastic or not, I’ll try creating and messing with 3D Boxes too.
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
You shouldn't be copying those changes from one branch to another. That is what the merge operation is for (although I didn't want you to do that either, until the changes worked). If you just want specific commits, and not the whole lot, then you can also cherry pick them onto another branch (including master). Alternatively, you could create a new branch on top of that branch if you want to work with those changes without affecting the original branch. Anything but typing them in by hand. Let your tools work for you.
There are heaps of comments that need removal. You have to keep an eye on those as they get out-of-date quickly, and cause confusion if you leave them in and come back to it after some time. I could see some that I thought needed removing, but didn't touch them in case you still needed them. I might have removed a few around the code that I was changing. I think I also put some new ones in that actually didn't apply to my new code, but to the code that I changed (I wasn't planning on changing it when I went in there).
It took me a while to realise that you had changed the camera to look down the Y axis. I am use to it looking down the -Z axis. I had to keep doing mental gymnastics every time I remembered that -Z was up. When the camera looks down -Z (its default position), the X and Y axes are both positive and it is easier to think about.
Using the bounds of a node is a good way of placing it without knowing its internal structure. You just want to know how much space it takes up and place it accordingly. It is a good idea to get used to working with them.
There are heaps of comments that need removal. You have to keep an eye on those as they get out-of-date quickly, and cause confusion if you leave them in and come back to it after some time. I could see some that I thought needed removing, but didn't touch them in case you still needed them. I might have removed a few around the code that I was changing. I think I also put some new ones in that actually didn't apply to my new code, but to the code that I changed (I wasn't planning on changing it when I went in there).
It took me a while to realise that you had changed the camera to look down the Y axis. I am use to it looking down the -Z axis. I had to keep doing mental gymnastics every time I remembered that -Z was up. When the camera looks down -Z (its default position), the X and Y axes are both positive and it is easier to think about.
Using the bounds of a node is a good way of placing it without knowing its internal structure. You just want to know how much space it takes up and place it accordingly. It is a good idea to get used to working with them.
Re: Animate the PI = 4 experiment
.
They look properly positioned to me, relative to each other that is. Of course they still need to be moved half the straight track length in the negative x direction.
Since last time, I transcribed (special case!) your 3D box track alignment code and messed with the 3D bounding boxes. I added blue bounding box helpers to the initial track positions as shown. As in yesterdays’ image, before they were repositioned they were overlapping, sharing the same x axis. I could have removed them after the tracks were repositioned but left them in for the comparison. The 0,0,0 axis helper shows us the center of the screen’s view. The spinning blue ball is retired. Note that the track bounding box includes the sphere halves extending outside the track.
The track positioning looks good and the buffer spacing works properly. While attempting to move the boxes about, I noticed the problem was gone. I don’t know what fixed it. I suppose a merge might show us, or not.
I‘ll get rid of the boxes and clean up some comments next.
.
They look properly positioned to me, relative to each other that is. Of course they still need to be moved half the straight track length in the negative x direction.
Since last time, I transcribed (special case!) your 3D box track alignment code and messed with the 3D bounding boxes. I added blue bounding box helpers to the initial track positions as shown. As in yesterdays’ image, before they were repositioned they were overlapping, sharing the same x axis. I could have removed them after the tracks were repositioned but left them in for the comparison. The 0,0,0 axis helper shows us the center of the screen’s view. The spinning blue ball is retired. Note that the track bounding box includes the sphere halves extending outside the track.
The track positioning looks good and the buffer spacing works properly. While attempting to move the boxes about, I noticed the problem was gone. I don’t know what fixed it. I suppose a merge might show us, or not.
I‘ll get rid of the boxes and clean up some comments next.
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
OK! I figured it out. There were a couple of problems that I was flying right by without noticing them. The first was the AxisHelper that I added to the curve to check its orientation. It was only meant to be there as a visual guide, so I was ignoring it. But THREE did not ignore it. It used it to calculate the bounds of the curved tracks (and why wouldn't it?). That was causing the curved tracks to be offset in the Y dimension.
The second problem was a simple mathematical mistake. I was trying to add a small buffer distance between the tracks, and used this code to add it in:
but that is wrong. I had buffer set to 0 so that it should not have added any distance between the tracks, but it still seemed to change things. Every time I read that code, I read it wrongly. It should be:
which forces the subtraction before it does the multiplication.
Now everything lines up where it should.
I've cleaned up some of the code and pushed it all to the track-alignment branch. I'll merge it if you are happy with it.
The second problem was a simple mathematical mistake. I was trying to add a small buffer distance between the tracks, and used this code to add it in:
- Code:
range += buffer * tracks.length-1;
but that is wrong. I had buffer set to 0 so that it should not have added any distance between the tracks, but it still seemed to change things. Every time I read that code, I read it wrongly. It should be:
- Code:
range += buffer * (tracks.length-1);
which forces the subtraction before it does the multiplication.
Now everything lines up where it should.
I've cleaned up some of the code and pushed it all to the track-alignment branch. I'll merge it if you are happy with it.
Re: Animate the PI = 4 experiment
To center it in X, just calc the bounds of the group that contains all of the tracks and change its X position to accommodate. It is already centered in Z, but you could do that at the same time if you want to. It won't actually change the position from where it currently is. Same goes for Y.
Re: Animate the PI = 4 experiment
I'm happy. I've saved my changes and pulled the latest branch changes. Please go ahead and merge if you'd be so kind.
I'll wait for that before trying to center the tracks in x - even though "It won't actually change the position from where it currently is". Relative? Confusing. Repeat, I'm happy.
I'll wait for that before trying to center the tracks in x - even though "It won't actually change the position from where it currently is". Relative? Confusing. Repeat, I'm happy.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
Everything is merged.
I meant it won't change Y or Z, it will change X. Center it in X, Y, and Z even though it won't move in Y or Z. That way, the app knows that it is centered, which is where the app wants it. If changes are made to Track, and it is not centered anymore, then the app won't care as it will be centering it itself anyway.
Airman wrote:I'll wait for that before trying to center the tracks in x - even though "It won't actually change the position from where it currently is". Relative? Confusing.
I meant it won't change Y or Z, it will change X. Center it in X, Y, and Z even though it won't move in Y or Z. That way, the app knows that it is centered, which is where the app wants it. If changes are made to Track, and it is not centered anymore, then the app won't care as it will be centering it itself anyway.
Re: Animate the PI = 4 experiment
I had to roll back your changes after the track-alignment branch point, but I added them to that branch so they aren't lost, just the commits had to go. That's why you have that green branch. That is your local version, which is master. The blue branch is the remote master, and the red branch is track-alignment. You need to get rid of that green branch by reverting to the green dot where the red and blue branch off from (Moving circular section ...). Make sure you are on the master branch. Right-click that 'Moving circular section' commit and choose 'Reset current branch to this commit'. Then choose a Hard Reset.
You shouldn't be pulling new changes when you have uncommitted changes. If they can go, then get rid of them, otherwise commit them and when you pull, choose to rebase (it is a checkbox on the dialog that comes up when pulling). Then hope for the best, as it is hit or miss as to whether the changes will rebase nicely or not. Merging track-alignment was not very nice at all, so I had to make some changes to get it to merge. That is also why you shouldn't be adding changes from one branch onto the other. Some times that will be okay, and other times it will not. In this case, it caused problem when GIT tried to find the common lines of code.
You shouldn't be pulling new changes when you have uncommitted changes. If they can go, then get rid of them, otherwise commit them and when you pull, choose to rebase (it is a checkbox on the dialog that comes up when pulling). Then hope for the best, as it is hit or miss as to whether the changes will rebase nicely or not. Merging track-alignment was not very nice at all, so I had to make some changes to get it to merge. That is also why you shouldn't be adding changes from one branch onto the other. Some times that will be okay, and other times it will not. In this case, it caused problem when GIT tried to find the common lines of code.
Re: Animate the PI = 4 experiment
You don't need to touch the track-alignment branch ever again. Pull those changes onto master and keep working on that. Of course, you can switch to the track-alignment branch and pull those changes, which will just stop it from showing those numbers and down arrows. If you do, make sure you switch back to the master branch to work.
Re: Animate the PI = 4 experiment
Thanks, that's a relief. Now I can get back to rangeX, rangeY and rangeZ.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
The motion code will need to be worked on shortly. Some of it is already there, but you will need to implement the motion code for each Section sub-class. This should only need to be implemented in StraightSection (which is easy, so do this first) and CurvedSection. Logically, you would think that the KinematicSection and GeometricSection classes would need to implement motion, and you could do that, but there is a way to let CurvedSection handle it. You just have to realise that the CurvedSection class has both the radius and the length of the circumference (whether it is PI=4 or PI=3.14). So you can assume that the length represents the complete circumference and just work with percentages of 360°.
The ball has a velocity. The CurvedSection has a length. The motion methods are given a time. So you just use the velocity equation to figure out how far the ball can travel along that length in that time. Then calculate that as a ratio to the length (which is not really a percentage, but close enough) and rotate around the circle by that amount.
Remember that the Section.applyMotion( time, ball ) method must return the amount of time left over if the ball reaches the end of this Section before the total time is used up (0 otherwise). The Track.applyMotion( time ) method already takes care of that and will switch to the next Section when it needs to.
Looking at the latest commit, it seems you are trying to move the tracks over in the X dimension. While what you are doing will work, it is not the right way to do it. You should be moving the group containing the tracks over instead. Then all tracks are aligned with each other, but their parent group is the one being centered. The code will be much simpler.
The ball has a velocity. The CurvedSection has a length. The motion methods are given a time. So you just use the velocity equation to figure out how far the ball can travel along that length in that time. Then calculate that as a ratio to the length (which is not really a percentage, but close enough) and rotate around the circle by that amount.
Remember that the Section.applyMotion( time, ball ) method must return the amount of time left over if the ball reaches the end of this Section before the total time is used up (0 otherwise). The Track.applyMotion( time ) method already takes care of that and will switch to the next Section when it needs to.
Looking at the latest commit, it seems you are trying to move the tracks over in the X dimension. While what you are doing will work, it is not the right way to do it. You should be moving the group containing the tracks over instead. Then all tracks are aligned with each other, but their parent group is the one being centered. The code will be much simpler.
Re: Animate the PI = 4 experiment
.
The initial view. The balls and tracks are where they belong. I centered the group as you suggested. Yep, thee code is simpler for it.
I’d like to mention two more of your robust oop track improvements over my rudimentary procedural programming efforts.
To Create an array of Tracks, such as: kinematic, straight, and geometric, one lists the tracks and the sequence in which they are to be added, top to bottom. The application allows whatever series of however many tracks you like. Let me tell you, that's very provocative.
var Buffer allows one to select the distance between those tracks. At 0, the tracks just touch; buffer = 0.5 in the image shown. Whatever distance one selects, the series of tracks selected will ‘expand’, accordion-like, to the buffer separation value.
Here’s another update to the PI=4 Experiment. JavaScript Application Class Hierarchy diagram.
And some more cleanup.
.
The initial view. The balls and tracks are where they belong. I centered the group as you suggested. Yep, thee code is simpler for it.
I’d like to mention two more of your robust oop track improvements over my rudimentary procedural programming efforts.
To Create an array of Tracks, such as: kinematic, straight, and geometric, one lists the tracks and the sequence in which they are to be added, top to bottom. The application allows whatever series of however many tracks you like. Let me tell you, that's very provocative.
var Buffer allows one to select the distance between those tracks. At 0, the tracks just touch; buffer = 0.5 in the image shown. Whatever distance one selects, the series of tracks selected will ‘expand’, accordion-like, to the buffer separation value.
Here’s another update to the PI=4 Experiment. JavaScript Application Class Hierarchy diagram.
And some more cleanup.
Very good. I'll see if I can follow your directions.
The motion code will need to be worked on shortly.
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
Airman wrote:To Create an array of Tracks, such as: kinematic, straight, and geometric, one lists the tracks and the sequence in which they are to be added, top to bottom. The application allows whatever series of however many tracks you like. Let me tell you, that's very provocative.
Then you'll be even more impressed when you realise that the same is true for sections.
Create a new track and add more sections to it. Add multiple curves that rotate different directions. You could even have different radii. Everything will still work perfectly. Well, it will once motion is implemented. That's the beauty of breaking it down into smaller units that control their own areas, as well as operating on generic ideas of things rather than specifics. Since a Track just has an array of Sections, it doesn't care how many there are, or what type they are. It can even handle Sections that don't even exist yet! As long as they adhere to the Section interface, they will fit in and work fine.
We haven't dealt with balls much yet, but they will be the same. We could substitute the Ball class with another that operates the same way and it will just slot right in.
It's been a long road, and you've had to learn a lot, but I'm glad to see you not only appreciate it, but also notice it yourself, which is actually more important. You've done really well. I've pushed a bit here and there, and worried that I was pushing too hard or trying to reach too far, but you've persevered and researched and learnt. Take a moment to appreciate what you have done. I love that feeling when a design finally comes together and works as you want it to. Makes me smile every time.
Re: Animate the PI = 4 experiment
.
Thanks Nevyn, I appreciate your patience, and your smile. I’m sure I’ll smile soon enough - not counting the grin I'm wearing, no motion yet.
Basically, I added three new methods. 1. Section.applyMotion(). 2. StraightSection.applyMotion( time, ball ), and 3. CurvedSection.applyMotion( time, ball ). I believe Section.applyMotion() will remain blank. I've only worked on StraightSection.applyMotion so far. Here’s some of your directions and my attempt at the corresponding code.
The function returns either 0 or the value of unusedTime. The method calculates the fractionalDistanceTraveled but I don’t see how that value can be used. I tried changing this.ball.position.x within this function without effect.
Warm, cold?
.
Thanks Nevyn, I appreciate your patience, and your smile. I’m sure I’ll smile soon enough - not counting the grin I'm wearing, no motion yet.
Basically, I added three new methods. 1. Section.applyMotion(). 2. StraightSection.applyMotion( time, ball ), and 3. CurvedSection.applyMotion( time, ball ). I believe Section.applyMotion() will remain blank. I've only worked on StraightSection.applyMotion so far. Here’s some of your directions and my attempt at the corresponding code.
- Code:
// return unused time when the section's end is reached, otherwise 0.
// The ball has a velocity. The Section has a length. The motion methods
// are given a time. Use the velocity equation to figure out how far the
// ball can travel along that length in that time. Then calculate that
// as a ratio to the length
StraightSection.prototype.applyMotion = function( time, ball )
{
var unusedTime = 0;
var incrementalTime = this.time;
var incrementalDistance = this.ball.velocity * incrementalTime;
var fractionalDistanceTraveled = incrementalDistance/this.length; // not used
this.ball.distance += incrementalDistance;
this.ball.position.x += incrementalDistance;
var distanceCrossed = this.ball.distance - this.length;
if ( distanceCrossed > 0 ) // the section end has been crossed
{
unusedTime = distanceCrossed/this.ball.velocity;
return unusedTime;
}
else
{
return 0;
};
};
The function returns either 0 or the value of unusedTime. The method calculates the fractionalDistanceTraveled but I don’t see how that value can be used. I tried changing this.ball.position.x within this function without effect.
Warm, cold?
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Re: Animate the PI = 4 experiment
That's a decent start. We'll change a few things, move them around more like, but the code is basically correct.
Although...
What is this.time? Why would a Section have a time property? Did you mean just time? If so, and it should be, then there is no need for incrementalTime. Same with this.ball, it should just be ball.
We don't really need fractionalDistanceTraveled in this implementation, although it can certainly be used if you wanted to go that way. We can just use lengths directly here, since we are dealing with a straight. That's what makes it the easier implementation.
As I typed out the following code, I realised that we don't have enough information at the moment. We need to know how far along this section the ball currently is in order to calculate the max possible distance to travel. To get that info, we could add another parameter that tells us that distance, but instead, I'm going to introduce a new property of the Section class that tells us the starting distance (total track distance) of this section. That saves calculating that distance each time we apply motion (which is every frame). We will let the Track class pass in this distance when it calls Section.init.
Now we have to alter the Track.init method to give us that distance along the section:
You need to add that new method to the Ball class:
Fill in that method by adjusting the ball's position and distance, then apply the roll.
Although...
What is this.time? Why would a Section have a time property? Did you mean just time? If so, and it should be, then there is no need for incrementalTime. Same with this.ball, it should just be ball.
We don't really need fractionalDistanceTraveled in this implementation, although it can certainly be used if you wanted to go that way. We can just use lengths directly here, since we are dealing with a straight. That's what makes it the easier implementation.
As I typed out the following code, I realised that we don't have enough information at the moment. We need to know how far along this section the ball currently is in order to calculate the max possible distance to travel. To get that info, we could add another parameter that tells us that distance, but instead, I'm going to introduce a new property of the Section class that tells us the starting distance (total track distance) of this section. That saves calculating that distance each time we apply motion (which is every frame). We will let the Track class pass in this distance when it calls Section.init.
- Code:
function Section( length ) // Creating section, this is an abstract class, so it won't be created directly
{
this.length = length;
this.distance = 0;
};
Section.prototype.init = function( distance, params )
{
this.distance = distance;
};
- Code:
StraightSection.prototype.applyMotion = function( time, ball )
{
// calc the possible distance traveled
var distance = ball.velocity * time;
// calc how much of this section's length is remaining given the ball's current distance
var lengthLeft = this.length - (ball.distance - this.distance);
var timeLeft = 0;
// determine if we have gone too far
if( distance > lengthLeft )
{
// we've gone too far
// calc time left
timeLeft = (distance-lengthLeft) / ball.velocity;
// adjust distance
distance = lengthLeft;
}
// apply motion
var v = new THREE.Vector3( distance, 0, 0 );
// the ball needs a lot of information to do its thing
// so we are going to pass in a vector that adjusts the position of the ball
// and the actual distance traveled (since this can't be determined from the vector)
// and the time it moved in
// that allows the ball to apply a rolling effect
ball.applyMotion( v, distance, time - timeLeft );
// return the time that was not used
return timeLeft;
}
Now we have to alter the Track.init method to give us that distance along the section:
- Code:
Track.prototype.init = function( params )
{
// ...
var d = 0;
for( var i=0; i<this.sections.length; i++ )
{
var s = this.sections[i];
// initialize section, passing it the distance that the section starts at
s.init( d, params );
// increment the distance by this sections length
d += s.length;
// ...
};
// ...
};
You need to add that new method to the Ball class:
- Code:
// @param changeVector - the change in position vector
// @param distance - the distance traveled that the changeVector represents
// @param time - the amount of time that the change occurs over
Ball.prototype.applyMotion = function( changeVector, distance, time )
{
// TODO adjust the position and distance properties
// TODO apply rolling effect
}
Fill in that method by adjusting the ball's position and distance, then apply the roll.
Re: Animate the PI = 4 experiment
Now we come to something new. We had to alter the Section.init method to set the starting distance along the track. Which means that the Section.init method is now not abstract. That is very important because up until now, we have been able to treat it as a signature and not a method. Now, it actually does something, so it needs to be executed, which is a bit tricky because this method gets overriden by the sub-classes.
Remember all of that discussion about the prototypes? This is where that comes in handy. We now have a situation where we need to call 2 implementations of the same method on the one object. How can we possibly do that? Well, we could manually dig down into the prototype hierarchy, but no self-respecting programmer would ever do that . We need a way that works with our class hierarchy. We need a way that works no matter how many sub-classes there are, i.e. Class A extends B which extends C which extends D, etc, a class chain, if you will.
It really is quite easy, actually, but probably not that obvious. All we need to do is invoke the parent classes method inside of the child classes method implementation.
The B.run method invokes the A.run method using the prototype of its own prototype. It has to jump over its own prototype because that would call B.run instead of A.run, which would get into an infinite loop as it calls itself over and over again (which is called recursion and is quite useful, but dangerous).
If the method had parameters, you would need to pass those along in the method call, too.
So you need to go through all of the Section sub-classes and if they have an implementation of the init method, then add the following to the top of the method body.
Remember all of that discussion about the prototypes? This is where that comes in handy. We now have a situation where we need to call 2 implementations of the same method on the one object. How can we possibly do that? Well, we could manually dig down into the prototype hierarchy, but no self-respecting programmer would ever do that . We need a way that works with our class hierarchy. We need a way that works no matter how many sub-classes there are, i.e. Class A extends B which extends C which extends D, etc, a class chain, if you will.
It really is quite easy, actually, but probably not that obvious. All we need to do is invoke the parent classes method inside of the child classes method implementation.
- Code:
function A()
{
}
A.prototype = Object.create( {} );
A.prototype.run = function() {};
function B()
{
A.call( this );
}
B.prototype = Object.create( A.prototype );
B.prototype.run = function()
{
this.prototype.prototype.run.call( this );
};
The B.run method invokes the A.run method using the prototype of its own prototype. It has to jump over its own prototype because that would call B.run instead of A.run, which would get into an infinite loop as it calls itself over and over again (which is called recursion and is quite useful, but dangerous).
If the method had parameters, you would need to pass those along in the method call, too.
So you need to go through all of the Section sub-classes and if they have an implementation of the init method, then add the following to the top of the method body.
- Code:
this.prototype.prototype.init.call( this, distance, params );
Re: Animate the PI = 4 experiment
.
The current output, after changing Track.init by adding the distance variable d to the parameters passed to this.sections[i].init.
I wasn’t able to make all the changes you suggested yesterday:
1. The first difficulty occurred when I tried to alter the Track.init method to include the distance along the section, adding the variable d to the parameters passed to Track.init. Changing from s.init( params ); to s.init( d, params ). This resulted in increased tube section radii as shown in the image. The straight section tube radius = radius and the curved track tube radius becomes twice the params tube radius. That change was left in place.
2. The other difficulty occurred when I tried adding the new init call this.prototype.prototype.init.call( this, distance, params ), to the Section subclass init methods. When placed in StraightSection.init() or CurvedSection.init(), results in the TypeError, “Cannot read property ‘prototype’ of undefined”. The prototype.prototype call additions were Not made.
///////\\\\\\\///////\\\\\\\///////\\\\\\\
I’ll try working on CurvedSection.applyMotion next.
\\\\\\\///////\\\\\\\///////\\\\\\\///////
11:19 Post Script. Change that. I needed to make the corresponding changes to the subclass function received parameter list. I corrected the straight sections back easily enough. Working on CurvedSection .init and .applyMotion.
///////\\\\\\\///////\\\\\\\///////\\\\\\\
11:55 Post Post Script. The tubes are back to good. The prototype.prototype call problem remains.
.
The current output, after changing Track.init by adding the distance variable d to the parameters passed to this.sections[i].init.
I wasn’t able to make all the changes you suggested yesterday:
1. The first difficulty occurred when I tried to alter the Track.init method to include the distance along the section, adding the variable d to the parameters passed to Track.init. Changing from s.init( params ); to s.init( d, params ). This resulted in increased tube section radii as shown in the image. The straight section tube radius = radius and the curved track tube radius becomes twice the params tube radius. That change was left in place.
2. The other difficulty occurred when I tried adding the new init call this.prototype.prototype.init.call( this, distance, params ), to the Section subclass init methods. When placed in StraightSection.init() or CurvedSection.init(), results in the TypeError, “Cannot read property ‘prototype’ of undefined”. The prototype.prototype call additions were Not made.
///////\\\\\\\///////\\\\\\\///////\\\\\\\
I’ll try working on CurvedSection.applyMotion next.
\\\\\\\///////\\\\\\\///////\\\\\\\///////
11:19 Post Script. Change that. I needed to make the corresponding changes to the subclass function received parameter list. I corrected the straight sections back easily enough. Working on CurvedSection .init and .applyMotion.
///////\\\\\\\///////\\\\\\\///////\\\\\\\
11:55 Post Post Script. The tubes are back to good. The prototype.prototype call problem remains.
.
LongtimeAirman- Admin
- Posts : 2078
Join date : 2014-08-10
Page 5 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 5 of 8
Permissions in this forum:
You cannot reply to topics in this forum