Jump to content
Hash, Inc. Forums

Relook: Star Sphere WIP: writing .mdl code with JavaScript


Julian

Recommended Posts

This is an outgrowth of an idea I originally floated in this post on the SDK forum. My objective is to create a sphere of randomly distributed stars 10 km in radius, where each star is a one-patch object with a diameter randomly ranging from 7.5 to 30 m. I also want to be able to control the size of all stars simultaneously with a pose slider, to ensure that the stars will always be rendered at about one pixel regardless of the resolution of the render or the focal length of the camera.

 

After Yves put me on the track of a bones-based solution, I first tried to implement this idea in which each star has two bones, one to position it randomly and one to scale it, but I had to scrap it. First of all, the program hung when I tried to go from 1024 stars to 2048. Second, I found that having to evaulate an expression with a Rand() function on every bone made the model load up very slowly in an action or choreography. What I'm planning to do now is a two-stage approach: first, I create the randomly distributed and scaled stars with expressions -- this time, using only one bone per star -- then I bake the position of the bones in place by opening it in an action window and exporting as a model. After that, I'm going to create the pose slider that will scale each bone uniformly.

 

The procedure goes like this: I made a single circular patch consisting of two splines, 15 m in diameter and centered on (0, 0, 10000 m). The CPs are assigned to a bone called Radius1 starting at the origin with a length of 10000 m. I replicated this model by saving the file, importing the model into itself and repeating 11 times to give 2048 stars, with bones numbered from Radius1 to Radius2048. What I need to do next is to apply these expressions to each of the 2048 bones:

 

Transform.Rotate.X = 360*(-0.5+Rand())

Transform.Rotate.Y = 360*(-0.5+Rand())

(The Rand() function returns values between 0 and 1, so this will orient the bones randomly in a range of values between -180 and 180 degrees.)

 

Transform.Scale.X = 0.5+Rand()

Transform.Scale.Y = ..|X

(Scales each bone by X randomly between 50% and 150%, and sets the Y scale equal to the X scale.)

 

I can generate the names of all the bones using a FOR loop, and I'm choosing to do this using JavaScript, because I can very quickly use a web browser to save the ouput to a text file, open the .mdl file in a text editor, and paste the relationship into the right place. The relationship container in the model file looks like this:

 

[RELATION]
Name=Relationship1
KeyDim=1
[OBJECTSHORTCUT]
MatchName=Bones

[ENDOBJECTSHORTCUT]
[FileInfo]
LastModifiedBy=Julian Fong
[EndFileInfo]
[ENDRELATION]

...and the code I want to insert goes in where the blank line is. The JavaScript that generates the code is an HTML file which is presented here in its entirety:

 

<script language="JavaScript">

var string1 = "[EMPTYDRIVER]<br>"
string1 += "MatchName=Transform<br>"
string1 += "[EMPTYDRIVER]<br>"
string1 += "MatchName=Rotate<br>"
string1 += "[EXPRESSION]<br>"
string1 += "Value=360*(-0.5+Rand())<br>"
string1 += "MatchName=X<br>"
string1 += "[ENDEXPRESSION]<br>"
string1 += "[EXPRESSION]<br>"
string1 += "Value=360*(-0.5+Rand())<br>"
string1 += "MatchName=Y<br>"
string1 += "[ENDEXPRESSION]<br>"
string1 += "[ENDEMPTYDRIVER]<br>"
string1 += "[EMPTYDRIVER]<br>"
string1 += "MatchName=Scale<br>"
string1 += "[EXPRESSION]<br>"
string1 += "Value=0.5+Rand()<br>"
string1 += "MatchName=X<br>"
string1 += "[ENDEXPRESSION]<br>"
string1 += "[EXPRESSION]<br>"
string1 += "Value=..|X<br>"
string1 += "MatchName=Y<br>"
string1 += "[ENDEXPRESSION]<br>"
string1 += "[ENDEMPTYDRIVER]<br>"
string1 += "[ENDEMPTYDRIVER]<br>"
string1 += "[ENDOBJECTSHORTCUT]<br>"

for(var i = 1; i <= 2048; i++) {
document.write("[OBJECTSHORTCUT]<br>")
document.write("MatchName=Radius" + i + "<br>")
document.write(string1)
}

</script>

