Recreating the Star Wars opening crawl in AutoCAD using F# – Part 4

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.

  1. The initial blue text
  2. The theme music
  3. The star field
  4. The disappearing Star Wars logo
  5. 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

Star Wars 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 bas
ed 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"< /span>); (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.

Leave a Reply

Your email address will not be published. Required fields are marked *