Friday, September 28, 2012

Parametric Airplane Variations in SolidWorks

I'm currently in the process of designing my third remote-control airplane from scratch. (Footage of the first airplane can be found here.) My objective for this new airplane is to make an airplane that is stable, controllable, and able to capture aerial video.

SolidWorks was very helpful in the planning and building phases. However, I found that parametric tweaking of the design once it was completed was difficult: for example, changing the length of the aileron would require editing the size of the aileron cutout in the wing, and then editing the aileron itself. Parts with more dependencies, such as the fuselage, were more complex.

Luckily, there's a very elegant solution: global variables and equations. This is relatively easy to learn and enables rapid parametric tweaking of the model to get it just right. Here are the self-documenting global variables I'm using for my new airplane (in units of inches and degrees):

"Fuselage height"= 2
"Fuselage length"= 25
"Fuselage width" = 4

"Wing chord"= 8
"Wing height"= 1.25
"Wing offset from nose"= 9
"Wing platform height"= 2
"Wing platform angle"= 180 - 45

"Nose length"= 8

"Rudder length on fuselage"= 5
"Rudder angle"= 90 + 45

"Elevator height above fuselage"= 5
"Elevator platform width"= 4
"Elevator length"= 15
"Elevator width"= 3
"Elevator throw angle"= 45

"Aileron length"= 12
"Aileron width"= 4
"Aileron throw angle"= 45

"Propeller diameter"= 9

A few parametric changes

Here is what the airplane looks like with the parameters above.

Dimetric view of the original configuration
And here it is from the side:

Side view of the original configuration
Though I haven't flown it yet, I expect this configuration to be moderately aerobatic and moderately stable.

Let's say we wanted a more streamlined version for racing and aerobatics.  Here are some parameters I might change to do this, along with comments explaining the change.

"Fuselage height"= 1                  // flatten the fuselage
"Fuselage length"= 22                 // shorten the fuselage
"Wing chord"= 6                       // shorten the wing chord
"Wing height"= 1                      // decrease the wing height
"Wing offset from nose"= 6            // bring the wing towards the nose
"Wing platform height"= 1             // lower the wing
"Elevator height above fuselage"= 2.5 // lower the elevator
"Elevator platform width"= 3          // decrease the elevator platform area
"Propeller diameter"= 5               // make the props smaller

Side view of the "racer" configuration
The "racer" configuration is smaller, leaner, and more compact.

Now suppose we wanted to go in the opposite direction and make a cargo-style airplane with low wing loading and a high carrying capacity.

"Fuselage height"= 4            // increase the fuselage height
"Fuselage length"= 32           // increase the fuselage length
"Wing chord"= 9                 // lengthen the wing chord
"Rudder angle"= 90 + 30         // decrease the rudder sweep
"Elevator platform width"= 5.5  // increase the elevator platform area

Side view of the "cargo" configuration
The cargo configuration is larger and stockier. It would be well suited for carrying fairly large payloads.

These examples convey the usefulness of global variables and equations. They enabled me to create three functionally unique variations on my original model in a matter of minutes.

How it's done

In SolidWorks, global variables can be derived from mathematical operations/functions and/or other variables.

Here's a simple example. Let's say you want to be able to control the length of the aileron easily, without having to slog through editing sketches. First, navigate to Tools > Equations and define two variables.

"Aileron length"= 10
"Aileron wing cutout"= "Aileron length" + (1/8)

Click "Export" to save the variables externally as a txt file. Then click the checkbox next to "Link to external file" and direct SolidWorks to your txt file. Now, SolidWorks will read the variable values from the txt file.

Now, let's say you already have a part model for the wing and aileron. To use these variables, you must open each part, navigate to Equations, and click "Import" to load the variables from the txt file. This enables you to dimension your sketches using the variables defined in the txt file. This is done in a similar way as in Excel: Smart Dimension the sketch entity, type equals (=), and then type the desired equation, referencing variable names in quotes. Make sure to not link these equations with the txt file. These equations are part-specific; only the variables are global.

When you dimension a part using an equation, the dimension will appear with a red capital sigma next to it. Here's a sketch for of one of my simpler parts, the elevator:

Change the variables as desired in the txt file. If you update parts or entire assemblies by typing Control+Q. SolidWorks uses sophisticated algorithms to look at part dependencies and figure out the logically correct solve order.

As a word of caution, there is nothing preventing you from putting in absurd or geometry-breaking values, like negative distances. It is up to you, the modeler, to clearly know what each parameter is, which parameters it affects, and what its acceptable range is.

Friday, September 14, 2012

The Colored Orbit Buddhabrot

In this post, I'll discuss what I believe to be a new method for coloring the Buddhabrot, which was conceived of by my brother, Michal Dichter, and that I coded in MATLAB.


The Buddhabrot is a fractal derived from the Man
delbrot set, which is also a fractal. If you'd like to first learn what the Buddhabrot is, you can do so here or here.

Ordinarily, a Buddhabrot is generated by iterating a large number of randomly chosen complex points according to the Mandelbrot equation and only considering orbit lengths between 1 and 100, for example. This method would yield an image like this.