After generating all the code and pasting it in, my model file now has a size of 1.8 MB, but that's okay, because including the model in an action and saving it as a new model will cut the size down almost by half. Opening it in an action window produces this result:

 

stardist-bones.gif

 

But here's where I run into something unexpected: the Rand() function isn't producing a uniform distribution of bones. This is what the action looks like in muscle mode, from a front view:

 

stardist-frt.gif

 

and a top view:

 

stardist-top.gif

 

Clearly, there's a higher concentration of stars around the poles, and I don't want that. I'm not even sure why this is happening -- something to do with the way the X and Y rotations are combining to produce the orientation of the bones? So now, I'm looking for some advice on how I can fix this. Maybe I can modify the expressions used to orient the bones originally, but how?

 

If I export the action as a model and delete the original relationship, the bones will have random rotations in their Bone Position properties, but they will no longer be controlled by the expression and I can move them around. However, rotating the bones by hand is impractical. I could try using another expression to isolate just the bones clustered around the poles and randomizing their orientation again.

 

As I had done before, I tested this expression on a model that contained only one bone before I'm going to apply it to all 2048 bones of my working model. Given that the name of the model is StarSphere2 and the first bone is called Radius1, I can apply this expression to Radius1:

 

Transform.Rotate.X = If(80<Abs(..|..|..|..|..|..|..|..|Objects|StarSphere2|Bones|Radius1.Bone Position.Rotate.X)<100,180*(-0.5+Rand()))

 

In other words, if the absolute value of the original X-rotation of this bone is between 80 and 100 degrees (which takes care of both the "north pole" and "south pole" clusters), rotate it by X again a random value between -90 and 90 degrees.

 

I have to try this out to see how it'll look, but one problem that I can see may arise is getting stars clustered in two latitudinal circles. Maybe it'll happen and maybe it won't. Does anyone have any better ideas?

Link to comment
Share on other sites

Julian,

 

The clustering you see is what you should expect. On a sphere, an arc length is shorter close to the poles than around the equator, but your algorithm places just as many bones at latitude 0 as it does at latitude 89. The solution is to have a non uniform random distribution for the latitude angle. It may be shown that this distribution should have a frequency function as f(Lat) = cos(Lat)

 

This is an algorithm that might help you to solve your problem. It produces evenly random distributed points on a half sphere.

N = 2048;
for (int i=1,  i < N, i++){
   Lat = asin(rand()); // Latitude angle [0,pi/2). This is the "magic" line...
   Long = 2*pi*rand(); // Longitude angle [0,2*pi)
   X[i]= R*cos(Lat)*cos(Long); // X coordinate for the light source
   Y[i] = R*cos(Lat)*sin(Long); // Y coordinate for the light source
   Z[i] = R*sin(Lat); // Z coordinate for the light source
}

Link to comment
Share on other sites

After some more thought, I think your problem will be solved if you simply change your line

Transform.Rotate.X = 360*(-0.5+Rand())

to

Transform.Rotate.X = 90 + 180/Pi()*ASin(2*Rand() - 1)

 

EDIT:

I wasn't sure if A:M trig functions used radians or not. Now I think it's correct though...

Link to comment
Share on other sites

Between the two solution suggested by Anders, I would prefer the first one as it would be more efficient to generate the vectors this way rather than rotating them.

 

Another faster variation of first Anders example would be:

 

N = 2048;
for (int i=1,  i < N, i++){
  costheta = sqrt( Rand() );
  sintheta = sqrt( 1.0 - costheta * costheta );
  phi = Rand() * 2.0 * Pi();
  X[i]= R*cos(phi)*sintheta;
  Y[i] = R*sin(phi)*sintheta;
  Z[i] = R*costheta;
}

Link to comment
Share on other sites

[...]Another faster variation of first Anders example would be:[...]

Yes, you are correct Yves, but I think you got a typo in your code. What I think meant was:

N = 2048;
for (int i=0,  i < N, i++){
  costheta = Rand();
  sintheta = sqrt( 1.0 - costheta * costheta );
  phi = Rand() * 2.0 * Pi();
  X[i]= R*cos(phi)*sintheta;
  Y[i] = R*sin(phi)*sintheta;
  Z[i] = R*costheta;
}

I think costheta must have a uniform distribution. Am I right? (Edit: I guess I am since otherwise there will be a dimension error)

(I also corrected the loop index, but that was trivial)

Link to comment
Share on other sites

