var N2=class{ borderX=[[25,242],[25,242,460],[25,242,460,675]]; //An array that indicates the horizontal positions of the lines between freebody diagrams. borderY=230; //The y position of the bottom the rectangle surrounding the freebody and acceleration diagrams. studAccGroup=[]; //A reference to the group where the student acceleration vector is located. accStudentGroup; //The group containing the groups the keyAccGroup array. keyAccGroup=[]; //A reference to the group where the acceleration vector is located for the key. accKeyGroup; //The group containing the groups the studAccGroup array. forceAxesGroup=[]; //A reference to the group contain the coordinate axes for each freebody diagram. accAxesGroup=[]; //A reference to the group that contains the coordinate axes for each acceleration diagram legendString=""; //legendString is a string containing all of the legends. It is used when entering force labels to verify that a label corresponds to one of the legends. forceTypes=""; forceTypeArray=[]; subscriptIndexOffset=0; feedback; buttonDownFlag=false; keyDownFlag=false; focusForceNum=undefined; focusForceColor=undefined; active = false; highlightForceColor='red'; greenHighlightForceColor='limegreen'; axesColor='SkyBlue'; forceLabelHighlightBox=undefined; answer={forces:[], status:false, touchingFeedback:[], touchingStatus:[], accelerations:[{angle:999,angleIDs:[]},{angle:999,angleIDs:[]},{angle:999,angleIDs:[]}], xAxisAngles:[0,0,0],fbdAxesAngleIDs:[[[],[],[],[]],[[],[],[],[]],[[],[],[],[]]],accAxesAngleIDs:[[[],[],[],[]],[[],[],[],[]],[[],[],[],[]]],angles:[],n2Equations:["","","","","",""],n2Show:["","","","","",""]}; fbdSVG; fbdSVG1; fbdSVG2; deleteButtonRef; angleButtonRef; tickMarkControlRef; forceButtonRef; loadKeyButtonRef; downloadJSONButtonRef; focusElement=undefined; forceLength=50; forceLabelOffset=7; oldForceLabel; forceColor='black'; forceDotColor='red'; studForceGroup=undefined; //reference to the SVG group that contains the student force vectors. studAngleGroup; accLength=30; axesLength=92; axesLengthText=this.axesLength-8; accAxesLength=30; startPointForceNum; endPointForceNum; startPointAxisNum; endPointAxisNum; focusFlag=false; accSelectedFlag; axisSelectedFlag; forceSelectedFlag; angleSelectedFlag; angleFeedbackFlag; startVsEndPointFlag=undefined; //true means the start point on an angle is being dragged. false means the endpoint is being dragged. keyForceGroup=undefined; //reference to the SVG group that contains the student force vectors. fbdObject=[]; fbdObjectKey=[]; cm=[]; //The coordinates of each freebody diagram cmAcc=[]; //The coordinates of each acceleration indicator cmKey=[]; cmRadius=12; forceFlag=[]; inputDoneFlag=true; angleTolerance=.01; response; feedbackStyle; accelerationFeedbackFlag; keyFlag=false; startingFlag=true; initialLoadFlag=true;//Flag that is set to true if this.answer is initally blank indicating that no this.answer has yet been submitted. tickMarksChecked=[]; //An array of tick mark numbers. If a number appears in this list, then the forces with that number of tick marks has previously been checked. tickMarkTableStartupFlag; n2StartupFlag=true; forceSets; n2Answers; n2Response=["","",""]; netForceXArray=[[],[],[]]; netForceYArray=[[],[],[]]; aXArray=[[],[],[]]; aYArray=[[],[],[]]; correctNetForceX=[[],[],[]]; correctNetForceY=[[],[],[]]; correctAX=[[],[],[]]; correctAY=[[],[],[]]; correctN2=[["",""],["",""],["",""]]; //The correct Newton's 2nd Lae equations returned to MOER. Each this.answer is derived from the fbd once that fbd is correct. showN2=[["",""],["",""],["",""]]; //The "showthis.answer" version of each equation that is returned to MOER. forceVariableList=[[],[],[]]; //A string for each object of the force labels on that object. Does NOT contain labels replaced by labels in tick mark alias table. forceVariableArray=[[],[],[]]; //An array of the force labels on each object. Includes labels that were replaced by labels from tick mark alias table. variableList; constructor(questionNum,questionPartNum,questionTickMarkAliasNum,maxNumTickMarks,questionN2PartNum,forceAngleIncrement,coordinateAxesRotate,initialCoordinateXAxisAngle,isNormalForceAllowed,isTensionForceAllowed,isWeightForceAllowed,isFrictionForceAllowed,isDragForceAllowed,isElectricForceAllowed,isMagneticForceAllowed,isContactForceAllowed,twoSubscriptLabel,shapes,fbdList,frictionlessSurfaces,relativeMotions,contactForces,summary,n2EquationFlags){ this.questionNum=questionNum; this.questionPartNum=questionPartNum; this.questionTickMarkAliasNum=questionTickMarkAliasNum; this.maxNumTickMarks=maxNumTickMarks; if (this.maxNumTickMarks>25){this.maxNumTickMarks=25}; this.questionN2PartNum=questionN2PartNum; this.forceAngleIncrement=forceAngleIncrement; //When a force vector is rotated, this variable controls the increment between angles. The increment is expressed in degrees and must divide evenly into 360 degrees. this.coordinateAxesRotate=coordinateAxesRotate; //The program draws coordinate axes on both the freebody diagrams and on the acceleration indicator. If coordinateAxesRotate is set to true, a red dot will appear on the +x axis of the freebody diagram axes and students can drag this dot to rotate the axes. When set to false, the red dot is not visible and the axes cannot be rotated. this.initialCoordinateXAxisAngle=initialCoordinateXAxisAngle; //initialCoordinateXAxisAngle is the array of angles that indicates the initial angle of the x-axis for the coordinate system of each freebody diagram. The angles should be specified in degrees. 0 degrees means horizontally to the right. +00 degrees is straight down. -90 degrees is straight up. The angles should be consistent with the angle snap as specified by forceAngleIncrement. The number of angles in the array should be the same as the number of freebody diagrams. //Example: this.initialCoordinateXAxisAngle=[0,0,0]; this.isNormalForceAllowed=isNormalForceAllowed; this.isTensionForceAllowed=isTensionForceAllowed; this.isWeightForceAllowed=isWeightForceAllowed; this.isFrictionForceAllowed=isFrictionForceAllowed; this.isDragForceAllowed=isDragForceAllowed; // Not yet implemented. this.isElectricForceAllowed=isElectricForceAllowed; //Only allowed if twoSubscriptLabel is set to false. Not yet implemented. this.isMagneticForceAllowed=isMagneticForceAllowed; //Only allowed if twoSubscriptLabel is set to false. Not yet implemented. this.isContactForceAllowed=isContactForceAllowed; this.twoSubscriptLabel=twoSubscriptLabel; //If set to true, then force labels are of the form Nab where N is the force type and ab are subscripts referring to the by and on labels. If set to false, then force labels are of the form FNab, where N, a and b are all subscripts. N is the force type and a and b are the by and on labels. twoSubscriptLabel is automatically set to false (the three subscript mode) if the problem includes either electric or magnetic forces. this.shapes=shapes; /* Each shape is an object of the form: { description:[] an array where each element is a line of the description of the shape, Each item in the list is one line of text from the description contained in the pictureSVG. legend:a letter that represent the object in the FBD type:object||string||spring stretched||spring compressed||spring relaxed||spring unknown||system. touching:[] an array of the segments where the shape is being touched. Each item in the array is an object of form: {shape:the shapeNum of the shape touching this object, point1:{x:x-coord,y:y-coord}, point2:{x:x-coord,y:y-coord}} acceleration:an angle that represents the direction of the acceleration of the object. Angles should be expressed in degrees. The angle is an angle between -180degrees and +180degrees. 0 degrees is straight right. +90 degrees is straight down. -90 degrees is straight up. 999 degrees indicates that the object is not accelerating. The acceleration angle of a system should not be specified. forces:[] an array of force vectors. Each item in the array is an object of form: {x:(x component of force), y:(y component of force), by:(the shapeNum of the object the force is by), on:(the shapeNum of the object the force is on), type:('N'||'W'||'T'||'f')} parts: If the shape is a system, then parts is a string that lists the legends of all of the parts. If the shape is not a system, parts is an empty string. touching:[] Initially a string that contains all of the legends of the objects that touches a given shape. Do not include commas, spaces or other separators between the legends in this string. This string is converted into an array in the startup function. The array consists of the shapeNums of the objects touching the given object. } */ this.fbdList=fbdList; //this.fbdList: A string of object legends for which to draw FBDs. The number of legends in the list should be consistent with the number of center of mass dots in the FBD SVG whose URL is listed in #3 above. Note that the legends contained in FreebodyData.txt are replaced by shapeNums. this.frictionlessSurfaces=frictionlessSurfaces; //frictionlessSurfaces: A string of pairs of surfaces that are frictionless. Each pair consist of two legends. Pairs are separated by commas. The startup function replaces this string with an array of form: {shape1:(the shapeNum for shape 1), shape2:(the shapeNum for shape 2)} this.relativeMotions=relativeMotions; //relativeMotions: Relative motion string. List item should be separated by commas. Each item in the list should be in form 'A angle B'. The 'A' and 'B' are object legends. 'A 30 B' indicates that object A is sliding at a 30 degree angle relative to object B. This also means that 'B' is moving at a -150 degree angle relative to 'A'. The angle 999 means that the two objects aren't moving relative to each other this.contactForces=contactForces; //contactForces: A single contact can be decomposed into normal and frictional components. This array of objects gives info about the directions of each contact force and its corresponding normal and friction components. Each object in the array is of the form: // {contactAngle:(an angle that describes the direction of the contact force), normalAngle:(the angle of the normal force component), frictionAngle:(the angle of the friction force component), by:(the shapeNum of the object the force is by), on:(the shapeNum of the object the force is on), description:(a text string that describes the direction from the description of the problem)} //A contact force may not include a friction force. If the friction force is zero, set frictionAngle to 999. //The angle is an angle between -180degrees and +180degrees. 0 degrees is straight right. +90 degrees is straight down. -90 degrees is straight up. The description must be a phrase the fits inside the quotes of a sentence like: the contact force is directed "diagonally downward". //The program uses contactForceFlag to indicate whether the student this.answer includes the contactForce for each item in the array. Initially, the flag should be set to false. //The program uses normalFrictionForceFlag to indicate whether the student this.answer includes either the normal or friction force from each item in the contactForces array. Initially, the flag should be set to false. //The program will check to see if the student this.answer includes each contact force and the associated normal/friction forces. If both are present, an error will be flagged. If one or the other is present, then the correct this.answer will be altered to include the force that student has included and exclude the other. //Example: this.contactForces=[{by:"H", on:"Y", contactAngle:45, normalAngle: 0, frictionAngle:90, description:"at an angle of 45 degrees below the +x axis", contactForceFlag:false, normalFrictionForceFlag:false}]; this.summary=summary; this.n2EquationFlags=n2EquationFlags;//An array of true/false flags that indicates which equations students must write. this.answer.xAxisAngles=this.initialCoordinateXAxisAngle; this.setKeyFlag(); this.loadOriginalFBD(); } updateAllLegends(){ for (var shapeNum=0;shapeNum1){ this.answer.touchingFeedback[partNumber]="
You entered the letters "+extraLetters+", but those letters don't correspond to the legends of any of the objects. Please fix."; this.answer.touchingStatus[partNumber]=false; this.updateAnswerBox(); return; } //Every letter is a legend. Check to see if the student has included an object in its own list of touching objects if (studTouching.includes(this.shapes[shapeNumber].legend)){ this.answer.touchingFeedback[partNumber]="
You entered the letter "+this.shapes[shapeNumber].legend+", which corresponds to the "+this.getDescription(shapeNumber)+". You must not include the "+this.getDescription(shapeNumber)+" in its own list of touching objects. Please fix."; this.answer.touchingStatus[partNumber]=false; this.updateAnswerBox(); return; } //Check to see if student has entered the legend of a system that includes the given object for (var i=0;i1){ var letters=""; var descrips=""; for (var i=0;i Legend $shapes Edit Key Tick Marks 0 +y +x +y +x DummyText 0 Acceleration a ≠ 0 a = 0 '; dataKey=' DummyText 0 Acceleration a ≠ 0 a = 0 The key displays the correct forces and accelerations only. It does not display the coordinate axes or any angles. '; break; case 2: width="533px"; data=' Legend $shapes Edit Key Tick Marks 0 +y +x +y +x DummyText 0 Acceleration a ≠ 0 a = 0 +y +x +y +x DummyText 1 Acceleration a ≠ 0 a = 0 '; dataKey=' DummyText 0 Acceleration a ≠ 0 a = 0 DummyText 1 Acceleration a ≠ 0 a = 0 The key displays the correct forces and accelerations only. It does not display the coordinate axes or any angles. '; break; case 3: width="800px"; data=' Legend $shapes Edit Key Tick Marks 0 +y +x +y +x DummyText 0 Acceleration a ≠ 0 a = 0 +y +x +y +x DummyText 1 Acceleration a ≠ 0 a = 0 +y +x +y +x DummyText 2 Acceleration a ≠ 0 a = 0 '; dataKey=' DummyText 0 Acceleration a ≠ 0 a = 0 DummyText 1 Acceleration a ≠ 0 a = 0 DummyText 2 Acceleration a ≠ 0 a = 0 The key displays the correct forces and accelerations only. It does not display the coordinate axes or any angles. '; break; } var qn=1000*this.questionNum+this.questionPartNum; //console.log('document.getElementById("FBDDiv"+qn)='+document.getElementById("FBDDiv"+qn)); data=data.replace(/9.49/g,this.cmRadius); dataKey=dataKey.replace(/9.49/g,this.cmRadius); //console.log("qn="+qn); document.getElementById("FBDDiv"+qn).innerHTML=data; document.getElementById("FBDDiv"+qn).style.width=width; var ansID="ans"+(this.questionNum-1)+"-"+this.questionPartNum; //console.log("ansID="+ansID); //ansID="TestDiv"; var divKey=document.createElement("div"); divKey.setAttribute("id","divKey"); divKey.style.width=width; divKey.innerHTML=dataKey; document.getElementById(ansID).innerHTML=""; document.getElementById(ansID).appendChild(divKey); if (this.keyFlag){ divKey.style.display="block"; } else { divKey.style.display="none"; } this.loadFBD(); } loadFBD() { //console.log("Inside loadFBD"); this.loadFBD1(); //console.log("After loadFBD1") this.getInfoOnCMsInFBDs(); //console.log("After getInfoOnCMsInFBDs") this.startup(); //console.log("After startup") this.loadFBD2(); //console.log("After loadFBD2") this.loadKey(); //console.log("After loadKey") this.labelFBDs(); //console.log("After labelFBDs") //createCheckForcesButton(); this.addOnChangeEventToLegendInputs(); //console.log("After addOnChangeEventToLegendInputs"); this.addOnChangeEventToTickMarkInputs(); //console.log("After addOnChangeEventToTickMarkInputs") this.updateAllLegends(); //console.log("After updateAllLegends"); this.addOnChangeEventToTouchingInputs(); this.updateAllTouchings(); if (!this.initialLoadFlag){ //console.log("Before displayFBDFeedback"); this.displayFBDFeedback(); //console.log("After displayFBDFeedback"); this.displayN2Feedback(); //console.log("After displayN2Feedback"); } //console.log("Inside loadFBD: this.startingFlag="+this.startingFlag); } loadFBD1() { //console.log("Running loadFBD1"); var qn=1000*this.questionNum+this.questionPartNum; this.fbdSVG1=document.getElementById("svg"+qn); //console.log("this.fbdSVG1="+this.fbdSVG1); //console.log(this.fbdSVG1); this.fbdSVG=document; this.fbdSVG2=document.getElementById("svgKey"+qn); //console.log("this.fbdSVG2="+this.fbdSVG2); this.studForceGroup=this.fbdSVG.createElementNS("http://www.w3.org/2000/svg", "g"); this.studForceGroup.setAttribute("id","StudentForceGroup"+qn); //console.log("this.studForceGroup"); //console.log(this.studForceGroup); this.fbdSVG1.appendChild(this.studForceGroup); this.drawAccelerationIndicator(); this.studAngleGroup=this.fbdSVG.createElementNS("http://www.w3.org/2000/svg", "g"); this.studAngleGroup.setAttribute("id","StudentAngleGroup"); this.fbdSVG1.appendChild(this.studAngleGroup); } loadFBD2(){ //console.log("Running loadFBD2") //Save this.shapes.legend to this.shapes.internalLegend for (var shapeNum=0;shapeNumMath.PI){ this.shapes[shapeNum].acceleration-=2*Math.PI; } if (this.shapes[shapeNum].acceleration<-Math.PI){ this.shapes[shapeNum].acceleration+=2*Math.PI } } } //Convert the parts of objects that are systems into shapeNums. for (var shapeNum=0;shapeNumMath.PI){ angle-=2*Math.PI; } if (angle<-Math.PI){ angle+=2*Math.PI } } //console.log("angle="+angle+" firstSpace="+firstSpace+" secondSpace="+secondSpace); var shape2=this.getShapeNum(aRelativeMotion[aRelativeMotion.length-1]); if (shape2==9999){ window.alert("The relativeMotions string contains the legend ''"+shape2+"'' that doesn't correspond to any of the this.shapes. Please fix!"); return; } var aRelativeMotion={shape1:shape1, shape2:shape2, angle:angle}; relativeMotionsArray.push(aRelativeMotion); startIndex=endIndex+1; } this.relativeMotions=relativeMotionsArray; //console.log("this.relativeMotions= {shape1:"+this.relativeMotions[0].shape1+", shape2:"+this.relativeMotions[0].shape2+", angle:"+this.relativeMotions[0].angle+"},{shape1:"+this.relativeMotions[1].shape1+", shape2:"+this.relativeMotions[1].shape2+", angle:"+this.relativeMotions[1].angle+"}"); //Convert legends in contactForces to shapeNums and angles to radians. for (var i=0;iMath.PI){ this.contactForces[i].contactAngle-=2*Math.PI; } if (this.contactForces[i].contactAngle<-Math.PI){ this.contactForces[i].contactAngle+=2*Math.PI } } if (this.contactForces[i].normalAngle!=999){ this.contactForces[i].normalAngle*=Math.PI/180; if (this.contactForces[i].normalAngle>Math.PI){ this.contactForces[i].normalAngle-=2*Math.PI; } if (this.contactForces[i].normalAngle<-Math.PI){ this.contactForces[i].normalAngle+=2*Math.PI } } if (this.contactForces[i].frictionAngle!=999){ this.contactForces[i].frictionAngle*=Math.PI/180; if (this.contactForces[i].frictionAngle>Math.PI){ this.contactForces[i].frictionAngle-=2*Math.PI; } if (this.contactForces[i].frictionAngle<-Math.PI){ this.contactForces[i].frictionAngle+=2*Math.PI } } //console.log("this.contactForces["+i+"]={by:"+this.contactForces[i].by+", on:"+this.contactForces[i].on+", contactAngle:"+this.contactForces[i].contactAngle+", description:'"+this.contactForces[i].description+"'}"); } } loadAnswer(startupFlag){ var qn='qn'+(this.questionNum*1000+this.questionPartNum); //console.log("this.questionPartNum="+this.questionPartNum+" qn="+qn); var stuAnswer=document.getElementById(qn).value; //console.log("qn="+qn+"\nstuAnswer="+stuAnswer); if (stuAnswer!=""){ if (!startupFlag){ var previouslyLoadedAngles=this.answer.angles } this.answer=JSON.parse(stuAnswer); if (startupFlag){ this.initialLoadFlag=false; } else { this.answer.angles=previouslyLoadedAngles } this.accelerationFeedbackFlag=this.answer.accelerationFeedbackFlag; //console.log("typeof this.accelerationFeedbackFlag="+(typeof this.accelerationFeedbackFlag)); this.angleFeedbackFlag=this.answer.angleFeedbackFlag; //console.log("Inside loadAnswer: this.angleFeedbackFlag="+this.angleFeedbackFlag); } //console.log("this.initialLoadFlag="+this.initialLoadFlag); for (var forceNum=0;forceNum=this.shapes.length){ window.alert("The $shapes array inside MOER contains information about each of the this.shapes in a problem, including the forces on each shape. One of the forces in the forces array for the "+this.getDescription(shapeNum)+" is 'on' an object that doesn't exist in this problem. This is typically caused when the force array from a different problem is copied into a new problem. Reset all of the force arrays to [ ] in $shapes and try again."); } if (force.by>=this.shapes.length){ window.alert("The $shapes array inside MOER contains information about each of the this.shapes in a problem, including the forces on each shape. One of the forces in the forces array for the "+this.getDescription(shapeNum)+" is 'by' an object that doesn't exist in this problem. This is typically caused when the force array from a different problem is copied into a new problem. Reset all of the force arrays to [ ] in $shapes and try again."); } this.loadForceKey(force); } this.accKeyGroup=this.fbdSVG.createElementNS("http://www.w3.org/2000/svg", "g"); this.fbdSVG2.appendChild(this.accKeyGroup); this.loadAccelerationKey(fbdNum); } } editKey(){ //This function is triggered when the user clicks the 'Edit Key' button. It replaces the forces in this.answer with the correct forces in this.shapes. It is intended to allow an instructor to edit the key. if (!window.confirm("Warning: This step cannot be undone.\nReplace the forces in the freebody diagram above with those in the key?")){return} this.answer.forces=[]; for (var fbdNum=0;fbdNum"; for (var i=0;i"; } else { text+="
  • F"+this.forceTypeArray[i].alternate+" -- "+this.forceTypeArray[i].name+"
  • "; } } } text+=""; return text; } displayLegend(){ var text="Legend"; text+="
      "; for (var i=0;i"; } text+="
    "; text+="
    Possible Force Types"; text+=this.forceTypeList(); this.feedback.innerHTML=text; this.feedback.setAttribute("style","color:black"); } createForceTypeArray(){ /** Each force type has the following form: { name: the name of the force type internal: the symbol for force type used internally by the program alternate: an alternate symbol for each force type display: the symbol for each force type that is displayed to the user allowed: a boolean flag that indicate whether a particular force type is allowed } **/ if(this.isElectricForceAllowed || this.isMagneticForceAllowed){this.twoSubscriptLabel=false} //If the problem includes either electric or magnetic forces, then three subscript force labeling is required. var aForceType={name:"Normal Force", internal:"N", alternate:"N", display:"",allowed:false} if (this.isNormalForceAllowed){aForceType.allowed=true;} this.forceTypeArray.push(aForceType); aForceType={name:"Tension Force", internal:"T", alternate:"T", display:"",allowed:false} if (this.isTensionForceAllowed){aForceType.allowed=true;} this.forceTypeArray.push(aForceType); aForceType={name:"Gravitational Force or Weight", internal:"W", alternate:"G", display:"",allowed:false} if (this.isWeightForceAllowed){aForceType.allowed=true;} this.forceTypeArray.push(aForceType); aForceType={name:"Friction Force", internal:"f", alternate:"F", display:"",allowed:false} if (this.isFrictionForceAllowed){aForceType.allowed=true;} this.forceTypeArray.push(aForceType); aForceType={name:"Drag Force", internal:"D", alternate:"D", display:"",allowed:false} if (this.isDragForceAllowed){aForceType.allowed=true;} this.forceTypeArray.push(aForceType); aForceType={name:"Contact Force", internal:"C", alternate:"C", display:"",allowed:false} if (this.isContactForceAllowed){aForceType.allowed=true;} this.forceTypeArray.push(aForceType); aForceType={name:"Electric Force", internal:"E", alternate:"E", display:"",allowed:false} if (this.isElectricForceAllowed){aForceType.allowed=true;} this.forceTypeArray.push(aForceType); aForceType={name:"Magnetic Force", internal:"B", alternate:"B", display:"",allowed:false} if (this.isMagneticForceAllowed){aForceType.allowed=true;} this.forceTypeArray.push(aForceType); aForceType={name:"Unassigned", internal:"F", alternate:"F", display:"Type",allowed:false} this.forceTypeArray.push(aForceType); this.forceTypes=""; for (var i=0;i
    "+"$shapes='"+shapesJSON+"'"; this.feedback.style='color:black'; //console.log("$shapes='"+shapesJSON+"'"); } deleteForce(){ if (this.focusForceNum==undefined){return} if (this.deleteButtonRef.style.opacity==.25){return} //console.log("this.focusForceNum="+this.focusForceNum); if (this.angleFeedbackFlag){ //console.log("this.studAngleGroup.length="+this.studAngleGroup.length+" this.focusForceNum="+this.focusForceNum); var angleFBD=this.answer.angles[this.focusForceNum].fbd; var startAngleID=this.answer.angles[this.focusForceNum].startPointForceID; //console.log("angleFBD="+angleFBD+" startAngleID="+startAngleID); if (startAngleID!=undefined){ //if (!this.deleteOldFBDAngleIDs(this.focusForceNum,startAngleID)){ if (!this.deleteOldFBDAngleIDs(angleFBD,startAngleID)){ this.deleteOldAccAngleIDs(this.focusForceNum,startAngleID); } } var endAngleID=this.answer.angles[this.focusForceNum].endPointForceID; if (endAngleID!=undefined){ //if (!this.deleteOldFBDAngleIDs(this.focusForceNum,endAngleID)){ if (!this.deleteOldFBDAngleIDs(angleFBD,endAngleID)){ this.deleteOldAccAngleIDs(this.focusForceNum,endAngleID); } } this.studAngleGroup.removeChild(this.studAngleGroup.children[this.focusForceNum]); this.answer.angles.splice(this.focusForceNum, 1); } else { //console.log("deleteForce() this.focusForceNum="+this.focusForceNum); var forceReference=this.answer.forces[this.focusForceNum].reference; var group=forceReference.parentNode; this.studForceGroup.removeChild(group); this.answer.forces.splice(this.focusForceNum, 1); } this.focusForceNum=undefined; this.accelerationFeedbackFlag=undefined; this.angleFeedbackFlag=undefined; this.answer.accelerationFeedbackFlag=undefined; this.deleteButtonRef.style.opacity=.25; this.updateAnswerBox(); } oneTick(x,N,color){ //console.log("color="+color); var tickMarkGroup=document.createElementNS("http://www.w3.org/2000/svg", "g"); for (var i=0;ip1.y"); if (Math.abs(p3.x-p1.x)>.1) { xText+=rect.height/2/Math.tan(angle); } yText+=rect.height/2; } else { //console.log("p3.y<=p1.y"); if (Math.abs(p3.x-p1.x)>.1) { xText-=rect.height/2/Math.tan(angle); } yText-=rect.height/2; } } else { if (p3.x>p1.x) { xText+=rect.width/2; yText+=rect.width/2*Math.tan(angle); } else { xText-=rect.width/2; yText-=rect.width/2*Math.tan(angle); } } textElement.setAttribute("x",xText); textElement.setAttribute("y",yText); //console.log("angle="+angle+" Math.tan(rect.height/rect.width)="+Math.tan(rect.height/rect.width)); //console.log("xText="+xText+" yText="+yText+" left="+rect.left+" top="+rect.top+" right="+rect.right+" bottom="+rect.bottom+" x="+rect.x+" y="+rect.y+" width="+rect.width+" height="+rect.height); } forceLabelInput(evt){ if (this.focusElement!=undefined) { //console.log("forceLabelInput"); this.clearForceFocus(); } this.inputDoneFlag=false; var that=this; this.fbdSVG1.onkeydown=function(){ that.keyDownFunction(event,that); } this.fbdSVG1.onkeyup=function(){ that.keyUpFunction(event,that); } document.onkeydown=function(){ that.keyDownFunction(event,that); } document.onkeyup=function(){ that.keyUpFunction(event,that); } var forceLabel=evt.target.parentNode; if (forceLabel.tagName=='g'){ forceLabel=forceLabel.children[1]; } this.focusElement=forceLabel; //console.log("forceLabel.tagName="+forceLabel.tagName); var forceLabelBBox=forceLabel.getBBox(); this.forceLabelHighlightBox=this.fbdSVG.createElementNS("http://www.w3.org/2000/svg", "rect"); //console.log("(x,y)=("+forceLabelBBox.x+","+forceLabelBBox.y+")"); this.forceLabelHighlightBox.setAttribute("x",forceLabelBBox.x); this.forceLabelHighlightBox.setAttribute("y",forceLabelBBox.y); this.forceLabelHighlightBox.setAttribute("width",forceLabelBBox.width); this.forceLabelHighlightBox.setAttribute("height",forceLabelBBox.height); this.forceLabelHighlightBox.setAttribute("fill","yellow"); //console.log("parentNode="+forceLabel.parentNode+" parent parentNode="+forceLabel.parentNode.parentNode); forceLabel.parentNode.parentNode.insertBefore(this.forceLabelHighlightBox,forceLabel.parentNode); this.oldForceLabel=[this.focusElement.children[0+this.subscriptIndexOffset].textContent,this.focusElement.children[1+this.subscriptIndexOffset].textContent]; forceLabel.children[0+this.subscriptIndexOffset].textContent=""; forceLabel.children[1+this.subscriptIndexOffset].textContent=""; } keyDownFunction(evt,that) { evt.preventDefault(); //prevents the default behaviour of keystrokes. In particular, //prevents the backspace from sending the browser to the previous page. var key = evt.key; //console.log("key=" + key); //console.log("typeof=" + (typeof codeOfKey)); that.feedback.textContent=""; that.processKeyDown(key); } processKeyDown(key) { //console.log("key=" + key + " this.keyDownFlag=" + this.keyDownFlag + " this.focusElement=" + this.focusElement); if (this.keyDownFlag) { return }; if (this.focusElement == undefined) { return }; var value=""; //console.log("this.legendString="+this.legendString); var length=this.focusElement.children[1+this.subscriptIndexOffset].textContent.length; //console.log("length="+length); if (key == 'Backspace') { if (length!=0) { this.focusElement.children[1+this.subscriptIndexOffset].textContent =this.focusElement.children[1+this.subscriptIndexOffset].textContent.substring(0,length-1); } else if (this.focusElement.children[0+this.subscriptIndexOffset].textContent!="") { this.focusElement.children[0+this.subscriptIndexOffset].textContent=""; } return; } else if (key=='Enter') { this.inputDoneFlag=true; } else if (length==2 || key=='Shift' || key=='CapsLock' || key=='Tab'){ return; } else if (this.focusElement.children[0+this.subscriptIndexOffset].textContent=="") { //console.log("inside if"); if (this.forceTypes.indexOf(key)==-1) { this.feedback.innerHTML="You typed the letter '"+key+"'. This is not one of the force types. The possible force types are:"+this.forceTypeList(); this.feedback.setAttribute("style","color:red"); return; } else { this.focusElement.children[0+this.subscriptIndexOffset].textContent=key+" "; //console.log("this.focusElement.children[0].textContent="+this.focusElement.children[0].textContent); } } else if (length<2) { //console.log("this.legendString="+this.legendString+" key="+key); if (this.legendString.indexOf(key)!=-1) { //console.log("Changing this.focusElement"); this.focusElement.children[1+this.subscriptIndexOffset].textContent+=key; } else { this.feedback.innerHTML="You typed the letter '"+key+"'. This letter does not correspond to any of the legends:
      "; for (var i=0;i"; } this.feedback.innerHTML+="
    "; this.feedback.setAttribute("style","color:red"); return; } } length=this.focusElement.children[1+this.subscriptIndexOffset].textContent.length; if (length==2){ var forceVector=this.focusElement.parentNode.children[0]; //console.log("(x1,y1)=("+forceVector.getAttribute("x1")+","+forceVector.getAttribute("y1")+")"); //console.log("(x2,y2)=("+forceVector.getAttribute("x2")+","+forceVector.getAttribute("y2")+")"); var p1={x:parseFloat(forceVector.getAttribute("x1")),y:parseFloat(forceVector.getAttribute("y1"))} var p2={x:parseFloat(forceVector.getAttribute("x2")),y:parseFloat(forceVector.getAttribute("y2"))} this.moveForceLabel(p1,p2,this.focusElement); this.saveForceToAnswers(forceVector) this.updateAnswerBox(); } } keyUpFunction(evt,that) { //console.log("keyUpFunction: that.inputDoneFlag="+that.inputDoneFlag); if (that.inputDoneFlag) { that.clearForceFocus(); } that.keyDownFlag = false; return false; } buttonDown(object,key){ this.buttonDownFlag=true; //console.log("buttonDown key="+key); this.feedback.textContent=""; this.processKeyDown(key); } buttonUp(object,key) { //console.log("buttonUp: key="+key); if (this.inputDoneFlag) { this.clearForceFocus(); } this.buttonDownFlag = false; return false; } clearFocus(accFlag) { //console.log("Inside clearFocus: this.focusElement="+this.focusElement+" accFlag="+accFlag); if (accFlag){ if (this.focusElement!=undefined && this.focusElement.children[1].textContent.length==0) { this.focusElement.children[0+this.subscriptIndexOffset].textContent=this.oldForceLabel[0]; //this.focusElement.children[1+this.subscriptIndexOffset].textContent=this.oldForceLabel[1]; this.focusElement.children[1+this.subscriptIndexOffset].textContent='\u00A0\u00A0'; } } else { if (this.focusElement!=undefined && (this.focusElement.children[0+this.subscriptIndexOffset].textContent=="" || this.focusElement.children[1+this.subscriptIndexOffset].textContent.length<2)) { this.focusElement.children[0+this.subscriptIndexOffset].textContent=this.oldForceLabel[0]; this.focusElement.children[1+this.subscriptIndexOffset].textContent=this.oldForceLabel[1]; } } this.fbdSVG1.onkeydown=undefined; this.fbdSVG1.onkeyup=undefined; document.onkeydown=undefined; document.onkeyup=undefined; if (this.forceLabelHighlightBox!=undefined && this.forceLabelHighlightBox.parentElement!=undefined){ this.forceLabelHighlightBox.parentElement.removeChild(this.forceLabelHighlightBox); } this.focusElement=undefined; } clearForceFocus(accFlag) { var qn=1000*this.questionNum+this.questionPartNum; //console.log("Inside clearForceFocus(): this.focusForceNum="+this.focusForceNum+" accFlag="+accFlag+" this.accelerationFeedbackFlag="+this.accelerationFeedbackFlag+" this.angleFeedbackFlag="+this.angleFeedbackFlag); this.clearFocus(accFlag); this.forceLabelHighlightBox=undefined; //This line needed to avoid a Microsoft Edge error. if (this.angleFeedbackFlag){ if (this.focusForceNum!=undefined){ //console.log("this.focusForceNum="+this.focusForceNum); if ((typeof this.focusForceNum) == 'object'){ for (var i=0;i=0;forceNum--) { var force=forces[forceNum].children[0]; var p1={x:parseFloat(force.getAttribute("x1")), y:parseFloat(force.getAttribute("y1"))}; var p2={x:parseFloat(force.getAttribute("x2")), y:parseFloat(force.getAttribute("y2"))}; var m=(p2.y-p1.y)/(p2.x-p1.x); var b=p2.y-m*p2.x; if (m==Infinity || m==-Infinity) { b=p1.x; } var distanceToForce=this.distanceBetweenPointAndLine(mousePosition,m,b); //console.log("OUTSIDE IF: distanceToForce="+distanceToForce); if (distanceToForcethat.borderY) { //Touch is below rectangle surround freebody and accelration diagrams that.clearForceFocus(that.accSelectedFlag); //console.log("focus has been cleared"); return } */ if (that.buttonDownFlag){return} if (!selectedElement) { //console.log("that.makeDraggable()"); that.clearForceFocus(that.accSelectedFlag); //selectedElement = evt.target; var forceReference=that.findSelectedObjectReference(getMousePosition(evt)); selectedElement=forceReference; //console.log("Inside startDrag: that.axisSelectedFlag="+that.axisSelectedFlag+" that.forceSelectedFlag="+that.forceSelectedFlag); //console.log("startDrag: selectedElement="+selectedElement); if (forceReference==undefined){ selectedElement=undefined; that.focusForceNum=undefined; //console.log("No element is selected"); return; } that.focusForceNum=that.findSelectedObjectNumber(forceReference); //console.log("Inside startDrag: forceReference="+forceReference+" that.focusForceNum="+that.focusForceNum); if (that.focusForceNum!=undefined){ that.highlightForce(that.focusForceNum,that.greenHighlightForceColor); that.deleteButtonRef.style.opacity=1; that.tickMarkControlRef.style.opacity=1; that.tickMarkControlRef.children[5].textContent=that.answer.forces[that.focusForceNum].numTickMarks; } else { var focusAngleNum=that.findSelectedAngleNumber(forceReference); if (focusAngleNum!=undefined){ that.highlightAngle(focusAngleNum,that.greenHighlightForceColor); that.deleteButtonRef.style.opacity=1; } } var classList=evt.target.classList; //console.log("inside startDrag: classList="+classList); classList+=" "; if (classList.indexOf("draggable")!=-1 || classList.indexOf("rotate")!=-1 || forceReference!=undefined) { //selectedElement = evt.target; if (selectedElement.tagName!='line') { selectedElement=forceReference; } if (that.angleSelectedFlag){ that.focusForceNum=that.findSelectedAngleNumber(forceReference); //console.log("Just before startAngleDrag: that.focusForceNum="+that.focusForceNum+" selectedElement="+selectedElement); if (startAngleDrag(evt)){ return; } else { that.angleSelectedFlag=false; } } //console.log("forceReference="+forceReference); //console.log("INSIDE startDrag: selectedElement.tagName="+ selectedElement.tagName+"forceReference.tagName="+forceReference.tagName+" equal? = "+(forceReference==selectedElement)); //console.log("Inside startDrag: selectedElement="+selectedElement); p1.x=parseFloat(selectedElement.getAttributeNS(null, "x1")); p1.y=parseFloat(selectedElement.getAttributeNS(null, "y1")); p2.x=parseFloat(selectedElement.getAttributeNS(null, "x2")); p2.y=parseFloat(selectedElement.getAttributeNS(null, "y2")); offset = getMousePosition(evt); //console.log("inside startDrag() offset=("+offset.x+","+offset.y+")"); offset.x -= p1.x; offset.y -= p1.y; offsetEnd.x=(p2.x-p1.x); offsetEnd.y=(p2.y-p1.y); if (Math.sqrt(Math.pow((offsetEnd.x-offset.x),2) + Math.pow((offsetEnd.y-offset.y),2))<10 || that.accSelectedFlag || that.axisSelectedFlag){ dragVsRotate=false; } else { dragVsRotate=true; } } } } function drag(evt) { //console.log("INSIDE drag: evt="+evt); var mousePos=getMousePosition(evt); if (mousePos.y>that.borderY) {return} //Touch is below rectangle surround freebody and accelration diagrams var numFBDs=that.fbdList.length; //console.log("drag: mousePos=("+mousePos.x+","+mousePos.y+")"+" that.borderY="+that.borderY+" that.borderX[numFBDs-1][3]="+that.borderX[numFBDs-1][numFBDs]); if (mousePos.y<0 || mousePos.x<5 || mousePos.x>that.borderX[numFBDs-1][numFBDs]) {return} //Touch is outside rectangle surrounding freebody and acceleration diagrams //console.log("mousePos=("+mousePos.x+","+mousePos.y+")"); //console.log("Running drag selectedElement="+selectedElement); if (that.angleSelectedFlag){ angleDrag(evt); return; } if (that.axisSelectedFlag){ axesDrag(evt); return; } if (that.accSelectedFlag){ accelerationDrag(evt); return; } if (that.forceSelectedFlag ){ forceDrag(evt); return; } } function startAngleDrag(evt) { var clickTolerance=8; var mousePosition=getMousePosition(evt); //console.log("Inside startAngleDrag: selectedElement="+selectedElement+" selectedElement.tagName="+selectedElement.tagName); if (selectedElement.tagName=="line"){return false} var pathAttribute=selectedElement.getAttribute("d").split(" "); p1.x=parseFloat(pathAttribute[1]); p1.y=parseFloat(pathAttribute[2]); p2.x=parseFloat(pathAttribute[9]); p2.y=parseFloat(pathAttribute[10]); var distanceToStart=Math.sqrt(Math.pow(p1.x-mousePosition.x,2)+Math.pow(p1.y-mousePosition.y,2)); var distanceToEnd=Math.sqrt(Math.pow(p2.x-mousePosition.x,2)+Math.pow(p2.y-mousePosition.y,2)); offset = getMousePosition(evt); //console.log("inside startDrag() offset=("+offset.x+","+offset.y+")"); if (distanceToStartthat.borderX[that.fbdList.length-1][that.fbdList.length]) {return} //Touch is in the keypad region //console.log("Running drag selectedElement="+selectedElement); if (selectedElement) { evt.preventDefault(); coord = getMousePosition(evt); var angleNum=that.findSelectedAngleNumber(selectedElement); //console.log("angleNum="+angleNum+" angleRef="+angleRef); //console.log("inside drag() coord=("+coord.x+","+coord.y+")"); //console.log("that.startVsEndPointFlag="+that.startVsEndPointFlag); var fbdNum=that.fbdList.length-1; var accFlag=false; //accFlag indicates whether the angle is near a freebody diagram or near an acceleration indicator. //Determine which freebody diagram or acceleration indicator the mouse is near. for (var i=0;imaxRadius){ p1.x=centerX+(p1.x-centerX)*maxRadius/actualRadius; p1.y=centerY+(p1.y-centerY)*maxRadius/actualRadius; } } else { var accNumAttacedToEnd=that.getAccNumAttachedToAngle(angleRef.endPointForceID); var pAcc={x:0,y:0}; pAcc.x=parseFloat(that.answer.accelerations[accNumAttacedToEnd].x2); pAcc.y=parseFloat(that.answer.accelerations[accNumAttacedToEnd].y2); var maxRadius=Math.sqrt(Math.pow(pAcc.x-centerX,2)+Math.pow(pAcc.y-centerY,2))*.6; var actualRadius=Math.sqrt(Math.pow(p1.x-centerX,2)+Math.pow(p1.y-centerY,2)); //console.log("actualRadius="+actualRadius+" maxRadius="+maxRadius); if (actualRadius>maxRadius){ p1.x=centerX+(p1.x-centerX)*maxRadius/actualRadius; p1.y=centerY+(p1.y-centerY)*maxRadius/actualRadius; } } } //Snap p1 to one of the axes if p1 is brought close that.startPointAxisNum=undefined; that.startPointForceNum=undefined; var endPointAxisNum=that.getAccAxisNum(fbdNum,angleRef.endPointForceID); for (var axesNum=2;axesNum<6;axesNum++){ if (axesNum-2==endPointAxisNum){continue} var axis=that.accAxesGroup[fbdNum].children[axesNum]; var nearestPoint=that.coordinatesOfClosestPoint(p1,axis); var distance=Math.sqrt(Math.pow(nearestPoint.x-p1.x,2)+Math.pow(nearestPoint.y-p1.y,2)); if (distancep1.x) { p3.x=p1.x+that.axesLength/Math.sqrt(1+Math.pow(slope,2)); p3.y=p1.y+slope*(p3.x-p1.x); p3Axes.x=p1Axes.x+that.accAxesLength/Math.sqrt(1+Math.pow(slope,2)); p3Axes.y=p1Axes.y+slope*(p3Axes.x-p1Axes.x); } else { p3.x=(p1.x-that.axesLength/Math.sqrt(1+Math.pow(slope,2))); p3.y=(p1.y+slope*(p3.x-p1.x)); p3Axes.x=(p1Axes.x-that.accAxesLength/Math.sqrt(1+Math.pow(slope,2))); p3Axes.y=(p1Axes.y+slope*(p3Axes.x-p1Axes.x)); } } else { if (p2.y>p1.y) { p3.x=p1.x; p3.y=p1.y+that.axesLength; p3Axes.x=p1Axes.x; p3Axes.y=p1Axes.y+that.accAxesLength; } else { p3.x=p1.x; p3.y=p1.y-that.axesLength; p3Axes.x=p1Axes.x; p3Axes.y=p1Axes.y-that.accAxesLength; } } var plusXText=that.forceAxesGroup[fbdNum].children[1]; var angle3=Math.atan2((p3.y-p1.y),(p3.x-p1.x))+Math.PI/20; var rect=plusXText.getBBox(); var xText=p1.x+that.axesLengthText*Math.cos(angle3)-rect.width/2; var yText=p1.y+that.axesLengthText*Math.sin(angle3); plusXText.setAttribute("x",xText); plusXText.setAttribute("y",yText); //console.log("plusXText.tagName="+plusXText.tagName+" xText="+xText+" yText="+yText); var plusXAccText=that.accAxesGroup[fbdNum].children[1]; angle3=Math.atan2((p3.y-p1.y),(p3.x-p1.x))+Math.PI/10; rect=plusXAccText.getBBox(); var xText=p1Axes.x+that.accAxesLength*Math.cos(angle3)-rect.width/2; var yText=p1Axes.y+that.accAxesLength*Math.sin(angle3); plusXAccText.setAttribute("x",xText); plusXAccText.setAttribute("y",yText); that.answer.xAxisAngles[fbdNum]=Math.round(Math.atan2((p3.y-p1.y),(p3.x-p1.x))*180/Math.PI); that.updateAnswerBox(); //console.log("Saving xAxisAngles="+that.answer.xAxisAngles[fbdNum]); break; } //console.log("i="+i+" p1=("+p1.x+","+p1.y+") angle="+angle/piOver180); axis.setAttributeNS(null, "x2",p3.x); axis.setAttributeNS(null, "y2",p3.y); accAxis.setAttributeNS(null, "x2",p3Axes.x); accAxis.setAttributeNS(null, "y2",p3Axes.y); } } } function accelerationDrag(evt){ var mousePos=getMousePosition(evt); if (selectedElement) { evt.preventDefault(); coord = getMousePosition(evt); //console.log("inside accelerationDrag() coord=("+coord.x+","+coord.y+")"); var angle; p2.x=coord.x + offsetEnd.x - offset.x; p2.y=coord.y + offsetEnd.y - offset.y; //console.log("p1.x="+p1.x+"p2.x="+p2.x); var slope=(p2.y-p1.y)/(p2.x-p1.x); angle=Math.atan(slope); //console.log("slope="+slope+" angle="+angle); var piOver180=Math.PI/180; //console.log("slope="+slope+" angle="+angle); angle=Math.floor((angle+.5*that.forceAngleIncrement*piOver180)/(that.forceAngleIncrement*piOver180))*that.forceAngleIncrement*piOver180; //console.log("angle="+angle/piOver180); if (Math.abs(angle/piOver180)!=90) { slope=Math.tan(angle); if (p2.x>p1.x) { p2.x=p1.x+that.accLength/Math.sqrt(1+Math.pow(slope,2)); p2.y=p1.y+slope*(p2.x-p1.x); } else { p2.x=(p1.x-that.accLength/Math.sqrt(1+Math.pow(slope,2))); p2.y=(p1.y+slope*(p2.x-p1.x)); } } else { if (p2.y>p1.y) { p2.x=p1.x; p2.y=p1.y+that.accLength; } else { p2.x=p1.x; p2.y=p1.y-that.accLength; } } selectedElement.setAttributeNS(null, "x2",p2.x); selectedElement.setAttributeNS(null, "y2",p2.y); //console.log("slope="+slope+" p2=("+p2.x+","+p2.y+")"); //console.log("drag(): dragVsRotate="+dragVsRotate+" p1=("+p1.x+","+p1.y+") p2=("+p2.x+","+p2.y+")"); var textElement=selectedElement.parentNode.childNodes[1]; var angle=Math.atan2((p2.y-p1.y),(p2.x-p1.x)); //console.log("angle="+angle*180/Math.PI); var rect=textElement.getBBox(); var xText=p1.x+(that.accLength+7)*Math.cos(angle)-rect.width/2; var yText=p1.y+(that.accLength+7)*Math.sin(angle); textElement.setAttribute("x",xText); textElement.setAttribute("y",yText); } } function forceDrag(evt) { var mousePos=getMousePosition(evt); if (selectedElement) { evt.preventDefault(); coord = getMousePosition(evt); //console.log("inside drag() coord=("+coord.x+","+coord.y+")"); //console.log("dragVsRotate="+dragVsRotate+" that.focusForceNum="+that.focusForceNum); var angle; if (dragVsRotate && that.focusForceNum!=undefined){ p1.x=coord.x - offset.x; p1.y=coord.y - offset.y p2.x=coord.x + offsetEnd.x - offset.x; p2.y=coord.y + offsetEnd.y - offset.y; selectedElement.setAttributeNS(null, "x1", p1.x); selectedElement.setAttributeNS(null, "y1", p1.y); selectedElement.setAttributeNS(null, "x2", p2.x); selectedElement.setAttributeNS(null, "y2", p2.y); //console.log("drag(): dragVsRotate="+dragVsRotate+" p1=("+p1.x+","+p1.y+") p2=("+p2.x+","+p2.y+")"); } else { p2.x=coord.x + offsetEnd.x - offset.x; p2.y=coord.y + offsetEnd.y - offset.y; //console.log("p1.x="+p1.x+"p2.x="+p2.x); var slope=(p2.y-p1.y)/(p2.x-p1.x); angle=Math.atan(slope); //console.log("slope="+slope+" angle="+angle); var piOver180=Math.PI/180; //console.log("slope="+slope+" angle="+angle); angle=Math.floor((angle+.5*that.forceAngleIncrement*piOver180)/(that.forceAngleIncrement*piOver180))*that.forceAngleIncrement*piOver180; //console.log("angle="+angle/piOver180); if (Math.abs(angle/piOver180)!=90) { slope=Math.tan(angle); if (p2.x>p1.x) { p2.x=p1.x+that.forceLength/Math.sqrt(1+Math.pow(slope,2)); p2.y=p1.y+slope*(p2.x-p1.x); } else { p2.x=(p1.x-that.forceLength/Math.sqrt(1+Math.pow(slope,2))); p2.y=(p1.y+slope*(p2.x-p1.x)); } } else { if (p2.y>p1.y) { p2.x=p1.x; p2.y=p1.y+that.forceLength; } else { p2.x=p1.x; p2.y=p1.y-that.forceLength; } } selectedElement.setAttributeNS(null, "x2",p2.x); selectedElement.setAttributeNS(null, "y2",p2.y); //console.log("slope="+slope+" p2=("+p2.x+","+p2.y+")"); //console.log("drag(): dragVsRotate="+dragVsRotate+" p1=("+p1.x+","+p1.y+") p2=("+p2.x+","+p2.y+")"); } var textElement=selectedElement.parentNode.childNodes[1]; that.moveForceLabel(p1,p2,textElement); } else { //changeCursor(evt); } } function endDrag(evt) { //console.log("INSIDE endDrag(): coord=()"+coord.x+","+coord.y+")"); var numFBDs=that.fbdList.length; if (coord.x>that.borderX[numFBDs-1][3] || (coord.y>that.borderY)) { selectedElement=undefined; return } //Touch is in the keypad region //console.log("inside endDrag selectedElement="+selectedElement); //console.log("endDrag(): dragVsRotate="+dragVsRotate+" p1=("+p1.x+","+p1.y+") p2=("+p2.x+","+p2.y+")"); //console.log("Inside endDrag: that.axisSelectedFlag="+that.axisSelectedFlag+" that.forceSelectedFlag="+that.forceSelectedFlag+" that.accSelectedFlag="+that.accSelectedFlag+" that.angleSelectedFlag="+that.angleSelectedFlag); if (that.accSelectedFlag){ that.saveAccToAnswers(selectedElement); } else if (that.axisSelectedFlag) { that.saveAxesToAnswers(selectedElement); } else if (that.forceSelectedFlag) { that.saveForceToAnswers(selectedElement); } else if (that.angleSelectedFlag){ //console.log("Inside endDrag: that.startVsEndPointFlag="+that.startVsEndPointFlag); that.saveAngleToAnswers(selectedElement); } selectedElement = undefined; that.forceSelectedFlag=false; //console.log("that.forceSelectedFlag set to FALSE"); that.accSelectedFlag=false; that.axisSelectedFlag=false; that.angleSelectedFlag=false; dragVsRotate=undefined; that.startVsEndPointFlag=undefined; that.updateAnswerBox(); that.setAngleMarkers(); //Set the angle markers that mark whether each end of an angle is attached or not. } function getMousePosition(evt) { var CTM = svg.getScreenCTM(); //console.log("getMousePosition(): evt.clientX="+evt.clientX); var returnX; if (evt.clientX!=undefined && evt.clientX===0) { returnX=0; } else { returnX=evt.clientX || evt.touches[0].clientX; } var returnY; if (evt.clientY!=undefined && evt.clientY===0) { returnY=0; } else { returnY=evt.clientY || evt.touches[0].clientY; } return { x: (returnX - CTM.e) / CTM.a, y: (returnY - CTM.f) / CTM.d } } function changeCursor(evt) { var classList=evt.target.classList; classList+=" "; if (classList.indexOf("draggable")!=-1 || classList.indexOf("rotate")!=-1 || classList.indexOf("default")!=-1) { var selectedElement = evt.target; if (selectedElement.getAttributeNS(null,"d")!=undefined){return}; var p1={x:0,y:0}; var p2={x:0,y:0}; p1.x=parseFloat(selectedElement.getAttributeNS(null, "x1")); p1.y=parseFloat(selectedElement.getAttributeNS(null, "y1")); p2.x=parseFloat(selectedElement.getAttributeNS(null, "x2")); p2.y=parseFloat(selectedElement.getAttributeNS(null, "y2")); var mousePos=getMousePosition(evt); if (Math.sqrt(Math.pow((mousePos.x-p2.x),2) + Math.pow((mousePos.y-p2.y),2))<10){ //console.log("mouseover p2"); var slope=(p2.y-p1.y)/(p2.x-p1.x); var angle=Math.atan(slope); var piOver180=Math.PI/180; if (Math.abs(angle)piOver180*70){ selectedElement.setAttribute("class","rotateEW"); } else if (angle>0) { selectedElement.setAttribute("class","rotateNESW"); } else { selectedElement.setAttribute("class","rotateNWSE"); } } else if (Math.sqrt(Math.pow((mousePos.x-p1.x),2) + Math.pow((mousePos.y-p1.y),2))<10) { //console.log("mouseover p1"); selectedElement.setAttribute("class","draggable"); } else { selectedElement.setAttribute("class","default"); } } } } distanceBetweenPointAndLine(point,m,b) { if (m==Infinity || m==-Infinity) {return Math.abs(b-point.x)} return Math.abs(m*point.x-point.y+b)/Math.sqrt(m*m+1); } createFBDSVGCSS() { var sheet = document.createElementNS("http://www.w3.org/2000/svg", 'style'); var sheetInnerHTML = ""; sheetInnerHTML +=".center {"; sheetInnerHTML +="display:block;"; sheetInnerHTML +="margin-left:auto;" sheetInnerHTML +="margin-right:auto;" sheetInnerHTML +="}"; sheetInnerHTML += ".button {"; sheetInnerHTML += "cursor:pointer;"; sheetInnerHTML += "}"; sheetInnerHTML += ".draggable {"; sheetInnerHTML += "cursor:move;"; sheetInnerHTML += "}"; sheetInnerHTML += ".rotateEW {"; sheetInnerHTML += "cursor:ew-resize;"; sheetInnerHTML += "}"; sheetInnerHTML += ".rotateNS {"; sheetInnerHTML += "cursor:ns-resize;"; sheetInnerHTML += "}"; sheetInnerHTML += ".rotateNESW {"; sheetInnerHTML += "cursor:nesw-resize;"; sheetInnerHTML += "}"; sheetInnerHTML += ".rotateNWSE {"; sheetInnerHTML += "cursor:nwse-resize;"; sheetInnerHTML += "}"; sheetInnerHTML += ".static {"; sheetInnerHTML += "cursor:not-allowed;"; sheetInnerHTML += "}"; sheetInnerHTML += ".label {"; sheetInnerHTML += "cursor:text;"; sheetInnerHTML += "}"; sheetInnerHTML += "text, tspan{"; sheetInnerHTML += "cursor: default;"; sheetInnerHTML += "-webkit-user-select: none;"; sheetInnerHTML += "-moz-user-select: none;"; sheetInnerHTML += "-ms-user-select: none;"; sheetInnerHTML += "user-select: none;"; sheetInnerHTML += "}"; sheetInnerHTML += "text::selection{"; sheetInnerHTML += "background: none;"; sheetInnerHTML += "}"; sheet.textContent = sheetInnerHTML; this.fbdSVG1.appendChild(sheet); } highlightForce(forceNum,color){ var qn=1000*this.questionNum+this.questionPartNum; //console.log("Inside hightlightForce: forceNum="+forceNum+" color="+color+" accelerationFeebackFlag="+this.accelerationFeedbackFlag+ " typeof this.accelerationFeedbackFlag="+(typeof this.accelerationFeedbackFlag)); if (color==undefined) { color=this.highlightForceColor; } else { color=this.greenHighlightForceColor; } //console.log("this.angleSelectedFlag="+this.angleSelectedFlag); if (this.angleSelectedFlag || this.angleFeedbackFlag){ this.highlightAngle(forceNum,color); return; } if (this.accelerationFeedbackFlag){ if ((typeof forceNum)=='object'){ for (var i=0;i=p1.y)){angle+=Math.PI} if ((p2.xp1.y) {angle=Math.PI/2} else {angle=-Math.PI/2} } return angle; } saveAxesToAnswers(selectedElement){ //console.log("Inside saveAxesToAnswers"); var fbdNum; var breakFlag=false; for (var i=0;i
    Note: The direction of your force is only off by a little bit. Slightly nudge it either clockwise or counterclockwise."; //this.feedback.innerHTML+=response; return response; } else { return ""; } } determinePartOfSystemTouchingObject(system,object) { //console.log("system="+this.getDescription(system)+" object="+this.getDescription(object)); //Both system and object are shapeNums. var touching=this.shapes[object].touching; var parts=this.shapes[system].parts; for (var i=0;i0){return "is accelerating to the right"} else {return "is accelerating to the left"} } else if (vector.y>0) { if (vector.x==0){return "is accelerating upward"} else if (vector.x>0){return "is accelerating up and to the right"} else {return "is accelerating up and to the left"} } else { if (vector.x==0){return "is accelerating downward"} else if (vector.x>0){return "is accelerating down and to the right"} else {return "is accelerating down and to the left"} } } unlabeledForceCheck() { for (var forceNum=0;forceNumError: Missing force label.

    You have not entered a label for the force that is highlighted in red. The label indicates the type of force:"; this.response+=this.forceTypeList(); this.response+="The label also includes two subscripts:"; this.response+="
    • First subscript: the legend of the object the force is \'by\'.
    • "; this.response+="
    • Second susbscript: the legend of the object the force is \'on\'.
    "; this.response+="Enter the label. (#1)"; this.response+=""; this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; this.focusForceNum=forceNum; //this.highlightForce(this.focusForceNum); return false; } } return true; } convertAngleToDegrees(angle){ if (angle==999){return 999}; angle=Math.round(angle*180/Math.PI); while (angle<-180){angle+=360} while (angle>=180){angle-=360} return angle; } findIntersection(angleArray1,angleArray2){ var angleArray3=[]; for (var i=0;iError: Incorrect force label.

    The force that is highlighted in red is attached to the "+this.getDescription(this.fbdList[onCMNum])+" which indicates the force is acting on the "+this.getDescription(this.fbdList[onCMNum])+". However, the second subscript of the force label (i.e., the 'on' subscript) is '"+this.shapes[onShapeNum].legend+"', which indicates the force is acting on the "+this.getDescription(onShapeNum)+". The object identified by the 'on' subscript either has to be identical to the object to which the force is attached, or it must be a part of the object to which the force is attached. Fix the label on the highlighted force. (#3)"; this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; this.focusForceNum=forceNum; //this.highlightForce(this.focusForceNum); return false; } } return true; } isByObjectPartOfOnObject() { //console.log("this.answer.forces.length=" +this.answer.forces.length); for (var forceNum=0;forceNum
    The force highlighted in red is by the "+this.getDescription(byShapeNum)+" on the "+this.getDescription(onShapeLabelNum)+". It is impossible for an object to propel itself by exerting a force on itself. Hence, when drawing the freebody diagram for a particular object, only include forces by other objects, not forces by the particular object or parts of that object. Either fix the label on the highlighted force or remove the force. (#4)"; this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; this.focusForceNum=forceNum; //this.highlightForce(this.focusForceNum); return false; } else if (this.isPartOfSystem(byShapeNum,this.shapes[onShapeNum].parts)){ this.response; if (onShapeNum==onShapeLabelNum) { this.response="Error: Incorrect force label.

    The force highlighted in red is 'on' the "+ this.getDescription(onShapeNum)+ " and the label indicates that it is 'by' the "+this.getDescription(byShapeNum)+". However, the "+this.getDescription(byShapeNum)+ " is part of the "+this.getDescription(onShapeNum)+". When drawing the freebody diagram for a particular object, only include forces by other objects, not forces by the particular object or parts of that object. Either fix the label on the highlighted force, or remove the force. (#5)"; this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; this.focusForceNum=forceNum; //this.highlightForce(this.focusForceNum); return false; } else if (this.isPartOfSystem(onShapeLabelNum,this.shapes[onShapeNum].parts)) { this.response="Error: Incorrect force label.

    According to the label, the force highlighted in red is 'by' the "+this.getDescription(byShapeNum)+" and is 'on' the "+this.getDescription(onShapeLabelNum)+". However, the force is attached to the "+this.getDescription(onShapeNum)+". The "+this.getDescription(onShapeLabelNum)+" is part of the "+this.getDescription(onShapeNum)+". However, the "+this.getDescription(byShapeNum)+" is also part of the "+this.getDescription(onShapeNum)+". The forces on the freebody body diagram of a particular object must always be by a different object. You must not include force by the same object, or parts of that same object. Either remove the highlighted force or fix its label. (#6)"; this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; this.focusForceNum=forceNum; //this.highlightForce(this.focusForceNum); return false; } } } return true; } isOnObjectPartOfByObject() { //console.log("this.answer.forces.length="+this.answer.forces.length); for (var forceNum=0;forceNum
    The force highlighted in red is the "+this.forceTypeString(studentForce.forceType)+" force by "+this.getDescription(studentForce.by)+" on the "+this.getDescription(studentForce.on)+". A contact force can be resolved into normal and friction force components. Resolve the highlighted contact force into its normal and friction components. (#87)"; this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; this.focusForceNum=i; //this.highlightForce(i); return false; } } return true; } areBothContactAndNormalFrictionForcesIncluded() { //The array contactForces indicates the normal/friction forces that are equivalent to each contact force. Initially, this function checks to see if the student has included both a contact force and the corresponding normal or friction forces. If so, an error is flagged. for (var i=0;iError: Duplicate force(s) on the "+this.getDescription(fbdOnNum)+".

    A contact force can be decomposed into normal and frictional components. However, you must not include both the contact force and its components. On the diagram for the "+this.getDescription(fbdOnNum)+", either delete the contact force, or delete the normal and/or friction components. (#85)"; this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; if (force3==undefined){ this.focusForceNum=[force1,force2]; } else { this.focusForceNum=[force1,force2,force3]; } //console.log("this.focusForceNum="+this.focusForceNum); //this.highlightForce(this.focusForceNum); return false; } if (lastOnNum==undefined){ doneFlag=true; } else { this.updateCorrectAnswers(lastOnNum); //this.consoleLogShapeForces(); } //console.log("End of While loop"); } } return true; } updateCorrectAnswers(fbdOnNum) { //The check is complete to confirm that the student force diagram does not contain both a contact force and the normal/friction components. Now change the correct this.answers. If the student drew a contact force, eliminate the normal and friction forces in the this.answer and replace them with a contact force. If the student included a normal and/or friction, then eliminate the contact force and replace it with the normal/friction components. var fbd; for (var i=0;i
    The two forces highlighted in red are labeled as "+this.forceTypeString(force1.forceType)+" forces and they act on the same object. One is by the "+ this.getDescription(force1.by)+ " and the other is by the "+ this.getDescription(force2.by)+ ". However, the "+ this.getDescription(force2.by)+ " is a part of the "+ this.getDescription(force1.by)+ ". Hence, the two force appear to be duplicates. Remove one of them. (#12)"; this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; this.focusForceNum=[forceNum1,forceNum2]; //this.highlightForce(this.focusForceNum); return false; } if (this.shapes[force2.by].type=='system' && this.isPartOfSystem(force1.by,this.shapes[force2.by].parts)){ this.response="Error: Duplicate forces.

    The two forces highlighted in red are labeled as "+this.forceTypeString(force1.forceType)+" forces and they act on the same object. One is by the "+ this.getDescription(force1.by)+ " and the other is by the "+ this.getDescription(force2.by)+ ". However, the "+ this.getDescription(force1.by)+ " is a part of the "+ this.getDescription(force2.by)+ ". Hence, the two force appear to be duplicates. Remove one of them. (#13)"; this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; this.focusForceNum=[forceNum1,forceNum2]; //this.highlightForce(this.focusForceNum); return false; } } } } return true; } areContactForcesByTouchingObjects() { for (var forceNum=0;forceNum
    For the force highlighted in red, you indicated that the force type is '"+forceType+"'. A "+forceType+" force is a contact force. Contact forces only exist when the object the force is 'by' touches the object the force is 'on'. The label of the highlighted force indicates that this force is 'by' the "+this.getDescription(byShapeNum)+" and 'on' the "+this.getDescription(onShapeNum)+". However, the "+this.getDescription(byShapeNum)+" is not touching the "+this.getDescription(onShapeNum)+". Either this force does not exist, or it is mislabeled. Either way, fix it. (#14)"; this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; this.focusForceNum=forceNum; //this.highlightForce(this.focusForceNum); return false; } } } return true; } isWeightByEarth() { for (var forceNum=0;forceNumthis.angleTolerance) { this.response="Error: Incorrect force direction.

    According to the label, the force highlighted in red is the weight by the earth on the "+this.getDescription(onShapeNum)+". The direction of the gravitational attraction by the earth on the "+this.getDescription(onShapeNum)+" is towards the center of the earth. Fix the direction of this force. (#16)"; this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; this.focusForceNum=forceNum; //this.highlightForce(this.focusForceNum); this.response+=this.smallAngleFeedbackAddendum(angleDiff); return false; } else { this.forceFlag[forceNum]=true; } } //Also handle case for an upward weight force acting ON the earth. if (force.forceType=="W" && this.getDescription(onShapeNum).trim()=='Earth') { var angleDiff=Math.abs(force.angle+Math.PI/2); if (angleDiff>this.angleTolerance) { this.response="Error: Incorrect force direction.

    According to the label, the force highlighted in red is the weight by the "+this.getDescription(byShapeNum)+" on the earth. Is the gravitational attraction by the "+this.getDescription(byShapeNum)+" on the earth really in the direction indicated by highlighted vector? Fix the direction of this force. (#17)"; this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; this.focusForceNum=forceNum; //this.highlightForce(this.focusForceNum); this.response+=this.smallAngleFeedbackAddendum(angleDiff); return false; } else { this.forceFlag[forceNum]=true; } } } return true; } checkForFrictionBySpringsOrStrings() { for (var forceNum=0;forceNum
    The label indicates that the force highlighted in red is a friction force by the "+this.getDescription(byShapeNum)+" on the "+this.getDescription(onShapeNum)+". None of the problems in this simulation involve a spring rubbing against an object. Hence, none of the problems in this simulation involve a friction force by or on a spring. Remove the highlighted force. (#79)"; this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; this.focusForceNum=forceNum; //this.highlightForce(this.focusForceNum); return false; } if (force.forceType=='f' && (this.shapes[onPartNum].type.includes('string') || this.shapes[byPartNum].type.includes('string'))){ this.response="Error: Incorrect force.

    The label indicates that the force highlighted in red is a friction force by the "+this.getDescription(byShapeNum)+" on the "+this.getDescription(onShapeNum)+". None of the problems in this simulation involve a string rubbing against an object. Hence, none of the problems in this simulation involve a friction force by or on a string. Remove the highlighted force. (#80)"; this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; this.focusForceNum=forceNum; //this.highlightForce(this.focusForceNum); return false; } } return true; } checkDirectionOfTensionByString() { for (var forceNum=0;forceNumthis.angleTolerance){ //Student tension is in the wrong direction. Check to see if it is opposite to the correct direction. var tensionAngle=force.angle; if (tensionAngle>0){tensionAngle-=Math.PI} else {tensionAngle+=Math.PI} if (Math.abs(instructorForce.angle-tensionAngle)
    The force highlighted in red is a tension by the "+this.getDescription(onPartNum)+" on the "+this.getDescription(byPartNum)+". The direction of the tension force indicates the "+this.getDescription(onPartNum)+" is pushing the "+this.getDescription(byPartNum)+". It is impossible to push the "+this.getDescription(byPartNum)+". It can only be pulled. Change the direction of the highlighted force so the "+this.getDescription(onPartNum)+" is pulling the "+this.getDescription(byPartNum)+", not pushing it. (#19)"; this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; this.focusForceNum=forceNum; //this.highlightForce(this.focusForceNum); } else { this.response="Error: Incorrect force direction.

    The force highlighted in red is a tension by the "+this.getDescription(onPartNum)+" on the "+this.getDescription(byPartNum)+". The tension force should be parallel to the "+this.getDescription(byPartNum)+". Change the direction of the highlighted force so it is parallel to the "+this.getDescription(byPartNum)+". (#21)"; this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; this.focusForceNum=forceNum; //this.highlightForce(this.focusForceNum); this.response+=this.smallAngleFeedbackAddendum(angleDiff); } return false; } } } } this.forceFlag[forceNum]=true; return true; } checkDirectionOfForceBySpring(){ for (var forceNum=0;forceNumthis.angleTolerance){ //Student tension is in the wrong direction. Check to see if it is opposite to the correct direction. var tensionAngle=force.angle; if (tensionAngle>0){tensionAngle-=Math.PI} else {tensionAngle+=Math.PI} if (Math.abs(instructorForce.angle-tensionAngle)
    The label indicates that the force highlighted in red is a normal force. However, the spring is stretched, and stretched springs pull. They do not push. Fix the label of the highlighted force. (#22a)"; } else { this.response="Error: Incorrect force label.

    The label indicates that the force highlighted in red is a tension force. However, the spring is compressed, and compressed springs push. They do not pull. Fix the label of the highlighted force. (#23a)"; } this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; this.focusForceNum=forceNum; //this.highlightForce(this.focusForceNum); return false; } var angleDiff=Math.abs(force.angle-instructorForce.angle); if (angleDiff>this.angleTolerance){ //Student tension is in the wrong direction. Check to see if it is opposite to the correct direction. var tensionAngle=force.angle; if (tensionAngle>0){tensionAngle-=Math.PI} else {tensionAngle+=Math.PI} if (Math.abs(instructorForce.angle-tensionAngle)
    The force highlighted in red is by the "+this.getDescription(byPartNum)+" on the "+this.getDescription(onPartNum)+". The direction of this force must be parallel to the "+this.getDescription(springPartNum)+". Change the direction of the highlighted force so that it is parallel to the "+this.getDescription(springPartNum)+". (#73)"; this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; this.focusForceNum=forceNum; //this.highlightForce(this.focusForceNum); //console.log("angleDiff="+angleDiff+" reverseAngleDiff="+reverseAngleDiff); this.response+=this.smallAngleFeedbackAddendum(angleDiff); this.response+=this.smallAngleFeedbackAddendum(reverseAngleDiff); return false; } //The force is parallel to the string. Check to be sure the T or N labels agree with its direction. if ((reverseAngleDiff
    According to its label, the force highlighted in red is a normal force. However, this force is on a string. A normal force is a push. It is impossible to push a string. Hence, the highlighted force cannot be a normal force. Fix it! (#32)" } else { this.response="Error: Incorrect force label.

    According to its label, the force highlighted in red is a normal force. However, this force is by a string. A normal force is a push. It is impossible to push with a string. Hence, the highlighted force cannot be a normal force. Fix it! (#33)" } this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; this.focusForceNum=forceNum; //this.highlightForce(this.focusForceNum); return false; } } //console.log("this.shapes[onPartNum].type="+this.shapes[onPartNum].type+" this.shapes[byPartNum].type="+this.shapes[byPartNum].type); if (this.shapes[onPartNum].type.includes('spring') || this.shapes[byPartNum].type.includes('spring')){ continue; } //console.log("byPartNum="+this.getDescription(byPartNum)+" onPartNum="+this.getDescription(onPartNum)); if (!this.checkDirectionOfNormalAndTensionPart2 (forceNum,force,byPartNum,onPartNum,true)) { return false; } } return true; } checkDirectionOfNormalAndTensionPart2 (forceNum,force,byPartNum,onPartNum,reverseFlag) { var fbdShapeNum=this.fbdList[this.getObjectForceIsAttachedTo(force)]; for (var i=0;i
    The force highlighted in red is a normal force by the "+this.getDescription(byPartNum)+" on the "+this.getDescription(onPartNum)+". The direction of the normal force indicates the "+this.getDescription(byPartNum)+" is pulling on the "+this.getDescription(onPartNum)+". The "+this.getDescription(byPartNum)+" pushes on the "+this.getDescription(onPartNum)+". It does not pull on it. Change the direction of the highlighted force so the "+this.getDescription(byPartNum)+" is pushing the "+this.getDescription(onPartNum)+", not pulling on it. (#34)"; } else { this.response="Error: Incorrect force direction.

    The force highlighted in red is a normal force by the "+this.getDescription(onPartNum)+" on the "+this.getDescription(byPartNum)+". The direction of the normal force indicates the "+this.getDescription(onPartNum)+" is pulling on the "+this.getDescription(byPartNum)+". The "+this.getDescription(onPartNum)+" pushes on the "+this.getDescription(byPartNum)+". It does not pull on it. Change the direction of the highlighted force so the "+this.getDescription(onPartNum)+" is pushing the "+this.getDescription(byPartNum)+", not pulling on it. (#35)"; } this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; this.focusForceNum=forceNum; //this.highlightForce(this.focusForceNum); return false; } else { if (reverseFlag){ this.response="Error: Incorrect force direction.

    The force highlighted in red is a normal force by the "+this.getDescription(byPartNum)+" on the "+this.getDescription(onPartNum)+". One of the definitions of the word 'normal' is 'perpendicular'. The normal force by the "+this.getDescription(byPartNum)+" on the "+this.getDescription(onPartNum)+" should be perpendicular to surface where the "+this.getDescription(byPartNum)+" and the "+this.getDescription(onPartNum)+" touch. Change the direction of the highlighted force so that it is perpendicular to this surface. (#36)"; } else { this.response="Error: Incorrect force direction.

    The force highlighted in red is a normal force by the "+this.getDescription(onPartNum)+" on the "+this.getDescription(byPartNum)+". One of the definitions of the word 'normal' is 'perpendicular'. The normal force by the "+this.getDescription(onPartNum)+" on the "+this.getDescription(byPartNum)+" should be perpendicular to surface where the "+this.getDescription(onPartNum)+" and the "+this.getDescription(byPartNum)+" touch. Change the direction of the highlighted force so that it is perpendicular to this surface. (#37)"; } this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; this.focusForceNum=forceNum; //this.highlightForce(this.focusForceNum); //console.log("angleDiff="+angleDiff+" 16 deg="+(Math.PI*16/180)); this.response+=this.smallAngleFeedbackAddendum(angleDiff); return false; } } } else if (force.forceType=='T' && instructorForce.forceType=='T') { var angleDiff=Math.abs(force.angle-instructorForceAngle); if (angleDiff>this.angleTolerance){ //Student tension is in the wrong direction. Check to see if it is opposite to the correct direction. var reverseInstructorForceAngle=instructorForceAngle; if (instructorForceAngle>0){reverseInstructorForceAngle-=Math.PI} else {reverseInstructorForceAngle+=Math.PI} if (Math.abs(force.angle-reverseInstructorForceAngle)this.angleTolerance){ //Contact force angle does not match angle of shapeForce. Find the force in contactForces. var dirPhrase=""; for (var contactNum=0;contactNum
    According to the label, the force highlighted in red is the friction force by the "+this.getDescription(force.by)+" on the "+this.getDescription(force.on)+". However, the surface between the "+this.getDescription(byPartNum)+" and the "+this.getDescription(onPartNum)+" is frictionless. Remove this force. (#66)"; this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; this.focusForceNum=forceNum; //this.highlightForce(this.focusForceNum); return false; } } } return true; } checkDirectionOfFrictionDueToRelativeMotion(){ for (var forceNum=0;forceNumthis.angleTolerance){ //Student friction is in the wrong direction. Check to see if it is opposite to the correct direction. var reverseInstructorAngle=instructorForce.angle; if (reverseInstructorAngle>0){reverseInstructorAngle-=Math.PI} else {reverseInstructorAngle+=Math.PI} //console.log("INSIDE IF: instructorForce.angle="+instructorForce.angle+" force.angle="+force.angle+" reverseInstructorAngle="+reverseInstructorAngle); this.response; if (Math.abs(force.angle-reverseInstructorAngle) tolerance"); this.response="Error: Incorrect force direction.

    The force highlighted in red is labeled as a friction force by the "+this.getDescription(byShapeNum)+" on the "+this.getDescription(onShapeNum)+". The direction of a friction force is always parallel to surface that the object is touching. It should also be directed opposite to the way the "+this.getDescription(onShapeNum)+" is moving relative to the "+this.getDescription(byShapeNum)+". Change the direction of the highlighted force so that it points in the direction opposite to the direction that the "+this.getDescription(onShapeNum)+" is moving relative to the "+this.getDescription(byShapeNum)+". (#45)"; this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; this.focusForceNum=forceNum; //this.highlightForce(this.focusForceNum); this.response+=this.smallAngleFeedbackAddendum(angleDiff); } return false; } } } this.forceFlag[forceNum]=true; return true; } checkDirectionOfFrictionDueToRelativeMotionPart3(forceNum,force,onShapeNum,byShapeNum, onPartNum,byPartNum,direction) { //console.log("onPartNum="+onPartNum+" byPartNum="+byPartNum+" direction="+direction); for (var i=0;i0){reverseInstructorAngle-=Math.PI} else {reverseInstructorAngle+=Math.PI} var angleDiff=Math.abs(force.angle-instructorForce.angle); var reverseAngleDiff=Math.abs(force.angle-reverseInstructorAngle) if (angleDiff>this.angleTolerance && reverseAngleDiff>this.angleTolerance){ this.response="Error: Incorrect force direction.

    The force highlighted in red is labeled as a friction force by the "+this.getDescription(byShapeNum)+" on the "+this.getDescription(onShapeNum)+". However, this friction should be parallel to the surface between the "+this.getDescription(byShapeNum)+" and the "+this.getDescription(onShapeNum)+". Change the direction of the highlighted force so that it points in the direction that is parallel to the surface between the "+this.getDescription(onShapeNum)+" and the "+this.getDescription(byShapeNum)+". (#61)"; this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; this.focusForceNum=forceNum; //this.highlightForce(this.focusForceNum); return false; } } } //this.forceFlag[forceNum]=true; return true; } areForcePairsOpposite() { for (var forceNum1=0;forceNum10){angle1Reverse-=Math.PI} else {angle1Reverse+=Math.PI} //console.log("angle1=+"+angle1+" angle2="+angle2+" angle1Reverse="+angle1Reverse); if (Math.abs(angle1Reverse-angle2)>this.angleTolerance){ this.response; if ((byShapeNum1==byPartNum1 && onShapeNum1==onPartNum1 && byShapeNum2==byPartNum2 && onShapeNum2==onPartNum2) || (byShapeNum1==onShapeNum2 && onShapeNum1==byShapeNum2)) { this.response="Error: Incorrect force directions.

    The two forces highlighted in red are both labeled as "+this.forceTypeString(force1.forceType)+" forces. One is by the "+ this.getDescription(byShapeNum1)+ " on the "+ this.getDescription(onShapeNum1)+ ". The other is by the "+ this.getDescription(byShapeNum2)+ " on the "+ this.getDescription(onShapeNum2)+ ". Notice that the 'by' and 'on' labels of the two forces are reversed.

    Remember, if one object touches a second object, the second object touches the first. "+this.forceTypePhrase(force1.forceType, this.getDescription(force1.by),this.getDescription(force1.on))+" In particular the two highlighted forces should be in opposite directions. Change the direction of the highlighted forces so that they are indeed in opposite directions. (#46)"; } else if (byShapeNum1==byPartNum1 && onShapeNum1==onPartNum1) { this.response="Error: Incorrect force directions.

    The two forces highlighted in red are both labeled as "+this.forceTypeString(force1.forceType)+" forces. One is by the "+ this.getDescription(byShapeNum1)+ " on the "+ this.getDescription(onShapeNum1)+ ". The other is by the "+ this.getDescription(byShapeNum2)+ " on the "+ this.getDescription(onShapeNum2)+ ". More specifically, however, the second force is by the "+ this.getDescription(byPartNum2)+ " on the "+ this.getDescription(onPartNum2)+ ". Notice that the 'by' and 'on' labels of the two forces are reversed.

    Remember, if one object touches a second object, the second object touches the first. "+this.forceTypePhrase(force1.forceType, this.getDescription(force1.by),this.getDescription(force1.on))+" In particular the two highlighted forces should be in opposite directions. Change the direction of the highlighted forces so that they are indeed in opposite directions. (#47)"; } else if (byShapeNum2==byPartNum2 && onShapeNum2==onPartNum2) { this.response="Error: Incorrect force directions.

    The two forces highlighted in red are both labeled as "+this.forceTypeString(force1.forceType)+" forces. One is by the "+ this.getDescription(byShapeNum1)+ " on the "+ this.getDescription(onShapeNum1)+ ". More specifically, however, it is by the "+ this.getDescription(byPartNum1)+ " on the "+ this.getDescription(onPartNum1)+ ". The other force is by the "+ this.getDescription(byShapeNum2)+ " on the "+ this.getDescription(onShapeNum2)+ ". Notice that the 'by' and 'on' labels of the two forces are reversed.

    Remember, if one object touches a second object, the second object touches the first. "+this.forceTypePhrase(force1.forceType, this.getDescription(force2.by),this.getDescription(force2.on))+" In particular the two highlighted forces should be in opposite directions. Change the direction of the highlighted forces so that they are indeed in opposite directions. (#48)"; } else { this.response="Error: Incorrect force directions.

    The two forces highlighted in red are both labeled as "+this.forceTypeString(force1.forceType)+" forces. One is by the "+ this.getDescription(byShapeNum1)+ " on the "+ this.getDescription(onShapeNum1)+ ". The other force is by the "+ this.getDescription(byShapeNum2)+ " on the "+ this.getDescription(onShapeNum2)+ ". More specifically, however, one force is by the "+this.getDescription(byPartNum1)+" on the "+this.getDescription(onPartNum1)+" and the other is by the "+ this.getDescription(byPartNum2)+ " on the "+ this.getDescription(onPartNum2)+ ". Notice that the 'by' and 'on' labels of the two forces are reversed.

    Remember, if one object touches a second object, the second object touches the first. "+this.forceTypePhrase(force1.forceType, this.getDescription(byPartNum2),this.getDescription(onPartNum2))+" In particular the two highlighted forces should be in opposite directions. Change the direction of the highlighted forces so that they are indeed in opposite directions. (#49)"; } this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; this.focusForceNum=[forceNum1,forceNum2]; //this.highlightForce(this.focusForceNum); return false; } if (this.forceFlag[forceNum1]){this.forceFlag[forceNum2]=true} if (this.forceFlag[forceNum2]){this.forceFlag[forceNum1]=true} } } } return true; } doesWeightExist() { //Check to see if there is a weight force on each object if the problem contains the earth. var earthFlag=false; for (var shapeNum=0;shapeNum
    The Earth is one of the objects in this problem. Since all of the other objects in the problem are near to the earth, a weight force will act on every one of them. On your freebody diagram of the "+this.getDescription(fbdNum)+" there is no weight force on the "+this.getDescription(onFBDNum)+". Add this force. (#50)"; } else { this.response="Error: Missing force on the freebody diagram of the Earth.

    The Earth is one of the objects in this problem. Every other object in the problem will gravitationally attract the Earth. On your freebody diagram for the Earth, you did not include a weight force by the "+this.getDescription(byFBDNum)+". Add this force. (#51)"; } this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; return false; } } } return true; } doesEachContactHaveANormal() { //Check to see if each time two surfaces touch, there is a normal/tension force. for (var i=0;i
    The "+this.getDescription(byFBDNum)+" is touching the "+this.getDescription(onFBDNum) +". On your freebody diagram of the "+this.getDescription(fbdNum) +", however, you have not included a contact force by the "+this.getDescription(byFBDNum)+" on the "+this.getDescription(onFBDNum) +". Contact forces include normal, tension and friction forces. Add whichever force is appropriate. (#52)"; this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; return false; } } } return true; } doTensionsByStringsExist() { //Check that there is a tension by every string for (var i=0;i
    The "+this.getDescription(byFBDNum)+" is touching the "+this.getDescription(onFBDNum) +". On your freebody diagram of the "+this.getDescription(fbdNum) +", however, you have not included a contact force by the "+this.getDescription(byFBDNum)+" on the "+this.getDescription(onFBDNum) +". Contact forces include normal, tension and friction forces. Add whichever force is appropriate. (#53)"; } else { this.response="Error: Missing force on the freebody diagram of the "+this.getDescription(fbdNum)+".

    The "+this.getDescription(byFBDNum)+" is touching the "+this.getDescription(onFBDNum) +" more than once. On your freebody diagram of the "+this.getDescription(fbdNum) +", however, you have only included one contact force by the "+this.getDescription(byFBDNum)+" on the "+this.getDescription(onFBDNum) +". Add another copy of this force. (#54)" } this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; return false; } } } return true; } doesFrictionFromRelativeMotionExist() { //Check that there is a friction for every surface moving relative to another for (var i=0;i
    The "+this.getDescription(onFBDNum)+" is moving relative to the "+this.getDescription(byFBDNum) +". Put in other words, the "+this.getDescription(onFBDNum)+" is rubbing against the "+this.getDescription(byFBDNum) +". On your freebody diagram of the "+this.getDescription(fbdNum) +", however, you have not included a friction force by the "+this.getDescription(byFBDNum)+" on the "+this.getDescription(onFBDNum) +". Add this force. (#55)"; this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; return false; } } } return true; } checkNormalAndFrictionComponentsOfContactForce(){ //Checks whether 'contact force' direction given in contactForces is composed of the correct friction and normal combo. var beginForceNum=0; for (var contactForceNum=0;contactForceNum
    According to the problem description, the force by the "+this.getDescription(contactForce.by)+" on the "+this.getDescription(contactForce.on)+" is directed "+contactForce.description+". The force by the "+this.getDescription(contactForce.by)+" on the "+this.getDescription(contactForce.on)+" is a contact force. It is the equivalent to the sum of the normal and friction forces. However, the direction of the normal force by the "+this.getDescription(contactForce.by)+" on the "+this.getDescription(contactForce.on)+" already matches the direction of the contact force described in the problem. Hence there is no friction force.

    The force highlighted in red is labeled as a friction force by the "+this.getDescription(friction.by)+" on the "+this.getDescription(friction.on)+". This force does not exist. Drag it into the force bin to remove it. (#63)"; this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; this.focusForceNum=frictionForceNum; //this.highlightForce(this.focusForceNum); return false; } } else if (friction==undefined){ var onShapeNum=this.fbdList[this.getObjectForceIsAttachedTo(normal)]; this.response="Error: Missing force on the freebody diagram of the "+this.getDescription(onShapeNum)+".

    According to the problem description, the force by the "+this.getDescription(contactForce.by)+" on the "+this.getDescription(contactForce.on)+" is directed "+contactForce.description+". The force by the "+this.getDescription(contactForce.by)+" on the "+this.getDescription(contactForce.on)+" is a contact force. It is the equivalent to the sum of the normal and friction forces. So far, you have only drawn a normal force by the "+this.getDescription(contactForce.by)+" on the "+this.getDescription(contactForce.on)+" and its direction does not match the direction of the contact force described in the problem. Hence, there must also be a friction force by the "+this.getDescription(contactForce.by)+" on the "+this.getDescription(contactForce.on)+".

    Currently, your freebody diagram of the "+this.getDescription(onShapeNum)+" does not include a friction force by the "+this.getDescription(contactForce.by)+" on the "+this.getDescription(contactForce.on)+". Draw this friction force so that when it is added to the normal force by the "+this.getDescription(normal.by)+" on the "+this.getDescription(normal.on)+", the sum is directed "+contactForce.description+". (#64)"; this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; return false; } else { var contactAngleDiffSign=this.calculateSignAngleDifference(contactForce.contactAngle-normal.angle); var frictionAngleDiffSign=this.calculateSignAngleDifference(friction.angle-normal.angle); //console.log("contactForce.contactAngle="+contactForce.contactAngle+" normal.angle="+normal.angle+" friction.angle="+friction.angle); //console.log("contactAngleDiffSign="+contactAngleDiffSign+" frictionAngleDiffSign="+frictionAngleDiffSign); if (contactAngleDiffSign!=frictionAngleDiffSign) { var onShapeNum=this.fbdList[this.getObjectForceIsAttachedTo(normal)]; this.response="Error: Incorrect force direction.

    According to the problem description, the force by the "+this.getDescription(contactForce.by)+" on the "+this.getDescription(contactForce.on)+" is directed "+contactForce.description+". The force by the "+this.getDescription(contactForce.by)+" on the "+this.getDescription(contactForce.on)+" is a contact force. It is the equivalent to the sum of the normal and friction forces. The contact and normal forces you drew, however, do not sum to give a contact force that is directed "+contactForce.description+".

    The force highlighted in red is labeled as a friction force by the "+this.getDescription(friction.by)+" on the "+this.getDescription(friction.on)+". This force points in the wrong direction. Change its direction so that when it is added to the normal force by the "+this.getDescription(normal.by)+" on the "+this.getDescription(normal.on)+", the sum is directed "+contactForce.description+". (#65)"; this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; this.focusForceNum=frictionForceNum; //this.highlightForce(this.focusForceNum); return false; } this.forceFlag[frictionForceNum]=true; } } if (normal!=undefined){ beginForceNum=normalForceNum+1; //console.log("Normal is DEFINED: normalForceNum="+normalForceNum+" beginForceNum="+beginForceNum+" contactForceNum="+contactForceNum); contactForceNum--; } else { //console.log("Normal is UNDEFINED"); beginForceNum=0; } } return true; } forcesForStretchedCompressedSprings(){ //Check that there is force for every stretched and compressed spring for (var i=0;i
    This problem involves a spring. The spring is touching the "+this.getDescription(otherNum)+". On your freebody diagram of the "+this.getDescription(fbdNum) +", however, you have not included a spring force by the "+this.getDescription(byFBDNum)+" on the "+this.getDescription(onFBDNum) +". Spring forces are either pushes (i.e., normal forces) or pulls (i.e., tension forces). Add whichever is appropriate. (#56)"; this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; return false; } } } return true; } validateFrictionForN2(){ //this.consoleLogForceFlags("Starting validateFrictionForN2"); for (var fbdNum=0;fbdNum2) {continue} var force1=this.answer.forces[forceNum1]; var onShapeNum1=force1.on; var byShapeNum1=force1.by; //if 'on' object is a system, determine the part that is touching the 'by' object. var onPartNum1=onShapeNum1; if (this.shapes[onShapeNum1].type=='system'){ var onPartNum1=this.determinePartOfSystemTouchingObject(onShapeNum1,byShapeNum1); } //if 'by' object is a system, determine the part that is touching the 'on' object. var byPartNum1=byShapeNum1; if (this.shapes[byShapeNum1].type=='system'){ var byPartNum1=this.determinePartOfSystemTouchingObject(byShapeNum1,onPartNum1); } //console.log("onShapeNum1="+onShapeNum1+" byShapeNum1="+byShapeNum1+" onPartNum1="+onPartNum1+" byPartNum1="+byPartNum1); //Check to see if byShapeNum1 is an object for which a FBD is being drawn. var foundFlag=false; for (var fbdNum=0;fbdNum
    According to its label, the force highlighted in green is a "+this.forceTypeString(force1.forceType)+" force by the "+this.getDescription(force1.by)+" on the "+this.getDescription(force1.on)+". Remember, if one object touches a second object, the second object touches the first. "+this.forceTypePhrase(force1.forceType, this.getDescription(force1.by),this.getDescription(force1.on))+" Forces always come in pairs. Add a second "+this.forceTypeString(force1.forceType)+" force that is by the "+this.getDescription(force1.on)+" on the "+this.getDescription(force1.by)+". (#58)"; this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; this.focusForceNum=forceNum1; this.focusForceColor='green'; //this.highlightForce(this.focusForceNum,"green"); return false; } if (this.forceFlag[forceNum1]){this.forceFlag[forceNum2]=true} if (this.forceFlag[forceNum2]){this.forceFlag[forceNum1]=true} } return true; } checkForFrictionAndSpringForceFromAccelerationNetForceMismatch(checkExistingForcesFlag) { //checkExistingForcesFlag = true means that only N2 consistency is only checked for existing student forces. //checkExistingForcesFlag = false means that N2 consistency is used to identify missing forces that are either static friction forces or forces by a springs with an unknown status //this.consoleLogForceFlags("INSIDE checkForFrictionAndSpringForceFromAccelerationNetForceMismatch") for (var fbdNum=0;fbdNum
    In this problem, the "+this.getDescription(shapeNum)+" "+accDirString+". However, your freebody diagram of the "+this.getDescription(shapeNum)+" shows a net force that is not consistent with this. You have not yet drawn the friction force by the "+this.getDescription(unknownTouchNum)+" on the "+this.getDescription(shapeNum)+". Add this force so that the directions of the net force and acceleration match. (#57)" this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; //console.log("this.response="+this.response); return false; } } isSpringForceConsistentWithN2(shapeNum,touchNum,checkExistingForcesFlag){ //console.log("INSIDE isSpringForceConsistentWithN2: shapeNum="+shapeNum+" touchNum="+touchNum); if (checkExistingForcesFlag){ //First determine whether a spring force by touchingSegment on shapeNum already exists. var foundFlag=false; for (var forceNum=0;forceNum
    In this problem, the "+this.getDescription(shapeNum)+" "+accDirString+". However, your freebody diagram of the "+this.getDescription(shapeNum)+" shows a net force that is not consistent with this. The force that is highlighted in red is labeled as the "+this.forceTypeString(force.forceType)+" by the "+this.getDescription(force.by)+" on the "+this.getDescription(force.on)+". The highlighted force is parallel to the "+this.getDescription(touchNum)+" as it should be, but that would still be true even if it pointed in the opposite direction. Change the direction of the highlighted force so that the directions of the net force and acceleration are consistent. (#72)" this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; this.focusForceNum=forceNum; //this.highlightForce(this.focusForceNum); return false; } else { var forceComponentWithoutForce= this.determineStudentNetForceComponentExcludingForceNum(shapeNum,dir,forceNum); //console.log("forceComponent="+forceComponent+" forceComponentWithoutForce="+forceComponentWithoutForce); if (forceComponentWithoutForce==accComponent || forceComponentWithoutForce==null){ this.response="Error: Incorrect force.

    In this problem, the "+this.getDescription(shapeNum)+" "+accDirString+". However, your freebody diagram of the "+this.getDescription(shapeNum)+" shows a net force that is not consistent with this. Even if the "+this.forceTypeString(force.forceType)+" force that is highlighted in red is reversed, the directions of the net force and acceleration would not be consistent. Hence, this "+this.forceTypeString(force.forceType)+" force must not exist. Remove it. (#83)" this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; this.focusForceNum=forceNum; //this.highlightForce(this.focusForceNum); return false; } else { this.response="Unknown error on the freebody diagram of the "+this.getDescription(shapeNum)+"

    The direction of the net force on the "+this.getDescription(shapeNum)+" is not consistent with the direction of the acceleration. Unfortunately, this program cannot determine the source of this inconsistency. (#84)" this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; return false; } } } } } else { //No spring force was found. //Check to see if N2 is violated in direction of the spring. //console.log("No existing spring force was found: shapeNum="+shapeNum+" touchNum="+touchNum); var onPartNum=shapeNum; if (this.shapes[shapeNum].type=='system'){ onPartNum=this.determinePartOfSystemTouchingObject(shapeNum,touchNum) } //console.log("touchNum="+touchNum+" description="+this.getDescription(touchNum)); var dir; for (var forceNum=0;forceNum
    In this problem, the "+this.getDescription(shapeNum)+" is "+accDirString+". However, your freebody diagram of the "+this.getDescription(shapeNum)+" shows a net force that is not consistent with this. You have not yet included a force by the "+this.getDescription(touchNum)+" on the "+this.getDescription(shapeNum)+". Add this force so that the directions of the net force and acceleration are consistent. (#76)" this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; return false; } } return true; } isExistingFrictionDirectionConsistentWithN2(forceNum,touchingSegment){ //Both force and forceNum refer to a friction force. //console.log("forceNum="+forceNum+" touchingSegment="+touchingSegment); //Check to see if the force has already been verified if (this.forceFlag[forceNum]){return true} //Direction of friction is not dictated by relative motions or contact forces. A previous function already checked that its direction is parallel to the surface between the 'by' and 'on' objects. However, it's direction could still be the direction indicated in 'force' or the opposite direction. Check to see if the direction of 'force' causes a mismatch between the net force and acceleration. If so, see if the opposite direction resolves this mismatch. If not, see if a zero 'force' resolves the mismatch. //Determine the direction of the segment between shapeNum and the touching shape. var force=this.answer.forces[forceNum]; var shapeNum=this.fbdList[this.getObjectForceIsAttachedTo(force)]; //console.log("Inside isExistingFrictionDirectionConsistentWithN2: shapeNum="+shapeNum+"force={type:"+force.forceType+", by:"+force.by+", on:"+force.on+"}"); //console.log("touchingSegment="+touchingSegment); var dir=this.findInstructorFrictionForceDirection(force); if (dir==undefined){return true}; //Check to see if there is a mismatch between the components of accelerationCalculated and the net force along dir. //console.log("dir="+dir); var forceComponent=this.determineStudentNetForceComponent(shapeNum,dir); var accComponent=this.determineAccelerationCalculatedComponent(shapeNum,dir); //console.log("accComponent="+accComponent); if (accComponent==null) {return true} //console.log("shapeNum="+shapeNum+" touchingSegment.shape="+touchingSegment+" accComponent="+accComponent+" forceComponent="+forceComponent); //console.log(" accelerationCalculated={x:"+accelerationCalculated[shapeNum].x+", y:"+accelerationCalculated[shapeNum].y+"}"); var accDirString=this.getAccDirString(this.shapes[shapeNum].acceleration); //console.log("dir={x:"+dir.x+"y:"+dir.y+"}"); if (forceComponent==accComponent || forceComponent==null){ this.forceFlag[forceNum]=true; return true; } else { var forceComponentWithNegForce=this.determineStudentNetForceComponentWithNegForce(shapeNum,dir,forceNum); //console.log("forceComponent="+forceComponent+" forceComponentWithNegForce="+forceComponentWithNegForce); if (forceComponentWithNegForce==accComponent || forceComponentWithNegForce==null){ this.response="Error: Incorrect force direction.

    In this problem, the "+this.getDescription(shapeNum)+" "+accDirString+". However, your freebody diagram of the "+this.getDescription(shapeNum)+" shows a net force that is not consistent with this. The friction force that is highlighted in red must be parallel to the surface between the "+this.getDescription(shapeNum)+" and the "+this.getDescription(touchingSegment)+". However, it will still be parallel if it points in the opposite direction. Change the direction of the highlighted friction force so that the directions of the net force and acceleration are consistent. (#62)" this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; this.focusForceNum=forceNum; //this.highlightForce(this.focusForceNum); return false; } else { var forceComponentWithoutForce= this.determineStudentNetForceComponentExcludingForceNum(shapeNum,dir,forceNum); //console.log("forceComponent="+forceComponent+" forceComponentWithoutForce="+forceComponentWithoutForce); if (forceComponentWithoutForce==accComponent || forceComponentWithoutForce==null){ this.response="Error: Incorrect force.

    In this problem, the "+this.getDescription(shapeNum)+" "+accDirString+". However, your freebody diagram of the "+this.getDescription(shapeNum)+" shows a net force that is not consistent with this. Even if the friction force that is highlighted in red is reversed, the directions of the net force and acceleration would not be consistent. Hence, this friction force must not exist. Remove it. (#81)" this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; this.focusForceNum=forceNum; //this.highlightForce(this.focusForceNum); return false; } else { this.response="Unknown error on the freebody diagram of the "+this.getDescription(shapeNum)+"

    The direction of the net force on the "+this.getDescription(shapeNum)+" is not consistent with the direction of the acceleration. Unfortunately, this program cannot determine the source of this inconsistency. (#82)" this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; return false; } } } return true; } checkForForcesOnPartsThatAreNotOnSystem(){ for (var fbdNum=0;fbdNumthis.angleTolerance)){ //console.log("this.answer.forces[forceNum1].angle=" +this.answer.forces[forceNum1].angle+" this.answer.forces[forceNum2].angle="+this.answer.forces[forceNum2].angle); this.response="Error: Incorrect force direction.

    The forces highlighted in red are labeled as the "+this.forceTypeString(force1.type)+" by the "+this.getDescription(force1.by)+" on the "+this.getDescription(force1.on)+" and the "+this.forceTypeString(force2.type)+" by the "+this.getDescription(force2.by)+" on the "+this.getDescription(force2.on)+". These are the same force, so should be in the same direction. The forces in your diagrams however point in different directions. The direction of one or the other is incorrect. Fix it so that the two forces point in the same direction. (#71)"; this.focusForceNum=[forceNum1,forceNum2]; //this.highlightForce(this.focusForceNum); this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; return false; } break; } else if (force1.forceType=="C" && force2.forceType=="N"){ normalForceFlag=true; } else if (force1.forceType=="C" && force2.forceType=="f"){ frictionForceFlag=true; } else if ((force1.forceType=="N" || force1.forceType=="f") && force2.forceType=="C"){ contactForceFlag=true; } } } if (force1.forceType=="C" && normalForceFlag && frictionForceFlag){continue} if ((force1.forceType=="N" || force1.forceType=="f") && contactForceFlag) {continue} if (foundForce) {continue} //console.log("force1.forceType="+force1.forceType+" normalForceFlag="+normalForceFlag+" frictionForceFlag="+frictionForceFlag); if (force1.forceType=="C" && !(normalForceFlag && frictionForceFlag)){ //The part contains a contact flag and the system does not contain one or the other or both of the corresponding normal and friction forces. this.response="Error: Missing force on the freebody diagram of the "+this.getDescription(shapeNum)+".

    The force highlighted in green on the freebody diagram of the "+this.getDescription(force1On)+" shows a contact force by the "+this.getDescription(force1.by)+" on the "+this.getDescription(force1.on)+". The "+this.getDescription(force1On)+" is part of the "+this.getDescription(shapeNum)+" while the "+this.getDescription(force1.by)+" is not. Hence this force also acts on the "+this.getDescription(shapeNum)+". Either add this force to the "+this.getDescription(shapeNum)+" or add both the normal and frictional component forces of this contact force. (#86)"; this.focusForceNum=forceNum1; this.focusForceColor='green'; //this.highlightForce(this.focusForceNum,'green'); this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; return false; } else { //force1 is an external force that does not appear on the this.shapes.length system. this.response="Error: Missing force on the freebody diagram of the "+this.getDescription(shapeNum)+".

    The force highlighted in green on the freebody diagram of the "+this.getDescription(force1On)+" shows a "+this.forceTypeString(force1.type)+" force by the "+this.getDescription(force1.by)+" on the "+this.getDescription(force1.on)+". The "+this.getDescription(force1On)+" is part of the "+this.getDescription(shapeNum)+" while the "+this.getDescription(force1.by)+" is not. Hence this force also acts on the "+this.getDescription(shapeNum)+". Add this force to the "+this.getDescription(shapeNum)+". (#67)"; this.focusForceNum=forceNum1; this.focusForceColor='green'; //this.highlightForce(this.focusForceNum,'green'); this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; return false; } } } } return true; } checkForForcesOnSystemThatAreNotOnParts(){ for (var fbdNum=0;fbdNum
    The forces highlighted in red are labeled as the "+this.forceTypeString(force1.forceType)+" force by the "+this.getDescription(force1.by)+" on the "+this.getDescription(force1.on)+" and the "+this.forceTypeString(force2.forceType)+" force by the "+this.getDescription(force2.by)+" on the "+this.getDescription(force2.on)+". These are the same force, so should be in the same direction. The forces in your diagrams however point in different directions. The direction of one or the other is incorrect. Fix it so that the two forces point in the same direction. (#70)"; this.focusForceNum=[forceNum1,forceNum2]; //this.highlightForce(this.focusForceNum); this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; return false; } break; } } if (frictionFoundFlag) {continue} //force1 is an external force that does not appear on the this.shapes.length system. if (force1.on!=force2.on) { this.response="Error: Missing force on the freebody diagram of the "+this.getDescription(force2On)+".

    The force highlighted in green on the freebody diagram of the "+this.getDescription(shapeNum)+" is labeled as the "+this.forceTypeString(force1.type)+" force by the "+this.getDescription(force1.by)+" on the "+this.getDescription(force1.on)+". However, this force is actually on the "+this.getDescription(force2.on)+" which is part of the "+this.getDescription(force1.on)+". Hence this force also acts on the freebody diagram of the "+this.getDescription(force2On)+". Add this force to the freebody diagram of the "+this.getDescription(force2On)+". (#68)"; } else { this.response="Error: Missing force on the freebody diagram of the "+this.getDescription(force2On)+".

    The force highlighted in green on the freebody diagram of the "+this.getDescription(shapeNum)+" shows a "+this.forceTypeString(force1.type)+" force by the "+this.getDescription(force1.by)+" on the "+this.getDescription(force1.on)+". Hence this force also acts on the freebody diagram of the "+this.getDescription(force2On)+". Add this force to the freebody diagram of the "+this.getDescription(force2On)+". (#69)"; } this.focusForceNum=forceNum1; this.focusForceColor='green'; //this.highlightForce(this.focusForceNum,'green'); this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; return false; } } } return true; } checkForNetForceAccelerationMismatch(){ //Check to see if there is a mismatch between the directions of the net force and acceleration for (var fbdNum=0;fbdNum
    "+this.summary}; this.feedbackStyle='color:green'; //this.feedback.innerHTML=this.response; } drawAccelerationIndicator(){ this.accStudentGroup=this.fbdSVG.createElementNS("http://www.w3.org/2000/svg", "g"); this.fbdSVG1.appendChild(this.accStudentGroup); var qn=1000*this.questionNum+this.questionPartNum; for (var fbdNum=0;fbdNum"; for (var i=0;i"; } this.feedback.innerHTML+=""; this.feedback.setAttribute("style","color:red"); return; } } length=this.focusElement.children[1+this.subscriptIndexOffset].textContent.length; if (length<=1){ var accVector=this.focusElement.parentNode.children[0]; //console.log("(x1,y1)=("+accVector.getAttribute("x1")+","+accVector.getAttribute("y1")+")"); //console.log("(x2,y2)=("+accVector.getAttribute("x2")+","+accVector.getAttribute("y2")+")"); var p1={x:parseFloat(accVector.getAttribute("x1")),y:parseFloat(accVector.getAttribute("y1"))} var p2={x:parseFloat(accVector.getAttribute("x2")),y:parseFloat(accVector.getAttribute("y2"))} var angle=Math.atan2((p2.y-p1.y),(p2.x-p1.x)); var rect=this.focusElement.getBBox(); var xText=p1.x+(this.accLength+7)*Math.cos(angle)-rect.width/2; var yText=p1.y+(this.accLength+7)*Math.sin(angle); this.focusElement.setAttribute("x",xText); this.focusElement.setAttribute("y",yText); this.saveAccToAnswers(accVector); this.updateAnswerBox(); this.inputDoneFlag=true; } } accKeyUpFunction(evt,that) { //console.log("keyUpFunction: this.inputDoneFlag="+this.inputDoneFlag); if (that.inputDoneFlag) { that.clearForceFocus(true); } that.keyDownFlag = false; return false; } saveAccToAnswers(selectedElement) { //console.log("selectedElement="+selectedElement); if (selectedElement==undefined){return} var p1={x:0,y:0}; var p2={x:0,y:0}; p1.x=parseFloat(selectedElement.getAttributeNS(null, "x1")); p1.y=parseFloat(selectedElement.getAttributeNS(null, "y1")); p2.x=parseFloat(selectedElement.getAttributeNS(null, "x2")); p2.y=parseFloat(selectedElement.getAttributeNS(null, "y2")); //console.log("tagName: selectedElement.parentElement.children[1].children[1]="+selectedElement.parentElement.children[1].children[1].tagName); var accSubscript= selectedElement.parentElement.children[1].children[1].textContent.trim(); //console.log("accSubscript="+accSubscript); var reference=selectedElement; var angle=this.createAngle(p1,p2); var fbdNum=undefined; for (var i=0;iMath.PI){return 1} else {return 0}; } angleIsPositive(angle){ if (angle>0){return 1} else {return 0}; } createEllipse([centerX,centerY],[radiusX,radiusY], [startAngle, endAngle], rotationAngle){ //Returns a SVG arc (ellipse). //centerX,centerY :center of ellipse //radiusX,radiusY :radii of ellipse //startAngle,endAngle :The starting and ending angle of the ellipse, in radians. //rotationAngle :The rotation angle of the entire ellipseφ, in radian. var deltaAngle=endAngle-startAngle; deltaAngle=deltaAngle%(2*Math.PI); //if (deltaAngle<-Math.PI) {deltaAngle+=2*Math.PI}; //console.log("startAngle="+startAngle+" endAngle="+endAngle+" deltaAngle="+deltaAngle); var rotMatrix=this.rotateMatrix(rotationAngle); var [startX,startY]=this.addMatrices ( this.multiplyMatrices ( rotMatrix, [radiusX*Math.cos(startAngle), radiusY*Math.sin(startAngle)] ), [centerX,centerY] ); var [endX, endY] = ( this.addMatrices ( this.multiplyMatrices ( rotMatrix, [radiusX * Math.cos(startAngle+deltaAngle), radiusY * Math.sin(startAngle+deltaAngle)] ), [centerX,centerY] ) ); var fA = this.angleBiggerThanPI(deltaAngle); fA=0; var fS = this.angleIsPositive(deltaAngle); if (Math.abs(deltaAngle)>Math.PI){fS=1-fS} //console.log("fA="+fA+" fX="+fS); return "M " + startX + " " + startY + " A " + [ radiusX , radiusY , rotationAngle*180/Math.PI, fA, fS, endX, endY ].join(" "); } getEllipseParameters (angleRef){ var pathAttribute=angleRef.d; /* for (var i=0;i<11;i++){ //console.log("i="+i+": "+parseFloat(pathAttribute[i])); } */ var start={x:parseFloat(pathAttribute[1]),y:parseFloat(pathAttribute[2])}; var end={x:parseFloat(pathAttribute[9]),y:parseFloat(pathAttribute[10])}; var radius={x:parseFloat(pathAttribute[4]),y:parseFloat(pathAttribute[5])}; var rotationAngle=parseFloat(pathAttribute[6]); var fA=parseFloat(pathAttribute[7]); var fS=parseFloat(pathAttribute[8]); return {start:start,end:end,radius:radius,rotationAngle:rotationAngle,fA:fA,fS:fS}; } newAngle(){ var qn=1000*this.questionNum+this.questionPartNum; //Create a SVG arc (ellipse). //centerX,centerY :center of ellipse //radiusX,radiusY :radii of ellipse //startAngle,endAngle :The starting and ending angle of the ellipse, in radians. //rotationAngle :The rotation angle of the entire ellipseφ, in radian. var [initialX,initialY]=[22,150]; var centerX=this.cm[0].x; var centerY=this.cm[0].y; var radiusX=Math.sqrt(Math.pow(initialX-centerX,2)+Math.pow(initialY-centerY,2)); var radiusY=radiusX; var startAngle=Math.atan2(initialY-centerY,initialX-centerX); //var endAngle=startAngle+this.forceAngleIncrement*Math.PI/180; var endAngle=startAngle+20*Math.PI/180; var rotationAngle=0; var angleGroup=this.fbdSVG.createElementNS("http://www.w3.org/2000/svg", "g"); var svgPath = document.createElementNS("http://www.w3.org/2000/svg", "path"); svgPath.setAttribute("d",this.createEllipse([centerX,centerY],[radiusX,radiusY], [startAngle, endAngle], rotationAngle)); svgPath.setAttribute("stroke","black"); svgPath.setAttribute("fill","none"); svgPath.setAttribute("stroke-width","1"); svgPath.setAttribute("marker-start","url(#angleDot"+qn+")"); svgPath.setAttribute("marker-end","url(#angleDot"+qn+")"); svgPath.setAttribute("class","draggable"); angleGroup.appendChild(svgPath); this.studAngleGroup.appendChild(angleGroup); //Push new angle into this.answers var pathAttribute=svgPath.getAttribute("d").split(" "); var angle={fbd:0, d:pathAttribute, reference:svgPath, startObject:undefined, endObject:undefined }; this.answer.angles.push(angle); //Label the angle var angleLabel=this.fbdSVG.createElementNS("http://www.w3.org/2000/svg", "text"); angleLabel.setAttribute("font-size","8"); angleLabel.setAttribute("fill",this.forceColor); angleLabel.setAttribute("class","label"); angleLabel.setAttribute("onclick","n2"+qn+".angleLabelInput(evt)"); angleGroup.appendChild(angleLabel); //Create the subscript angle label var angleLabel1=this.fbdSVG.createElementNS("http://www.w3.org/2000/svg", "tspan"); angleLabel1.setAttribute("class","label"); angleLabel1.textContent="θ"; angleLabel.appendChild(angleLabel1); var angleLabel2=this.fbdSVG.createElementNS("http://www.w3.org/2000/svg", "tspan"); angleLabel2.setAttribute("class","label"); angleLabel2.setAttribute("dominant-baseline","mathematical"); var uniqueSubscript=this.getUniqueAngleSubscript(); angleLabel2.textContent=uniqueSubscript angleLabel.appendChild(angleLabel2); this.moveAngleLabel(0,this.answer.angles.length-1); this.answer.angles[this.answer.angles.length-1].subscript=uniqueSubscript; this.updateAnswerBox(); } moveAngleLabel(fbdNum,angleNum){ //console.log("fbdNum="+fbdNum+" angleNum="+angleNum); var angleRef=this.answer.angles[angleNum].reference; var pathAttribute=angleRef.getAttribute("d").split(" "); var start={x:parseFloat(pathAttribute[1]),y:parseFloat(pathAttribute[2])}; var end={x:parseFloat(pathAttribute[9]),y:parseFloat(pathAttribute[10])}; var radius={x:parseFloat(pathAttribute[4]),y:parseFloat(pathAttribute[5])}; var angleCM; if (Math.pow(start.x-this.cmAcc[fbdNum].x,2)+Math.pow(start.y-this.cmAcc[fbdNum].y,2)1){ nearestPoint=p2; } else if (parameter<0){ nearestPoint=p1; } return nearestPoint; } getForceNumAttachedToAngle(angleEndID){ //console.log("Inside getForceNumAttachedToAngle: angleEndID="+angleEndID); for (var forceNum=0;forceNum0){ numTicks--; this.answer.forces[this.focusForceNum].numTickMarks--; this.tickMarkControlRef.children[5].textContent=numTicks; var forceRef=this.answer.forces[this.focusForceNum].reference; //forceRef.removeAttribute('marker-end'); var id="#greenHighlightArrow"+numTicks+qn; forceRef.setAttribute("marker-end","url("+id+")"); this.updateAnswerBox(); this.tickMarkControlRef.children[4].style.fill='white'; } } enterTickNum(){ if (this.tickMarkControlRef.opacity==.25) {return} var that=this; this.fbdSVG1.onkeydown=function(){ that.tickMarkKeyDownFunction(event,that); } this.fbdSVG1.onkeyup=function(){ that.tickMarkKeyUpFunction(event,that); } document.onkeydown=function(){ that.tickMarkKeyDownFunction(event,that); } document.onkeyup=function(){ that.tickMarkKeyUpFunction(event,that); } this.tickMarkControlRef.children[4].style.fill="yellow"; } tickMarkKeyDownFunction(evt,that){ //console.log("Running tickMarkKeyDown"); evt.preventDefault(); //prevents the default behaviour of keystrokes. In particular, prevents the backspace from sending the browser to the previous page. var key = evt.key; //console.log("key=" + key); //console.log("typeof=" + (typeof codeOfKey)); that.feedback.textContent=""; that.processTickMarkKeyDown(key); } processTickMarkKeyDown(key) { var qn=1000*this.questionNum+this.questionPartNum; //console.log("key=" + key + " this.keyDownFlag=" + this.keyDownFlag + " this.focusElement=" + this.focusElement); if (this.keyDownFlag) { return }; var tickMarkText=this.tickMarkControlRef.children[5]; var value=""; //console.log("this.legendString="+this.legendString); var length=tickMarkText.textContent.length; //console.log("length="+length); if (key == 'Backspace') { tickMarkText.textContent =tickMarkText.textContent.substring(0,length-1); if (tickMarkText.textContent.length==0){ tickMarkText.textContent="0"; } else { tickMarkText.textContent=parseInt(tickMarkText.textContent)+""; } } else if (!"0123456789".includes(key)) { return; } else if (length==2){ // return; } else { tickMarkText.textContent+=key; tickMarkText.textContent=parseInt(tickMarkText.textContent)+""; } if (parseInt(this.tickMarkControlRef.children[5].textContent)>this.maxNumTickMarks){ this.tickMarkControlRef.children[5].textContent=this.maxNumTickMarks+""; } length=tickMarkText.textContent.length; if (length<=2){ this.answer.forces[this.focusForceNum].numTickMarks=tickMarkText.textContent; this.updateAnswerBox(); var id="#greenHighlightArrow"+tickMarkText.textContent+qn; this.answer.forces[this.focusForceNum].reference.setAttribute("marker-end","url("+id+")"); } //console.log("tickMarkText.textContent="+tickMarkText.textContent); } tickMarkKeyUpFunction(evt,that) { that.keyDownFlag = false; if (parseInt(that.tickMarkControlRef.children[5].textContent)>that.maxNumTickMarks){ that.tickMarkControlRef.children[5].textContent=that.maxNumTickMarks+""; var markerString="url(#greenHighlightArrow"+that.maxNumTickMarks+qn+")" that.answer.forces[that.focusForceNum].reference.setAttribute("marker-end",markerString); } //console.log("that.tickMarkControlRef.children[5].textContent="+that.tickMarkControlRef.children[5].textContent); return false; } areTickMarkForcestheSameType() { //This is the first of the functions that checks tick marks. Hence. set the tickMarksChecked array to empty to indicate that none of the tick marks have yet been checked. this.tickMarksChecked=[]; for (var forceNum1=0;forceNum1
    The forces highlighted in red are the "+this.forceTypeString(force1.forceType)+" by the "+this.getDescription(force1.by)+" on the "+this.getDescription(force1.on)+" and the "+this.forceTypeString(force2.forceType)+" by the "+this.getDescription(force2.by)+" on the "+this.getDescription(force2.on)+". The two forces are marked with the same number of tick marks. The only forces that should be marked with the same number of tick marks are:
    • A Newton's Third Law force pair. Two forces that are equal because of the Third Law have the same force type and have subscripts that are switched.
    • The tension forces by opposite ends of the same string. All strings in this simulations are massless. All pulleys are massless and frictionless. Because of this, opposite ends of all strings exert the same magnitude tension force.
    • A force on a system of objects and the same force on one of the parts of the system. A force on a system of objects will be exerted on one or more of the system's parts. Be aware that a single force on a system might be a combination of forces on the different parts of that system.
    The highlighted forces do not fit any of these categories so should not have been marked with an equal number of tick marks. (#95)"; this.focusForceNum=[forceNum1,forceNum2]; this.feedbackStyle='color:red'; return false; } } } return true; } flagMissingTensionsByStringsTickMarks() { //Checks for missing tick marks on tension forces by opposite ends of the same string for (var forceNum1=0;forceNum1
    The forces highlighted in red are the "+this.forceTypeString(force1.forceType)+" by the "+this.getDescription(force1.by)+" on the "+this.getDescription(force1.on)+" and the "+this.forceTypeString(force2.forceType)+" by the "+this.getDescription(force2.by)+" on the "+this.getDescription(force2.on)+". The two tension forces are caused by the same string, hence, they must be equal. Mark the two forces with the same number of tick marks. (#96)"; this.focusForceNum=[forceNum1,forceNum2]; this.feedbackStyle='color:red'; return false; } else if (force1.numTickMarks!=force2.numTickMarks) { this.response="Error: Incorrect tick marks.

    The forces highlighted in red are the "+this.forceTypeString(force1.forceType)+" by the "+this.getDescription(force1.by)+" on the "+this.getDescription(force1.on)+" and the "+this.forceTypeString(force2.forceType)+" by the "+this.getDescription(force2.by)+" on the "+this.getDescription(force2.on)+". The two tension forces are caused by the same string, hence, they must be equal. Mark the two forces with the same number of tick marks. (#97)"; this.focusForceNum=[forceNum1,forceNum2]; this.feedbackStyle='color:red'; return false; } //console.log("object1FBDNum="+object1FBDNum+" object2FBDNum="+object2FBDNum); if (object1FBDNum!=undefined && object2FBDNum!=undefined && this.doObjectsConnectedToStringHaveDifferentAccelerations(object1FBDNum,object2FBDNum)){return false} } } } return true; } doObjectsConnectedToStringHaveDifferentAccelerations(object1FBDNum,object2FBDNum){ //Checks whether two objects that are connected to the ends of the same string have different accelerations. Returns true if the accelerations are different. //console.log("Acceleration #1="+this.answer.accelerations[object1FBDNum].subscript+" Acceleration #2="+this.answer.accelerations[object2FBDNum].subscript); if (this.answer.accelerations[object1FBDNum].subscript!= this.answer.accelerations[object2FBDNum].subscript){ //console.log("The accelerations are not equal") this.response='Error: Incorrect Acceleration Labels.

    The problem statement and/or picture indicates that the '+this.getDescription(this.fbdList[object1FBDNum])+' and the '+this.getDescription(this.fbdList[object2FBDNum])+' are connected to the ends of the same string. This means they must have the same speeds and the same accelerations. However, the labels for the two highlighted accelerations are different. Change one or the other acceleration label so that the two labels are the same. (#332)
    '; this.feedbackStyle='color:red'; //this.feedback.innerHTML=this.response; this.focusForceNum=[object1FBDNum,object2FBDNum]; //this.highlightForce(this.focusForceNum); this.answer.accelerationFeedbackFlag=true; return true; } return false; } flagMissingThirdLawForcePairTickMarks(){ //This function flags a Third Law force pair that doesn't contain the same number of tick marks. for (var forceNum1=0;forceNum1
    The forces highlighted in red are the "+this.forceTypeString(force1.forceType)+" by the "+this.getDescription(force1.by)+" on the "+this.getDescription(force1.on)+" and the "+this.forceTypeString(force2.forceType)+" by the "+this.getDescription(force2.by)+" on the "+this.getDescription(force2.on)+". Notice that both are "+this.forceTypeString(force1.forceType)+" forces. One is attached to the system, the "+this.getDescription(force2On)+", and the other is attached to a part of that system, the "+this.getDescription(force1On)+". These two forces are the same so should be marked with the same number of tick marks. (#100)"; this.focusForceNum=[forceNum1,forceNum2]; this.feedbackStyle='color:red'; return false; } else { this.response="Error: Incorrect tick marks.

    The forces highlighted in red are the "+this.forceTypeString(force1.forceType)+" by the "+this.getDescription(force1.by)+" on the "+this.getDescription(force1.on)+" and the "+this.forceTypeString(force2.forceType)+" by the "+this.getDescription(force2.by)+" on the "+this.getDescription(force2.on)+". Notice that both are "+this.forceTypeString(force1.forceType)+" forces. One is attached to the system, the "+this.getDescription(force2On)+", and the other is attached to a part of that system, the "+this.getDescription(force1On)+". These two forces are the same so should be marked with the same number of tick marks. (#101)"; this.focusForceNum=[forceNum1,forceNum2]; this.feedbackStyle='color:red'; return false; } } } } } return true; } /* flagMissingTickMarksForIndividualAndSystemForces1() { //This function flags a force on a system and the same force on one of the parts of the system if they do not have the same number of tick marks. //Another function that only flags the correct pairs but doesn't flag any errors for (var fbdNum=0;fbdNum
    The forces highlighted in red are the "+this.forceTypeString(force1.forceType)+" by the "+this.getDescription(force1.by)+" on the "+this.getDescription(force1.on)+" and the "+this.forceTypeString(force2.forceType)+" by the "+this.getDescription(force2.by)+" on the "+this.getDescription(force2.on)+". Notice that both are "+this.forceTypeString(force1.forceType)+" forces. One is attached to the system, the "+this.getDescription(force2On)+", and the other is attached to a part of that system, the "+this.getDescription(force1On)+". These two forces are the same so should be marked with the same number of tick marks. (#100)"; this.focusForceNum=[forceNum1,forceNum2]; this.feedbackStyle='color:red'; return false; } else { this.response="Error: Incorrect tick marks.

    The forces highlighted in red are the "+this.forceTypeString(force1.forceType)+" by the "+this.getDescription(force1.by)+" on the "+this.getDescription(force1.on)+" and the "+this.forceTypeString(force2.forceType)+" by the "+this.getDescription(force2.by)+" on the "+this.getDescription(force2.on)+". Notice that both are "+this.forceTypeString(force1.forceType)+" forces. One is attached to the system, the "+this.getDescription(force2On)+", and the other is attached to a part of that system, the "+this.getDescription(force1On)+". These two forces are the same so should be marked with the same number of tick marks. (#101)"; this.focusForceNum=[forceNum1,forceNum2]; this.feedbackStyle='color:red'; return false; } } } } } } return true; } */ checkToSeeIfAnglesAreAttached() { for (var angleNum=0;angleNum. The ends of angles are either marked with a triangle or a circle. A triangle indicates that end of the angle is attached to something. A circle means that end of the angle is not attached to anything. The circle end of the highlighted angle is not attached to anything. Either delete the angle or attach both ends to something. (#102)"; this.focusForceNum=angleNum; this.answer.angleFeedbackFlag=true; this.feedbackStyle='color:red'; return false; } } return true; } isAngleAttachedToTwoAxes(){ for (var angleNum=0;angleNum
    The angle highlighted in red is labeled θ"+angle.subscript+". Both ends of the angle are attached to coordinate axes. Labeling the angle between two axes clutters the diagram. Either attach one end of the angle to something other than a coordinate axis or delete it. (#103)"; this.focusForceNum=angleNum; this.answer.angleFeedbackFlag=true; this.feedbackStyle='color:red'; return false; } } return true; } isAngleAttachedToTwoForces(){ //Checks whether the ends of an angle are both attached to a force. It does not flag an error for a force that lies along an axis. for (var angleNum=0;angleNum
    The angle highlighted in red is labeled θ"+angle.subscript+". The angle of a force should be measured relative to one of the coordinate axes, not relative to another force. Move one of the ends of the highlighted angle so that it is attached to a coordinate axis. (#104)"; this.focusForceNum=angleNum; this.answer.angleFeedbackFlag=true; this.feedbackStyle='color:red'; return false; } } return true; } isAngleForForceOnAxis(){ //Flags any angle that marks a force that lies along an axis. The function assumes that one end of every angle is on attached to an axis. for (var angleNum=0;angleNum. This angle is attached to the acceleration vector of the "+this.getDescription(this.fbdList[fbdNum])+". Because it is zero degrees, it may be diffcuit to see but will appear as a red dot on that acceleration vector. There is no need to label the angle of a force that is directed along one of the axes. Delete the highlighted angle. (#109)"; } else { var force1; for (var forceNum=0;forceNum. This angle is attached to the "+this.forceTypeString(force1.forceType)+" force by the "+this.getDescription(force1.by)+" on the "+this.getDescription(force1.on)+". Because it is zero degrees, it may be diffcuit to see but will appear as a red dot on that force vector. There is no need to label the angle of a force that is directed along one of the axes. Delete the highlighted angle. (#105)"; } this.focusForceNum=angleNum; this.answer.angleFeedbackFlag=true; this.feedbackStyle='color:red'; return false; } else if (Math.abs(angleDiff-90)<.0001){ this.response="Error: The highlighted angle equals 90 degrees.

    The angle highlighted in red is labeled θ"+angle.subscript+". It equals 90 degrees. There is no need to label the angle of a force that is directed along one of the axes. Delete the highlighted angle. (#106)"; this.focusForceNum=angleNum; this.answer.angleFeedbackFlag=true; this.feedbackStyle='color:red'; return false; } else if (Math.abs(angleDiff-180)<.0001){ this.response="Error: The highlighted angle equals 180 degrees.

    The angle highlighted in red is labeled θ"+angle.subscript+". It equals 180 degrees. There is no need to label the angle of a force that is directed along one of the axes. Delete the highlighted angle. (#107)"; this.focusForceNum=angleNum; this.answer.angleFeedbackFlag=true; this.feedbackStyle='color:red'; return false; } else if (angleDiff>90){ this.response="Error: The highlighted angle is obtuse.

    The angle highlighted in red is labeled θ"+angle.subscript+". It is larger than 90 degrees. Soon you will be asked to write the Newton's Second Law equation corresponding to this freebody diagram. This simulation requires that all of the angles in such equations must be acute. Change the endpoints of the highlighted angle so that it is acute, not obtuse. (#108)"; this.focusForceNum=angleNum; this.answer.angleFeedbackFlag=true; this.feedbackStyle='color:red'; return false; } } return true; } determineAnglesOfAllAngles(){ //Determine the angle between the vectors attached to the ends of all angles. for (var angleNum=0;angleNum
    The two angles that are highlighted in red are both labeled θ"+angle1.subscript+" but they are not equal. The same label cannot simultaneously represent two unequal angles. Change one or the other label. (#112)"; this.focusForceNum=[angleNum1,angleNum2]; this.answer.angleFeedbackFlag=true; this.feedbackStyle='color:red'; return false; } } } return true; } flagEqualAnglesWithDifferentLabels(){ for (var angleNum1=0;angleNum1
    The two angles that are highlighted in red are labeled θ"+angle1.subscript+" and θ"+angle2.subscript+". The two angles are equal, but have different labels. Shortly, you will be writing the Newton's Second Law equations for the freebody diagram. The goal will be to write the equations with the fewest number of variables possible so that it is possible to solve them for an unknown. Since the two angles are equal, they should have the same label. Click on one or the other label to change it so that the two angles have the same label. (#110)"; this.focusForceNum=[angleNum1,angleNum2]; this.answer.angleFeedbackFlag=true; this.feedbackStyle='color:red'; return false; } } } return true; } flagAnglesThatAreComplements(){ for (var angleNum1=0;angleNum1
    The two angles that are highlighted in red are labeled θ"+angle1.subscript+" and θ"+angle2.subscript+". One angle is the complement of the other (i.e., the two angles sum to 90 degrees). Labeling the complement of an angle makes the Newton's Second Law equations that you will write shortly needlessly complicated. Redraw one or the other highlighted angles so that the two angles are equal, not complements. (#111)"; this.focusForceNum=[angleNum1,angleNum2]; this.answer.angleFeedbackFlag=true; this.feedbackStyle='color:red'; return false; } } } return true; } flagMissingAngles(){ var returnFlag=true; for (var i=0;i
    The force that is highlighted is the "+this.forceTypeString(force.forceType)+" by the "+this.getDescription(force.by)+" on the "+this.getDescription(force.on)+". Notice that this force does not lie along any of the coordinate axes. Shortly, you will be writing the Newton's Second Law equations for this freebody diagram. When you do, you will write expressions for the x and y components of this force. To do so, you must draw and label the angle of this force. Draw and label the angle of the highlighted force. (#113)"; this.focusForceNum=forceNum; this.feedbackStyle='color:red'; return false; } } for (var fbdNum=0;fbdNum.01 && acc.angleIDs.length==0){ this.response="Error: Missing angle on the highlighted acceleration.

    The acceleration that is highlighted is the acceleration of the "+this.getDescription(this.fbdList[fbdNum])+". Notice that this acceleration does not lie along any of the coordinate axes. Shortly, you will be writing the Newton's Second Law equations for the object. When you do, you will write expressions for the x and y components of this acceleration. To do so, you must draw and label the angle of this acceleration. Draw and label the angle of the highlighted acceleration. (#114)"; this.focusForceNum=fbdNum; this.answer.accelerationFeedbackFlag=true; this.feedbackStyle='color:red'; return false; } } return true; } checkForces() { //console.log("checkForces()"); //this.clearForceFocus(); this.answer.accelerationFeedbackFlag=undefined; this.answer.angleFeedbackFlag=undefined; //console.log(this.feedback); this.feedback.textContent=""; this.forceFlag=[]; for (var forceNum=0;forceNum"+this.shapes[force.by].legend+this.shapes[force.on].legend+""; } else { label="F"+this.getDisplayForceType(force.forceType)+" "+this.shapes[force.by].legend+this.shapes[force.on].legend+"" } return label; } getForceLabel(forceNum){ var force=this.answer.forces[forceNum]; var label; if (force.by==9999 || force.on==9999){return ""} if (this.twoSubscriptLabel){ //console.log("force.by="+force.by+" force.on="+force.on+" this.shapes[force.by].legend="+this.shapes[force.by].legend+" this.shapes[force.on].legend="+this.shapes[force.on].legend); label=this.getDisplayForceType(force.forceType)+"_("+this.shapes[force.by].legend+this.shapes[force.on].legend+")"; } else { label="F_("+this.getDisplayForceType(force.forceType)+" "+this.shapes[force.by].legend+this.shapes[force.on].legend+")"; } return label; } getAccelerationLabel(fbdNum){ var sub=this.answer.accelerations[fbdNum].subscript; var label="a"; if (sub!=""){ label+="_("+sub+")"; } return label; } setFocusFlag() { this.focusFlag=true; //console.log("Inside setFocusFlag(): this.focusFlag="+this.focusFlag); } removeFocusFlag() { this.focusFlag=false; //console.log("Inside removeFocusFlag(): this.focusFlag="+this.focusFlag); } determineQuadrantForEachVector(){ //Determines the quadrant of every force. Possible this.answers: 1,2,3,4,+x,-x,+y,-y. for (var forceNum=0;forceNum180){angleDiff-=360}; if (angleDiff==180){force.quadrant="-x"} else if (angleDiff>90){force.quadrant="3"} else if (angleDiff==90){force.quadrant="-y"} else if (angleDiff>0){force.quadrant="4"} else if (angleDiff==0){force.quadrant="+x"} else if (angleDiff>-90){force.quadrant="1"} else if (angleDiff==-90){force.quadrant="+y"} else if (angleDiff>-180){force.quadrant="2"} //console.log("angleDiff="+angleDiff+" forceAngle="+forceAngle+" xAxisAngle="+xAxisAngle+" quadrant="+force.quadrant); } for (var fbdNum=0;fbdNum180){angleDiff-=360}; if (angleDiff==180){acc.quadrant="-x"} else if (angleDiff>90){acc.quadrant="3"} else if (angleDiff==90){acc.quadrant="-y"} else if (angleDiff>0){acc.quadrant="4"} else if (angleDiff==0){acc.quadrant="+x"} else if (angleDiff>-90){acc.quadrant="1"} else if (angleDiff==-90){acc.quadrant="+y"} else if (angleDiff>-180){acc.quadrant="2"} //console.log("fbdNum="+fbdNum+" angleDiff="+angleDiff+" accAngle="+accAngle+" xAxisAngle="+xAxisAngle+" quadrant="+acc.quadrant); } } getQuadrant(str){ switch (str){ case "+x": return "is directed along the +x axis"; case "-x": return "is directed along the -x axis"; case "+y": return "is directed along the +y axis"; case "-y": return "is directed along the -y axis"; case "1": return "points in the +x, +y direction"; case "2": return "points in the -x, +y direction"; case "3": return "points in the -x, -y direction"; case "4": return "points in the +x, -y direction"; case "0": return "is zero" } return ""; } determineAngleLabelAndAxisForEachVector() { //Each vector should be labeled with an angle if it doesn't lie along an axis. Record the subscript of that angle and the axis the angle is attached to in this.answers.forces. for (var forceNum=0;forceNum>"+angleSubscript+"<<"); if (angleSubscript.trim()==""){ return "theta"; } else { return "theta_"+angleSubscript; } } determineN2Laws(){ //console.log("Running determineN2Laws"); this.determineQuadrantForEachVector(); this.determineAngleLabelAndAxisForEachVector(); for (var fbdNum=0;fbdNumnet x=max. Notice that Newton's Second Law is an equation, not an expression. It includes an equals sign. The answer you entered in the x-direction is an expression, not an equation. (#115)"; return false; } else if (this.n2Answers[fbdNum].y.trim()!="" && !this.n2Answers[fbdNum].y.includes("=")){ this.n2Response[fbdNum]="Error: Missing equals sign in the this.answer for the y-direction.

    Newton's Second Law in the y-direction is Fnet y=may. Notice that Newton's Second Law is an equation, not an expression. It includes an equals sign. The this.answer you entered in the y-direction is an expression, not an equation. (#116)" return false; } return true; } parseN2Laws(fbdNum){ //console.log("fbdNum="+fbdNum); //Parses the x and y N2 Law strings into arrays. var equalPos=this.n2Answers[fbdNum].x.indexOf("="); //console.log("this.n2Answers[fbdNum]={x:"+this.n2Answers[fbdNum].x+", y:"+this.n2Answers[fbdNum].y+"}"); var netForceXObject=this.parseString(this.n2Answers[fbdNum].x.substring(0,equalPos)); var maXObject=this.parseString(this.n2Answers[fbdNum].x.substring(equalPos+1,this.n2Answers[fbdNum].x.length)); //console.log("netForceXObject.terms="+netForceXObject.terms+" maXObject.terms="+maXObject.terms); var equalPos=this.n2Answers[fbdNum].y.indexOf("="); var netForceYObject=this.parseString(this.n2Answers[fbdNum].y.substring(0,equalPos)); var maYObject=this.parseString(this.n2Answers[fbdNum].y.substring(equalPos+1,this.n2Answers[fbdNum].y.length)); //console.log("netForceYObject.terms="+netForceYObject.terms+" maYObject.terms="+maYObject.terms); //console.log("netForceXObject.correctFlag="+netForceXObject.correctFlag+"maXObject.correctFlag="+maXObject.correctFlag); if (!netForceXObject.correctFlag || !maXObject.correctFlag){ this.n2Response[fbdNum]="Error: Mismatched parenthesis in the x-direction equation. (#117)" return false; } if (!netForceYObject.correctFlag || !maYObject.correctFlag){ this.n2Response[fbdNum]="Error: Mismatched parenthesis in the y-direction equation. (#118)" return false; } //console.log("About to formatForcesAndAccelerations"); this.netForceXArray[fbdNum]=this.formatForcesAndAccelerations(fbdNum,netForceXObject.terms); //console.log("fbdNum="+fbdNum+" this.netForceXArray:"); //console.log(this.netForceXArray[fbdNum]); this.aXArray[fbdNum]=this.formatForcesAndAccelerations(fbdNum,maXObject.terms); this.netForceYArray[fbdNum]=this.formatForcesAndAccelerations(fbdNum,netForceYObject.terms); this.aYArray[fbdNum]=this.formatForcesAndAccelerations(fbdNum,maYObject.terms); this.moveForcesAndAccsToCorrectSideOfEquation(fbdNum); this.separateMassforTerms(fbdNum); return true; } consoleCorrectNetForceAndAccArray(fbdNum){ console.log("fbdNum="+fbdNum); console.log("this.correctNetForceX[fbdNum].length="+this.correctNetForceX[fbdNum].length); for (var i=0;iendFactor.length){ //console.log("Error: Break") break; } } //console.log("endFactor="+endFactor+" endArgPos="+endArgPos); term.arg=endFactor.substring(startArgPos,endArgPos); term.factor= factor.substring(0,trigPos)+endFactor.substring(endArgPos+1,endFactor.length); //console.log("factor="+factor+"term.arg="+term.arg+" term.factor="+term.factor); } else { //The arguement isn't in parenthesis term.arg=endFactor; term.factor=factor.substring(0,trigPos); } break; } } if (term.trig==undefined){term.factor=factor} //console.log("term.force="+term.force+" term.factor="+term.factor); return term; } createHTMLTerm(str){ while (str.includes("_")){ var startPos=str.indexOf("_"); var endPos=startPos+2; var parenNum=0; if (str.charAt(startPos+1)=="("){ while(str.charAt(endPos)!=")" || parenNum!=0){ if (str.charAt(endPos)=="("){parenNum++} else if (str.charAt(endPos)==")"){parenNum--} endPos++; if (endPos>25){ console.log("Error: endPos is too large!"); break; } } //console.log("str="+str+" startPos="+startPos+" endPos="+endPos); str=str.substring(0,endPos)+""+str.substring(endPos+1,str.length); str=str.substring(0,startPos)+""+str.substring(startPos+2,str.length); //console.log("str="+str); } else if ("0123456789".includes(str.charAt(startPos+1))) { while ("0123456789".includes(str.charAt(endPos))){ endPos++; if (endPos>25){ console.log("Error: endPos is too large!"); break; } } str=str.substring(0,endPos)+""+str.substring(endPos,str.length); str=str.substring(0,startPos)+""+str.substring(startPos+1,str.length); } else { str=str.substring(0,startPos+2)+""+str.substring(startPos+2,str.length); str=str.substring(0,startPos)+""+str.substring(startPos+1,str.length); } } while (str.includes("theta")){ str=str.replace("theta","θ"); } return str; } moveForcesAndAccsToCorrectSideOfEquation(fbdNum){ var fNetX=this.netForceXArray[fbdNum]; var fNetY=this.netForceYArray[fbdNum]; var aX=this.aXArray[fbdNum]; var aY=this.aYArray[fbdNum]; //console.log("fNetY.length="+fNetY.length+" aY.length="+aY.length); for (var termNum=0;termNum0 && str.charAt(i-1)=="_") || (i>2 && "sin,cos,tan,sec,csc,cot".includes(str.substring(i-3,i)))){ subscriptFlag=true; subscriptNum++; } else { switch(str.charAt(i)){ case "(": endParenthesis=")"; break; case "[": endParenthesis="]"; break; case "{": endParenthesis="}"; break; } var factor=[]; if (startPos!=i){ factor.push(str.substring(startPos,i).trim()); } factor=this.arrayMultiply(factorArray.terms,factor); //console.log("factor="+factor); factorArray=this.parseString(str.substring(i+1,str.length)); //console.log("factorArray.terms="+factorArray.terms+" factorArray.pos="+factorArray.pos); factorArray.terms=this.arrayMultiply(factor,factorArray.terms); //console.log("factorArray.terms="+factorArray.terms); i=factorArray.pos+i; //console.log("str="+str+" str.substring(i,i+3)="+str.substring(i,i+3)); startPos=i+1; } } else if (")]}".includes(str.charAt(i)) && endParenthesis==undefined && i!=str.length-1){ if (subscriptFlag){ subscriptNum--; if (subscriptNum==0){ subscriptFlag=false; } continue; } var factor=[]; if (startPos!=i){ factor.push(str.substring(startPos,i).trim()); } //console.log("factor="+factor); factorArray.terms=this.arrayMultiply(factorArray.terms,factor); //console.log("factorArray.terms="+factorArray.terms); returnArray.terms=returnArray.terms.concat(factorArray.terms); //console.log("returnArray.terms="+returnArray.terms); returnArray.pos=i; return returnArray; } else if (")]}".includes(str.charAt(i)) && endParenthesis!=undefined ){ if (str.charAt(i)==endParenthesis){ if (subscriptFlag){ subscriptNum--; if (subscriptNum==0){ subscriptFlag=false; } continue; } var factor=[]; if (startPos!=i){ factor.push(str.substring(startPos,i).trim()); } //console.log("factor="+factor+" factorArray.terms="+factorArray.terms); factorArray.terms=this.arrayMultiply(factorArray.terms,factor); //console.log("factorArray.terms="+factorArray.terms); if (i==str.length-1){ returnArray.terms=returnArray.terms.concat(factorArray.terms); } //console.log("returnArray.terms="+returnArray.terms); endParenthesis=undefined; startPos=i+1; } else { return {pos:0,terms:[],correctFlag:false}; } } else if ("+-".includes(str.charAt(i)) && endParenthesis==undefined){ if (startFlag){continue} if (subscriptFlag){ if (str.charAt(i)=="+"){ str=str.substring(0,i)+""+str.substring(i+1,str.length); } else { str=str.substring(0,i)+""+str.substring(i+1,str.length); } continue; } var factor=[]; if (startPos!=i){ factor.push(str.substring(startPos,i).trim()); } //console.log("factor="+factor); factorArray.terms=this.arrayMultiply(factorArray.terms,factor); //console.log("factorArray.terms="+factorArray.terms); returnArray.terms=returnArray.terms.concat(factorArray.terms); //console.log("returnArray.terms="+returnArray.terms); startFlag=true; startPos=i; factorArray.terms=[]; } else if (i==str.length-1 && endParenthesis==undefined){ var factor=[]; if (startPos!=i+1){ if (")]}".includes(str.charAt(i)) && !subscriptFlag){ factor.push(str.substring(startPos,i).trim()); } else { factor.push(str.substring(startPos,i+1).trim()); } } //console.log("factor="+factor+" factorArray.terms="+factorArray.terms); factorArray.terms=this.arrayMultiply(factorArray.terms,factor); //console.log("factorArray.terms="+factorArray.terms); returnArray.terms=returnArray.terms.concat(factorArray.terms); //console.log("returnArray.terms="+returnArray.terms); returnArray.pos=str.length-1; } else if (i==str.length-1){ return {pos:0,terms:[],correctFlag:false}; } } for (var i=0;i0 && str.charAt(i-1)=="_"){ numFlag=false; strPart+=str.charAt(i); } else if ("0123456789.".includes(str.charAt(i)) && numFlag){ numFlag=true; numPart+=str.charAt(i); } else if ("0123456789".includes(str.charAt(i))) { numFlag=true; if (numPart!=""){ numPartArray.push(numPart); } numPart=str.charAt(i); } else { numFlag=false; strPart+=str.charAt(i); } } if (numPart!=""){ numPartArray.push(numPart); } var numericFactor=1; for (var i=0;i25"); break } for (var studTermNum=0;studTermNum25"); break } //console.log("counter="+counter); for (var studTermNum=0;studTermNum
    One of the terms in the x-direction equation is "+term.HTMLterm+". The term includes a trigonometric function. However, the force `"+term.force+"` lies along the x-axis. The existence of a trigonometric function would indicate that the vector does not lie on an axis. Delete the trigonometric function. (#124)"; return false; } else { this.n2Response[fbdNum]="Error: Incorrect trigonometric function in the y-direction equation.

    One of the terms in the y-direction equation is "+term.HTMLterm+". The term includes a trigonometric function. However, the force `"+term.force+"` lies along the y-axis. The existence of a trigonometric function would indicate that the vector does not lie on an axis. Delete the trigonometric function. (#125)"; return false; } } else if (correctTerm.trig!=undefined && term.trig==undefined){ if (component==0){ this.n2Response[fbdNum]="Error: Missing trigonometric function in the x-direction equation.

    One of the terms in the x-direction equation is "+term.HTMLterm+". The term does not include a trigonometric function. The absense of a trigonometric function indicates that the force, `"+term.force+"`, must lie on the x-axis. However, this isn't true. `"+term.force+"` does not lie on an axis. Insert the appropriate trigonometric function into this term. (#126)"; return false; } else { this.n2Response[fbdNum]="Error: Missing trigonometric function in the y-direction equation.

    One of the terms in the y-direction equation is "+term.HTMLterm+". The term does not include a trigonometric function. The absense of a trigonometric function indicates that the force, `"+term.force+"`, must lie on the y-axis. However, this isn't true. `"+term.force+"` does not lie on an axis. Insert the appropriate trigonometric function into this term. (#127)"; return false; } } else { //console.log("Made it here"); if (component==0){ this.n2Response[fbdNum]="Error: Incorrect trigonometric function in the x-direction equation.

    One of the terms in the x-direction equation is "+term.HTMLterm+". This term contains the trigonometric function "+this.getFullTrigFunction(term.trig)+". This is not the correct trigonometric function in order to calculate the x-component of `"+term.force+"`. Change the term to include the correct trigonometric function. (#128)"; return false; } else { this.n2Response[fbdNum]="Error: Incorrect trigonometric function in the y-direction equation.

    One of the terms in the y-direction equation is "+term.HTMLterm+". This term contains the trigonometric function "+this.getFullTrigFunction(term.trig)+". This is not the correct trigonometric function in order to calculate the y-component of `"+term.force+"`. Change the term to include the correct trigonometric function. (#129)"; //console.log("Made it here"); return false; } } } } //console.log("Completed force term loop") for (var termNum=0;termNum
    One of the terms in the x-direction equation is "+term.HTMLterm+". The term includes a trigonometric function. However, the acceleration `"+term.acceleration+"` lies along the x-axis. The existence of a trigonometric function would indicate that the vector does not lie on an axis. Delete the trigonometric function. (#130)"; return false; } else { this.n2Response[fbdNum]="Error: Incorrect trigonometric function in the y-direction equation.

    One of the terms in the y-direction equation is "+term.HTMLterm+". The term includes a trigonometric function. However, the acceleration `"+term.acceleration+"` lies along the y-axis. The existence of a trigonometric function would indicate that the vector does not lie on an axis. Delete the trigonometric function. (#131)"; return false; } } else if (correctTerm.trig!=undefined && term.trig==undefined){ if (component==0){ this.n2Response[fbdNum]="Error: Missing trigonometric function in the x-direction equation.

    One of the terms in the x-direction equation is "+term.HTMLterm+". The term does not include a trigonometric function. The absense of a trigonometric function indicates that the acceleration, `"+term.acceleration+"`, must lie on the x-axis. However, this isn't true. `"+term.acceleration+"` does not lie on an axis. Insert the appropriate trigonometric function into this term. (#132)"; return false; } else { this.n2Response[fbdNum]="Error: Missing trigonometric function in the y-direction equation.

    One of the terms in the y-direction equation is "+term.HTMLterm+". The term does not include a trigonometric function. The absense of a trigonometric function indicates that the acceleration, `"+term.acceleration+"`, must lie on the y-axis. However, this isn't true. `"+term.acceleration+"` does not lie on an axis. Insert the appropriate trigonometric function into this term. (#133)"; return false; } } else { if (component==0){ this.n2Response[fbdNum]="Error: Incorrect trigonometric function in the x-direction equation.

    One of the terms in the x-direction equation is "+term.HTMLterm+". This term contains the trigonometric function "+this.getFullTrigFunction(term.trig)+". This is not the correct trigonometric function in order to calculate the x-component of `"+term.acceleration+"`. Change the term to include the correct trigonometric function. (#134)"; return false; } else { this.n2Response[fbdNum]="Error: Incorrect trigonometric function in the y-direction equation.

    One of the terms in the y-direction equation is "+term.HTMLterm+". This term contains the trigonometric function "+this.getFullTrigFunction(term.trig)+". This is not the correct trigonometric function in order to calculate the y-component of `"+term.acceleration+"`. Change the term to include the correct trigonometric function. (#135)"; return false; } } } } return true; } getFullTrigFunction(str){ switch(str){ case "cos": return "cosine"; case "sin": return "sine"; case "tan": return "tangent"; case "sec": return "secant"; case "csc": return "cosecant"; case "cot": return "cotangent"; } } checkForCorrectAngleInTrig(fbdNum,component){ var netForceArray; var accArray; var correctNetForce; var correctAcc; if (component==0){ netForceArray=this.netForceXArray[fbdNum]; accArray=this.aXArray[fbdNum]; correctNetForce=this.correctNetForceX[fbdNum]; correctAcc=this.correctAX[fbdNum]; } else { netForceArray=this.netForceYArray[fbdNum]; accArray=this.aYArray[fbdNum]; correctNetForce=this.correctNetForceY[fbdNum]; correctAcc=this.correctAY[fbdNum]; } for (var termNum=0;termNum
    One of the terms in the x-direction equation is "+term.HTMLterm+". This term is missing the mass, `"+correctTerm.mass+"`. Newton's Second Law states `vec(F)_text(net) = mvec(a)`, not `vec(F)_text(net) = vec(a)`. Insert the mass into your equation. (#186)" return false; } else { this.n2Response[fbdNum]="Error: Missing mass in the y-direction equation.

    One of the terms in the y-direction equation is "+term.HTMLterm+". This term is missing the mass, `"+correctTerm.mass+"`. Newton's Second Law states `vec(F)_text(net) = mvec(a)`, not `vec(F)_text(net) = vec(a)`. Insert the mass into your equation. (#187)" return false; } } else { if (component==0){ this.n2Response[fbdNum]="Error: Incorrect mass in the x-direction equation.

    One of the terms in the x-direction equation is "+term.HTMLterm+". The mass of this term is "+term.mass+". However, it is supposed to be "+correctTerm.mass+". Fix the mass in this term. (#188)"; return false; } else { this.n2Response[fbdNum]="Error: Incorrect mass in the y-direction equation.

    One of the terms in the y-direction equation is "+term.HTMLterm+". The mass of this term is "+term.mass+". However, it is supposed to be "+correctTerm.mass+". Fix the mass in this term. (#188)"; return false; } } } } return true; } checkForSigns(fbdNum,component){ var netForceArray; var accArray; var correctNetForce; var correctAcc; if (component==0){ netForceArray=this.netForceXArray[fbdNum]; accArray=this.aXArray[fbdNum]; correctNetForce=this.correctNetForceX[fbdNum]; correctAcc=this.correctAX[fbdNum]; } else { netForceArray=this.netForceYArray[fbdNum]; accArray=this.aYArray[fbdNum]; correctNetForce=this.correctNetForceY[fbdNum]; correctAcc=this.correctAY[fbdNum]; } for (var termNum=0;termNum
    The term in the x-direction equation, "+term.HTMLterm+", appears twice. There is no need to duplicate a term. Remove the duplicate. (#157)"; return false; } else { this.n2Response[fbdNum]="Error: Duplicate term in the y-equation.

    The term in the y-direction equation, "+term.HTMLterm+", appears twice. There is no need to duplicate a term. Remove the duplicate. (#158)"; return false; } } else if (term.force==term1.force){ if (component==0){ this.n2Response[fbdNum]="Error: Extra term in the x-equation.

    One of the terms in the x-direction equation is "+term.HTMLterm+". This equation includes the force `"+term.force+"`. However, the x-component of this force is already described by the term "+term1.HTMLterm+". There should only be one term for each force. Remove the term "+term.HTMLterm+". (#159)"; return false; } else { this.n2Response[fbdNum]="Error: Extra term in the y-equation.

    One of the terms in the y-direction equation is "+term.HTMLterm+". This equation includes the force `"+term.force+"`. However, the y-component of this force is already described by the term "+term1.HTMLterm+". There should only be one term for each force. Remove the term "+term.HTMLterm+". (#160)"; return false; } } } //console.log("component="+component); if (component==0){ this.n2Response[fbdNum]="Error: Extra term in the x-equation.

    One of the terms in the x-direction equation is "+term.HTMLterm+". This term doesn't match any of the terms in the correct solution. Either edit or remove it. (#173)"; return false; } else { this.n2Response[fbdNum]="Error: Extra term in the y-equation.

    One of the terms in the y-direction equation is "+term.HTMLterm+". This term doesn't match any of the terms in the correct solution. Either edit or remove it. (#174)"; } } } for (var termNum=0;termNum
    The term in the x-direction equation, "+term.HTMLterm+", appears twice. There is no need to duplicate a term. Remove the duplicate. (#161)"; return false; } else { this.n2Response[fbdNum]="Error: Duplicate term in the y-equation.

    The term in the y-direction equation, "+term.HTMLterm+", appears twice. There is no need to duplicate a term. Remove the duplicate. (#162)"; return false; } } else if (term.acceleration==term1.acceleration && term.mass==term1.mass){ if (component==0){ this.n2Response[fbdNum]="Error: Extra term in the x-equation.

    One of the terms in the x-direction equation is "+term.HTMLterm+". This equation includes the acceleration `"+term.acceleration+"` and the mass `"+term.mass+"`. However, the x-component of this mass and acceleration is already described by the term "+term1.HTMLterm+". There should only be one term for each mass and acceleration. Remove the term "+term.HTMLterm+". (#163)"; return false; } else { this.n2Response[fbdNum]="Error: Extra term in the y-equation.

    One of the terms in the y-direction equation is "+term.HTMLterm+". This equation includes the acceleration `"+term.acceleration+"` and the mass `"+term.mass+"`. However, the y-component of this mass and acceleration is already described by the term "+term1.HTMLterm+". There should only be one term for each mass and acceleration. Remove the term "+term.HTMLterm+". (#164)"; return false; } } else if (term.mass==undefined){ for (var fbdNum1=0;fbdNum125){ console.log("Error: endPos is too large!"); break; } } mass=term.factor.substring(pos,endPos+1); } else { mass=term.factor.substring(pos,pos+3); } } else { mass=term.factor.charAt(pos); } if (component==0){ this.n2Response[fbdNum]="Error: Incorrect mass in one of the x-direction equation terms.

    One of the terms in the x-direction equation is "+term.HTMLterm+". This term contains the mass `"+mass+"`. However, the mass of the "+this.getDescription(this.fbdList[fbdNum])+" is `"+this.shapes[this.fbdList[fbdNum]].mass+"`, not `"+mass+"`. Edit the mass in this term. (#167)"; return false; } else { //console.log("term.HTMLterm="+term.HTMLterm); this.n2Response[fbdNum]="Error: Incorrect mass in one of the y-direction equation terms.

    One of the terms in the y-direction equation is "+term.HTMLterm+". This term contains the mass `"+mass+"`. However, the mass of the "+this.getDescription(this.fbdList[fbdNum])+" is `"+this.shapes[this.fbdList[fbdNum]].mass+"`, not `"+mass+"`. Edit the mass in this term. (#168)"; return false; } } else { if (component==0){ this.n2Response[fbdNum]="Error: Missing mass in one of the x-direction equation terms.

    One of the terms in the x-direction equation is "+term.HTMLterm+". This term does not seem to contain a mass. Newton's Second Law is Fnet x=max, not Fnet x=ax. (#169)"; return false; } else { this.n2Response[fbdNum]="Error: Missing mass in one of the y-direction equation terms.

    One of the terms in the y-direction equation is "+term.HTMLterm+". This term does not seem to contain a mass. Newton's Second Law is Fnet y=may, not Fnet y=ay. (#170)"; return false; } } } else { if (component==0){ this.n2Response[fbdNum]="Error: Extra term in the x-equation.

    One of the terms in the x-direction equation is "+term.HTMLterm+". This term doesn't match any of the terms in the correct solution. Either edit or remove it. (#171)"; return false; } else { this.n2Response[fbdNum]="Error: Extra term in the y-equation.

    One of the terms in the y-direction equation is "+term.HTMLterm+". This term doesn't match any of the terms in the correct solution. Either edit or remove it. (#172)"; } } } } } return true; } checkForMissingTerms(fbdNum,component){ //console.log("Running checkForMissingTerms: fbdNum="+fbdNum+" component="+component); var qn0="qn"+(this.questionNum*1000+this.questionN2PartNum+2*fbdNum+component); if (document.getElementById(qn0).value.trim()==""){ return true; }; var netForceArray; var accArray; var correctNetForce; var correctAcc; if (component==0){ netForceArray=this.netForceXArray[fbdNum]; accArray=this.aXArray[fbdNum]; correctNetForce=this.correctNetForceX[fbdNum]; correctAcc=this.correctAX[fbdNum]; } else { netForceArray=this.netForceYArray[fbdNum]; accArray=this.aYArray[fbdNum]; correctNetForce=this.correctNetForceY[fbdNum]; correctAcc=this.correctAY[fbdNum]; } for (var termNum=0;termNum
    The "+this.getDescription(this.fbdList[fbdNum])+" has a mass `"+this.shapes[this.fbdList[fbdNum]].mass+"` and an acceleration `"+correctTerm.acceleration+"`. However, the y-direction equation is missing the term with mass `"+correctTerm.mass+"` and acceleration `"+correctTerm.acceleration+"`. Add this term to the y-direction equation. (#179)"; return false; } } } return true; } compileVariableList(){ var variableList=""; //List all of the masses for (var fbdNum=0;fbdNum