97,000,000 orbits of lengths between 1 and 100. Highlights boosted in Photoshop.
It is known that orbit lengths on the order of roughly 20,000 or greater yield much more visually interesting orbits because they linger for many more iterations. However, these orbits are exceptionally difficult to find, as their initial points invariably lie just outside the border of the Mandelbrot set. Even with more sophisticated orbit finding techniques, it may be difficult to find more than a few thousand such orbits in one day of computational time.

Consequently, in the same amount of computational time, fewer orbits are found, and as a result, the Buddhabrot is less dominated by random noise (above) and individual orbits can clearly be seen (below).

268 orbits of lengths between 100,000 and 250,000. 3rd root taken (explained later).
The Colored Orbit Method

When plotting the Buddhabrot conventionally, no distinction is made between individual orbits. They are all summed together to form the final Buddhabrot image.

In this method, each orbit is assigned a random RGB color and then added to the final Buddhabrot image. Here is some output using this method:

268 randomly colorized orbits of lengths between 100,000 and 250,000. (cropped, highlights boosted)

There are plenty of post-processing techniques that could be applied to this image to increase its aesthetic appeal, including adjusting the saturation, shifting hues, and selective blurring. For the image above, I shifted the hues and boosted the highlights slightly.

Coding implementation

In my code, I start by loading a matrix of initial points (top row) and their orbits lengths (bottom row), which, in this case, are between 100,000 and 250,000. How I found these orbits is a blog post in and of itself, so for now, let's just take them for granted. Here are the first three columns.

-0.6388 + 0.3730i
-0.3710 - 0.5960i
-0.3485 - 0.6056i

Each initial point is iterated normally, according to the Mandelbrot equation, and a temporary Buddhabrot matrix is created for just that one orbit. Here's what this matrix looks like for a single ordinary orbit.

Orbit of the point -0.3485 - 0.6056i. Orbit length = 157,494. (detail)
My code randomly assigns an RGB value to each orbit, normalizes it (explained below), and then adds it to the final Buddhabrot image, similar to how a Nebulabrot is made, but with numerous color channels instead of three. When all the orbits have been added, the code normalizes the image matrix so that none of the color channels exceed 255.

Individually normalizing orbits

For most orbits, the range of the image matrix - the difference between the value of the most-hit pixel and the least-hit pixel - is between 10 and 100. In the image above, for example, the range is 23.

However, there are orbits with extremely high ranges on the order of 750 or greater. These are problematic because when working with grayscale images, the RGB standard only allows for 256 shades of gray. Therefore it is technically impossible to view such orbits as they truly are. We can view an approximation of the orbit by rescaling the matrix to fit the RGB standard by multiplying the entire matrix by 255/max, where max is the highest value in the matrix.

Here is a single orbit with a range of about 1,000. This image was rescaled as described above.

Orbit of the point -0.2279 + 0.7457i. Orbit length = 128,435. Range before rescaling = 798. (detail)
High range orbits are tiny. They exist in smaller areas than low range orbits, therefore the total number of hits, the orbit length, must be packed into a smaller area. This causes the number of hits per pixel to be relatively high.

It is usually true that when Buddhabrot matrices have extremely high ranges, the frequency of each value between 0 and max appearing in the matrix is not uniform over the entire range. In other words, a handful of pixels are so exceptionally bright that they reduce darker regions to pure blacks when the image is rescaled.

To make an analogy, if you look up at the sky on a clear night, you will be able to see some number of bright stars. Yet there are also countless nebulae and dimmer stars you cannot see because the bright stars are so bright that they obscure everything else. In this case, trying to theoretically divide the perceived brightness by some number would be of no use in seeing the fainter objects. To do this, the proportionality between the light and dark regions must itself be altered.

The solution is taking the nth root of the entire matrix. This is a fairly standard data transformation. Consider the shape of the function y=sqrt(x), plotted here against y=x for comparison.

This function alters values more intelligently than simply dividing or multiplying them by a constant. The higher a value, the more it is downscaled. Meanwhile, lower values remain about the same (relatively speaking).

It is important to realize that this transformation reduces the range of the image. For example, taking the 3rd root reduces the range more than would taking the 2nd root. In my code, I determine which root, n, is necessary to reduce the range to a pre-set value,
rangeSet. Here's the math behind this:
\[ \text{range}^{1/n} = \text{rangeSet} \] \[ 1/n = \text{log}_{\text{range}} \text{rangeSet} \] \[ 1/n = \frac{\text{log(rangeSet)}}{\text{log(range)}} \] \[ n = \frac{\text{log(range)}}{\text{log(rangeSet)}} \]

Setting rangeSet equal to 255 would be logical. However, the more the range is reduced, the more of the orbit becomes visible. The tradeoff, though, is that there are fewer shades of gray in the resulting image. A good value for rangeSet, then, should expose as much of the orbit as possible while preserving enough shades of gray that the image appears smooth. I have found that a value of 25 works well. Here's the orbit shown earlier, before and after the transformation.

