Happy Friday! It’s time to unveil the completed Star Wars opening crawl inside AutoCAD…
After an intro and seeing various pieces implemented, in today’s post we’re going to add the crawl text and animate its movement into the distance.
- The initial blue text
- The theme music
- The star field
- The disappearing Star Wars logo
- The crawling text
As the surprise “bonus” item 6, I decided to add a planet and – at the end of the crawl – shift the view downwards to show its surface: an effect I’ve seen in the opening crawl for at least one of the films (I forget which). The effect took me some work to get it looking right. If you run the code, try orbiting the view to see the relative positions… I ended up having to both pan and orbit to get effect I wanted. The planet itself could look better: I would have liked to give the surface some texture and even a translucent atmosphere, and some of the stars appear in the foreground… but you have to stop somewhere.
It’s in this post that we’ll finally use the Star Wars API to get the opening crawl text for the various episodes. We’ll ask the user to choose the episode to display, and get the text associated with that (although as the API understandably lists the films in order of cinematic release rather than the internal chronology, we get the data for all the films and select the text for the one with the matching episode number).
There isn’t very much to the whole thing: we create the crawl text based on what’s provided by the API, adding in some text for the episode number (in roman numerals) and title. The effect is OK: I struggled for some time to get the exact angle as used in the films, but didn’t quite manage it. Once again the speed of the crawl is likely to vary per-system – I haven’t taken the time or effort to get this to synchronise the timing perfectly for different hardware profiles.
Without further ado, here are videos of all six opening crawls. Yes, during a lull in my day I went ahead and captured all of them. The only difference between them is the crawl text contents: feel free to choose your favourite episode and just watch that. :-)
[Apologies for any advertising that pops up as you're watching: YouTube cleverly noticed the use of the (copyrighted) Star Wars theme music in the video, so I acknowledged the 3rd party content, giving the owner the right to remunerate people watching the videos. Fair enough, considering (I'm not using YouTube ads to generate revenue for myself). As long as they don't decide to pull the videos, which could still happen.]
Episode I: The Phantom Menace
Episode II: Attack of the Clones
Episode III: Revenge of the Sith
Episode IV: A New Hope
Episode V: The Empire Strikes Back
Episode VI: Return of the Jedi
Episode VII: The Force Awakens
Here’s the final F# code. The code for accessing SWAPI is really greatly simplified thanks to the use of F#’s JSON Type Provider (although in a real application I’d have added code to catch the exception thrown when the service is inaccessible).
module StarWars.Crawler
open Autodesk.AutoCAD.ApplicationServices
open Autodesk.AutoCAD.ApplicationServices.Core
open Autodesk.AutoCAD.DatabaseServices
open Autodesk.AutoCAD.EditorInput
open Autodesk.AutoCAD.Geometry
open Autodesk.AutoCAD.Runtime
open FSharp.Data
open System
open System.Windows.Media
// Our JSON Type Provider providing access to SWAPI
type Film = JsonProvider<"http://swapi.co/api/films">
// The intro music MP3 file
let mp3 =
"http://s.cdpn.io/1202/Star_Wars_original_opening_crawl_1977.mp3"
// The layers we want to create as a list of (name, (r, g, b))
let layers =
[
("Stars", (255, 255, 255));
("Intro", (75, 213, 238));
("Crawl", (229, 177, 58));
("Planet", (238, 232, 170))
]
// Create layers based on the provided names and colour values
// (only creates layers if they don't already exist... could be
// updated to make sure the layers are on/thawed and have the
// right colour values)
let createLayers (tr:Transaction) (db:Database) =
let lt =
tr.GetObject(db.LayerTableId, OpenMode.ForWrite) :?> LayerTable
layers |>
List.iter (fun (name, (r, g, b)) ->
if not(lt.Has(name)) then
let lay = new LayerTableRecord()
lay.Color <-
Autodesk.AutoCAD.Colors.Color.FromRgb(byte r, byte g, byte b)
lay.Name <- name
lt.Add(lay) |> ignore
tr.AddNewlyCreatedDBObject(lay, true)
)
// Get a view by name
let getView (tr:Transaction) (db:Database) (name:string) =
let vt =
tr.GetObject(db.ViewTableId, OpenMode.ForRead) :?> ViewTable
if vt.Has(name) then
tr.GetObject(vt.[name], OpenMode.ForRead) :?> ViewTableRecord
else
null
// Add an entity to a block and a transaction
let addToDatabase (tr:Transaction) (btr:BlockTableRecord) o =
btr.AppendEntity(o) |> ignore
tr.AddNewlyCreatedDBObject(o, true)
// Flush the graphics for a particular document
let refresh (doc:Document) =
doc.TransactionManager.QueueForGraphicsFlush()
doc.TransactionManager.FlushGraphics()
// Transform between the Display and World Coordinate Systems
let dcs2wcs (vtr:AbstractViewTableRecord) =
Matrix3d.Rotation(-vtr.ViewTwist, vtr.ViewDirection, vtr.Target) *
Matrix3d.Displacement(vtr.Target - Point3d.Origin) *
Matrix3d.PlaneToWorld(vtr.ViewDirection)
// Poll until a music file has downloaded fully
// (could sleep or use a callback to avoid this being too
// CPU-intensive, but hey)
let rec waitForComplete (mp:MediaPlayer) =
if mp.DownloadProgress < 1. then
System.Windows.Forms.Application.DoEvents()
waitForComplete mp
// Poll until a specified delay has elapsed since start
// (could sleep or use a callback to avoid this being too
// CPU-intensive, but hey)
let rec waitForElapsed (start:DateTime) delay =
let elapsed = DateTime.Now - start
if elapsed.Seconds < delay then
System.Windows.Forms.Application.DoEvents()
waitForElapsed start delay
// Run operation f n times and then wait until
// a specified delay has elapsed since start
let rec performNTimesOrUntilElapsed (start:DateTime) delay f n =
let elapsed = DateTime.Now - start
if n > 0 || elapsed.Seconds < delay then
if n > 0 then f()
System.Windows.Forms.Application.DoEvents()
performNTimesOrUntilElapsed start delay f (n-1)
// Get the roman numerals for an integer
let roman n =
let numerals =
[(1000, "M"); (900, "CM"); (500, "D"); (400, "CD"); (100, "C");
(90, "XC"); (50, "L"); (40, "XL"); (10, "X"); (9, "IX");
(5, "V"); (4, "IV"); (1, "I")]
let rec acc (v, r) (m, s) =
if (v < m) then (v, r) else acc (v-m, r+s) (m, s)
List.fold acc (n, "") numerals |> snd
// Get info on all 6 Star Wars episodes
let allFilms =
let films = Film.Load("http://swapi.co/api/films")
films.Results
// Create the intro text as an MText object relative to the view
// (has a parameter to the function doesn't execute when loaded...
// also has hardcoded values that make it view-specific)
let createIntro _ =
let mt = new MText()
mt.Contents <-
"{\\fFranklin Gothic Book|b0|i0|c0|p34;" +
"A long time ago, in a galaxy far,\\Pfar away...}"
mt.Layer <- "Intro"
mt.TextHeight <- 0.5
mt.Width <- 10.
mt.Normal <- Vector3d.ZAxis
mt.TransformBy(Matrix3d.Displacement(new Vector3d(1., 6., 0.)))
mt
// Generate a quantity of randomly located stars... a list of (x,y)
// tuples where x and y are between 0 and 1. These will later
// get transformed into the relevant space (on the screen, etc.)
let locateStars quantity =
// Create our random number generator
let ran = new System.Random()
// Note: _ is only used to make sure this function gets
// executed when it is called... if we have no argument
// it's a value that doesn't require repeated execution
let randomPoint _ =
// Get random values between 0 and 1 for our x and y coordinates
(ran.NextDouble(), ran.NextDouble())
// Local recursive function to create n stars at random
// locations (in the plane of the screen)
let rec randomStars n =
match n with
| 0 -> []
| _ -> (randomPoint 0.) :: randomStars (n-1)
// Create the specified number of stars at random locations
randomStars quantity
// Take locations from 0-1 in X and Y and place them
// relative to the screen
let putOnScreen wid hgt dcs (x, y) =
// We want to populate a space that's 2 screens high (so we
// can pan/rotate downwards at the end of the crawl), hence
// the additional multiplier on y
let pt = new Point3d(wid * (x - 0.5), hgt * ((y * -1.5) + 0.5), 0.)
pt.TransformBy(dcs)
// Create a polyline from a vertex list
let polyFromVerts n w m lay verts =
let pl = new Polyline(List.length verts)
pl.Normal <- n
List.iteri
(fun i v ->
pl.AddVertexAt(i, new Point2d(fst v, snd v), 0., w, w)
)
verts
pl.Closed <- true
pl.TransformBy(m)
pl.Layer <- lay
pl
// Create the Star Wars logo from a list of vertex lists
let createLogo n w (p:Point3d) =
let verts =
[
[
(3.44258, 3.55679214868466);
(3.44258, 3.17553);
(2.8459, 3.17553);
(2.8459, 2.26434);
(2.40686, 2.26434);
(2.40686, 3.17612262061896);
(1.75655462335279, 3.17429248415801);
(1.72128333960472, 3.16669182023216);
(1.70763551483587, 3.15445862216978);
(1.70053119527792, 3.13782570874412);
(1.69872472722736, 3.12280350074472);
(1.69905468222644, 3.1191373340882);
(1.70087251722259, 3.11283393221843);
(1.70607593564196, 3.10180962306061);
(1.71621799674036, 3.08484068174517);
(1.73226620616926, 3.06151479594735);
(1.75494342528422, 3.03081841447927);
(1.78484748611009, 2.99191745820677);
(1.82294826291299, 2.94341738705363);
(1.87617804512918, 2.87460815638392);
(1.92246977710667, 2.81233246534401);
(1.95733932573181, 2.76278154528292);
(1.97888966827266, 2.72730122581048);
(1.99798396461003, 2.61953152517619);
(1.97570333908842, 2.50537461239132);
(1.91940141039899, 2.40109844384521);
(1.83646768287547, 2.32393324739609);
(1.80352751564867, 2.30404166777099);
(1.77133382731068, 2.28892985436458);
(1.73039688525943, 2.27813889539216);
(1.6754267050528, 2.27134897179126);
(1.59812878352077, 2.26745012799826);
(1.48927961179153, 2.26536230567172);
(1.33992701030606, 2.26450009692313);
(1.14136357854396, 2.26434001353564);
(1.04351, 2.26434);
(0.353207, 2.26434);
(0.353207, 2.68233);
(1.40159102170787, 2.68233);
(1.41748209051223, 2.69338144406387);
(1.43267712171974, 2.70939240011938);
(1.44055338604423, 2.73157466234716);
(1.44119451950013, 2.7367666633397);
(1.44105860097375, 2.73978455802721);
(1.43997063735183, 2.74322220229697);
(1.43439829934938, 2.75301581981413);
(1.4213959120805, 2.77071589918082);
(1.39979463633981, 2.7968603003749);
(1.36874714824015, 2.83262747962015);
(1.32697107584659, 2.87983734880692);
(1.23683919774444, 2.98529715322583);
(1.18339822046774, 3.06424778707422);
(1.16026089514802, 3.13669377471704);
(1.15987518361763, 3.21768150787052);
(1.18779743410553, 3.32756187144587);
(1.25599700588018, 3.43654449583423);
(1.36395601807001, 3.52111523123897);
(1.50483659505006, 3.55390286925382)
];
[
(4.39994870002626, 3.54702);
(4.84743214991296, 2.25278501118727);
(4.41662097329373, 2.25517027344357);
(4.36118115868732, 2.43645696748207);
(3.81392247714979, 2.4388933038331);
(3.75959503424206, 2.26439);
(3.32291291955518, 2.26439);
(3.76562469596367, 3.54702)
];
[
(3.93947639266304, 2.78028246119739);
(4.0036225829752, 2.96145971580626);
(4.09333768797878, 3.21205266525835);
(4.17869162137176, 2.9611015523335);
(4.23989035006252, 2.78062648416092)
];
[
(5.54586280271403, 3.54702);
(5.73207467832675, 3.54636901457996);
(5.88112842531212, 3.54435138355133);
(5.98711334518623, 3.54111056421452);
(6.04631520398901, 3.53650379705603);
(6.11453578628832, 3.51811946058964);
(6.17810911285892, 3.48782673131194);
(6.23583237180861, 3.44644129584782);
(6.28596484458466, 3.39522327740142);
(6.32595493380338, 3.3422331096743);
(6.35270689747694, 3.29157143345469);
(6.3680867123442, 3.23557047460095);
(6.37643733628869, 3.16568660578167);
(6.36992931993028, 3.05606170860937);
(6.33210762885031, 2.95400067046692);
(6.26513711406835, 2.86555263333169);
(6.17366080953793, 2.79497351541438);
(6.14309800665153, 2.7769730033941);
(6.11700705528388, 2.76203194437172);
(6.1127843266014, 2.7596756036369);
(6.13885495853737, 2.73241774032095);
(6.17040343063679, 2.70456002005355);
(6.19335603450253, 2.68673568368009);
(6.86633739559967, 2.67707780533635);
(6.8742792314147, 2.26439);
(6.43526528939969, 2.26439);
(6.2418481178589, 2.26499101663952);
(6.11292604672176, 2.26701660378081);
(6.03406510582276, 2.27102207373412);
(5.9876655077232, 2.2786416155789);
(5.93407000168671, 2.30917788766821);
(5.85931623340334, 2.36822655452807);
(5.75524877978626, 2.46058692597491);
(5.61622131796047, 2.59075075972907);
(5.53661647932702, 2.66649674082322);
(5.5380039627306, 2.26439);
(5.0687, 2.26439);
(5.0687, 3.54702)
];
[
(5.53061, 3.22193);
(5.67562434528338, 3.22193);
(5.76549694080276, 3.22143652653738);
(5.8227611211413, 3.21969654059112);
(5.85910049504742, 3.21603930873005);
(5.8835168373915, 3.20907338608929);
(5.91186718137154, 3.19493342317029);
(5.93449705925347, 3.17520079608985);
(5.9461460801979, 3.14851545023844);
(5.94993368385409, 3.1219164675812);
(5.95063789889132, 3.09471968284322);
(5.94382358152697, 3.06635439430217);
(5.92659250539508, 3.04172336065536);
(5.90396433067426, 3.02224480067035);
(5.89094323070014, 3.01257677365093);
(5.85401438673261, 3.00939777579999);
(5.81195751173521, 3.00827403940718);
(5.75772390976083, 3.00788023741928);
(5.69388269742043, 3.00778);
(5.53061, 3.00778)
];
[
(1.57896862502186, 2.10589);
(1.67880828853815, 1.81260822369826);
(1.71379594628957, 1.71227377415791);
(1.73527827819659, 1.65374851057995);
(1.75920847744905, 1.72143678846549);
(1.79238473443157, 1.81629031452661);
(1.82529369636899, 1.9108498644361);
(1.85381147382987, 1.99319569627039);
(1.87388905179331, 2.05149770143778);
(1.8912822178088, 2.10154);
(2.32183990237932, 2.10154);
(1.87988984918951, 0.813874);
(1.58239936659396, 0.813874);
(1.56171424001044, 0.873466415317032);
(1.53295553769481, 0.956661589981072);
(1.49226696124087, 1.07432847254541);
(1.44570537030483, 1.20935203857834);
(1.39914465363298, 1.34447313546997);
(1.35858792560323, 1.46260098506068);
(1.35731504835852, 1.46634287831389);
(1.12278635463374, 0.823096);
(0.828087008371704, 0.823096);
(0.391580354628849, 2.10592925972778);
(0.820152580844014, 2.10564013321748);
(0.837989023926543, 2.05516759780397);
(0.859154236649471, 1.99519000485767);
(0.889137620919735, 1.91045548589063);
(0.92363489102879, 1.81330725414169);
(0.95817174136269, 1.71621235357281);
(0.979127616594254, 1.65771496109079);
(1.13684321996318, 2.10589)
];
[
(3.28904785776228, 2.11550813590997);
(3.73761724891686, 0.824937551963215);
(3.30662126941653, 0.822954990623427);
(3.24971008910397, 1.00471601224713);
(2.69993521628902, 1.00914873793685);
(2.64593333868056, 0.827492143980233);
(2.21818710344123, 0.831947254825196);
(2.6611172170532, 2.11491182100224)
];
[
(2.83075227905127, 1.35366041729816);
(2.87923452796852, 1.48620742282014);
(2.91710514665256, 1.58972911414478);
(2.98271654250203, 1.76897755710787);
(3.04320129635346, 1.59028356566733);
(3.05209063261682, 1.56402552642664);
(3.06700400017455, 1.52004510187737);
(3.0854341923654, 1.46582399091646);
(3.12362427253239, 1.35375);
(2.96565, 1.35375)
];
[
(6.86738701658834, 2.11055);
(6.86636282954738, 1.71143316113769);
(6.80968306747308, 1.71166033453278);
(6.76879036498295, 1.71182022759786);
(6.70799358315783, 1.71199016269997);
(6.63307325593112, 1.7122001636159);
(6.55001968254444, 1.71242014609799);
(6.46487686643307, 1.71259008296755);
(6.38353162001043, 1.71274);
(6.3119, 1.71274);
(6.27257783369629, 1.71274);
(6.24205274248879, 1.71264056973548);
(6.22660212251127, 1.7125107967416);
(6.2251332275232, 1.71152567832907);
(6.21826693670348, 1.69895889104225);
(6.21368777582741, 1.67971131922799);
(6.21435024322066, 1.66802861722527);
(6.21739642079077, 1.6617633178101);
(6.23123505854596, 1.64006984102296);
(6.25133503104544, 1.61172470022028);
(6.27506976405328, 1.58087731010094);
(6.30343445446908, 1.54536194073766);
(6.33613016667173, 1.50446982038211);
(6.36882765268814, 1.46364795338937);
(6.39759648916047, 1.42768942522637);
(6.42698279823146, 1.39058529555372);
(6.45061281453046, 1.35962111552111);
(6.46946283661554, 1.33294897542795);
(6.48427168722091, 1.30886051606526);
(6.49558070778541, 1.2857968585775);
(6.50373858922054, 1.26288773947885);
(6.50912773260329, 1.23975035055556);
(6.51327721344814, 1.21302571797147);
(6.51175336049161, 1.12736501317236);
(6.48652470110405, 1.04165809320606);
(6.44008974336086, 0.963007688635264);
(6.37622624858673, 0.897126531506992);
(6.34136126462528, 0.869847315902196);
(6.30454078761051, 0.848196296706673);
(6.25735068769598, 0.832813348711309);
(6.1946926284406, 0.82334703105736);
(6.10791480816548, 0.817863341131013);
(5.98694154482626, 0.815070019097485);
(5.82230156071494, 0.814036130538472);
(5.60480157525874, 0.813819020750331);
(5.57579464995636, 0.813819);
(5.31033615083933, 0.814524554163534);
(5.1147019020476, 0.816483302483575);
(4.987492484323, 0.819719154126808);
(4.92211642121057, 0.824809172133531);
(4.86255265351012, 0.851353630113647);
(4.78599624454765, 0.907412194416292);
(4.67949985067332, 1.00068616876809);
(4.53106852156296, 1.14052267830742);
(4.47120745637841, 1.19730089863496);
(4.43722713582279, 1.22872889310876);
(4.43479568627557, 0.813638441379537);
(3.97790059619618, 0.815626482435268);
(3.9754088950639, 2.09644);
(4.85435350955452, 2.09644);
(4.9524384120744, 2.08116590914619);
(5.0421132756076, 2.04495127286902);
(5.11968748422517, 1.9926567681356);
(5.18396561975463, 1.92855365816238);
(5.23369876113581, 1.8565584931861);
(5.26760265295347, 1.78012486674058);
(5.28395979085181, 1.70241419345942);
(5.28004510702221, 1.62839637688364);
(5.25918823660479, 1.55956085936412);
(5.23129767989095, 1.49769758838801);
(5.19287713035147, 1.44636773420326);
(5.14201539837177, 1.39663698792505);
(5.08439692602106, 1.35247865701708);
(5.02763448373853, 1.32012290369814);
(5.01926153493527, 1.31567062139799);
(5.0201401696421, 1.31465755077974);
(5.03635825437325, 1.29789855762624);
(5.05404454395319, 1.28126513332677);
(5.07123638095395, 1.26662725426366);
(5.08545995358084, 1.25609066762876);
(5.09155854789262, 1.25261446887105);
(5.11520296200932, 1.24773262641588);
(5.1839384665362, 1.2437210842384);
(5.31499237416825, 1.24167909762829);
(5.52681922465014, 1.24108996855417);
(5.63722185856806, 1.24103002684722);
(5.75066647981753, 1.24129939671563);
(5.83424919549954, 1.24231918109318);
(5.89196733024185, 1.24477834974135);
(5.92703259711752, 1.248815584514);
(5.94186413459469, 1.25312246895092);
(5.94452720971008, 1.2550352046588);
(5.94806585844389, 1.26045738811548);
(5.95485974741274, 1.27515168783283);
(5.95764005574495, 1.28273863090887);
(5.95785068079404, 1.28531001171654);
(5.95662977011935, 1.29060599364051);
(5.95018212309273, 1.30349479459504);
(5.9355344205503, 1.32495058423466);
(5.9118829225398, 1.3549665197346);
(5.87849452603907, 1.39425562054069);
(5.83444545149862, 1.44431638015785);
(5.73724255457614, 1.5576766597549);
(5.68852920511552, 1.65032448217977);
(5.6739520592985, 1.72315250284916);
(5.67339629718887, 1.7776616505618);
(5.67582917215106, 1.81532530917471);
(5.6869349467792, 1.86440938045866);
(5.70875633588307, 1.91894581413262);
(5.74384006043308, 1.97499767954953);
(5.79476342169442, 2.02753710966927);
(5.86330983842433, 2.07103004485607);
(5.95041130228872, 2.10023544319704);
(6.0548074435266, 2.11055)
];
[
(4.4345937394936, 1.56212643206149);
(4.43518042620646, 1.67044148437216);
(4.43556787492364, 1.77298062965549);
(4.59383378518243, 1.77217924888779);
(4.64881620553342, 1.77178759329077);
(4.70616920376935, 1.77085699790493);
(4.75610194813188, 1.76893449843586);
(4.79998632985362, 1.76368084421198);
(4.84719213337829, 1.71746157785606);
(4.8610886482744, 1.66244080023347);
(4.85668777505293, 1.63727739146715);
(4.84628131477829, 1.61325711967745);
(4.82603724633321, 1.58950045930602);
(4.79564620691826, 1.57081593809471);
(4.76918190987008, 1.56332632934342);
(4.73276899886416, 1.5595967363527);
(4.67535969661275, 1.55779395371568);
(4.58510256995686, 1.55724810481441);
(4.52224299866422, 1.5581940498193)
]
]
// Displace our vertices to the specified point and then
// apply a hardcoded, view-specific scaling
let m1 = Matrix3d.Displacement(p.GetAsVector())
let m = m1 * Matrix3d.Scaling(1.4, p + new Vector3d(3.5,0.,0.))
verts |> List.map (polyFromVerts n w m "Crawl")
// Format the "opening crawl" text appropriately for MText
let crawlText num (title:string) (text:string) =
let lines =
text.Split([|"\r\n"|], StringSplitOptions.None) |>
Array.toList
let rec convert (lns:string list) =
match lns with
| [] -> ""
| (x::xs) ->
let ln =
if xs = [] || (xs <> [] && xs.Head = "") then
"\\pql;" + x + "\\P"
else
"\\pqd;" + x + "\\P"
ln + convert xs
String.Format(
"\\pxsm1.5,qc;{{\\fFranklin Gothic Demi|b0|i0|c0|p34;" +
"Episode {0}\\P\\P\\fFranklin Gothic Medium Cond|b0|i0|c0|p34;" +
"\\H1.3333x;\\W0.95;{1}\\P\\fFranklin Gothic Demi|b0|i0|c0|" +
"p34;\\H0.74999x;\\W1;\\P\\pqd;\\H0.83334x;{2}}}",
roman num, title.ToUpper(), convert lines
)
// Create the crawl text object with the provided text contents
// (again with hardcoded values that are view-specific)
let createCrawlText text =
// Create our MText object
let mt = new MText()
// Set its contents and other properties
mt.Contents <- text
mt.Layer <- "Crawl"
mt.TextHeight <- 0.3
mt.Width <- 6.
mt
// Move the crawl text through space in a number of increments
let moveThroughSpace (doc:Document) (ent:Entity) steps delay =
// Record our start time, so we stop when delay has elapsed
let start = DateTime.Now
// Specify our displacement increment
let mat = Matrix3d.Displacement(new Vector3d(0.,0.2,0.))
// Define a local recursive function to move the object
// n times (but to check for user escape and terminate, as
// needed)
let rec moveLoop n =
ent.TransformBy(mat)
refresh doc
let elapsed = DateTime.Now - start
if n > 0 &&
not(
(HostApplicationServices.Current.UserBreak() ||
elapsed.TotalSeconds >= delay)
) then
moveLoop (n-1)
// Move the MText a number of times along the Y axis
moveLoop steps
// Commands to recreate the open crawl experience for a selected
// Star Wars episode
[<CommandMethod("EPISODE")>]
let episode() =
// Make sure the active document is valid before continuing
let doc = Application.DocumentManager.MdiActiveDocument
if doc <> null then
let db = doc.Database
let ed = doc.Editor
// Ask for the episode number
let pso = new PromptIntegerOptions("\nEnter episode")
pso.LowerLimit <- 1
pso.UpperLimit <- 6 // Change to 7 in December 2015... :-)
let psr = ed.GetInteger(pso)
if psr.Status = PromptStatus.OK then
// Start our transaction and create the required layers
use tr = doc.TransactionManager.StartTransaction()
createLayers tr db
// Get our special Initial and Crawl views
let ivtr = getView tr db "Initial"
let cvtr = getView tr db "Crawl"
if ivtr = null || cvtr = null then
ed.WriteMessage(
"\nPlease load StarWarsCrawl.dwg before running command.")
doc.TransactionManager.EnableGraphicsFlush(true)
let btr =
tr.GetObject(doc.Database.CurrentSpaceId, OpenMode.ForWrite)
:?> BlockTableRecord
// Set the initial view: this gives us higher quality text
ed.SetCurrentView(ivtr)
// First we create the intro text
let intro = createIntro ()
intro |> addToDatabase tr btr
// Make sure the intro text is visible
doc |> refresh
ed.UpdateScreen()
// We'll now perform a number of start-up tasks, while our
// initial intro text is visible... we'll start vy recording
// our start time, so we can synchronise our delay
let start = DateTime.Now
// Get our view's DCS matrix
let dcs = dcs2wcs(cvtr)
// Create our planet with hardcoded, view-specific values
// (do it here as demand-loading ASM may take time)
let planet = new Solid3d()
planet.CreateSphere(3.)
planet.Layer <- "Planet"
let bot =
Point3d.Origin.TransformBy(dcs) +
new Vector3d(0.,-1.1,-3.2)
planet.TransformBy(Matrix3d.Displacement(bot.GetAsVector()))
planet |> addToDatabase tr btr
// Create a host of stars at random screen positions
locateStars 1000 |>
List.iter
(fun xy ->
let p = putOnScreen cvtr.Width cvtr.Height dcs xy
let dbp = new DBPoint(p)
dbp.Layer <- "Stars"
dbp |> addToDatabase tr btr)
// Open the intro music over the web
let mp = new MediaPlayer()
mp.Open(new Uri(mp3))
// Wait for the download to complete before playing it
waitForComplete mp
// Get the data associated with the specified episode
// (this can take some time)
let epNum = psr.Value
let epInfo =
allFilms |>
Array.pick
(fun e -> if e.EpisodeId = epNum then Some(e) else None)
// Have a minimum delay of 5 seconds showing the intro text
waitForElapsed start 5
// Start the audio at 8.5 seconds in
mp.Position <- new TimeSpan(0, 0, 0, 8, 500)
mp.Play()
// Switch to the crawl view: this will also change the
// visual style from 2D Wireframe to Realistic
ed.SetCurrentView(cvtr)
// Remove the intro text
intro.Erase()
// Draw the SW logo and "move it away" from the camera,
// once again with hardcoded, view-specific values
let es =
createLogo cvtr.ViewDirection 0.0833
(new Point3d(-1.,-5.,0.))
es |> List.iter (addToDatabase tr btr)
// Use a simple scaling to give the impression of movement
// (yes, with hardcoded, view-specific values)
let rec moveAway (es:Polyline list) n =
let m = Matrix3d.Scaling(0.91, new Point3d(3.,12.,0.))
es |>
List.iter
(fun e ->
e.TransformBy(m)
refresh doc
System.Threading.Thread.Sleep(15))
if n > 0 &&
not(HostApplicationServices.Current.UserBreak()) then
moveAway es (n-1)
moveAway es 40
// Remove the polylines making up the logo
es |> List.iter (fun e -> e.Erase())
if not(HostApplicationServices.Current.UserBreak()) then
// Create the crawl text
let crawl =
crawlText epNum epInfo.Title epInfo.OpeningCrawl |>
createCrawlText
crawl |> addToDatabase tr btr
// Start moving it through space, with 300 steps or for 68
// seconds (whichever comes first)
moveThroughSpace doc crawl 300 68.
crawl.Erase()
// After the crawl is done, rotate the view down onto
// the surface of the planet
if not(HostApplicationServices.Current.UserBreak()) then
// We'll display the target by a portion of the distance
// between the top set of stars and the bottom
let pt2 = new Point3d(0., -0.5 * cvtr.Height, 0.)
let dpt1 = Point3d.Origin.TransformBy(dcs)
let dpt2 = pt2.TransformBy(dcs)
let del = (dpt2 - dpt1) / 100.
let disp = Matrix3d.Displacement(del)
// We'll rotate the view direction at the same time,
// so that the planet appears at the bottom
let rot =
Matrix3d.Rotation(-0.005, Vector3d.XAxis, cvtr.Target)
// Our view table record needs to be writeable, of course
cvtr.UpgradeOpen()
// Perform the specified view changes 100 times,
// waiting until 12 seconds have elapsed
performNTimesOrUntilElapsed DateTime.Now 12
(fun f ->
cvtr.Target <- cvtr.Target.TransformBy(disp)
cvtr.ViewDirection <-
cvtr.ViewDirection.TransformBy(rot)
ed.SetCurrentView(cvtr)
System.Threading.Thread.Sleep(50)
)
100
tr.Commit() // Commit the transaction
mp.Stop() // Stop the music
I hope you’ve found this series entertaining, if not particularly useful. It was certainly a fun diversion – for me, at least – to work through the various challenges around getting this to work with a tolerable level of quality. It never ceases to amaze me what you can do inside AutoCAD with the right programming language, a bit of time and a mildly obsessive personality. :-)
Update:
See this more recent post for the opening crawl for Episode VII: The Force Awakens.