Animate the PI = 4 experiment

Page 8 of 9 Previous  1, 2, 3, 4, 5, 6, 7, 8, 9  Next

Go down

Animate the PI = 4 experiment - Page 8 Empty Re: Animate the PI = 4 experiment

Post by LongtimeAirman on Tue Jan 21, 2020 6:55 pm

.
Animate the PI = 4 experiment - Page 8 V2with10
More progress with Pause.

Photo-finishes are again possible, PI=4 v2 now has new GUI controls: Pause, Next Frame, Reset and More. Besides using the top right button, one may also Pause using the spacebar. Last verson, the spacebar was very sloppy, pressing the spacebar ‘once’ would create a pause and a frame advance, or a manual advance by holding the spacebar down. Now, with a spacebar press and release, a single Pause action is generated. Each subsequent press advances the scene a single frame.

The animation function is much cleaner than the last version.

The time variable has become very important. I’m currently having some difficulty in implementing ‘Pause at markers’. I see that that function worked well in version 1. Hmmm.
.

LongtimeAirman
Admin

Posts : 1461
Join date : 2014-08-10

Back to top Go down

Animate the PI = 4 experiment - Page 8 Empty Re: Animate the PI = 4 experiment

Post by LongtimeAirman on Wed Jan 22, 2020 6:49 pm

.
Animate the PI = 4 experiment - Page 8 Gtrack22
‘Pause at markers’ is working, the three balls shown here are pausing just after the starting lines. The previous version only paused at four locations: 0, 2*r, 4*r, and 6*r; the balls now also stop at the start( -2*r ), PIg( 2*r + 2*Math.PI*r), and end( total - distNext < dT*velocity ). When pausing, the balls don’t stop exactly at those marker locations, just wherever their frame motion step occurs within the frame distance interval (dT*velocity) just after each marker, or in the last interval before the end marker.

I got tired of messing with several colors and orange always appearing as white, so I changed to the marker colors as shown. The start and end markers are red, all others are green except for the non 2*r PIg marker. As the curved track balls pass the 0 mark switches to red, otherwise, as before, the other markers appear to turn black.

Note that the tracks now appear much nicer. I added a new directional light to the scene which makes a big difference. I think I’ll add point lights to the balls next. I’ll try making the balls and markers a little nicer too.

Objects certainly make more sense to me now, having replaced all threejs standard - new THREE.Vector3 objects - with Point objects. Exactly like Vector3, Point objects are used for positioning track pieces or the balls’ starting points.

Also ColorObjects, with currently unused properties [“position”] and [“color”], I used the [“hSLValue”]) for both initialization and animation.

Another object with several pieces - ballMesh - is copied to each track as needed.
 
I loaded a bunch of new property: value pairs into what I’ve referred to as my table array. These are used for both initialization and animation.

processMotion and processMotionForTrack work, the code is cleaner.

I’ve done some reading and can almost understand the difference between Parent and child, or the prototype and the object it creates (?). Or prototype chains. All well and good, except I don’t see where other class types should be created here.

Thanks for your constant attention and guidance. This project has clearly benefited by “rewriting the code”, as you suggested; but I’m not at all sure I accomplished what you’d hoped or even gotten the gist of the table array. Have I missed something important? You mentioned possible future classes, and inheritance?
.

LongtimeAirman
Admin

Posts : 1461
Join date : 2014-08-10

Back to top Go down

Animate the PI = 4 experiment - Page 8 Empty Re: Animate the PI = 4 experiment

Post by Nevyn on Thu Jan 23, 2020 10:20 pm

Creating the Point class was a good exercise. However, as you have pointed out, the THREE.Vector3 class already does all of that and more, with the advantage of also being the class that needs to be used by THREE. It doesn't help this application or clean up the code.

The classes that I think need to be developed are Track, Section, and Ball. A Track has an array of Sections (where each Section is between 2 markers, or it is straight vs curved, either way will work with the latter probably being a bit easier), and a Ball. The Section class then has 2 sub-classes: StraightSection and CurvedSection.

The Section class, which is abstract (i.e. it does not get instantiated, but is sub-classed), has a method such as applyMotion( ball, time ) which will return a number representing the amount of time remaining after the motion has been applied, which can be 0, but may also be >0. If >0, then the next Section should be used to apply more motion.

You could also sub-class CurvedSection with classes like Pi4Section and Pi314Section, so that each of those can apply the appropriate motion. So the code currently in the processMotionForTrack* functions should be put into those classes. This should be done in some way such that the majority of the code (common to both curved sections) is in the CurvedSection class, and the sub-classes just provide what is different for their respective curves.

A Track has no idea that it is straight or curved or Pi=4 or Pi=3.14 (although it may have methods to figure that out from the Sections that it has in it, if you need that). It only knows that it has Sections and a Ball. It will have an applyMotion( time ) method (which does not return a number since it has to use up the time given to it), and that method will take care of finding the current Section, applying its motion, and moving on to the next Section if it needs to (in the amount of time it is given for this frame).

The Ball class will contain the Object3D to represent that ball, and may have other properties to help with other calculations (such as the total length it has moved through all Sections which Track can use to find the current Section that the Ball is in).

The visual nodes for the tracks should be created in these classes too. The Section class will have some method such as createSceneObjects which creates the appropriate Object3D nodes to represent it. The Track class (which has a similar method) will take care of putting them together and placing them in the correct positions so that they connect to each other. That may be a bit tricky, and is why I think it is easier to use straight vs curved, rather than each Section being between 2 markers. If the complete circle is represented by one node, then all connections are on the straight line and you don't need to connect 2 parts of a curve where it gets messy.

I was starting to think that I had led you down a complicated path, but you have made some progress without me (which is great), so it is probably worth continuing. See if you can take my words and convert them into some classes. I recommend that each class be put into its own JS file, rather than in the main HTML file, as it will be easier to switch between them as you work. Then import them into the HTML file with a script tag.
Nevyn
Nevyn
Admin

Posts : 1796
Join date : 2014-09-11
Location : Australia

http://www.nevyns-lab.com

Back to top Go down

Animate the PI = 4 experiment - Page 8 Empty Re: Animate the PI = 4 experiment

Post by LongtimeAirman on Fri Jan 24, 2020 11:10 pm

.
Animate the PI = 4 experiment - Page 8 Threet10

I recommend that each class be put into its own JS file, rather than in the main HTML file, as it will be easier to switch between them as you work. Then import them into the HTML file with a script tag.
I was stunned when I first read that. Still am. I admire your patience Nevyn. You’ve been spending quite some time providing specific directions that I've yet to grasp. Separate scripts tabs are a whole nuther order of sophistication.

Luckily, you'e provided several code examples. For example, I did a review of the many scripts included in CPIM Testbed. As a first step, please consider the following three objects: aTrackC, aSectionC0, aBallC. These are sample objects that should be created by prototype track, section and ball class functions.    
Code:

 var aTrackC = {
 trackRadius: r,
 tubeRadius: tRadius,
 numTrkParts: 2,
 trackCParts1: 'straightTwo', position: posC.x + tracks.r, posC.y, posC.z,
 trackCParts2: 'circleTrack', position: posC.x + 2*tracks.r, posC.y, posC.z - tracks.r,
 trackCSections: c0, c1, c2, c3, c4, c5, c6,
 startTime: timeStart,
 currentTime: timeCur,
 };

 var aSectionC0 = { // c0 for pi4 track. s0 for straight, g0 for pig
 partOfTrack: piKTrack,
 sectName: c0,
 sectType: 'straight',
 start: c0.x, c0.y, c0.z,
 stop: c1.x, c1.y, c1.z,
 markerAngle: Math.PI/2,
 };

 var aBallC = {
 radius: bRadius,
 trkTime: trTimeInSecs,
 dT: 1/60,
 velocity: 10*tracks.r/trTimeInSecs,
 ballVelocity: 10*r/trTimeInSecs,
 nextDist: dT * velocity,
 paused: false,
 startPosition: c0.x, c0.y, c0.z,
 currentPosition: curPos.x, curPos.y, curPos.z,
 nextPosition: nexPos.x, nexPos.y, nexPos.z,
 distanceTraveled: sum,
 rollAngleZ: angleZ,
 trkAngleY: angleY,
 };