Anders,

 

I know I can count on you for that sort of thing ;)

 

You are right. the first Sqrt is wrong. And, I also missed something else. For a random distribution on the whole sphere (not only on half sphere) the costheta should be:

costheta = 1.0 - 2.0 * Rand();

Link to comment
Share on other sites

Anders, Yves, thanks for the help. I can retool the model so that the arrangement of bones depends on translation instead of rotation, which would involve using a short bone for each star with an Aim At constraint to a null at the origin. The necessity of reusing variables means I would be better off computing the coordinates within the script instead letting A:M do it with expressions.

 

In the SDK forum, I asked about generating a normal distribution with the Rand() function, and the thought occurs to me that if I used (Rand() + Rand()) /2, I would be able to produce values close to 0.5 more often than 0 or 1, a bit like rolling two dice. Anyway, I originally wanted to use an expression like that to control the size variablilty among the stars, but I probably won't be doing that now.

 

For future reference, the problem of picking random points on a sphere is also discussed in this page on Wolfram MathWorld.

Link to comment
Share on other sites

  • 2 weeks later...

Success! I decided to do the arrangment of stars in an action instead of a pose, because a translate driver in a pose always has two keyframes, one for the pose turned off and one for it turned on, whereas in an action I only need to use one keyframe. It keeps the file size smaller and reduces processing time. I created a model called StarSphere_1-repl2048.mdl which contains 2048 copies of a 15 meter star centered on the origin, each with a 15 meter long bone (Bone1 to Bone2048) starting at the origin. I also have a null called Origin at the origin.

 

The HTML script for generating the action file looks like this:

<pre>
[ACTIONFILE]
ProductVersion=11.1
[POSTEFFECTS]
[ENDPOSTEFFECTS]
[IMAGES]
[ENDIMAGES]
[SOUNDS]
[ENDSOUNDS]
[MATERIALS]
[ENDMATERIALS]
[POSTEFFECTS]
[ENDPOSTEFFECTS]
[OBJECTS]
[ENDOBJECTS]
[ACTIONS]
[ACTION]
StrideStartPosition=0 0 0
StrideEndPosition=0 0 0
DefaultModel=StarSphere_1-repl2048
PlayRange=0 0
[OBJECTSHORTCUT]
MatchName=Bones
<script>
for (var i=1; i<=2048; i++) {
  with (Math) {
     costheta = 1.0 - 2.0 * random()
     sintheta = sqrt( 1.0 - costheta * costheta )
     phi = random() * 2.0 * PI
     X = round( 1000000 * cos(phi) * sintheta )
     Y = round( 1000000 * sin(phi) * sintheta )
     Z = round( 1000000 * costheta )
  }
  document.write("[OBJECTSHORTCUT]\n")
  document.write("MatchName=Bone" + i + "\n")
  document.write("[EMPTYDRIVER]\n")
  document.write("MatchName=Transform\n")
  document.write("[EMPTYDRIVER]\n")
  document.write("MatchName=Scale\n")
  document.write("[EXPRESSION]\n")
  document.write("Value=0.5+Rand()\n")
  document.write("Name=X\n")
  document.write("MatchName=X\n")
  document.write("[ENDEXPRESSION]\n")
  document.write("[EXPRESSION]\n")
  document.write("Value=..|X\n")
  document.write("Name=Y\n")
  document.write("MatchName=Y\n")
  document.write("[ENDEXPRESSION]\n")
  document.write("[ENDEMPTYDRIVER]\n")
  document.write("[EMPTYDRIVER]\n")
  document.write("MatchName=Translate\n")
  document.write("[TRANSFDRIVER]\n")
  document.write("Name=X\n")
  document.write("MatchName=X\n")
  document.write("[SPLINE]\n")
  document.write("CPs=1\n")
  document.write("1\n")
  document.write("0 " + X + "\n")
  document.write("[ENDSPLINE]\n")
  document.write("[ENDTRANSFDRIVER]\n")
  document.write("[TRANSFDRIVER]\n")
  document.write("Name=Y\n")
  document.write("MatchName=Y\n")
  document.write("[SPLINE]\n")
  document.write("CPs=1\n")
  document.write("1\n")
  document.write("0 " + Y + "\n")
  document.write("[ENDSPLINE]\n")
  document.write("[ENDTRANSFDRIVER]\n")
  document.write("[TRANSFDRIVER]\n")
  document.write("Name=Z\n")
  document.write("MatchName=Z\n")
  document.write("[SPLINE]\n")
  document.write("CPs=1\n")
  document.write("1\n")
  document.write("0 " + Z + "\n")
  document.write("[ENDSPLINE]\n")
  document.write("[ENDTRANSFDRIVER]\n")
  document.write("[ENDEMPTYDRIVER]\n")
  document.write("[ENDEMPTYDRIVER]\n")
  document.write("[AIMATCONSTRAINT]\n")
  document.write("BoneTarget=..|Origin\n")
  document.write("[ENDAIMATCONSTRAINT]\n")
  document.write("[ENDOBJECTSHORTCUT]\n")
}
</script>[ENDOBJECTSHORTCUT]
[ENDACTION]
[ENDACTIONS]
[CHOREOGRAPHIES]
[ENDCHOREOGRAPHIES]
[ENDACTIONFILE]
</pre>

 

