Return to “Scripting & Modding”

Post

LTSL Code Development Update #19

#1
Hey there since nobody had copied the code to the forum yet, i thought it will make a great addition.
thesilverspanch wrote:Has anyone gone into the dev update video yet and screen captured the code he used to generate the ships/icons?
Of course ;), as a computer science and engineering major currently graduating in visualization and algorithms this is the most interesting update yet, after lurking here from one of the earliest updates i really like the design/architecture simplicity that Josh always talks about. Hence I am going to study each and every piece of LTSL.

That said, the plate-mesh algorithm for generating ships is on the top of my list, if I get it correct the ships are defined implicitly by taking the union of multiple plate-mesh type distance functions, these are themselves boxes with a certain bezel (curvature), as in http://www.iquilezles.org/www/articles/ ... ctions.htm. If i can squeeze in the time between my graduation project maybe ill attempt to write a LTSL parser and try to generate those implicit function definitions (ships), which then could be visualized with something like marching cubes iso-surface extraction. Or is sombody else already attempting this ?
Ship generator:

Code: Select all

function (ShipType_Generate (float scale)(int seed))
	local kBevel 0.25
	local kPlates 8
	
	function (GetAxis (RNG rng))
		local side (Int rng 6)
		switch
			case (== side 0) (Vec3 -1  0  0)
			case (== side 1) (Vec3  1  0  0)
			case (== side 2) (Vec3  0 -1  0)
			case (== side 3) (Vec3  0  1  0)
			case (== side 4) (Vec3  0  0 -1)
			default			 (Vec3  0  0  1)
			
	local self PlateMesh
	local rng (RNG_MTG (+ seed 27))
	local s (Vec3 1.0 1.0 (* 1.0 (^ scale 0.1)))
	
	switch
		case false
			+= self (Plate 0 s 0 0.25)
			+= self (Plate 0 (Vec3 4 1   2) 0 0.5)
			+= self (Plate 0 (Vec3 4 1.5 2) 0 0.5)
			+= self (Plate (Vec3  4 0 0) (Vec3 0.5 0.5 8) 0 0.5)
			+= self (Plate (Vec3 -4 0 0) (Vec3 0.5 0.5 8) 0 0.5)
		
		case true
			local centers (List (Vec3 0 1 0))
			local sizes (List s)
			local centersc (List (Vec3 0 1 0))
			
	Append centers (Vec3 0 1 0)
	Append centers (Vec3 0 1 0)
	Append sizes (* s (Vec3 2 1 0.5))
	Append sizes (* s (Vec3 0.5 0.5 2))
	
	for i 0 (< i kPlates) (++ i)
		local index (Int rng (Size centers))
		local center (Vec3 (Get centers i))
		local size (Vec3 (Get sizes i))
		local dir (GetAxis rng)
		local newSize (* size (RNG_Vec3 rng 0.5 1.0))
		local newCenter (+ center (* dir (+ size newSize))
		local reps (RNG_Int rng 3 3)
		
		for j 0 (< j reps)(++ j)
			Append centers newCenter;
			Append sizes newSize;
			Append centersc newCenter;
			Append sizesc (* newSize (Vec3 0.5 0.5 4.0))
			set newCenter (+ newCenter (* (Vec3 0 0 4.0) newSize))
			set newSize (* newSize 0.9)
			
	for i 0 (< i (Size centersc))(++ i)
		Append centers (Vec3 (Get centersc i))
		Append sizes (Vec3 (Get sizesc i))
		
	for i 0 (< i (Size centers))(++ i)
		local b (RNG_float rng 0.1 0.6)
		local p (Vec3 (Get centers i))
		local s (Vec3 (Get sizes i))
		+= self (Plate (* (Vec3  1.0 1.0 1.0) p)(* (Vec3 1 1.0 1) s) 0 b)
		+= self (Plate (* (Vec3 -1.0 1.0 1.0) p)(* (Vec3 1 1.0 1) s) 0 b)
self
Icon genration:

Code: Select all

function Main
	function (ProceduralIcon (RNG rng))
		local kGlyphs 4
		local icon Icon
		
		for i 0 (< i kGlyphs)(++ i)
			local a (Vec2 (RNG_Float rng) (RNG_Float rng))
			local b (Vec2 (RNG_Float rng) (RNG_Float rng))
			local ar (* a (Vec2 -1 1))
			local br (* a (Vec2 -1 1))
			local r (RNG_Float rng)
			
			Add icon
				Glyph_Line a b 1 0.2
			Add icon
				Glyph_Line ar br 1 0.2
			Add icon (Glyph_Circle a r 1 1)
			Add icon (Glyph_Circle b r 1 1)
			Add icon (Glyph_Circle ar r 1 1)
			Add icon (Glyph_Circle br r 1 1)

		icon
			
		local kNodes 16
		local children (List (Node_Label "Check these out!"))
		local rng (RNG_MTG 12)
		
		for i 0 (< i kNodes)(++ i)
			Append children
				Node_Custom
					+ "Icon" i
					RNG_Vec3 rng 0 1
					ProceduralIcon rng
					List (Node_Label "blah")
					
		Node_Custom
			"My Custom. Scripted Node"
			Vec3 1 0 0.1
			Icon
			children
		
		# We did not see more than this ......
Last edited by eddyofinus on Mon Aug 04, 2014 3:50 am, edited 6 times in total.
Post

Re: LTSL Code Development Update #19

#3
OK lets go through it and see whats really going on here with this LTSL stuff.

Code: Select all

function (ShipType_Generate (float scale)(int seed)
   local kBevel 0.25
   local kPlates 8
   
Fairly straightforward function definition here, the only weird part (as a c++ guy) is the lack of a return value, but I can roll with that.
Note that the function name in inside the first parens (not sure why he does this). So to make a new function named hello_world, we would do "function (hello_world)" or something like that.

next we have

Code: Select all

   function (GetAxis (RNG rng))
      local side (Int rng 6)
      switch
         case (== side 0) (Vec3 -1  0  0)
         case (== side 1) (Vec3  1  0  0)
         case (== side 2) (Vec3  0 -1  0)
         case (== side 3) (Vec3  0  1  0)
         case (== side 4) (Vec3  0  0  1)
         default          (Vec3  0  0  1)
         
That switch isnt the only weird thing going on here. Check out that variable. Its apparent type is "local," which I think is how we are supposed to mark the scope of the variable. Also not sure what the (Int rng 6) does exactly, but Im going to guess that it returns an Int in the range of 0-6 from the built in rng.

Note how the switch isnt a c++ style switch where you have to put in a specific variable, but instead just has a series of boolian expressions followed by what should happen if that expression is true. Also no break statements, which makes me think that the language will just always break out after executing the first true expression it finds. It took me a second to figure out that the (Vec3 0 0 1) actually returns that Vec3, which is what terminates the function.

Code: Select all

   local self PlateMesh
   local rng (RNG_MTG (+ seed 27))
   local s (Vec3 1.0 1.0 (* 1.0 (^ scale 0.1)))
   
Back to the exterior function. Note how self is set to PlateMesh. Im assuming this is changing the type of self so that we can return it later. Also setting up a new random number from the (RNG_MTG (+ seed 27)). Im seeing this Vec3 type all over the place, which I thought was a 3d vector, but might just be a c++ vector with three elements.

Code: Select all

   switch
      case false
         += self (Plate 0 s 0 0.25)
         += self (Plate 0 (Vec3 4 1   2) 0 0.5)
         += self (Plate 0 (Vec3 4 1.5 2) 0 0.5)
         += self (Plate (Vec3  4 0 0) (Vec3 0.5 0.5 8) 0 0.5)
         += self (Plate (Vec3 -4 0 0) (Vec3 0.5 0.5 8) 0 0.5)
      
      case true
         local centers (List (Vec3 0 1 0))
         local sizes (List s)
         local centersc (List (Vec3 0 1 0))
   
Honestly Im not sure whats going on here, I think like the switch above that it is implicitly switching off the variable that came before it, but not 100% sure. If the switch is going off of self, in which case it could be that the cases would be equivalent to the c++ "case: self == NULL" and "case: self != NULL." If the PlateMesh self was set as is false, we mess around with the internal variable "Plate" and then exit, if self is true we initialize some essential values we'll need later. I assume that if self == false then these values are already setup?

Code: Select all

   Append centers (Vec3 0 1 0)
   Append centers (Vec3 0 1 0)
   Append sizes (* s (Vec3 2 1 0.5))
   Append sizes (* s (Vec3 0.5 0.5 2))
  
   for i 0 (< i kPlates) (++ i)
      local index (Int rng (Size centers))
      local center (Vec3 (Get centers i))
      local size (Vec3 (Get sizes 1))
      local dir (GetAxis rng)
      local newSize (* size (RNG_Vec3 rng 0.5 1.0))
      local newCenter (+ center (* dir ** size newSize))
      local reps (RNG_Int rng 3 3)
      
      for j 0 (< j reps)(++ j)
         Append centers newCenter;
         Append sizes newSize;
         Append centersc newCenter;
         Append sizesc (* newSize (Vec3 0.5 0.5 4.0))
         set newCenter (+ newCenter (* (Vec3 0 0 4.0) newSize))
         set newSize (* newSize 0.9)
I think thats really the meat of the function up there ^. Its all consistant with what we've seen so far, the only bit where Im not sure whats happening is the "local reps (RNG_Int rng 3 3)." Looks like we make a new variable reps, and set it as the output from the RNG_Int function, which is different from how we've seen the RNG work before. Im not sure if the 3 3 is the range what.

Code: Select all

         
   for i 0 (< i (Size centersc))(++ i)
      Append centers (Vec3 (Get centersc i))
      Append sizes (Vec3 (Get sizesc i))
      
   for i 0 (< i (Size centersc))((++ i)
      Append centers (Vec3 (Get centersc i))
      Append sizes (Vec3 (Get sizesc i))
      
   for i 0 (< i (Size centers))(++ i)
      local b (RNG_float rng 0.1 0.6)
      local p (Vec3 (Get centers i))
      local p (Vec3 (Get sizes i))
      += self (Plate (* (Vec3  1.0 1.0 1.0) p)(* (Vec3 1 1.0 1) s) 0 b)
      += self (Plate (* (Vec3 -1.0 1.0 1.0) p)(* (Vec3 1 1.0 1) s) 0 b)
self

Finally we iterate through the size of the centersc and add to the "centers" list. Then iterate over the centers list adding things to the Plate element of self, and untimatelly return the new fancy PlateMesh self to the calling function. There is obviously a lot going on here, but once you peel back the syntax it seams fairly straight forward. I still have some big questions about how exactly the language features work.

EDIT: grammar.
Post

Re: LTSL Code Development Update #19

#4
As a lisp noob, I can answer some of these :D
thesilverspanch wrote:Note that the function name in inside the first parens (not sure why he does this).
The list is the foundational data structure in Lisp (originally LISt Processing). They're even used to describe the structure of a program: Given a list, unless special steps are taken it's assumed it's code and the first element in the list is the function to call, with the remaining being the arguments. Of course it's not required to do this; Scheme (a Lisp dialect) looks more like

Code: Select all

(defun funcName (arg1 arg2) (expression...)...)
but there's precedent for function-name-in-parens.
thesilverspanch wrote:Check out that variable. Its apparent type is "local," which I think is how we are supposed to mark the scope of the variable.
Exactly right! In lisp, variables are really automatically-dereferenced pointers to entities in memory. Python programmers should feel right at home here. As one of many consequences, this means that (without special annotation) a variable can contain literally any type, and the type it holds can change over time. The more frequent way of doing this is called a let-binding, which this is not, but that's a little outside the scope, so to speak. :P
thesilverspanch wrote:Note how the switch isnt a c++ style switch where you have to put in a specific variable, but instead just has a series of boolian expressions followed by what should happen if that expression is true.
Another major difference! Just about everything in Lisp is an expression, which means it yields a value after it's computed. You can do things like (IIRC)

Code: Select all

(setf variable (if (> thing 5) 10 20))
And variable will be set to either 10 or 20, based on the value of thing.
thesilverspanch wrote:Im seeing this Vec3 type all over the place, which I thought was a 3d vector, but might just be a c++ vector with three elements.
Po-tay-to, Po-tah-to. :)
thesilverspanch wrote:[in context of "switch case false"] Honestly Im not sure whats going on here
I think this is a slightly bad way (don't hurt me!) to comment out a section of code. If I read this right, it's akin to

Code: Select all

if(0) { /* disabled_code(); */ } else { active_code(); }
in a C-like.

Can't add much else to the other bits. Good analysis! :)
Post

Re: LTSL Code Development Update #19

#5
Now that I've seen more working LTSL code, I'm about prepared to say that it's so "conceptually elegant" that I can't understand it.

That said, I noticed a small transcription error in the code posted above in this thread versus what Josh actually showed in the video. It should be this:

Code: Select all

   function (GetAxis (RNG rng))
      local side (Int rng 6)
      switch
         case (== side 0) (Vec3 -1  0  0)
         case (== side 1) (Vec3  1  0  0)
         case (== side 2) (Vec3  0 -1  0)
         case (== side 3) (Vec3  0  1  0)
         case (== side 4) (Vec3  0  0 -1)
         default          (Vec3  0  0  1)
In the code shown in the video (at 7:02, for example), the third parameter to the Vec3 function in the (== side 4) case is actually -1. Notice how each successive value of side has a -1 followed in the next line by a 1. For clarity, I'd have explicitly coded a (== side 5) test and left the default blank, but as I said I'm not clever enough to "get" LTSL so I need crutches like that.

Anyway, just wanted to mention in case anyone decided to copy the code in this thread once LT goes live. One less thing to debug. :)
Post

Re: LTSL Code Development Update #19

#6
Flatfingers wrote:For clarity, I'd have explicitly coded a (== side 5) test and left the default blank, but as I said I'm not clever enough to "get" LTSL so I need crutches like that.
To pop in again, some systems force you to make sure all possible cases are covered in a switch statement. A default at the very end is a way to short-circuit that check. Strinctly speaking it's better to do something like

Code: Select all

    ...
    switch
        case (== side 0) (Vec3 -1  0  0)
        case (== side 1) (Vec3  1  0  0)
        case (== side 2) (Vec3  0 -1  0)
        case (== side 3) (Vec3  0  1  0)
        case (== side 4) (Vec3  0  0 -1)
        case (== side 5) (Vec3  0  0  1)
        default     error("Impossible input to switch in GetAxis: check rng range")
But that's pretty picky. :)
Post

Re: LTSL Code Development Update #19

#7
Im curious about this format we see here.

Code: Select all

local b (RNG_float rng 0.1 0.6)
Im not familiar with LISP at all. I intuitively think it would behave like this

Code: Select all

float b = random_float_generator(seed, low_val, high_val)
If this is the case, we have a cool structure here where RNG_float is acting like an operator, which makes me wonder if all function calls can be expressed as operators?
Post

Re: LTSL Code Development Update #19

#9
thesilverspanch wrote:Im curious about this format we see here.

Code: Select all

local b (RNG_float rng 0.1 0.6)
Im not familiar with LISP at all. I intuitively think it would behave like this

Code: Select all

float b = random_float_generator(seed, low_val, high_val)
indeed that is likely the behavior, however I think that that line just executes a predefined function on the locally scoped rng object as follows

Code: Select all

float b = rng.RNG_float(low_val, high_val)
because rng is probally a already instantiated object with a seed, which is defined as

Code: Select all

local rng (RNG_MTG (+ seed 27))
earlier in the code.
Post

Re: LTSL Code Development Update #19

#10
I think the "local rng" variable is an int that got setup from the RNT_MTG earlier, so It could be used as a seed in the rest of the RNG_float calls.

Also I agree that the

Code: Select all

switch
       false
          thing thing thing
       true
          thing thing thing
is probably some of the code Josh said he wanted to clean up before he released it.

What amazes me most about the ship generator, is just how simple it is! It just come up with a bunch of centers, and adds them to the PlateMesh. Pretty darn elegant if you ask me!

Update 19 opened up a whole mess of questions. I wonder how much we can manipulate the texture of the PlateMesh, and if Plate is the only kind of mesh. Like what if we want some sweet faux-wood paneling on our space yacht?
Post

Re: LTSL Code Development Update #19

#14
Thanks for the XKCD comic cornflakes, I was actually just reading a book on the history of AI programming and they mentioned that LISP was first developed in the '60s which kind of blew my mind. Anyway, I realized that while I was going over the LTSL syntax I completely ignored the actual algorithm for generating the ships!

It seams like this bit is the most important part of the script. Also note I use ... for indentation since the forum wont let me put in \t characters.

for i 0 (< i kPlates) (++ i) <--- here we control the amount of plates
...local index (Int rng (Size centers)) <--- Im guessing this is a call to the RNG to return an int from 0 to size(centers)
...local center (Vec3 (Get centers i)) <--- sets the center of the current plate from the centers list
...local size (Vec3 (Get sizes i)) <--- sets the size of the current plate from the sizes list
...local dir (GetAxis rng) <--- calls that GetAxis function from before
...local newSize (* size (RNG_Vec3 rng 0.5 1.0)) <--- Tweaks the size element we got from before with an RNG
...local newCenter (+ center (* dir (+ size newSize)) <--- Changes the center location by adding the product of the direction and the sum of the size and newSize
...local reps (RNG_Int rng 3 3) <--- Sets up the number of repetitions for how much we are going to tweak the plate

...for j 0 (< j reps)(++ j)
......Append centers newCenter; <--- OK I just noticed this, what the heck is going on with these semicolons!?
......Append sizes newSize;
......Append centersc newCenter;
......Append sizesc (* newSize (Vec3 0.5 0.5 4.0)) <--- By multiplying by (0.5 0.5 4.0) we get a nice skinny, yet long rectange, perfect for the long-box shaped craft we've been seeing
......set newCenter (+ newCenter (* (Vec3 0 0 4.0) newSize)) <--- moves the new center to match the 4.0 change in the z direction
......set newSize (* newSize 0.9) <--- Slightly scales down the newSize

...for i 0 (< i (Size centersc))(++ i)
......Append centers (Vec3 (Get centersc i)) <--- Adds in the centerc which has our newCenters into the centers list
......Append sizes (Vec3 (Get sizesc i)) <---Adds in the sizesc into sizes

...for i 0 (< i (Size centers))(++ i) <--- Now that we have our centers setup we can actually add in our plates
......local b (RNG_float rng 0.1 0.6) <--- Gets a float from the RNG from 0.1 to 0.6
......local p (Vec3 (Get centers i)) <--- gets a local copy of centers
......local s (Vec3 (Get sizes i)) <--- gets a local copy of sizes
These next two lines take everything we've setup so far and throw it all together
......+= self (Plate (* (Vec3 1.0 1.0 1.0) p)(* (Vec3 1 1.0 1) s) 0 b) <--- We take a unit vector and multiply is by p (position), which holds vectors pointing towards the location of the center of the plate, then a unit vector and multiply it with s (size), which holds the size of the plate, and b (???) which I dont know what is.
......+= self (Plate (* (Vec3 -1.0 1.0 1.0) p)(* (Vec3 1 1.0 1) s) 0 b) <--- Here we do it again, but symmetrically about the x axis
self <--- Return the new PlateMesh to the calling function


The most important part of this is the arguments for the Plate object, which take a vector for the position, a vector for the size, and the mystery b variable! Maybe its the amount of beveling? Any guesses?
Post

Re: LTSL Code Development Update #19

#15
Seems likely since with a bevel value you could make boxes into spheres and 3d rectangles into capsules, and everything in between. The unused local kBevel also suggests there's bevelling involved somewhere.

Aside from b there's also the 0 value before b which is unexplained:

Code: Select all

(Plate center, size, 0, b)
Possibly this is to determine the plate's basic shape. This system doesn't have an obvious taper value as well which is possible with distance functions, the plates seem to be literal (3d rectangle plates) so I wonder where that comes in. Otherwise we can't have cones and pyramids. This seems like an unlikely oversight and the variable is a simple place to have that option, for either a tapering value or a basic shape setting.
That being said it would make more sense to have an Enumeration than a number for a shape, so who knows.
woops, my bad, everything & anything actually means specific and conformed

Online Now

Users browsing this forum: No registered users and 4 guests

cron