Might these object arrays be acceptable? If so I’ll try creating their prototype next.  

I've been under the impression table arrays should be read only. Or would it be appropriate including changing time/distance variables in these objects?
.

LongtimeAirman
Admin

Posts : 1461
Join date : 2014-08-10

Back to top Go down

Animate the PI = 4 experiment - Page 8 Empty Re: Animate the PI = 4 experiment

Post by Nevyn on Sat Jan 25, 2020 4:19 pm

There is nothing inherently read-only about an array. It comes down to the specific array you are using and whether you want it to be read-only or not. In this app, the array of Tracks would be read-only (because there is no need to change them), the array of Sections in each Track would be read-only. However, plenty of other properties in these objects may be altered during runtime.

Those object definitions are a start. I don't think they look quite right yet, and some of them have data that they shouldn't. There is no need for a Track to know a radius (although it may use one, and I think it is more useful for a Section than a Track). The Section object has a property called partOfTrack, and this should not exist. A Section should not need to know what Track it is in. It should not know about Tracks at all. This is called 'Separation of Concerns', and it means that a class should never know what uses it (there are exceptions, but few), and should only know as little as possible about what it itself uses. Basically, you want a class to be as flexible as possible, so it should take care of itself as much as it can. When other classes know too much about it, it becomes very difficult to change any of those classes. They become too intertwined. Separate and Isolate is a good design philosophy, there are times when it isn't, but you will know them when you reach them because you can't make it work any other way.

What you are doing is trying to manipulate your existing code into classes, but this is not really a good way to go. You want to create the class structure first, create some methods (but with no code in them yet) that reflect what that class needs to do (which I have outlined above, a bit). When you can see how the classes fit together and what methods they are going to use from each other, then you fill in the methods with the code based on your existing code. You may find that you need slightly different code now, so think about the code generically. Think about what it is that that code is doing, and how you now want it to work based on the classes. Then re-implement it in those classes. Your mind will keep telling you that 'the existing code works, so we don't want to lose it or change it', but you need to shut that down. It is difficult, I know. Especially when you aren't comfortable with programming yet.
Nevyn
Nevyn
Admin

Posts : 1796
Join date : 2014-09-11
Location : Australia

http://www.nevyns-lab.com

Back to top Go down

Animate the PI = 4 experiment - Page 8 Empty Re: Animate the PI = 4 experiment

Post by LongtimeAirman on Sun Jan 26, 2020 6:47 pm

.
Animate the PI = 4 experiment - Page 8 Latest16

Only the ball knows where it’s at, I think we agree. I’ve been adding dT*velocity distance increments to the balls’ current position, while that’s true it occurs to me that the distances traveled depend entirely on the ball radius and number of z-axis rotations (rolls) it experiences.

The balls’ paths are either left, straight, or right ahead, so I suppose I can work with the notion of a track section not necessarily pinned to any specific location in the larger track. It may be more difficult, but I would prefer stringing 6 sections together for ‘practical’ reasons. Two animations, 1.marker color changes as the balls pass, and 2. balls pausing at markers, make it necessary to monitor the balls’ position with respect to the six section endpoints. Of course I’d differ to your better judgement.

Here’s a second stab at defining the section, track and ball ‘classes’. I seem to be adding all the motion variables to the ball – is that ok? Does each ball then behave independently? It seems wrong to me.
Code:

function section()  // Creating section c01
{
 new Item( section.name: c01 ), // c00-c05, s00-s05, g00-g05
 new Item( sectType: 'curvedPi4' ), // piKTrack, curvePiK, curvePiG
 new Item( startPos: c0.x, c0.y, c0.z ), // c0-c5.x, s0-s5.y, g0-g5.z
 new Item( startAngle: math.PI/2 ),
 new Item( endPos: c1.x, c1.y, c1.z, ),
 new Item( endAngle: 0 ),
 new Item( gLength: 2*math.PI*r/4 ),
 new Item( kDistance: 2*r ),
};

function track()  
{
 new Item( track.type: 'curvedPi4' ), // piKTrack, curvePiK, curvePiG
 new Item( track.radius: r ),
 new Item( track.tubeRadius: ballRadius ),
 new Item( track.numTrkParts: 2 ),
 new Item( track.trkPart1: 'straightTwo' ),
 new Item( track.posTrkPart1: posC.x + tracks.r, posC.y, posC.z ),
 new Item( track.trkPart2: 'circleTrack', position );
 new Item( track.posTrkPart2: posC.x + 2*tracks.r, posC.y, posC.z - tracks.r ),
 new Item( track.sect0: c00 ), // install markers using the section components.
 new Item( track.sect1: c01 ),
 new Item( track.sect2: c02 ),
 new Item( track.sect3: c03 ),
 new Item( track.sect4: c04 ),
 new Item( track.sect5: c05 ),
 new Item( track.sect6: c06 ),
);

function ball()  
{
 new Item( ball.trkTime: trTimeInSecs ),
 new Item( ball.dT: 1/60 ),
 new Item( ball.ballVelocity: 10*r/trTimeInSecs ),
 new Item( ball.paused: false ),
 new Item( ball.startPosition: c0.x, c0.y, c0.z ),
 new Item( ball.radius: bRadius ),
 new Item( ball.trackAngleZ: angleZ ),
 new Item( ball.rollAngleY: angleY ),
 new Item( ball.currentPosition: curPos.x, curPos.y, curPos.z ),
 new Item( ball.distanceTraveled: distanceTraveled),
 new Item( ball.nextDist: dT * velocity ),
 new Item( ball.nextPosition: nexPos.x, nexPos.y, nexPos.z ),
};

Putting it mildly, I’m having great difficulty following your instructions, embarrassed by how much I’ve slowed down – wondering about brain degeneracy. Note, I’m a believer, utilizing arrays of grouped objects and their methods, also known as classes, provide the best coding efficiencies. JavaScript is, after all, a prototype based language – as if I knew what that meant. The big picture, ‘prototypes’ and extra scripts have given me catatonic fits. As you said, my main mental hangup is a lack of experience knowing the logical solutions are there. The prototype/inheritance structure will likely provide ample means for a solution.

Next on my list is to read and follow along doing the console excersizes, for:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
.

LongtimeAirman
Admin

Posts : 1461
Join date : 2014-08-10

Back to top Go down

Animate the PI = 4 experiment - Page 8 Empty Re: Animate the PI = 4 experiment

Post by Nevyn on Sun Jan 26, 2020 8:58 pm

Yes, the balls are the only things that move, so it makes perfect sense that they are going to have motion based data. Each ball is independent of the others. They can't be linked because they can reach the end of their respective tracks before the others.

Even though the balls contain most of the motion data, a Section should (or at least it may) store its length, so that it can be used for quick calculations. For example, here is a method that could be on the Track class that finds the current Section given the Balls distance traveled.

Code:

Track.prototype.findCurrentSectionIndex = function()
{
  var d = 0;
  for( var i=0; i<this.sections.length; i++ )
  {
    d += this.sections[i].length;
    if( d > this.ball.distance )
    {
      return i;
    }
  }
  return 0;
}



Airman wrote:I’ve been adding dT*velocity distance increments to the balls’ current position, while that’s true it occurs to me that the distances traveled depend entirely on the ball radius and number of z-axis rotations (rolls) it experiences.


That is backwards. The rotation of the Ball should be based on the distance it travels. The distance it travels in a given frame is totally dependent on the time that it has, and its velocity.

I think the markers should be separate from the Sections. They could be another array of the Track class. Create a Marker class that stores the distance along the track that it is at (along with its Object3D node). Don't worry about stopping at the markers for now. That can be added back in once the motions are working. If we want that at all.

While it is only a convention, and not necessary, we generally make classes use title-case names, and methods and variables use lower-case (camel-case to be more precise). It makes it easy to know what is a class and what is a variable or method/function.

There is no need for the Item class that you are using above. They should be properties of the class.

Code:

function Section()  // Creating section c01
{
 this.name = c01; // c00-c05, s00-s05, g00-g05
 this.sectType = 'curvedPi4'; // piKTrack, curvePiK, curvePiG
 this.startPos = c0.x, c0.y, c0.z; // c0-c5.x, s0-s5.y, g0-g5.z
 this.startAngle = math.PI/2;
 this.endPos = c1.x, c1.y, c1.z;
 this.endAngle = 0;
 this.gLength = 2*math.PI*r/4;
 this.kDistance = 2*r;
};

However, that code is creating a specific section, when it shouldn't be. Well, that is not strictly correct, it is creating a specific section, but you can't use literal values like you are here. Any data that is specific to this particular section needs to be passed in as a parameter or assigned after the object is created.

It also contains angular data which is not relevant to all Section classes, only CurvedSection classes. I don't see the need for a name, type or start/end positions either. The type is now handled by the class being used to create that section and we won't need to know it once all motion is handled in methods of these classes, because each class understands its own motion. Separation and isolation.

Code:

function Section( length )  // Creating section, this is an abstract class, so it won't be created directly
{
 this.length = length;
};

// create the prototype to the Section class
Section.prototype = Object.create( {} );

function StraightSection( length )  // Creating straight section
{
 // call the Section constructor so we get all of its properties
 Section.call( this, length );
};

// create the prototype for the StraightSection class which extends Section
StraightSection.prototype = Object.create( Section.prototype );

function CurvedSection( radius, length )  // Creating curved section
{
 // call the Section constructor so we get all of its properties
 Section.call( this, length );

 this.radius = radius;
};

// create the prototype for the CurvedSection class which extends Section
CurvedSection.prototype = Object.create( Section.prototype );

function KinematicSection( radius )  // Creating curved section based on kinematics
{
 // call the CurvedSection constructor so we get all of its properties
 CurvedSection.call( this, radius, 8*radius );
};

// create the prototype for the Pi4Section class which extends CurvedSection
KinematicSection.prototype = Object.create( CurvedSection.prototype );

function GeometricSection( radius )  // Creating curved section based on geometry
{
 // call the CurvedSection constructor so we get all of its properties
 CurvedSection.call( this, radius, 2*Math.PI*radius );
};

// create the prototype for the GeometricSection class which extends CurvedSection
GeometricSection.prototype = Object.create( CurvedSection.prototype );

Notice how the KinematicSection and GeometricSection classes calculate different lengths to pass to the CurvedSection class, which then passes it on to the Section class so that it can be stored in the length property.

Don't worry about slowing down, that just means you are learning something.
Nevyn
Nevyn
Admin

Posts : 1796
Join date : 2014-09-11
Location : Australia

http://www.nevyns-lab.com

Back to top Go down

Animate the PI = 4 experiment - Page 8 Empty Re: Animate the PI = 4 experiment

Post by LongtimeAirman on Wed Jan 29, 2020 6:58 pm

.
Animate the PI = 4 experiment - Page 8 Protot10
The Section Object prototype chain.

Thanks for the Section code Nevyn. I even copied it with pencil and paper so as not to miss a thing, and then make a picture of it. Section as an object function and its prototype along with the descendants and their prototypes is a fantastic idea that would never have occurred to me; denizens in object space.

I’ve continued reviewing more references and a few youtube tutorial videos on js object oriented programming.  

Mozilla’s “Inheritance and the prototype chainhttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain, included information on the Object.create method and syntax that seemed to be closest to your Section code. The videos certainly gave a flavor for the code versatility, but they are using simple examples without animation. I have a much better idea what I’m up against.

Still mostly just learning, I’ve stripped the code down to the main functions and a spinning ball for the start of version 3.
.

LongtimeAirman
Admin

Posts : 1461
Join date : 2014-08-10

Back to top Go down

Animate the PI = 4 experiment - Page 8 Empty Re: Animate the PI = 4 experiment

Post by Nevyn on Wed Jan 29, 2020 7:50 pm

I was thinking about creating a class diagram like that, glad you did it for yourself. That sort of stuff really helps you keep everything straight.

The code I wrote above is just the hierarchy, with a few obvious properties. The real fun starts when you write some methods for these classes. Then you begin to see how to break a solution down into smaller parts and distribute them among the classes. When this is done right, the amount of required code is usually smaller and more maintainable.

I mentioned some methods a few posts ago. Have a look at that again and see if you can understand how it fits into this class hierarchy. Add in those method signatures (which means just the function name and possible parameters, no actual code in them yet) and think about what that method means to each particular class.

Note that you will need to create some abstract methods, which just means that they have the signature, but no code in them and they never will have code in them. The sub-classes then override the methods to provide implementations that work for that particular class. Here's an example:

Code:

function Animal()
{
}

Animal.prototype = Object.create( {} );

// An abstract method.
// This allows us to always call the makeSound method on any Animal sub-class, even if it doesn't provide one of its own.
Animal.prototype.makeSound = function()
{
  return null;
};

function Cat()
{
  Animal.call( this );
}

Cat.prototype = Object.create( Animal.prototype );

Cat.prototype.makeSound = function()
{
  return 'Meow';
};

function Dog()
{
  Animal.call( this );
}

Dog.prototype = Object.create( Animal.prototype );

Dog.prototype.makeSound = function()
{
  return 'Woof';
};

See how Animal is a generic idea, so we can't return a sound for it, since every different type of animal makes a different sound. However, Cat and Dog are specific animals and we know what they sound like, so we can return one. Cat and Dog override the makeSound method to provide the specific sound for that class. Because of the way the prototype hierarchy is searched for a method, the Cat and Dog implementations will be found first, so any object of those classes will use the correct one.

Now assume that we added another class for Fish, which don't make a sound (I don't know if that is strictly correct, but it is in this example). The Fish class would not override the makeSound method, but we can still call it, because it inherits it from Animal. The methods defined on the parent classes effectively define the interface to it. They define the contract for how to use it, while the concrete classes actually provide the implementations.

The parent classes can also contain methods that call other methods of that class, even though that parent class doesn't actually provide an implementation. Here's an example:

Code:

...

Animal.prototype.describe = function()
{
  var sound = this.makeSound();
  if( sound != null )
  {
    return 'This animal makes the sound ' + sound + '.';
  }
  else
  {
    return 'This animal does not make a sound.';
  }
}

...

If we then create a Dog and a Cat object and call the describe method on each of them, they will return 'This animal makes the sound Woof.' and 'This animal makes the sound Meow.' respectively. When used this way, the makeSound method is called a Hook Method, because it creates something that sub-classes can hook into. This is one way to put common code into the parent classes while still providing differences for specific sub-classes.

In general, you should try to put as much code into the parent classes as possible, while that code still makes sense for that class. That is, you never do anything Cat specific in the Animal class, but if you have some code that applies to any animal, then it should be in the Animal class. Make use of hook methods and properties to get the code to work while allowing the sub-classes to provide their own special content for it.
Nevyn
Nevyn
Admin

Posts : 1796
Join date : 2014-09-11
Location : Australia

http://www.nevyns-lab.com

Back to top Go down

Animate the PI = 4 experiment - Page 8 Empty Re: Animate the PI = 4 experiment

Post by LongtimeAirman on Fri Jan 31, 2020 4:57 pm

.
Animate the PI = 4 experiment - Page 8 Protoo11
Prototype class or not?

I think I’ve read and done enough console examples for the time being. Implementing a ball is turning out to be more difficult. You indicated that  
“the visual nodes for the tracks should be created in these classes too.”

Ok, here’s my latest attempt at creating the Ball Class.

Code:

function Ball ( name, x, y, z )  // name: "straight", "kinematic" or "geometric", and starting position
{
     this.name = name;
     this.radius = tracksData.bRadius;
     this.ballVelocity = 10*r/trTimeInSecs;

     this.createAndPlaceBall = function()
     {
          ballMesh = new THREE.Mesh;
          sphereGeom = new THREE.SphereGeometry( tracksData.bRadius, 12, 12 );
          sphereMat = new THREE.MeshBasicMaterial( { color: 'yellow', wireframe: true } );
          sphere = new THREE.Mesh( sphereGeom, sphereMat );
          ballMesh.add( sphere );
          ballMesh.position.set( x, y, z );
          scene.add( ballMesh );
     }
};