And here is the distribution of stars it generates, from a top view:

 

stardist2-top.gif

 

The next step will be to generate the pose allowing me to scale each bone.

Link to comment
Share on other sites

Well, it's pretty much done, but it's not very practical to use. Sure, it renders really fast, but on my 1.5 GHz Mac it takes nearly six minutes to load up the project. Basically, this is the point of the whole exercise:

 

Focal length 35 mm, 320x240 resolution, star size 20 m:

starsphere_35-320-12.jpg

 

Focal length 35 mm, 640x480 resolution, star size 12 m:

starsphere_35-640-12.jpg

 

Focal length 35 mm, 1024x768 resolution, star size 8 m:

starsphere_35-1024-8.jpg

 

Focal length 100 mm, 640x480 resolution, star size 5 m:

starsphere_100-640-5.jpg

 

What I'm probably going to end up doing is to start with this sphere of variable-size stars that I've got, and by exporting actions to models, create different versions where the stars are fixed at particular sizes, and delete all the bones from each of these versions to make them load more efficiently.

Link to comment
Share on other sites

Julian, Yves, aaver,

 

This looks like it is coming along quite well, it seems promising and I'm hoping you'll share it with the rest of us :)

 

This site: http://www.ap3d.com/betterspace/

Has a pretty decent starfied, it was done with Lightwave, but maybe there are somethings that apply (code is way beyond me)

 

My question is how does it look with motion blur. I have a tough time getting a good look with stars whenever I have the camera moving i.e. spaceship flyby.

 

I hope all goes well.

Link to comment
Share on other sites

Thank you for your comment! When no one else was replying to this topic, I thought nobody was interested. :rolleyes: I'd be willing to make this material available to download, but I'd have to document how to use the star sphere, because it's a little tricky.

 

I forgot to mention that I applied an action to the original sphere of 2048 stars that created 15 duplicates of it as action objects, for a total of 32768 stars. These duplicates all have different rotations and are scaled -100% on 0, 1, 2, or all 3 axes.

 

I'll try to render a test animation demonstrating motion blur soon. I'm also going to produce the boneless fixed-size variants, and before I'm done with the whole project, I want to investigate the possibility of using an expression to automate changing the size of the stars based on the focal length and output resolution of the camera.

Link to comment
Share on other sites

I've been following this thread, but didn't have anything to say about the technical aspect so left it alone. I still don't reall understand the advantage of this? I don't do animation very much so would like to know if it's something to with that.

Link to comment
Share on other sites

I've been following this thread, but didn't have anything to say about the technical aspect so left it alone. I still don't reall understand the advantage of this? I don't do animation very much so would like to know if it's something to with that.

Okay, the advantage is this. Let's say we don't scale the stars, and we start off with the rendering I made in which the stars are the right size.

 

35 mm focal length, 640x480 resolution, star size 12 m:

starsphere_35-640-12.jpg

 

If we leave the stars at 12 meters, then when the frame is rendered at a lower resolution, the stars become almost too small to see.

 

35 mm focal length, 320x240 resolution, star size 12 m:

starsphere_35-320-12.jpg

 

And if we zoom the camera in, the stars become huge --

 

100 mm focal length, 640x480 resolution, star size 12 m:

starsphere_100-640-12.jpg

 

This is what I'm trying to avoid. I want to keep the stars at just the right size so that they occupy only one pixel on the screen and don't lose brightness.