Orbit of the point -0.2279 + 0.7457i before and after root transformation. (detail)
While the difference is not dramatic in this example, it's important to perform this transformation to intelligently equalize the relative weight of the individual orbits before they are added to the final image. Aesthetically, it also produces smoother-looking orbits by providing gradience around bright pixels.


Here are some of the images I have generated with the techniques described above.

A few colorized orbits of lengths between 100,000 and 250,000. Highlights boosted in Photoshop. (detail)
Approximately 250 orbits of lengths on the order of 100,000. Highlights boosted in Photoshop. (crop)

Approximately 250 orbits of lengths on the order of 50,000. Saturated and highlights boosted in Photoshop. (crop)

Friday, September 7, 2012

The Fibonacci Vortex

Recently I came across this image called "Fibonnaci Vortex" by the artist Leif Podhajsky.

I thought it was pretty neat. I liked how the structure of the image - how it was created, mechanically - could be discerned just by looking at it. Yet the end result didn't look mechanical - it looked beautiful and infinite. I was inspired to write my own MATLAB code to perform this image manipulation. Here is some sample output:

With some post-processing and a better camera (the original image was from my cell phone), my image could be made to look much better.

The Code

At a glance, the code works like this:

1. Load the original image and store it as an x by y RGB matrix.

2. Find the first circular region - the outermost disc - in the matrix using the distance formula and store it as an RGB matrix. The area outside of the disc is zeroed out.
3. Repeatedly shrink and rotate this disc, each time overlaying it on the original image.

Coding Nuances

I ran into some coding difficulties that I ultimately solved in a slightly dubious way.

MATLAB conveniently provides the functions imrotate and imresize, but they do not work exactly as desired for this application.

Rotating a rectangular image by anything other than 180° or 360° inherently increases the rectangular envelope needed to fully contain the image. However, since the disc is centered and is wholly contained in the image, it will always stay within the image matrix. Therefore the dimensions of the disc's image matrix should stay the same size. This is achieved by specifying to imrotate to make the rotated image of the same dimensions as the original with the 'crop' parameter.

There's another problem though - during rotation, some areas outside the image are drawn into the frame. MATLAB assigns these new undefined regions to be pure black - RGB [0,0,0]. Ideally, these regions would be transparent, but that's not how the command works.

The imresize command works similarly. When an image is shrunk but its matrix dimensions stay the same, the image shrinks in the frame of its matrix and the new undefined regions are assigned to be pure black. Therefore it seemed convenient - for both rotation and resizing - to reserve the color pure black for transparent regions. However, I had to also account for the user inputting a dark image with patches of pure black.

I decided that in order to reserve pure blacks for transparency, I would have to eliminate them in the source image. Here is the function reserveBlacks that does this.

function R=reserveBlacks(R)
    for a=1:3 % iterate through the color channels (RGB)
        for yInd=1:yDim % iterate through the columns
            for xInd=1:xDim % iterate through the rows
                if R(yInd,xInd,a) == 0 % if pixel is black
                    R(yInd,xInd,a)=1; % make it off-black

If a pixel is pure black in any of its color channels, it is made off-black (i.e. a very dark gray - RGB [1,1,1]). This is not ideal, but it's also impossible to notice visually. And it greatly simplifies the image processing in MATLAB.

Setting the parameters

In my code, a disc is defined by its scale and angle of rotation, both relative to the original disc. It may seem logical to derive each disc from the one preceding it, but the problem is that rotating and resizing images is lossy, and iteratively repeating this lossy process would result in the discs looking more distorted with each successive level.

In the original image, the rate of change in scale between discs is not constant. It decreases as the discs get smaller, as if asymptotically. Therefore I thought to somehow use the function y=1/x, which decreases to asymptotically approach 0 (below).

The angle change between discs appeared to be roughly constant. Therefore I set up these equations, to be updated for each disc.

scale=scale-1/(level+A)*B; % level is the current disc number

The variable level keeps track of the current disc number and increases as the discs get smaller. The variables AB, and C were placeholders for parameters, to be figured out later. 

Choosing the disc parameters

Because performing the entire image manipulation process on an image took a minute or two, I wrote another program to quickly show an approximation of the final image. This would enable me to quickly figure out the correct parameters to govern scale and angle so that the image would look right.

Instead of performing the image manipulations, the program draws circles where the discs' edges will be in yellow and shows their angle in green. Here is an example using another picture.

Using this program, I arrived at the constants A=1.5, B=0.42, and C=10. The constant A shifts the 1/x function to the left by 1.5 units to get rid of its steepest part, which made the discs looks proportionally wrong. The factor of 0.42 was included to scale the overall effect of the 1/x function.

Final touches

To make a new disc, my program rotates and then scales the original disc. The problem is that both of these operations are lossy, so after both have been completed, the crisp circular border of the disc becomes distorted. To fix this, I used the distance formula to crop a circular region slightly smaller than the disc, getting rid of imperfections in its border.

(detail) Before and after edge cleaning.


Here are some more images I made using my program, all of which use approximately similar values for 
AB, and C.