// And in the init function
Ball.prototype = Object.create( {} );
var SBall = new Ball("straight", tracksData.sTkStart.x, 0, 0 );
SBall.prototype = Ball.prototype;
console.log(SBall);

The Ball class prototypal class/hierarchy seems to “work” properly – according to my console, with SBall and prototype properties. Unfortunately, including the function createAndPlaceBall within the class results in no visual output.

How does one create a visual node from within a class?
.

LongtimeAirman
Admin

Posts : 1461
Join date : 2014-08-10

Back to top Go down

Animate the PI = 4 experiment - Page 8 Empty Re: Animate the PI = 4 experiment

Post by Nevyn on Fri Jan 31, 2020 6:55 pm

You are referring to global variables a lot in this bit of code. Now, that isn't always a bad thing, but it should be avoided as much as possible. The main exception to that is constants. A constant is just a global variable that you choose not to alter at any point in the program, but it is conceptually different. A constant is meant to be shared in this way (and we name them with all upper-case letters to denote that in the program, although that is just a convention).

However, you are referring to data in tracksData and trTimeInSecs and r (even though you have the radius from another source). These should be parameters to the constructor. They actually should be slightly different values, too. Passing in the radius is good, but using trTimeInSecs is not. Just pass in the velocity and calculate what you want it to be before you call the constructor (which means it can also be shared amongst all balls, allowing 1 calculation instead of 3).

You have created a function, but have done it in the wrong way. It is legal, but not efficient, and does not use the prototype so we lose the benefits of that. By creating functions in the constructor, every time you call that constructor a new function gets created. Every object of that class now has its own version of that function, even though they are all the same. If it was created on the prototype, then only 1 instance of that function will ever exist, and all objects will use it.

Code:

function Ball( radius, velocity )
{
  this.radius = radius;
  this.velocity = velocity;
}

Ball.prototype = Object.create( {} );

Ball.prototype.createAndPlaceBall = function()
{
 ...
}

That function is a bit of a mess, too. Firstly, you are referring to variables that have not been declared. Throw in a few var declarations. This may not seem like a big deal, because the code will still work, but it can cause headaches in some circumstances. This is because of an advanced concept called 'Closures', which I won't explain too much, but the JS compiler will look for those variables outside of this function. If it happens to find one, it will use it. This can cause variables to be changed when you don't want them to be. These can even be variables in other code, not your own. For example, suppose THREE had a global variable called ballMesh, then this code would find that and use it. I know THREE doesn't want you to use it, so this may cause some serious problems that are very difficult to track down.

It seems you are creating ballMesh, but it doesn't have parentheses, so I doubt that is going to work (the JS compiler might actually still do it because you have used the new keyword, so it knows your intent, but it is still bad practice to rely on that). I also don't think it actually wants to be a Mesh. It looks like it should be a Group instead, and it may not be needed at all.

The scene should be passed in as a parameter, not referenced as a global variable. It also doesn't need to be a Scene object, it could be a Group, and it is up to the calling code to decide what it wants it to be, not the Ball class. Both Scene and Group have an add method, so both will work in this circumstance.

The creation of the geometry is referring to tracksData.bRadius, when it should be using this.radius. The geometry is also a prime candidate for being a constant (which you define directly above the Ball class, and only use it for the Ball class).

I actually wouldn't even pass in the scene (or group), I would store the object as a property of this class. Track and Section are going to alter the position of this ball, so they need a way to find the node. In this instance, in spite of the fact that I explicitly said to do it with a function, I would just create the node in the constructor.

Lastly, this function/method should not be placing the ball. It has no idea about Tracks or Sections, so it has no idea of where it should be placed. The Track class should take care of that once the Ball is created.

Code:

function Ball( radius, velocity )
{
  this.radius = radius;
  this.velocity = velocity;
}

Ball.prototype = Object.create( {} );

Ball.prototype.createObject3D = function( parentNode )
{
  // if all Balls can have different radii, then we need to create the geometry here
  var g = new THREE.SphereGeometry( this.radius, 12, 12 );
  // store the material as a property so that we can change it on a per Ball basis
  this.material = new THREE.MeshBasicMaterial( { color: 'yellow', wireframe: true } );
  // store the node as a property so we can use it later
  this.object3D = new THREE.Mesh( g, this.material );
  // add the node to the parent node
  parentNode.add( this.object3D );
}
Nevyn
Nevyn
Admin

Posts : 1796
Join date : 2014-09-11
Location : Australia

http://www.nevyns-lab.com

Back to top Go down

Animate the PI = 4 experiment - Page 8 Empty Re: Animate the PI = 4 experiment

Post by LongtimeAirman on Sun Feb 02, 2020 11:55 am

.
Many thanks for the continuing guidance. I hope you don’t mind I’ve included several of your descriptions as comments in the code for convenient reference.

Your exposition on my ball class problems included in the createAndPlaceBall function was an excellent (humbling) lesson. I cleaned it up as you suggested and found that it can place balls at a desired location. However, as you indicated, the Track class will place and move the balls – not the Ball class. And so I replaced that function with the Ball.prototype.createObject3D function you provided. Unfortunately, when I try to call it I get “TypeError: Cannot read property “add” of undefined”, for the line.

Code:

parentNode.add( this.object3D );
Before attempting the same approach to the tracks I’m now on a tangent looking for information on Nodes, ParentNodes, ChildNodes, etc, such as using them as hooks, without much luck. I see Nodes.js is a javaScript application that allows one to run js programs from that application and not just with a browser.

While I haven’t made much progress with version 3, I’ve been trying to keep up by practicing object oriented programming (oop) ‘on the side’. I’m in the second of Mozilla’s oop practice excersizes “Bouncing Balls”, which includes setting up a new project folder containing .html, .css and .js files to modify. Another utube oop system project that looked promising but which I’ve failed to recreate - “Floating Particles” came to a halt by the following incomplete line.
Code:

<meta name="viewport" content="width=device-width, initial height-scale
I'm just mentioning it, I'm not asking you for the missing code. I'll keep an eye out, sure I'll run across the canvas 'ctx content' code somewhere eventually.

By the way, I included tracksData because I assumed that PI=4 version 3 would include a tableArray. I must be mistaken, putting all the variables into separate classes seems the exact opposite. Object oriented programming strikes me as mostly modular, or functional, compared to procedural programming.

I haven’t forgotten the requirement to place each class within its own script code. I don't think that'll be a problem.
.

LongtimeAirman
Admin

Posts : 1461
Join date : 2014-08-10

Back to top Go down

Animate the PI = 4 experiment - Page 8 Empty Re: Animate the PI = 4 experiment

Post by Nevyn on Sun Feb 02, 2020 6:30 pm

You are getting that error because you are not passing in a scene or group object when you call that method.

I'm just calling it a node to differentiate it from objects. As-in a node in a hierarchy. They are basically interchangeable in this context.

Using a class hierarchy does not mean you can't use an array (I'm not exactly sure what you mean by table array). There will be an array, but it will end up being quite inconsequential. It will hold the Track objects, and we will iterate over it in the animate function, but that is about it. Using classes generally means the calling code is more simple, because the complexity is in the classes. It also does make it more modular, although there are ways to do that with other programming paradigms, too.

Using class inheritance allows us to use different implementations, without changing the interface to those classes. As long as there is a common interface, then the calling code treats all of them the same, but each different class does something different. This is called 'Polymorphism', literally meaning 'many changing forms'. In my animal example above, the Animal class provides the interface, with the makeSound method, and the Cat and Dog classes implement that in different ways, creating the Polymorphism. We can substitute any Cat object with any Dog object and the code will still work, although with different results.

That's what I'm trying to get at with the Section class hierarchy. We want to be able to treat all Sections the same, so the Track class can use them without needing to know what type of Section they are. This will simplify the Track code when dealing with Sections. It will literally just call a method, defined in the Section class, and each Section object does whatever it wants to, for its own needs. The Section classes will be given the Ball to operate on. They will make changes to the Ball by moving it along their path.