Link to comment
Share on other sites

Here's an animation test where I'm trying to focus on motion blur. In case you're wondering, the TIE fighter is a 3DS prop that I downloaded from scifi3d.theforce.net. With the default A-buffer antialiasing, the motion blur is confined to a narrow band of stars in the middle of the picture. This is something I've observed on previous versions of A:M, and I don't know why it happens. Is there something that the Hash team can do to improve the default motion blur routines, or do we have to use multipass if we want better results?

 

tiefighter-abuffer.jpg

 

With 3x3 multipass, all the stars are motion blurred, but they look a little too aliased to me:

 

tiefighter-mp09.jpg

 

So I went with 4x4 multipass, and it does look good, but these three seconds of footage took me nearly 5 hours to render. This is one frame:

 

tiefighter-mp16.jpg

 

And here's the full scene: (video removed) Right at the end of the scene, when the camera is moving slowly, you can see the stars shimmering, and I'll have to try to find a way around that. Maybe I'll have to make the stars bigger, or increase the number of passes.

 

What really seems to be holding up the rendering time is the TIE fighter itself, especially in the middle when it's close to the screen. I could try to render the TIE fighter in A-buffer with no background and an alpha channel, render just the starfield with multipass, and then composite them together.

Link to comment
Share on other sites

I've had no luck with using an expression to automate the star size. The size of the stars is controlled by a pose slider that I called "Median star size in meters". I set the keyframes up so that the percentage of the pose reflects the original size of the stars -- i.e., the stars were 15 m in diameter before I randomized the size, and the default position of the slider is 15%. Right now, the slider goes between 5 m and 30 m, but I think I'll reset the minimum down to 0 since I do actually have keyframes at 0.

 

Anyway, in the choregraphy I tried to set the position of the pose slider equal to this expression:

0.9*10000*Tan((2*ATan(17.5/..|..|..|Shortcut to Camera1.Focal Length))/Max(..|..|..|Shortcut to Camera1.Output Options.Resolution.Width, ..|..|..|Shortcut to Camera1.Output Options.Resolution.Height))

 

...but trying to apply the expression causes A:M to crash. (The 0.9 at the beginning is a fudge factor to make the stars a little dimmer, and I left it there instead of saying "9000*..." to make it easier to make adjustments.) I'm going to give up on trying to automate the process, and I'll just leave it up to the user to keyframe the pose slider at the same time that the camera's focal length changes.

Link to comment
Share on other sites

Aw, don't feel bad about it. I feel pretty stupid and lonely myself for getting sidetracked for a month working on something that may be too complicated for most people to use. :unsure:

 

I made a little mistake on the math in the expression in my previous post. The 2 is in the wrong place, but considering how much smaller the stars are compared to the distance to them, the error only affects the sixth decimal place and beyond, so it has no real effect.

 

Attached is a proof that shows how to determine how big the stars need to be. I'm probably going to include this graphic in the documentation when I release this project, because it's crucial to the understanding of how the star sphere works.

 

Assume that the camera is located at the origin, inside a star sphere of radius R meters. The camera's focal length is f millimeters, and it is set to render an image whose largest dimension is W pixels. We want to solve for the diameter s of a star which will occupy one pixel in the render. Let angle A be the angle of the camera's field of view, and angle C be the angle subtended by the star.

post-7-1103578998.gif

Link to comment
Share on other sites

The thing that needs to be kept in mind is that the formula I derived:

 

s = 2R tan [atan (17.5 / f) / W]

 

shouldn't be used as an exact measure of the size you want to set the stars at. For instance, plugging in the values R = 10000 m, f = 35 mm and W = 640 gives me the value s = 14.49 meters. However, because I made the star size vary randomly between 50% and 150% of the median, it would actually look better if you set the star size to 12 meters instead, which is what I did in my previous examples.

Link to comment
Share on other sites

I've done one more animation test in which the camera zooms in and the stars shrink at the same rate. To try to save time, I rendered the stars with multipass and the TIE fighter with A-buffer antialiasing, and composited them together in After Effects.

 

Here's one frame as the camera is zooming and panning simultaneously:

tiefighter-zoom.jpg

 

(video removed)

 

Immediately after this post, I'm going to make the star sphere model available to download in the Showcase forum.

Link to comment
Share on other sites

  • 6 months later...
  • 7 years later...

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...