Anyone who has ever played with a chemistry set should enjoy the description of this project 🙂 This learning application teaches students how to measure the volume of regular and irregular objects by their effect on water level in a container.
The application begins in an exploration mode with a layout consisting of a work area and a cabinet containing objects to measure, some containers, and a few tools.
Water may be added to a container from the faucet, which pours at three different rates depending on the slider position. All water levels and animation were drawn programmatically. After adding water, the student may drop an object into a container. Objects may be ‘added’ to empty containers as well. If any part of the object overlaps a container, it is automatically added to the container. In addition to collision detection of irregular objects, a general-purpose animation manager was written for this project. It controlled the animation of objects dropping into containers and setting of a ‘critical’ level, which was used to dispatch an event, from which the rise of the container’s water level was choreographed. This allows the water level to animate in a reasonable manner and be timed to correspond with the object reaching the water level.
Water level rises in a physically realistic manner in that all objects have a mathematical model; box, cylinder, or spherical. Depending on the container and object properties, water in a container may not rise by the volume of the object. For box and cylinder models, computing the water level rise is straightforward. For spherical models, roots of a cubic polynomial must be computed. I added a cubic root finder to this client’s math library. Fortunately, the physics of the problem result in only one root, so resolving multiple roots in an interval was not necessary.
Objects are stacked behind containers by default and when the student moves a container with an object ‘inside’ it, the object moves as well. However, if the student clicks such that the mouse is over the object, the object is moved instead. This is indicated by a glow filter to inform the student they are about to move the object instead of the container.
Layering, however, is not static. If an object is released in such a manner that it does not fall into a container, it is immediately layered in front of all containers. If the same object is later dropped into another container, it is layered behind all containers.
Collision detection includes not only container-object collisions, but detecting when an object is positioned so that it will drop into a container and when a container is in ‘pouring position’ above another container. Container pouring represented the most intricate part of this application.
Two models are involved in pouring, both of which were supplied by the science curricula manager responsible for this application. The first involves computing the pouring angle as a function of the horizontal position of each container’s spout. Given a pouring angle, the second model determines how the ‘side view’ of water in the pouring container is drawn and how much water leaves the container.
This water does not immediately transfer into the destination container. If water spills from a container in the drag handler, a Timer is initiated whose handler transfers water from a virtual ‘in buffer’ to the destination container according to a pouring constant whose value is container-dependent. This constant allows certain visual and interactive constraints (such as water not pouring in too fast or too slow into certain containers) to be met. Adjustment of the water level in the destination container is physically accurate in the presence of contained objects.
In addition to pouring into containers, students may pour water into the sink, which is treated as a faux container for just that purpose.
Overflow from containers is allowed. Certain containers are allowed to overflow into other containers, as shown below.
Three tools are provided, a Ruler (for measuring object dimensions), an eyedropper (for extraction and addition of tiny amounts of water), and a magnifying glass for measuring the meniscus of water in a graduated cylinder. The latter is dynamically drawn using a quadratic Bezier, according to properties assigned by the science curricula manager.
The meniscus is shown whenever a specified interior section of the lens is over the actual meniscus in a graduated cylinder. This test is performed inside the magnifying glass handler using a highly optimized circle-rectangle collision test. The magnifying glass and containers may establish a two-way connection (think of it as two-way binding) so that if water is added to a graduated cylinder, the magnified meniscus view is dynamically redrawn until the meniscus is ‘out of view’ of the lens.
In addition to the exploration view, the application also contains a test or practice view.
Each problem presents students with a selected number of objects, tools, and containers, sufficient to answer the question. Answers are deemed correct if they lie within a specified tolerance. To create a virtually limitless number of questions from a small base, initial volumes and object parameters may be randomized in the question XML. One example problem is
<question type="answer" number="9" category="all" target="Prism" textBox="true" > <questionText> <![CDATA[To the nearest 0.1 cm <span class='sup'><font size = '+4'>3</font></span>, what is the volume of this rectangular prism?]]> </questionText> <textBox> <![CDATA[cm <span class='sup'><font size = '+4'>3</font></span>]]> </textBox> <answer tolerance="0.1" /> <containers /> <objects> <object id="Prism" x="330" y="330"> <length> <generate>3.9 + 0.3*rnd()</generate> </length> <width> <generate>2.4+ 0.3*rnd()</generate> </width> <height> <generate>7.3 + 0.3*rnd()</generate> </height> </object> </objects> <tools> <tool id="ruler" x="460" y="300" /> </tools> </question>
Whenever a <generate> node is encountered, the parser sends the node’s contents to a specialized method that randomizes a value and generates the numerical value of the node. That value is either in-lined into question text or used to calculate object volume.
So, each time the student practices in this view, they receive a the same set of questions with differing values. The code is very efficient in the sense that the same base class for container-object interaction is used to create the two Views. The hierarchy for lab items is DraggableItem -> LabItem -> Lab Object -> LabContainer.
I suppose you can see why I’ve been so busy for the last couple months 🙂 Going to take a few days off now before returning to work on the Freehand Drawing Library.
Merry Christmas and best wishes for 2012 to all readers.
I’ve been very quiet over the last few months, and for good reason. Just finished a gig involving two pretty complex learning applications for physical science. I worked on one as a primary developer, and the function graphing engine I authored was used in another. This is the first of a couple posts summarizing the work before returning to development of the Freehand Drawing Library.
The prior update on the function graphing engine was summarized in this post. This particular learning application involves chemical reactions and equilibrium. A simulation is started in one tab, as shown below.
Depending on the simulation length, a substantial number of molecules may interact in a single time step. Clicking on the Graph tab shows a real-time line plot of the state of various reactions.
The x-axis is time steps and the programmer plots the most recent N steps.
A new addition to the graphing engine is the ability to adjust x- and y-axis bounds and tic marks independently. This provides highly customizable zoom capability.
The graph may be panned by dragging, and panning is setup in XML to be restricted to the first quadrant.
This was a very interesting project both because I enjoyed working with the other application developer and to see a cool use of the function graphing engine.
I’ve been insanely busy over the last couple months, for which I should be grateful, but at the same time a little bummed over the lack of posts. Here’s a quick update on the new Decorator architecture in the Freehand Drawing Library.
In the prior post, I mentioned that the drawing algorithm is now abstracted into its own API. A concrete implementation of any algorithm is injected into the BasicStroke class upon assigning a data provider. Now, the drawing API itself is abstracted. Using a Decorator pattern, it is now possible to dynamically change the line drawing style applied inside a specified drawing engine. This completely separates the algorithm for computing the constituent points of a stroke from the line style used to draw the stroke.
Just like SkinnableComponents in Flex, the stroke algorithm and visual representation are now completely decoupled. Here is a screenshot for a dashed-line Decorator. The same smoothed-stroke algorithm for solid strokes is used in this example, except it is now agnostic to how the small line segments are drawn.
I’ll have some additional exciting announcements in the near future. In the mean time, back to my gig!