While the Ball class is totally capable of placing itself, we don't want it to be responsible for that because it doesn't have, and shouldn't have, that kind of information. A Ball is there to be moved, it does not supply the path that it should move on. That is supplied by the Sections that are in the Track that the Ball belongs to.

That meta tag is incomplete. You can find that in any ThreeJS example project, and probably any of mine that you have worked on, too.
Nevyn
Nevyn
Admin

Posts : 1796
Join date : 2014-09-11
Location : Australia

http://www.nevyns-lab.com

Back to top Go down

Animate the PI = 4 experiment - Page 8 Empty Re: Animate the PI = 4 experiment

Post by LongtimeAirman on Tue Feb 04, 2020 6:52 pm

.
Animate the PI = 4 experiment - Page 8 Pieq4v10
None of the above, balls or tracks, are positioned as they will be when PI=4 v3 is finished. I include them 'as is' for comfort and reassurance, that the object code appears to be working as I make changes.

I’ve immersed myself with plenty of object oriented reading and examples, but that doesn’t mean I understand it yet. Using the previous Ball creator function and Ball prototype as example, I was unable to put tracks on the screen. There’s only one ball type, as opposed to three section or track types. I modified the ball code to add a parent instead of the Ball prototype, then did the same with the section code. The track section types (StraightSection, KCurvedSection and PCurvedSection) all call (and inherit from) the same SectionExists function. The Ball creator function now also calls the creator function BallExist to inherit the this.exists variable, making BallExists the prototype parent instead of Ball prototype.

Dare I ask, is that correct?
.

LongtimeAirman
Admin

Posts : 1461
Join date : 2014-08-10

Back to top Go down

Animate the PI = 4 experiment - Page 8 Empty Re: Animate the PI = 4 experiment

Post by Nevyn on Wed Feb 05, 2020 5:41 pm

That doesn't sound right. You don't need a BallExist class just to add a property. Just add it straight to the Ball class. You only want a parent class if there are other classes that are going to inherit from it. Sometimes you might create a parent class just because it makes sense conceptually, and it allows you to separate some code into the 2 classes (parent and child).

There is no way StraightSection can have the same parent class as KCurvedSection and PCurvedSection, because the latter 2 should be inheriting from CurvedSection. Again, you don't need that *Exists class, just add a boolean property if you need it (which would be added to the Section class so all sub-classes get it). I just noticed that you got rid of the Section and CurvedSection classes? Why? You have lost the Section.length property and added it directly to each sub-class, defying one of the main uses of a class hierarchy.

You have also destroyed the polymorphism by doing so. There is no common method for all classes of type Section, so there is no polymorphism. You have instead declared methods like StraightSection.prototype.createSTrack and KCurvedSection.prototype.createKTrack and GCurvedSection.prototype.createGTrack. These should all have the exact same method name, and the Section class should also have a method with the same name. Also, why is a Section creating a Track? A Section is a part of a Track, so there is no way that it can create one.

I don't actually see why you want that exists property either. I imagine you are thinking about being able to disable a track, but you have added this to the Ball and Section classes, which have nothing to do with Track, so I am not sure what this is supposed to do.

Once these classes are setup correctly, I doubt you will need that exists property. If you want to disable a particular Track, then you just don't create it, or don't add it to the array of Tracks. Maybe I am not seeing what you have in mind for that property. When will it be true and when will it be false? What does it mean to be true and what does it mean to be false?

There is one question that you should ask yourself when thinking about adding something to a class or setting up a class hierarchy. In this example, you are adding a boolean exists property, so you should ask yourself: 'Does this class have an exist property, or is it an exist?'. I know, that doesn't make sense, which basically gives you the answer.

Let's do that for the animal example I used before: 'Does a Cat have an Animal, or is a Cat an Animal?'. Clearly, it is the latter, so we go with a parent-child class arrangement. Let's assume we wanted to represent a brain for these animals, we then ask: 'Does a Cat have a brain, or is a Cat a brain?'. Clearly, it has a brain, so we go with a property. Then we ask, 'Does a Cat have a brain, or does an Animal have a brain?'. Again, all animals have a brain, so we add that property to the Animal class. By doing so, we also gave a brain to the Dog class. I'm sure they will appreciate that.

Code:

sBall = new Ball( true, 0.225*r, 1 );
sBall.prototype = Ball.prototype;

In the above code, you are instantiating a new Ball object. However, immediately after that, you are assigning the prototype, which you should not be doing. The JS compiler has already done that for you when it created your object. Prototypes are for classes, not objects, although it is the objects that get all the advantages of them. By that, I mean if you are working with a prototype, then you are working on a class, never an object. When you get to the creation of objects, you should never be working with a prototype. It is all handled for you behind the scenes.

This stuff is tricky. Very difficult to get your head around at first, but once you do, it becomes very powerful. Don't worry too much about mixing things up, I've tortured my fair share of class hierarchies before, and I'll probably do it again, too.
Nevyn
Nevyn
Admin

Posts : 1796
Join date : 2014-09-11
Location : Australia

http://www.nevyns-lab.com

Back to top Go down

Animate the PI = 4 experiment - Page 8 Empty Re: Animate the PI = 4 experiment

Post by Nevyn on Wed Feb 05, 2020 5:58 pm

For clarity, I mentioned those create*Track methods earlier, and suggested that they should have a common name (and that name should not have the word Track in it, either), and I wanted to point out that these names should actually exist as functions, not methods on a class. Also, they will actually create Track objects, not Sections.

Code:

function createSTrack()
{
  var track = new Track();
  track.sections.push( new StraightSection( 5 ) );
  return track;
}

function createKTrack( radius )
{
  var track = new Track();
  track.sections.push( new StraightSection( 1 ) );
  track.sections.push( new KCurvedSection( radius ) );
  return track;
}

function createPTrack( radius )
{
  var track = new Track();
  track.sections.push( new StraightSection( 1 ) );
  track.sections.push( new PCurvedSection( radius  ) );
  return track;
}

This is where you do track specific code. The Track class is just a generic holder of Sections and a Ball, but here, when we instantiate some of them, we make them unique based on whatever it is we want them to be. It is the objects of the class Track that are straight or curved, kinematic or geometric, etc, not the class itself.

Then we create an array of Tracks like this:

Code:

var tracks = [
  createSTrack(),
  createKTrack( radius ),
  createPTrack( radius )
];

Then we initialize the tracks like this:

Code:

var group = new THREE.Group();
for( var i=0; i<tracks.length; i++ )
{
  tracks[i].createObject3D( group );
}

There will be more to it than that, such as positioning the tracks based on their size, etc, but that is the gist of it.

Now, suppose we want to remove the geometric curved track, then all we have to do is comment it out like this:

Code:

var tracks = [
  createSTrack(),
  createKTrack( radius )
//  createPTrack( radius )
];

Voila! No more P track.
Nevyn
Nevyn
Admin

Posts : 1796
Join date : 2014-09-11
Location : Australia

http://www.nevyns-lab.com

Back to top Go down

Animate the PI = 4 experiment - Page 8 Empty Re: Animate the PI = 4 experiment

Post by LongtimeAirman on Sat Feb 08, 2020 4:12 pm

.
Animate the PI = 4 experiment - Page 8 Pieq4v11
Object oriented programming. Oh well, I guess what I studied way back when is called ancient Computer Science.

Thank you Sir, I removed all the changes I made last time.

Ok, javaScript is an object oriented language. All things except a few primatives or null can be defined as objects by using the available ‘new’ constructors. One can choose not to use all the formal constructors, except for a few required things such as, new THREE.Vector3. I must say, after being sheltered by three.js for years, it’s good to see javaScript is way bigger. Using the constructors involve the creation of many different objects and their prototypes, each of which include many available built-in javaScript methods or one can customize their own. MDN, developer.mozilla.org, is a great resource.

