Numbas.addExtension('eukleides',['math','jme','jme-display'], function(extension) { var euk = window.eukleides; var math = Numbas.math; var jme = Numbas.jme; var types = extension.types = {}; /** Wrapper to convert Numbas vector (list of numbers) to Eukleides Vector object */ function vec(vector) { return new euk.Vector(vector[0],vector[1]); } /** Wrapper to convert Eukleides Vector object to Numbas vector (list of numbers) */ function unvec(vector) { return [vector.x,vector.y]; } function registerType(constructor,name,casts,display) { jme.registerType(constructor,name,casts); if(display) { jme.display.registerType(constructor,display); } else { console.error("no display code for "+name); } types[name] = constructor; } var TAngle = function(angle) { this.value = angle; }; registerType( TAngle, 'eukleides_angle', { string: function(v) { return new TString(math.niceNumber(math.precround(math.mod(math.degrees(v.value),360),2))+'°'); } }, { tex: function(thing,tok,texArgs) { return this.number(tok.value)+'°'; }, jme: function(tree,tok,bits) { var deg = math.degrees(tok.value); if(Numbas.util.isInt(deg)) { return 'deg('+this.number(deg)+')'; } else { return 'rad('+this.number(tok.value)+')'; } }, displayString: function(a) { return math.niceNumber(math.precround(math.mod(math.degrees(a.value),360),2))+'°'.toString(); } } ); var TPoint = function(point) { this.value = point; }; registerType( TPoint, 'eukleides_point', {}, { tex: function(thing,tok,texArgs) { return '\\left( '+this.number(tok.value.x)+', '+this.number(tok.value.y, settings)+' \\right)'; }, jme: function(tree,tok,bits) { return 'point('+this.number(tok.value.x)+', '+this.number(tok.value.y)+')'; }, displayString: function(p) { return '('+math.niceNumber(p.value.x)+','+math.niceNumber(p.value.y)+')'; } } ); var TLine = function(line) { this.value = line; }; registerType( TLine, 'eukleides_line', {}, { jme: function(tree,tok,bits) { return 'line(point('+this.number(tok.value.x)+','+this.number(tok.value.y)+'),rad('+this.number(tok.value.a)+'))'; } } ); var TPointSet = function(point_set) { this.value = point_set; }; registerType( TPointSet, 'eukleides_point_set', { 'list': function(s) { return new TList(s.value.points.map(function(p){return new TPoint(p)})); } }, { tex: function(thing,tok,texArgs) { return tok.value.points.map(function(p) { return Numbas.jme.display.texify({tok:new TPoint(p)}) }).join(' \\ldots '); }, jme: function(tree,tok,bits) { return tok.value.points.map(function(p) { return Numbas.jme.display.treeToJME({tok: new TPoint(p)}) }).join(' .. '); }, displayString: function(l) { return tok.value.points.map(function(p) { return Numbas.jme.tokenToDisplayString(new TPoint(p)) }).join(' … '); } } ); var TCircle = function(circle) { this.value = circle; }; registerType( TCircle, 'eukleides_circle', {}, { jme: function(tree,tok,bits) { return 'circle(point('+this.number(tok.value.x)+','+this.number(tok.value.y)+'),'+this.number(tok.value.r)+')'; } } ); var TConic = function(conic) { this.value = conic; }; registerType( TConic, 'eukleides_conic', {}, { jme: function(tree,tok,bits) { var foci = tok.value.foci().map(function(p){ return Numbas.jme.display.treeToJME({tok:new TPoint(p)}) }); return 'conic('+foci[0]+','+foci[1]+','+this.number(tok.value.a)+')'; } } ); var TDrawing = function(objects,style) { this.value = { objects: objects || [], style: style || {} }; }; registerType( TDrawing, 'eukleides_drawing', {}, {} ); var TAngleLabel = function(a,b,c) { this.a = a; this.b = b; this.c = c; }; registerType( TAngleLabel, 'eukleides_angle_label', {}, { jme: function(tree,tok,bits) { var points = [tok.a,tok.b,tok.c].map(function(p){ return Numbas.jme.display.treeToJME({tok: new TPoint(p)}) }); return 'angle('+points.join(',')+')'; }, } ); function drawing_visitor(fn) { function visit(drawer,drawing,ctx) { drawer.push_local_settings(); Object.entries(drawing.style).forEach(function(d) { if(d[1]!==undefined) { drawer.local[d[0]] = d[1]; } }); drawing.objects.forEach(function(obj) { fn(drawer,obj,ctx); switch(obj.type) { case 'eukleides_drawing': visit(drawer,obj.value,ctx); break; case 'list': visit(drawer, {objects:obj.value, style:{}},ctx); break; default: } }); drawer.pop_local_settings(); } return visit; } var get_point_labels = drawing_visitor(function(drawer,obj) { switch(obj.type) { case 'eukleides_point': if(drawer.local.label) { drawer.add_point_label(obj.value); } break; } }); var draw_drawing = drawing_visitor(function(drawer,obj,ctx) { switch(obj.type) { case 'eukleides_point': if(drawer.local.label) { drawer.label_point(obj.value); } else { var point = drawer.draw_point(obj.value); if(ctx && drawer.local.draggable) { ctx.make_draggable(point, drawer.local.interactive_vars); } } break; case 'eukleides_point_set': if(drawer.local.label) { drawer.label_segment(obj.value.points[0],obj.value.points[1]); } else if(drawer.local.fill) { drawer.fill_polygon(obj.value); } else { drawer.draw_polygon(obj.value); } break; case 'eukleides_line': drawer.draw_line(obj.value); break; case 'eukleides_circle': if(drawer.local.fill) { if(obj.from!==undefined) { drawer.fill_arc(obj.value,obj.from,obj.to); } else { drawer.fill_circle(obj.value); } } else { if(obj.from!==undefined) { drawer.draw_arc(obj.value,obj.from,obj.to) } else { drawer.draw_circle(obj.value); } } break; case 'eukleides_conic': if(obj.from!==undefined) { drawer.draw_conic_arc(obj.value,obj.from,obj.to) } else { drawer.draw_conic(obj.value); } break; case 'eukleides_angle_label': drawer.label_angle(obj.a,obj.b,obj.c); break; case 'eukleides_drawing': case 'list': break; default: throw(new Numbas.Error('Eukleides trying to draw unknown object type: '+obj.type)); } }); var translate_types = { 'eukleides_point': function(p,u) { return new TPoint(p.value.translate(u)); }, 'eukleides_line': function(line,u) { return new TLine(line.value.translate(u)); }, 'eukleides_point_set': function(set,u) { return new TPointSet(set.value.translate(u)); }, 'eukleides_circle': function(circle,u) { var c2 = new TCircle(circle.value.translate(u)); c2.from = circle.from; c2.to = circle.to; return c2; }, 'eukleides_conic': function(conic,u) { return new TConic(conic.value.translate(u)); }, 'eukleides_drawing': function(drawing,u) { return new TDrawing(drawing.value.objects.map(function(x){return translate_object(x,u)}),drawing.value.style); }, 'eukleides_angle_label': function(l,u) { return new TAngleLabel(l.a.translate(u), l.b.translate(u), l.c.translate(u)); }, 'list': function(list,u) { return new TList(list.value.map(function(x){return translate_object(x,u)})); } }; function translate_object(x,v) { return translate_types[x.type](x,v); } var funcObj = Numbas.jme.funcObj; var TString = Numbas.jme.types.TString; var TNum = Numbas.jme.types.TNum; var TInt = Numbas.jme.types.TInt; var TList = Numbas.jme.types.TList; var TDict = Numbas.jme.types.TDict; var TBool = Numbas.jme.types.TBool; var TVector = Numbas.jme.types.TVector; var TRange = Numbas.jme.types.TRange; var THTML = Numbas.jme.types.THTML; var sig = Numbas.jme.signature; function named(s,name) { s.param_name = name; return s; } function spoint(name) { var s = sig.type('eukleides_point'); s.param_name = name; return s; } function sangle(name) { var s = sig.type('eukleides_angle'); s.param_name = name; return s; } function snum(name) { var s = sig.type('number'); s.param_name = name; return s; } function snumorangle(name_num,name_angle) { return sig.optional(sig.or(snum(name_num),sangle(name_angle))); } var sig_eukleides = sig.or.apply(sig,['eukleides_angle','eukleides_point','eukleides_point_set','eukleides_line','eukleides_circle','eukleides_conic','eukleides_angle_label','eukleides_angle','eukleides_drawing'].map(sig.type)); function sig_drawing_of(sig) { var f = function(args) { if(args.length==0) { return false; } var d = args[0]; if(d.type!='eukleides_drawing') { return false; } var items = sig(d.value.objects); if(items===false || items.length != d.value.objects.length) { return false; } else { return [{type:'eukleides_drawing', items: items}]; } } f.kind = 'eukleides_drawing'; f.signature = sig; return f; } extension.scope.setVariable('origin',new TPoint(new euk.Point(0,0))); extension.scope.addFunction(new funcObj('degrees',[TAngle],TNum,function(v){return math.degrees(v)}, {description: 'Convert an angle to a number of degrees.'})); var sig_translatable = sig.or.apply(sig,Object.keys(translate_types).map(sig.type)); extension.scope.addFunction(new funcObj('+',[sig_translatable,TVector],'?',null,{ evaluate: function(args,scope) { var x = args[0]; var v = vec(args[1].value); return translate_object(x,v); }, description: 'Translate an object or list of objects by the given vector.' })); extension.scope.addFunction(new funcObj('-',[sig_translatable,TVector],'?',null,{ evaluate: function(args,scope) { var x = args[0]; var v = vec(Numbas.vectormath.negate(args[1].value)); return translate_object(x,v); }, description: 'Translate an object or list of objects by the opposite of the given vector.' })); extension.scope.addFunction(new funcObj('sin',[TAngle],TNum,math.sin), {description: 'Sine'}); extension.scope.addFunction(new funcObj('cos',[TAngle],TNum,math.cos), {description: 'Cosine'}); extension.scope.addFunction(new funcObj('tan',[TAngle],TNum,math.tan), {description: 'Tangent'}); extension.scope.addFunction(new funcObj('cosec',[TAngle],TNum,math.cosec), {description: 'Cosecant'}); extension.scope.addFunction(new funcObj('sec',[TAngle],TNum,math.sec), {description: 'Secant'}); extension.scope.addFunction(new funcObj('cot',[TAngle],TNum,math.cot), {description: 'Tangent'}); extension.scope.addFunction(new funcObj('+',[TAngle,TAngle],TAngle,math.add, {description: 'Add two angles.'})); extension.scope.addFunction(new funcObj('-u',[TAngle],TAngle,math.negate, {description: 'Flip the direction of the given angle.'})); extension.scope.addFunction(new funcObj('-',[TAngle,TAngle],TAngle,math.sub, {description: 'Subtract two angles.'})); extension.scope.addFunction(new funcObj('*',[TNum,TAngle],TAngle,math.mul, {description: 'Multiply an angle by the given scale factor.'})); extension.scope.addFunction(new funcObj('*',[TAngle,TNum],TAngle,math.mul, {description: 'Multiply an angle by the given scale factor.'})); extension.scope.addFunction(new funcObj('/',[TAngle,TNum],TAngle,math.div, {description: 'Divide an angle by the given scale factor.'})); extension.scope.addFunction(new funcObj('deg',[TNum],TAngle,function(degrees) { var rad = math.radians(degrees); return rad; },{description: 'Construct an angle in degrees.'})); extension.scope.addFunction(new funcObj('rad',[TNum],TAngle,function(radians) { return radians; },{description: 'Construct an angle in radians.'})); extension.scope.addFunction(new funcObj('point',[TNum,TNum],TPoint,function(x,y) { return new euk.Point(x,y); },{description: 'A point at the given coordinates.'})); extension.scope.addFunction(new funcObj('point',[TNum,TAngle],TPoint,function(r,a) { return euk.Point.create_polar(r,a); },{description: 'A point at the given polar coordinates.'})); extension.scope.addFunction(new funcObj('point',[TPointSet,TNum],TPoint,function(set,t) { return euk.Point.create_point_on_segment(set,t); },{description: 'A point along the first edge of the given polygon.'})); extension.scope.addFunction(new funcObj('point',[TLine,TNum],TPoint,function(line,t) { return euk.Point.create_point_on_line(line,t); },{description:'A point on the given line, the given distance away from its origin.'})); extension.scope.addFunction(new funcObj('point_with_abscissa',[TLine,TNum],TPoint,function(line,x) { return euk.Point.create_point_with_abscissa(line,x); },{description:'A point on the given line with the given abscissa, with respect to the implicit coordinate system.'})); extension.scope.addFunction(new funcObj('point_with_ordinate',[TLine,TNum],TPoint,function(line,y) { return euk.Point.create_point_with_ordinate(line,y); },{description:'A point on the given line with the given ordinate, with respect to the implicit coordinate system.'})); extension.scope.addFunction(new funcObj('point',[TCircle,TAngle],TPoint,function(circle,a) { return euk.Point.create_point_on_circle(circle,a); },{description:'A point on the given circle at the given angle.'})); extension.scope.addFunction(new funcObj('list',[TPointSet],TList,function(ps){ return ps.points.map(function(p){return new TPoint(p)}); },{description: 'Convert a set of points to a list of points.'})); extension.scope.addFunction(new funcObj('midpoint',[TPointSet],TPoint,function(set) { return euk.Point.create_midpoint(set); },{description:'The midpoint of the given segment.'})); extension.scope.addFunction(new funcObj('barycenter',[TPointSet,TList],TPoint,function(points,weights) { return euk.Point.create_barycenter(points,weights); },{unwrapValues: true, description:'The barycenter of the given polygon.'})); extension.scope.addFunction(new funcObj('orthocenter',[TPoint,TPoint,TPoint],TPoint,function(A,B,C) { return euk.Point.create_orthocenter(A,B,C); },{description:'The orthocenter of the given triangle.'})); extension.scope.addFunction(new funcObj('reflect',[TPoint,TLine],TPoint,function(p,l) { return p.reflect(l); },{description:'Reflect a point in a line.'})); extension.scope.addFunction(new funcObj('symmetric',[TPoint,TPoint],TPoint,function(p,origin) { return p.symmetric(origin); },{description:'180° rotation of the first point around the second.'})); extension.scope.addFunction(new funcObj('rotate',[TPoint,TPoint,TAngle],TPoint,function(p,origin,angle) { return p.rotate(origin,angle); },{description:'Rotate the first point the given angle around the second.'})); extension.scope.addFunction(new funcObj('distance',[TPoint,TPoint],TNum,function(a,b) { return a.distance(b); },{description:'Distance between two points.'})); extension.scope.addFunction(new funcObj('homothetic',[TPoint,TPoint,TNum],TPoint,function(p,origin,k) { return p.homothetic(origin,k); },{description:'Homothecy (reduction or dilation) of the first point with respect to the second, and the given scale.'})); extension.scope.addFunction(new funcObj('x',[TPoint],TNum,function(p) { return p.abscissa(); },{description:'x coordinate of a point.'})); extension.scope.addFunction(new funcObj('y',[TPoint],TNum,function(p) { return p.ordinate(); },{description:'y coordinate of a point.'})); extension.scope.addFunction(new funcObj('-',[TPoint,TPoint],TVector,function(a,b) { return unvec(euk.Vector.create_from_points(b,a)); },{description:'Vector from the second point\'s position to the first\'s.'})); extension.scope.addFunction(new funcObj('vector',[TPointSet],TVector,function(set) { return unvec(euk.Vector.create_from_segment(set)); },{description:'Vector from the first point of the polygon to the second.'})); extension.scope.addFunction(new funcObj('vector',[TLine],TVector,function(line) { return unvec(euk.Vector.create_from_line(line)); },{description:'Unit vector in the direction of the given line.'})); extension.scope.addFunction(new funcObj('rotate',[TVector,TAngle],TVector,function(v,a) { return unvec(vec(c).rotate(a)); },{description:'Rotate a vector by the given angle.'})); extension.scope.addFunction(new funcObj('argument',[TVector],TAngle,function(v) { return vec(v).argument(); },{description:'Direction of the given vector.'})); extension.scope.addFunction(new funcObj('angle_between',[TVector,TVector],TAngle,function(u,v) { return euk.Vector.angle_between(vec(u),vec(v)); },{description:'Angle between two vectors.'})); extension.scope.addFunction(new funcObj('line',[TPoint,TAngle],TLine,function(origin,angle) { return new euk.Line(origin.x,origin.y,angle); },{description:'A line with the given origin and direction.'})); extension.scope.addFunction(new funcObj('line',[TPoint,TPoint],TLine,function(A,B) { return euk.Line.create_with_points(A,B); },{description:'A line containing the two given points.'})); extension.scope.addFunction(new funcObj('line',[TPoint,TVector],TLine,function(origin,u) { return euk.Line.create_with_vector(origin,vec(u)); },{description:'A line with the given origin and direction vector.'})); extension.scope.addFunction(new funcObj('line',[TPointSet],TLine,function(set) { return euk.Line.create_with_segment(set); },{description:'A line containing the given segment.'})); extension.scope.addFunction(new funcObj('parallel',[TLine,TPoint],TLine,function(line,p) { return line.parallel(p); },{description:'A line parallel to the given line and containing the given point.'})); extension.scope.addFunction(new funcObj('parallel',[TPointSet,TPoint],TLine,function(set,p) { return euk.Line.create_parallel_to_segment(set,p); },{description:'A line parallel to the given segment and containing the given point.'})); extension.scope.addFunction(new funcObj('perpendicular',[TLine,TPoint],TLine,function(line,p) { return line.perpendicular(p); },{description:'A line perpendicular to the given line and containing the given point.'})); extension.scope.addFunction(new funcObj('bisector',[TPoint,TPoint,TPoint],TLine,function(A,B,C) { return euk.Line.create_angle_bisector(A,B,C); },{description:'The bisector of the angle formed by the given points, and containing the second.'})); extension.scope.addFunction(new funcObj('bisector',[TLine,TLine],TLine,function(l1,l2) { return euk.Line.create_lines_bisector(l1,l2); },{description:'The bisector of the two given lines.'})); extension.scope.addFunction(new funcObj('altitude',[TPoint,TPoint,TPoint],TLine,function(A,B,C) { return euk.Line.create_altitude(A,B,C); },{description:'The line containing the first point and perpendicular to the segment between the second and third.'})); extension.scope.addFunction(new funcObj('median',[TPoint,TPoint,TPoint],TLine,function(A,B,C) { return euk.Line.create_median(A,B,C); },{description:'The line containing the first point and passing through the midpoint of the segment between the second and third.'})); extension.scope.addFunction(new funcObj('reflect',[TLine,TPoint],TLine,function(line,p) { return line.reflect(p); },{description:'Reflect a line in a point.'})); extension.scope.addFunction(new funcObj('symmetric',[TLine,TPoint],TLine,function(line,p) { return line.symmetric(p); },{description:'180° degree rotation of a line around the given point.'})); extension.scope.addFunction(new funcObj('rotate',[TLine,TPoint,TAngle],TLine,function(line,origin,angle) { return line.rotate(origin,angle); },{description:'Rotate a line by the given angle around the given point.'})); extension.scope.addFunction(new funcObj('homothetic',[TLine,TPoint,TNum],TLine,function(line,origin,k) { return line.homothetic(origin,k); },{description:'Homothecy (reduction or dilation) of a line with respect to the given point and scale factor.'})); extension.scope.addFunction(new funcObj('argument',[TLine],TAngle,function(line) { return line.argument(); },{description:'Direction angle of the given line.'})); extension.scope.addFunction(new funcObj('distance',[TLine,TPoint],TNum,function(l,p) { return euk.point_line_distance(p,l); },{description:'Distance between the given line and point.'})); extension.scope.addFunction(new funcObj('distance',[TPoint,TLine],TNum,function(p,l) { return euk.point_line_distance(p,l); },{description:'Distance between the given point and line.'})); extension.scope.addFunction(new funcObj('..',[TPoint,TPoint],TPointSet,function(a,b) { return new euk.Set([a,b]); },{description:'A segment between two points.'})); extension.scope.addFunction(new funcObj('..',[TPointSet,TPoint],TPointSet,function(set,p) { return set.add_tail_point(p); },{description:'Add a point to the end of a polygon.'})); extension.scope.addFunction(new funcObj('..',[TPoint,TPointSet],TPointSet,function(p,set) { return set.add_head_point(p); },{description:'Add a point to the start of a polygon.'})); extension.scope.addFunction(new funcObj('polygon',[sig.listof(sig.type('eukleides_point'))],TPointSet,function(points) { return new TPointSet(new euk.Set(points)); },{unwrapValues: true, description:'Construct a polygon from the given list of points.'})) extension.scope.addFunction(new funcObj('polygon',[TNum,TPoint,TNum,TAngle],TPointSet,function(n,origin,r,a) { return euk.Set.create_polygon(n,origin,r,a); },{description:'A regular polygon with the given number of sides and circumradius, with center at the given point and rotated by the given angle.'})); extension.scope.addFunction(new funcObj('segment',[TPointSet,TPoint],TPointSet,function(set,p) { return set.segment(p); },{description:'A segment from the first point of the given polygon to the given point.'})); extension.scope.addFunction(new funcObj('..',[TPointSet,TPointSet],TPointSet,function(a,b) { return a.concatenate(b); },{description:'Concatenate two polygons.'})); extension.scope.addFunction(new funcObj('..',[named(sig.type('eukleides_point'),'p1'),named(sig_drawing_of(sig.type('eukleides_point')),'p2')],TDrawing,null,{ evaluate: function(args,scope) { var p1 = args[0].value; var d = args[1].value; var p2 = d.objects[0].value; return new TDrawing([new TPointSet(new euk.Set([p1,p2]))],d.style); }, description: 'A segment between two points.' })); extension.scope.addFunction(new funcObj('..',[named(sig.type('eukleides_point_set'),'set'),named(sig_drawing_of(sig.type('eukleides_point')),'p')],TDrawing,null,{ evaluate: function(args,scope) { var s = args[0].value; var d = args[1].value; var p = d.objects[0].value; return new TDrawing([new TPointSet(s.add_tail_point(p))],d.style); }, description: 'Add a point to the end of a polygon.' })); extension.scope.addFunction(new funcObj('..',[named(sig.type('eukleides_point'),'p'),named(sig_drawing_of(sig.type('eukleides_point_set')),'set')],TDrawing,null,{ evaluate: function(args,scope) { var p = args[0].value; var d = args[1].value; var s = d.objects[0].value; return new TDrawing([new TPointSet(s.add_head_point(p))],d.style); }, description: 'Add a point to the start of a polygon.' })); extension.scope.addFunction(new funcObj('reflect',[TPointSet,TLine],TPointSet,function(set,line) { return set.reflect(line); },{description:'Reflect a polygon in the given point.'})); extension.scope.addFunction(new funcObj('symmetric',[TPointSet,TPoint],TPointSet,function(set,p) { return set.symmetric(p); },{description:'180° degree rotation of the given polygon around the given point.'})); extension.scope.addFunction(new funcObj('rotate',[TPointSet,TPoint,TAngle],TPointSet,function(set,origin,a) { return set.rotate(origin,a); },{description:'Rotation of a polygon by the given angle around the given point.'})); extension.scope.addFunction(new funcObj('cardinality',[TPointSet],TNum,function(set) { return set.cardinal(); },{description:'Number of vertices in the given polygon.'})); extension.scope.addFunction(new funcObj('perimeter',[TPointSet],TNum,function(set) { return set.path_length(); },{description:'Total length of the given polygon\'s edges.'})); extension.scope.addFunction(new funcObj('area',[TPointSet],TNum,function(set) { return set.area(); },{description:'Area of the given polygon.'})); extension.scope.addFunction(new funcObj('perpendicular',[TPointSet,TPoint],TLine,function(set,p) { return set.perpendicular_to_segment(p); },{description:'A line perpendicular to the given segment and containing the given point.'})); extension.scope.addFunction(new funcObj('perpendicular_bisector',[TPointSet],TLine,function(set) { return set.perpendicular_bisector(); },{description:'The perpendicular bisector of the given segment.'})); extension.scope.addFunction(new funcObj('center',[TPointSet],TPoint,function(set) { return set.isobarycenter(); },{description:'The isobarycenter (centre of gravity) of the given polygon.'})); extension.scope.addFunction(new funcObj('isobarycenter',[TPointSet],TPoint,function(set) { return set.isobarycenter(); },{description:'The isobarycenter (centre of gravity) of the given polygon.'})); extension.scope.addFunction(new funcObj('circle',[TPoint,TNum],TCircle,function(center,r) { return new euk.Circle(center,r); },{description:'A circle centered at the given point and with the given radius.'})); extension.scope.addFunction(new funcObj('circle',[TPointSet],TCircle,function(set) { return euk.Circle.create_circle_with_diameter(set); },{description:'The circle with the given segment as a diameter.'})); extension.scope.addFunction(new funcObj('circle',[TPoint,TPoint,TPoint],TCircle,function(A,B,C) { return euk.Circle.create_circumcircle(A,B,C); },{description:'The circle through the given points.'})); extension.scope.addFunction(new funcObj('incircle',[TPoint,TPoint,TPoint],TCircle,function(A,B,C) { return euk.Circle.create_incircle(A,B,C); },{description:'The circle inscribed in the triangle defined by the given points.'})); extension.scope.addFunction(new funcObj('center',[TCircle],TPoint,function(circle) { return circle.center(); },{description:'The center of the given circle.'})); extension.scope.addFunction(new funcObj('tangent',[TCircle,TAngle],TLine,function(circle,a) { return circle.tangent(a); },{description:'A line tangent to the given circle at the given heading.'})); extension.scope.addFunction(new funcObj('arc',[TCircle,TAngle,TAngle],TCircle,function(circle,from,to) { var c = new TCircle(circle); c.from = from; c.to = to; return c; },{unwrapValues:true, description: 'An arc of the given circle between the given angles.'})); extension.scope.addFunction(new funcObj('ellipse',[TPoint,TNum,TNum,TAngle],TConic,function(v,a,b,d) { return new euk.Ellipse(v,a,b,d); },{description:'An ellipse with the given center, major and minor axis, and rotated by the given angle.'})); extension.scope.addFunction(new funcObj('hyperbola',[TPoint,TNum,TNum,TAngle],TConic,function(v,x,y,a) { return new euk.Hyperbola(v,x,y,a); },{description:'A hyperbola with the given center, real and imaginary axis, and rotated by the given angle.'})); extension.scope.addFunction(new funcObj('parabola',[TPoint,TNum,TAngle],TConic,function(v,a,d) { return new euk.Parabola(v,a,d); },{description:'A parabola with the given summit and parameter, rotated by the given angle.'})); extension.scope.addFunction(new funcObj('parabola',[TPoint,TLine],TConic,function(A,l) { return euk.Conic.create_with_directrix(A,l,1); },{description:'A parabola with the given focus and directrix.'})); extension.scope.addFunction(new funcObj('conic',[TPoint,TLine,TNum],TConic,function(A,l,x) { return euk.Conic.create_with_directrix(A,l,x); },{description:'A conic with the given focus, directrix and eccentricity.'})); extension.scope.addFunction(new funcObj('conic',[TPoint,TPoint,TNum],TConic,function(A,B,a) { return euk.Conic.create_with_foci(A,B,a); },{description:'A conic with the given foci and eccentricity.'})); extension.scope.addFunction(new funcObj('center',[TConic],TPoint,function(conic) { return conic.center(); },{description:'The center of the given conic.'})); extension.scope.addFunction(new funcObj('foci',[TConic],TList,function(conic) { return conic.foci(); },{unwrapValues:true, description: 'The foci of the given conic.'})); extension.scope.addFunction(new funcObj('reflect',[TConic,TLine],TConic,function(conic,line) { return conic.reflect(line); },{description:'Reflect a conic in a line.'})); extension.scope.addFunction(new funcObj('symmetric',[TConic,TPoint],TConic,function(conic,p) { return conic.symmetric(p); },{description:'180° rotation of the given conic around the given point.'})); extension.scope.addFunction(new funcObj('rotate',[TConic,TPoint,TAngle],TConic,function(conic,origin,a) { return conic.rotate(origin,a); },{description:'Rotate a conic by the given angle around the given point.'})); extension.scope.addFunction(new funcObj('homothetic',[TConic,TPoint,TNum],TConic,function(conic,origin,k) { return conic.homothetic(origin,k); },{description:'Homothecy (reduction or dilation) of a conic with respect to the given point and scaling factor.'})); extension.scope.addFunction(new funcObj('major',[TConic],TNum,function(conic) { return conic.major_axis(); },{description:'The major axis of the given conic.'})); extension.scope.addFunction(new funcObj('minor',[TConic],TNum,function(conic) { return conic.minor_axis(); },{description:'The minor axis of the given conic.'})); extension.scope.addFunction(new funcObj('argument',[TConic],TAngle,function(conic) { return conic.argument(); },{description:'The direction of the given conic.'})); extension.scope.addFunction(new funcObj('point',[TConic,TNum],TPoint,function(conic,t) { return conic.point_on(t); },{description:'A point with the given argument on the given conic.'})); extension.scope.addFunction(new funcObj('eccentricity',[TConic],TNum,function(conic) { return conic.eccentricity(); },{description:'The eccentricity of the given conic.'})); extension.scope.addFunction(new funcObj('argument',[TConic,TPoint],TAngle,function(conic,p) { return conic.point_argument(p); },{description:'Polar angle of the given point with respect to the center of the given conic.'})); extension.scope.addFunction(new funcObj('tangent',[TConic,TNum],TLine,function(conic,t) { return conic.tangent(t); },{description:'A line tangent to the given conic at the given argument.'})); extension.scope.addFunction(new funcObj('arc',[TConic,TAngle,TAngle],TConic,function(conic,from,to) { var c = new TConic(conic); c.from = from; c.to = to; return c; },{unwrapValues:true, description: 'The portion of the given conic between the given arguments.'})); function wrap_vertices(vertices) { return new TList(vertices.map(function(v){ return new TPoint(v); })); } var sig_triangle = sig.or( sig.sequence(spoint('p1'), spoint('p2'), snumorangle('l2','a2')), sig.sequence(spoint('p1'), sig.optional(sig.sequence(snum('l1'), sig.optional(snumorangle('l2','a2')))), sig.optional(sangle('orientation'))), sig.sequence(sig.optional(sig.sequence(snum('l1'), sig.optional(sig.sequence(snumorangle('l2','a2'), snumorangle('l3','a3'))))), sig.optional(sangle('orientation'))) ); function remove_undefined(args) { return args.filter(function(a) { return a.type!='nothing'; }); } extension.scope.addFunction(new funcObj('triangle',[sig_triangle],TList,null,{ evaluate: function(args,scope) { args = remove_undefined(args); var vertices = []; // can give up to two vertices for(var i=0;i<2 && i=args.length) { x = 6; } else { x = args[i].value; i += 1; } } // can optionally give the two remaining lengths or angles if(ieukleides must be a dictionary, not "+initial_values.type)); } initial_values = initial_values.value; } } else { min_x = scope.evaluate(args[1]).value; min_y = scope.evaluate(args[2]).value; max_x = scope.evaluate(args[3]).value; max_y = scope.evaluate(args[4]).value; objects = args[5]; if(args[6]) { initial_values = scope.evaluate(args[6]); if(initial_values.type!='dict') { throw(new Numbas.Error("The final argument to eukleides must be a dictionary, not "+initial_values.type)); } initial_values = initial_values.value; } } var svg = create_svg(); var drawer = new euk.SVGDrawer(svg,document); if(min_x!==undefined) { drawer.setup_frame(min_x,min_y,max_x,max_y,1); } var ctx = new InteractiveContext(drawer,title_tree,objects,scope,initial_values,min_x===undefined); if(min_x===undefined) { var res = find_bounding_box(svg); drawer.setup_frame(res.min_x,res.min_y,res.max_x,res.max_y,1); ctx.draw(); } const tok = new THTML(svg); tok.ctx = ctx; return tok; }, description:'Draw a Eukleides diagram.' })); jme.lazyOps.push('eukleides'); jme.findvarsOps.eukleides = function(tree,boundvars,scope) { var vars = []; var initial_values; var args = tree.args; if(args.length<=3) { initial_values = args[2]; } else { for(var i=1;i<5;i++) { vars = vars.concat(jme.findvars(args[i],boundvars,scope)); } initial_values = args[6]; } if(initial_values) { vars = vars.concat(jme.findvars(initial_values,boundvars,scope)); } return vars; } extension.scope.addFunction(new funcObj('grid',['integer','integer','number','number','lambda'], TList, null, { evaluate: function(args,scope) { const [cols,rows,colgap,rowgap] = args.slice(0,4).map(v=>jme.unwrapValue(v)); const fn = args[4]; var o = []; for(let col=0;col { const from = args[0].value; const to = args[1].value; const time = scope.getVariable('time').value; return new TNum(clamp_frame(from,to,time)); } })); extension.scope.addFunction(new funcObj('frame_positions', ['number', 'list of number'], TList, null, { evaluate: (args,scope) => { const time = args[0].value; const frame_lengths = jme.unwrapValue(args[1]); let last = 0; return jme.wrapValue(frame_lengths.map((l,i) => { const o = clamp_frame(i>0 ? last : 0, last+l, time); last += l; return o; })); } })); extension.scope.addFunction(new funcObj('frame_at', ['number', 'list of number'], TInt, null, { evaluate: (args,scope) => { let time = args[0].value; const frame_lengths = jme.unwrapValue(args[1]); for(let i=0; i