For the record, weeks of flailing about is embarrassing, but also motivating. I’ve continued my efforts by watching/studying several youtube videos. As the presenters are quick to point out, in addition to simply running the code, the browser console allows real-time code interaction. After finding a decent video, I then find or recreate all its code (incl any necessary attendant files) in its own folder. Its best when I pause the video each and every line, making and/or saving the indicated changes as well as my own, to both files and/or console before continuing. My favorite videos so far, both about an hour long are:
Object Oriented JavaScript, by Derek Banas, https://www.youtube.com/watch?v=O8wwnhdkPE4 , and
Object-oriented Programming in JavaScript: Made Super Simple by Mosh, https://www.youtube.com/watch?v=PFmuCDHHpwk.

Reviewing them with non-stop pausing (not including the initial code copying), took me six to eight hours each. While I’ve yet to make any positive code changes to PI=4, v3 I know I’ve made all the oop changes indicated in those videos. ECMAScript 5 or 6, I’m at least passing familiar with new syntactical information.

I've gone back and re-read all your comments several times. I believe I even understand them. This mental hangup is fierce.

Not looking for a new career, what do you think of https://en.wikipedia.org/wiki/CS50 ?

P.S., I just now noticed your last post - can't wait to read it.
.

LongtimeAirman
Admin

Posts : 1461
Join date : 2014-08-10

Back to top Go down

Animate the PI = 4 experiment - Page 8 Empty Re: Animate the PI = 4 experiment

Post by LongtimeAirman on Tue Feb 11, 2020 2:32 pm

.
I’m finally able to study the code without jumping into the references; Post correction - Not.

Thus far, you’ve provided the code needed to create two classes, Ball and Section. The only class you haven’t formally defined is the Track class. It would be nice if I could figure out a Track constructor and prototype. Here’s my 'progress'.

You’ve described Track as an array of sections. Section is an abstract class that is not “created’ directly. The constructor requires a ( length ).
Code:

     function Section( length )  

Your latest code installment includes track creations. Here’s a line from the createSTrack constructor function.
Code:

     track.sections.push( new StraightSection( 5 ) );
For the straightTrack, length is 5. Five straight sections 2*r long. This clearly suggests ( length ) is the number of straight track sections. I should mention, up till now I wrongly interpreted ( length ) being equal to 2*r. That each track’s physical length equals 10*r. (2*r + 8*r = 10*r).

You also included both curved tracks. Here’s the core of the createKTrack constructor function.
Code:

     track.sections.push( new StraightSection( 1 ) );
     track.sections.push( new KCurvedSection( radius ) );
The length of the initial straight section, 1, followed by the complete circular track. The KCurvedSection (or GCurved Section) passes a track radius instead of a length.

I suppose the length being passed can be considered a kind of index or the array length.Passing the radius includes the implicit understanding that the radius conveys the entire circular track. radius might indicate length = 4. I believe you mentioned the code can go either way, I would prefer treating each circular track as four sections rather than 1.

Since Ball and Section are both function constructors, I guess Track should be too. But the argument passed is an object that’s used to build an array.
Code:

    function Track( type, value )  
     {  
     }
     Track.prototype = Object.create( {} );
The above runs, but the console indicates that Track is undefined. I haven’t been able to define Track using Track.sections.

Here's a quote from JavaScript The Definitive Guide, section 9.6.7. Note the bold emphasis – we might want … constructor“ is mine.
Constructor Overloading and Factory Methods

Sometimes we want to allow objects to be initialized in more than one way. We might want to create a Complex object initialized with a radius and an angle (polar coordinates) instead of real and imaginary components, for example, or we might want to create a Set whose members are the elements of an array rather than the arguments passed to the constructor.

One way to do this is to overload the constructor and have it perform different kinds of initialization depending on the arguments it is passed. Here is an overloaded version of the Set constructor, for example:

Code:

     function Set() {              
          this.values = {};     // The properties of this object hold the set    
          this.n = 0;           // How many values are in the set
          // If passed a single array-like object, add its elements to the set    
          // Otherwise, add all arguments to the set    
          if (arguments.length == 1 && isArrayLike(arguments[0]))        
          this.add.apply(this, arguments[0]);    
          else if (arguments.length > 0)        
          this.add.apply(this, arguments);
     }

I still feel lost. Am I making any valid comments?
.

LongtimeAirman
Admin

Posts : 1461
Join date : 2014-08-10

Back to top Go down

Animate the PI = 4 experiment - Page 8 Empty Re: Animate the PI = 4 experiment

Post by Nevyn on Tue Feb 11, 2020 5:06 pm

Airman wrote:For the straightTrack, length is 5. Five straight sections 2*r long. This clearly suggests ( length ) is the number of straight track sections. I should mention, up till now I wrongly interpreted ( length ) being equal to 2*r. That each track’s physical length equals 10*r. (2*r + 8*r = 10*r).

No, the length is meant to be the actual length of that single section, in meters or kilometers, or whatever units are in use. I couldn't be bothered to go back through all of the posts to find the right values, so I just chucked in something that seemed about right. I was more concerned with the structure of the code rather than the values it was being given.

Airman wrote:I suppose the length being passed can be considered a kind of index or the array length.

No, it absolutely can not be considered an index or length of any array. It is a distance. The complete distance of that particular section. The curved sections are given a radius because they will calculate different distances based on whether they use PI=3.14 or PI=4. The radius is only used in order to calculate that length (well, it may be needed for other things later, too).

Airman wrote:Passing the radius includes the implicit understanding that the radius conveys the entire circular track. radius might indicate length = 4. I believe you mentioned the code can go either way, I would prefer treating each circular track as four sections rather than 1.

I know it seems like using 4 sections to a circle is going to be easier, but it will not be when it comes time to join the sections together so that they line up and form a complete track. It will complicate that code quite a bit. It is do-able, but it may get a bit messy.

The Track class will look something like this:

Code:

function Track()
{
  this.sections = [];
  this.ball = new Ball();
}

Track.prototype = Object.create( {} );

Track.prototype.createObject3D = function( parentNode )
{
  this.object3D = new THREE.Group();
  for( var i=0; i<this.sections.length; i++ )
  {
    var node = new THREE.Group();
    this.sections[i].createObject3D( node );
    // TODO position node so that it lines up with the last section (if there is one)
    // retrieve the bounds of the node and use the X dimension to move each node over so that they line up with each other
  }
}

Track.prototype.applyMotion = function( time )
{
  var t = time.delta; // the amount of time we have to move the ball
  // TODO find index of current section that this.ball is in
  var index = ...;
  while( index < this.sections.length && t > 0 )
  {
    // allow section to move ball, it will return the amount of time left over
    t = this.sections[index].applyMotion( t, this.ball );
    index++;
  }
}

With respect to constructors, you can do some amazing things like you've shown above, but we don't need that. Don't over complicate things.
Nevyn
Nevyn
Admin

Posts : 1796
Join date : 2014-09-11
Location : Australia

http://www.nevyns-lab.com

Back to top Go down

Animate the PI = 4 experiment - Page 8 Empty Re: Animate the PI = 4 experiment

Post by LongtimeAirman on Wed Feb 12, 2020 12:29 pm

.
Animate the PI = 4 experiment - Page 8 Pieq4v12
The oop big picture eludes me.

The PI=4 Experiment has four circumferential sections. This is because, during circular motion, each quarter circle is being traversed in the same amount of time as an equivalent straight track, 4 diameters long.

Our tracks section lengths are based on the diameter. Why do we want to pass a radius? If we use a radius, then the tracks are ten sections long. The circular track should be eight. The total track length is 10 sections. Passing the radius instead of the diameter complicates things; wouldn't it simplify things to use one or the other?

Nevyn wrote:the length is meant to be the actual length of that single section, in meters or kilometers, or whatever units are in use

Why do we need to pass the length parameter to the straight section and radius parameter to the curved? They are the same distance. Provide any length you like, the curved functions will make curves of four diameters or eight radii, and the straight function will make straight sections of four diameters or eight radii in length. One length works for both since they are both equal to the same kinematic distance.
.

LongtimeAirman
Admin

Posts : 1461
Join date : 2014-08-10

Back to top Go down

Animate the PI = 4 experiment - Page 8 Empty Re: Animate the PI = 4 experiment

Post by Nevyn on Wed Feb 12, 2020 6:10 pm

Airman wrote:The PI=4 Experiment has four circumferential sections. This is because, during circular motion, each quarter circle is being traversed in the same amount of time as an equivalent straight track, 4 diameters long.

Our tracks section lengths are based on the diameter. Why do we want to pass a radius? If we use a radius, then the tracks are ten sections long. The circular track should be eight. The total track length is 10 sections. Passing the radius instead of the diameter complicates things; wouldn't it simplify things to use one or the other?

I find it odd that you are arguing for using the diameter. I never think in diameters because the radius is the defining characteristic of a curve. A diameter only applies to a circle. So you are wanting to use a diameter, while also wanting to split the circles into 4 individual sections, thereby breaking the circles and losing the diameter.

What we want is for this app to rely on the concept of velocity, and not diameters or radii. Those properties are only there to give us a length, which can then be used in the velocity equation. So we don't care that it is 4 diameters long. Velocity will work for any length.

I also don't understand why you insist on spllitting up those circles into 4 curves. I think it is because you have an association between marker positions and the ends of sections. We don't need to combine those two concepts into the same thing. We can separate them and make things much easier. Also, if you do link those together, you end up with 5 sections per circle because the PI marker will split the last section again.

For the markers, I am thinking of adding some methods to the classes like this:

Code:

function Marker( distance )
{
  this.distance = distance; // this is the absolute distance along the full track
  this.material = ...; // create a THREE.Material for the marker which we can manipulate later
  this.object3D = ...; // create a node to represent the marker, using this.material
}

Marker.prototype = Object.create( {} );

...

Track.prototype.addMarker = function( marker )
{
  var dist = 0;
  var md = marker.distance;
  for( var i=0; i<this.sections.length; i++ )
  {
    dist += this.sections[i].length;
    if( marker.distance < dist )
    {
      // the marker is on this section
      this.sections[i].addMarker( marker, md ); // position the marker based on the section it is in
      this.markers.push( marker ); // add the marker to this track
      break; // jump out of the for loop
    }
    md -= this.sections[i].length;
  }
};

...

Section.prototype.addMarker = function( marker, distance )
{
  // TODO each sub-class will implement this method to place a marker at the specific distance along its own length
  // distance is relative to this section
  // to put the marker at the start of the section, distance=0
  // to put the marker at the end of the section, distance=this.length
};

...

Each section class then handles its own shape and places the marker at the correct position (and with the correct rotation). Then the Track class will iterate through the markers and it only needs the absolute distance that the ball has traveled to do that. It doesn't need to understand curves or straights, kinematic or geometric. It knows the distance that each marker is at, and compares that to the current distance of the ball to determine when the markers have been reached.

Airman wrote:Why do we need to pass the length parameter to the straight section and radius parameter to the curved? They are the same distance. Provide any length you like, the curved functions will make curves of four diameters or eight radii, and the straight function will make straight sections of four diameters or eight radii in length. One length works for both since they are both equal to the same kinematic distance.

For one thing, it makes no sense to describe a straight section with a radius. Straights don't have radii. Most importantly, though, that length doesn't work for a geometric distance. A kinematic curve and a geometric curve will have different lengths given the same radius. So we can't just pass in the length, since the geometric curve will end up with a smaller radius. We want them to have the exact same radius, so that is what we should supply to each type of curve, and then let it figure out how much length that equals.

If we were to pass in the length, then the calling code would need to know that it is creating a kinematic or geometric curve, and make the appropriate calculation. That breaks the idea of keeping related code together in an appropriate class. The curve knows what type it is, so it can make the right calculation. What we are trying to achieve is to make the calling code (of these classes) treat them all in the same way, except for a few parameters here or there which make them specific. Once created, we want every section to be treated the same. We don't want to care if the ball is currently in a straight or curve, kinematic or geometric section. We only care that it is a section and leave that class to deal with moving the ball.

Look at your previous code. It was full of if/else blocks and switch statements. Always determining what type of section it was currently handling. Further more, all of this code was duplicated for each track, with slight differences. The new way of doing it replaces those if/else and switch statements with classes. The calling code doesn't care what type of section it is, it just gives it the ball and lets it do its thing. Each class understands its own motion, so it can do that, without a care in the world for any other type of section.

Let's say you find a bug in the way straight sections are working. In the old code, you had to find the straight section code for each type of track and fix it in all of them. In the new code, you just go to the straight section class and fix it once.

As a bonus, although this won't be used in this particular app, the new way allows you to create a track containing any number and type of sections. We could create a track that has a straight, then a geometric curve, then a kinematic curve, etc, etc. Everything will still work. To do that in the old code would mean coding up that specific combination, and then, it only works for that particular track. So if we wanted to add another type of track, then we have to code that up too. That is a lot of work for no reason. Using classes allows you to put things together in new ways without everything falling apart or needing severe re-writes. Most of the code will still work because it doesn't need to understand the specifics. It only needs to understand the common interface to those classes.
Nevyn
Nevyn
Admin

Posts : 1796
Join date : 2014-09-11
Location : Australia

http://www.nevyns-lab.com

Back to top Go down

Animate the PI = 4 experiment - Page 8 Empty Re: Animate the PI = 4 experiment

Post by LongtimeAirman on Thu Feb 13, 2020 6:39 pm

.
Sorry for the misunderstanding. I’m not arguing for “using the diameter” as the track section lengths. We were already using them as StevenO does in his experiment. Equivalent kinematic distances along straight and curved tracks. The tracks' physical dimensions are more important than the balls' velocity. Any ramp velocity works as long as the balls experience good rolling motion. Given the fact that PI=4 for circular motion, I believe it absolutely does make sense to think of a radius or a diameter as straight distances – given an equivalent curved path. The entire circular path as a single straight section is also valid. Looking at circular paths lately I keep trying to think in terms of equivalent curved or straight lengths.

Object oriented programming sounds great. I especially like the idea that the OOP methodology could easily allow the creation of customizable tracks. What I seem to be missing in the opp big picture is how to use it. Since I haven’t made much sense of PI=4 v3 yet, I’m still studying. Most recently, I reviewed a 27 minute, seven year old youtube video at http://www.objectplayground.com/. James Shore does a good job covering the prototype chain and inheritance and his site includes what he calls an object visualizer. Yes, it’s beginning to make sense and form patterns.

None of your code includes ECMAScript 6 reserved code words: class, extends or super. Instead, you’re using reserved words: Object, prototype, and new. That indicates the so called “classical” javaScript pattern for implementing classes, but I have no idea what’s next. Besides your own complex projects are there any oop sources you might suggest?
.

LongtimeAirman
Admin

Posts : 1461
Join date : 2014-08-10

Back to top Go down

Animate the PI = 4 experiment - Page 8 Empty Re: Animate the PI = 4 experiment

Post by Nevyn on Fri Feb 14, 2020 8:49 pm

I don't think I explained myself very well. You can, and should, use the radius (or diameter) to calculate the lengths of all sections. However, while we want to pass in the radius to the curved sections, we don't want to do that for the straight sections, even though it could also calculate its own length based on it. The reason for that is that a radius, as a concept, does not apply to a straight. It just doesn't make sense. So the straight section class should just take in a length. This also allows it to take on any length and not be tied to this particular application which happens to rely on radii.

One of the great things about classes is that they are reusable. At least we should strive to make them so as much as possible. Sometimes a particular class will be tailored to the situation you design it for, but if there is a way to make it more generic, then this should be done (unless it is too onerous or makes the class difficult to work with). Then, when we start another project and come across a similar class, we can just take an existing one we have already developed and worked the bugs out of.

As you develop a class, try to think of it in isolation to the application that it is being designed for. Of course you need to make sure it does what that application needs, and in a way that it needs it to be, but if the class is something that could be used elsewhere, then try to make sense of it on its own.

The classes we are developing here are actually a good example. There is nothing about a track that is specific to the PI=4 concept. The idea of a track, and its sections and even the ball that travels on it, can be used in many other apps. So we should develop these classes with that in mind. Then, when we use them for this app, we find that this app uses them in a limited way, based on the PI=4 concept. The app is more limited than the classes. The PI=4 concept creates the need for lengths based on radii, not the track class, nor the section class.

I might be moving a bit fast. You need time for these concepts to sink in a bit before you can use them in more generic ways like I am pushing for. Being able to see when and where to place code to get the best advantages takes a lot of practice, and I take it for granted.

You mention not being able to see how to use classes effectively, and I've been wondering the same thing. That you need to see the other side of it in order to understand why we want the classes to be the way they are. I have tried to show a little bit of that in the last few posts, such as the Track.prototype.applyMotion method, which makes use of the Section.prototype.applyMotion method, and also where I showed how to use the create*Track functions which actually create Track objects.

So let's have a look at how these classes can make the app code a lot smaller, easier, understandable, and maintainable. If we break down what the app needs, then we can fill in the blanks with our classes.

The first thing we need is a way to define a track. This should be easy to do and manipulate. It should be flexible, even if we don't actually need it for this particular app. Where possible, it should also be extensible, so that we can use it in new ways later, in some other app, should we ever need it.

The second thing we need is a way to convert the track definition into 3D scene nodes. You see, the track is used to calculate motion as its primary goal, but it is also used to create a representation of that track so that the user can see that motion (and the track itself).

The third thing we need is a way to cause motion of the ball on the track. This needs to update the internal variables, but it also needs to update the 3D scene nodes so that the user can see it. Notice how I separate the idea of motion and visual representation. It is easy to conflate the two into one, but they don't need to be. It may end up that they do indeed become intertwined with each other, but we should not assume it from the start. There are good reasons to go either way, so we need to determine which reasons make sense for the particular app we are developing.

The fourth thing we need is a way for the user to exert some control over the app. The basics will be start, stop, and reset, but there could be others if we want.

Okay, that gives us our needs. Now we need to figure out how to accomplish them. I'm going to leave the definition for a bit, and work on the initialization and motion based areas first. These are the simplest, from an interface perspective.

We need to ask ourselves what the app should be interacting with. What classes will the app need to know about in order to function effectively? Does it need to know about the balls? Does it need to know about sections? Does it need to know about tracks? The answers to these questions will tell us where to put certain methods in order to accomplish our goals.

The quick answer is that the app only needs to know about tracks in order to initialize and create motion. The track definitions will also need to know about sections and balls, but we will ignore that for now. So the Track class is where we will want some methods to perform initialization and motion based calculations.

We create some placeholder method signatures.

Code:

Track.prototype.init = function()
{
  // TODO initialize this track
};

Track.prototype.applyMotion = function( time )
{
  // TODO move the ball along the sections given the time to do so
};

These methods may change as we develop the app more, and figure out the things we actually want these methods to do, but these give us a general idea of what we want, for now.

We create some app code to use these methods.

Code:

// global variable to hold all Track objects in an array
var tracks = [];

function init()
{
  // TODO define the tracks
  ...
  // now initialize the Tracks
  for( var i=0; i<tracks.length; i++ )
  {
    tracks[i].init();
  }
  ...
}

// global variable to store the amount of seconds in a frame
var frameTime = 1/60;

function animate()
{
  requestAnimationFrame( animate );
  // apply motion to each track
  for( var i=0; i<tracks.length; i++ )
  {
    tracks[i].applyMotion( frameTime );
  }
  renderer.render( scene, camera );
}

Continuing on in next post...


Last edited by Nevyn on Fri Feb 14, 2020 9:21 pm; edited 1 time in total
Nevyn
Nevyn
Admin

Posts : 1796
Join date : 2014-09-11
Location : Australia

http://www.nevyns-lab.com

Back to top Go down

Animate the PI = 4 experiment - Page 8 Empty Re: Animate the PI = 4 experiment

Post by Nevyn on Fri Feb 14, 2020 9:20 pm

Defining the tracks does get more complicated, because we now need to know about sections and balls. We could hide the balls inside of the Track class, but I don't want to do that because we may want some sort of individualization of them. For example, we might want to use different colored balls for each different track, or we might want to use different shapes for balls (and therefore they would not really be balls, but some more generic concept, such as an avatar). Instead of a ball, we might want to use a little car that drives around the track, or a slug that slips along it, the possibilities are endless. The main point is that the Track class doesn't need to know what it is that is moving around the track. It only needs to know how to tell it to move. Actually, it is the Section classes that need to know that, not the Track class.

So our Track class needs to store whatever it is that is moving around it, and the sections that define the path. Even with those sections, the Track class should not know much about them. For a fully fledged concept of a track, we would only need to know where each section starts and ends (and at what angle, etc). Allowing other sections to be connected to it. I have, so far, limited this a bit in order to reduce the complexity of connecting them together. That is why I have been pushing the full circle rather than split curves idea. It simplifies connecting them together such that we only need to care about 1 dimension because all sections will start and end on that dimension. To be more precise, they will only vary in that dimension, with the other dimensions being equal. Within each section there can be variance in those other dimensions (or we couldn't create curves), but the start and end points must be on the same dimensions (which a circle satisfies because it starts and ends at the same spot), and only vary in 1.

What I am going to do is create a Ball in the Track constructor, just so that it will always have something to move, but that can be replaced by the calling code that is creating the Track if it wants to. This is why we have an init method and don't do it in the constructor.

Code:

function Track()
{
  this.sections = [];
  this.moveable = new Ball();
}

Track.prototype = Object.create( {} );

Track.prototype.add = function( section )
{
  this.sections.push( section );
  return this; // by returning this, we allow function chaining
};

Now we can create some functions to create some tracks.

Code:

// global variable to store the radius
var radius = 10;

function createSTrack()
{
  var track = new Track()
    .add( new StraightSection( 2 * radius * 5 ) )
  ;
  track.moveable.material.color = '#ff0000';
  return track;
}

function createKTrack()
{
  var track = new Track()
    .add( new StraightSection( 2 * radius ) )
    .add( new KinematicSection( radius ) )
  ;
  track.moveable.material.color = '#00ff00';
  return track;
}

function createGTrack()
{
  var track = new Track()
    .add( new StraightSection( 2 * radius ) )
    .add( new GeometricSection( radius ) )
  ;
  track.moveable.material.color = '#0000ff';
  return track;
}

By defining the add method on the Track class, we hide how the Track stores the Sections. This would allow us to change how that happens should we ever need to. I don't think we will need to in this app, but it is a good design decision anyway. It also simplifies the calling code, as we see above. I have used function chaining on the add method too, so it makes it more readable.

In all honesty, I would not directly access that material object to set the color. I would hide that behind some method so that different classes can apply the color in different ways. I am just being lazy here so that I don't have to define all of these things, adding a few new classes that don't really help this discussion. We may get to that at some stage, though.

Now that we have these functions, we can edit our init function from above.

Code:

function init()
{
  // define the tracks
  tracks.push( createSTrack() );
  tracks.push( createKTrack() );
  tracks.push( createGTrack() );
  // now initialize the Tracks
  for( var i=0; i<tracks.length; i++ )
  {
    tracks[i].init();
  }
  ...
}

I hope that helps to see how classes make the calling code simpler and more readable, and therefore, more maintainable and flexible.
Nevyn
Nevyn
Admin

Posts : 1796
Join date : 2014-09-11
Location : Australia

http://www.nevyns-lab.com

Back to top Go down

Animate the PI = 4 experiment - Page 8 Empty Re: Animate the PI = 4 experiment

Post by Nevyn on Fri Feb 14, 2020 9:26 pm

I've just realised that the first post has lost a lot of what I had written. I will add it in next.
Nevyn
Nevyn
Admin

Posts : 1796
Join date : 2014-09-11
Location : Australia

http://www.nevyns-lab.com

Back to top Go down

Animate the PI = 4 experiment - Page 8 Empty Re: Animate the PI = 4 experiment

Post by Sponsored content


Sponsored content


Back to top Go down

Page 8 of 9 Previous  1, 2, 3, 4, 5, 6, 7, 8, 9  Next

Back to top


 
Permissions in this forum:
You cannot reply to topics in this forum