Monday, September 9, 2019

The Ticking Clock

I remember thinking when I was a kid that stories were just a segment of the characters lives. You could think of an author picking a beginning point of the story at some key event, finding a good end point, and just giving the details of what happened in between. But as I began to write my own stories, I discovered that this isn’t the case at all. It may be true of the beginning and ending, but it’s that middle stuff that gets you. When outlining a story, there are some key things that have to occur at various places in a book. The setup for the story always occurs at the beginning, even if you have to rip it out of the middle of the sequential order or even after the end of the story. The reader has to understand what is going on and why it is important. Then the characters have to be seen doing their thing. This is where the reader really starts to enjoy living in this world we’ve created, but it only goes on for about the second quarter of the book. Then it all falls apart. The world that the reader has been enjoying is in danger. And then we tell how the threat was dealt with, for good or for ill. These may not occur sequentially along a timeline, but we must put them in this order in the story.

But you know all of that. What I’ve noticed recently is that there are some things that really are in sequential order. I tend to think of these things as the tick of a clock. Suppose our story begins with a massive explosion on Monday. As the story unfolds, the characters are going to move into the activities of Tuesday. These are different activities than would be in the story if the explosion occurred on Friday and they moved into the activities of Saturday. There are also activities that occur every day, like getting out of bed, or making coffee in the morning, or fixing lunch. We don’t think about these activities much because they usually aren’t key to the story, but we hang our story off of them as we move along. Let me see if I can explain this.

Suppose we had the exciting explosion to pull the reader into our story, but now the outline calls for setup. We want to establish that the protagonist is impacted by this explosion in some way. It happened on Monday, let’s say in the afternoon. What’s on his schedule on Monday afternoon? He’s driving home from work. Traffic is a mess because people are looking at the fire. Maybe he receives a phone call telling him that he needs to return to work because of this. The story progresses, but his normal activities are a constant. These ticks of the clock are those things he would’ve been doing if not for the event he is impacted by. In the story, we are establishing that he is an investigator who looks into explosions like this, but the clock is ticking forward, and he would normally be in bed. Because of the explosion he isn’t in bed, but he is tired after having had a long day already. The clock ticks forward to Tuesday morning. We’re still doing setup and we want to establish that he is a single father. We don’t have him cancel a trip to an amusement park for the kids, instead we look at the clock and have him trying to find someone to take his kids to school.

What I find interesting about these ticks of the clock is that they flow naturally into the story. It’s almost like we don’t have to make them up because they are just part of who the character is. On the one hand we’re looking at our outline and making up stuff that fits the outline, but on the other we are just watching the clock and writing down what is occurring at each tick. It’s that blend of the two that makes the story.

Thursday, July 25, 2019

Engaging the Lost

After watching several videos from the One for Israel Ministry (www.oneforisrael.org) I began to notice a trend. Many of these Jews who have come to accept Jesus as their messiah declare that they were surprised to learn that Jesus was a Jew. Some chose not to read the New Testament because the rabbis told them not to and they assumed that the New Testament was instructions to Christians to persecute Jews. “Jesus is not for us” is a common theme that these people were taught. So when they began to learn about Jesus being a Jew, and reading the New Testament that is written by Jews and about the Jewish scriptures they were shocked.

I don’t know many Jews, but I encounter plenty of gentiles who speak of God in ways that are inconsistent with who I know God to be. They speak of God as being an evil being and I question how anyone could read the Bible and come to that conclusion. But that’s just it. They haven’t read the Bible. They don’t know my Jesus. These people aren’t rejecting Jesus based on what they know about him but out of their ignorance. They speak as if they know. They speak as if they have read the scriptures. They may even quote from the scriptures, but they haven’t read to understand.

Just as Jews need to be shown passages like Isaiah 53 to help them see that Jesus is the prophesied Messiah, those who hate God need to be shown the scriptures in such a way that they can see that God isn’t what they think he is. We should not assume that just because they say they have read the Bible or that just because they reference scripture that they know what Christianity is about.

I think it is important for us to engage people on social media in such a way as to oppose the sin of the world. The more controversial the more important it is that we engage in these conversations because this is our opportunity to engage the lost. But it must not stop there. It isn’t enough to state that abortion is wrong, the divorce is wrong, that homosexuality is wrong, that the sins of the world are wrong. Doing that will make people angry enough to argue with us, but if we don’t turn the conversation to teaching God’s word then it is of no value. If we do use these conversations to teach God’s word then it can be of great value. These people may never read the Bible except for what we spoon feed them in these conversations. I don’t expect that I will ever win someone to Christ during one of these arguments, but it may convince someone to go read for themselves. If we can do that then they may be persuaded.

Tuesday, May 21, 2019

A 3D Python Maze for Art of Illusion

I've always had a fascination with mazes. While messing with the Python plugin for Art of Illusion I decided to write a maze generator in Python. The image to the right is a rendering of the maze that is produced by that code. I did it as a Scripted Object, which was probably not the best idea since it recalculates the maze whenever something changes, but my excuse is that I was testing out some things related to Python scripted objects in Art of Illusion.

I'm including the code below, but with the caveat that it has a defect at one of the boundaries that I haven't figured out yet. It doesn't remove a wall for one or two of the cells. Aside from that, it will produce a good 3D maze.

import random
mazeWidth = 25
mazeLength = 25
wallLength = 1.2
wallHeight = 2.25
wallThickness = 0.05

WALL_UP = 0
WALL_DOWN = 1
WALL_FIXED = 2

class cell():
  X = 0
  Y = 0
  N = WALL_UP
  S = WALL_UP
  E = WALL_UP
  W = WALL_UP
  visited = False
 
def createmaze(width, length):
  m = []
  for l in range(length):
    row = []
    for w in range(width):
      _cell = cell()
      _cell.X = w
      _cell.Y = l
      row.append(_cell)
    m.append(row)
   
  for l in range(length):
    m[l][0].N = WALL_FIXED
    m[l][width-1].S = WALL_FIXED
   
  for w in range(width):
    m[0][w].W = WALL_FIXED
    m[length-1][w].E = WALL_FIXED
   
  return m
 
def createWall(length, height, thickness):
  wall = Cube(thickness, length, height)
  return ObjectInfo(wall, CoordinateSystem(), "")
 
def drawMaze():
  for i in range(mazeLength):
    for j in range(mazeWidth):
      if maze[i][j].W != WALL_DOWN:
        obj = createWall(wallLength, wallHeight, wallThickness)
        if not obj is None:
          obj.getCoords().setOrientation(90.0, 0.0, 0.0)
          obj.getCoords().setOrigin(Vec3(j*wallLength-(wallLength*0.5)-(mazeWidth*wallLength/2), wallHeight/2, i*wallLength-(mazeLength*wallLength/2)))
          self.addObject(obj)
     
      if maze[i][j].N != WALL_DOWN:
        obj = createWall(wallLength, wallHeight, wallThickness)
        if not obj is None:
          obj.getCoords().setOrientation(90.0, 90.0, 0.0)
          obj.getCoords().setOrigin(Vec3(j*wallLength-(mazeWidth*wallLength/2), wallHeight/2, i*wallLength-(wallLength*0.5)-(mazeLength*wallLength/2)))
          self.addObject(obj)
 
  for i in range(mazeLength):
    obj = createWall(wallLength, wallHeight, wallThickness)
    if not obj is None:
      obj.getCoords().setOrientation(90.0, 0.0, 0.0)
      obj.getCoords().setOrigin(Vec3(mazeWidth*wallLength-(wallLength*0.5)-(mazeWidth*wallLength/2), wallHeight/2, i*wallLength-(mazeLength*wallLength/2)))
      self.addObject(obj)
   
  for j in range(mazeWidth):
    obj = createWall(wallLength, wallHeight, wallThickness)
    if not obj is None:
      obj.getCoords().setOrientation(90.0, 90.0, 0.0)
      obj.getCoords().setOrigin(Vec3(j*wallLength-(mazeWidth*wallLength/2), wallHeight/2, mazeLength*wallLength-(wallLength*0.5)-(mazeLength*wallLength/2)))
      self.addObject(obj)
     
def chooseUnvisited():
  neighbors = []
  if (current.N == WALL_UP) and (current.Y-1 >=0) and (not maze[current.Y-1][current.X].visited):
    neighbors.append(maze[current.Y-1][current.X])
  if (current.S == WALL_UP) and (current.Y+1 < mazeLength) and (not maze[current.Y+1][current.X].visited):
    neighbors.append(maze[current.Y+1][current.X])
  if (current.W == WALL_UP) and (current.X-1 >=0) and (not maze[current.Y][current.X-1].visited):
    neighbors.append(maze[current.Y][current.X-1])
  if (current.E == WALL_UP) and (current.X+1 < mazeWidth) and (not maze[current.Y][current.X+1].visited):
    neighbors.append(maze[current.Y][current.X+1])
 
  if len(neighbors) > 0:
    return random.choice(neighbors)
  else:
    return current
   
def removeWall(cur, nxt):
  print("Cur "+str(cur.X)+" "+str(cur.Y))
  print("Nxt "+str(nxt.X)+" "+str(nxt.Y))
  if nxt != cur:
    if cur.Y < nxt.Y and cur.S != WALL_FIXED:
      print("removeWall 1")
      maze[cur.Y][cur.X].S = WALL_DOWN
      maze[nxt.Y][nxt.X].N = WALL_DOWN
    elif cur.Y > nxt.Y and cur.N != WALL_FIXED:
      print("removeWall 2")
      maze[cur.Y][cur.X].N = WALL_DOWN
      maze[nxt.Y][nxt.X].S = WALL_DOWN
    elif cur.X < nxt.X and cur.E != WALL_FIXED:
      print("removeWall 3")   
      maze[cur.Y][cur.X].E = WALL_DOWN
      maze[nxt.Y][nxt.X].W = WALL_DOWN
    elif cur.X > nxt.X and cur.W != WALL_FIXED:
      print("removeWall 4")   
      maze[cur.Y][cur.X].W = WALL_DOWN
      maze[nxt.Y][nxt.X].E = WALL_DOWN    
    else:
      print("removeWall 5")   

maze = createmaze(mazeWidth, mazeLength)
theStack = []
current = maze[0][0] # choose initial
theStack.append(current) # push current

# choose unvisited neighbor, pop if none found
next = chooseUnvisited()
next.visited = True
print("Next "+str(next.X)+" "+str(next.Y))
while len(theStack) > 0:
  while next == current and len(theStack) > 0:
    current = theStack.pop()
    next = chooseUnvisited()
    next.visited = True
    print("Next "+str(next.X)+" "+str(next.Y))
 
  removeWall(current, next)
 
  current = next
  next = chooseUnvisited()
  next.visited = True
  if next != current:
    theStack.append(current)  
   
drawMaze()

Tuesday, May 14, 2019

Advantage of Python over Java

Here's a question for you: what is the advantage of using Python over Java? Because of the recent publication of the second edition of Extending Art of Illusion I've been spending time working on scripts for Art of Illusion. Straight out of the box, Art of Illusion provides scripting capability for Groovy and BeanShell. This makes sense because Art of Illusion is written in Java and both of these scripting languages are based on Java. But most other graphics programs use some variation on Python as their scripting language. In a post on Python Scripts in Art of Illusion I wrote about creating a plugin that adds Python to the languages that Art of Illusion supports. I use Python for things I do in my day job mostly because it's available. But with adding it to Art of Illusion I have a direct comparison between Java and Python. It got me thinking. Is there some reason to choose to script in Python rather than using Java, Groovy, or BeanShell?


When I setup the interpreter, I did my best to provide consistency between what can be done with Python and what can be done with Groovy. The types of scripts are the same. The editor windows are the same. The libraries linked in from Art of Illusion are the same. I'm using the Jython interpreter, so everything that can be called from a Java class can be called from Python. So, in considering the question it really does come down to language differences.


Groovy and Python handle blocks differently. Java is very similar to C, so a block is whatever is within curly braces. In Python blocks are handled by indenting. There's nothing particularly advantageous for one over the other. It is personal preference. Personally, I've fine with both. Comments are also handled differently. Groovy uses C++ style comments while Python uses # to mark a comment. Again, that's just personal preference. Strings are handled by both and what you can do with strings is very similar.


The biggest difference between Python and Java is that Python has powerful list processing capability built right in while Java treats it as more of an afterthought. Given that 3D graphics scenes are lists of objects and objects are lists of polygons and polygons are lists of vertices, how a scripting language processes lists is important. In Python, a list is denoted by square brackets, []. In Art of Illusion we're most likely to get a list by using values returned from some Java function. In addition to lists, Python has dictionaries (square brackets {}), which are an unordered table that maps keys to values. It also has sets, which are unordered collections of unique objects.


A list might simply be every object in the scene or every object of a certain type and we want to perform some operation on each of the objects in the list. We might use a dictionary if we want to refer to objects by name. Sets are only truly useful in combination. We might have a set of edge vertices and a set of interior vertices. We might have a set of visible vertices and a set of invisible vertices. If we wanted to perform some operation on all of the visible edge vertices then we could get the intersection of the edge vertices set and the visible set and then loop through that set rather than having a check in our loop for whether a set of conditions is met. Of course, it is possible to do this with Java as well, but having it inherent in the language is nice.

Sunday, May 12, 2019

Python Scripts in Art of Illusion

I took a break from the cloth simulator to look at adding Python as a scripting language for Art of Illusion. I had some success at pulling in the Jython library and was able to get the following Tool Script to add a Cube to the scene:

undo = UndoRecord(window, True)
obj = Cube(1.0, 1.0, 1.0)
objInfo = ObjectInfo(obj, CoordinateSystem(), "Cube "+str(1))
window.addObject(objInfo, undo)
window.updateImage()
window.setUndoRecord(undo)


In a video I demonstrated that a Groovy script could be used to add a Cube to the scene. The Groovy script was as follows:

undo = new UndoRecord(window, true);
obj = new Cube(1.0, 1.0, 1.0);
objInfo = new ObjectInfo(obj, new CoordinateSystem(), "Cube "+1);

window.addObject(objInfo, undo);                      
window.updateImage();
window.setUndoRecord(undo);

As you can tell, the code is essentially the same except Python doesn't use the new keyword, True is capitalized in Python, and Python won't concatenate an integer to a string without a conversion function. I'm sure there will be more noticeable differences once I start using it along with the language features of Python, but this was just a proof of concept.

Unfortunately, I've been less successful at implementing the code required to use Python to create a Scripted Object in Art of Illusion. It might be easier if I were modifying the Art of Illusion code rather than trying to do this as a plugin, but I keep running into one typecasting issue or another. I had hoped that I might use this as the vehicle to demonstrate the value of the reference material at the back of Extending Art of Illusion. I can say that I've had the book open to that material the whole time I've been working on this and I have put it to good use. I can also say that I'm very glad this book is available in hardback. It costs a little extra, but being able to open it and leave it open makes a world of difference.

UPDATE: After writing this I found a way to get it working for Scripted Objects. I put it in a repository at https://github.com/TimothyFish/AOI_with_python.git so I wouldn't lose what I had working. It's not ready for general use, but it is working. To use it, you will need to have the Jython.jar file in your class path for Art of Illusion. If you know what that means then have fun. If not then it's probably better that you don't mess with it. I would explain it but I only just got it working myself and I don't know that I can give you a clean cut set of instructions yet.

For anyone who is interested, the following is the Python version of the Axis script that is in Extending Art of Illusion:

from artofillusion.script import ScriptedObject
scene = theScript.getScene()
count = 0

length = 5.0
size = 0.01

def createAxis(length, diameter):
  axis = Cylinder(length, diameter, diameter, 1.0)
  return ObjectInfo(axis, CoordinateSystem(), "")
 
obj = createAxis(length, size)
if not obj is None:
  theScript.addObject(obj)
 
obj = createAxis(length, size)
obj.getCoords().setOrientation(90.0, 0.0, 0.0)
if not obj is None:
  theScript.addObject(obj)

obj = createAxis(length, size)
obj.getCoords().setOrientation(0.0, 0.0, 90.0)
if not obj is None:
  theScript.addObject(obj)


You may notice that I'm using "theScript" here instead of "script". This is because there is a conflict between "script" and "artofillusion.script" with Jython interprets the code.

Friday, May 10, 2019

Art of Illusion Examples

For each of the first ten chapters of Extending Art of Illusion I have created a video that demonstrates some aspect of it. One of the things I found when writing the book was that it is difficult to demonstrate animation on the printed page. In chapter 7 I give code for a tracker object that will rotate an object or objects so that the object is always pointing at one of the other scene objects, no matter where it is located in the scene. In the book I tried to show this with a sequence of still images taken from the animation. But in the video I have been able to show the video of a head with eyes that follow an object as it moves around the scene. Not only that, but it is possible to show that these changes are occurring even as the scene is being edited.

Below is the list of videos:

Chapter 1: Use a Script to Add a Cube to Art of Illusion
Chapter 2: Storing and Loading
Chapter 3: Position Objects on Floor
Chapter 4: Point at Objects
Chapter 5: Resting One Object on Another
Chapter 6: Adding Custom Objects
Chapter 7: Tracking Movement
Chapter 8: Procedural Modules
Chapter 9: The Room
Chapter 10: Modeling Cloth in Art of Illusion

Monday, April 29, 2019

Why Buy My Book About Scripts and Plugins

After the second edition of Extending Art of Illusion went to press my attention turned once again to the question of getting it into the hands of readers. Outside of people called Mom, people don't generally buy books just because someone wrote it. I've purchased a few books just because I knew the author, but I don't buy second and third books "just because."  Those of us who love books need a reason to buy a book. No, that's not right. We want a reason to buy a book. We are begging authors and publishers to give us a reason to buy a book. We want to see a book and say, "If I buy this book this is the knowledge I will gain or the story I will enjoy." We are cheering for authors to provide us with a good book. It's in that context that I make the case for people to buy the latest edition of Extending Art of Illusion.

I stopped to ask myself why anyone would need this book. It may seem like it is a little late to be asking that question. Over 550 pages into a project and with it already coming off the press is not the time to be asking whether it is worth doing, but there's something a little odd about doing a second edition of a book. On this edition, my focus was on very specific aspects of the book rather than on the project as a whole. It was important to me to update the reference material at the back of the book so that it matches the current release of Art of Illusion. But a higher priority than that was the cloth simulator described in Chapter 10. I have this urge to tell people that they should buy the book because it provides a cloth simulation for Art of Illusion, but the cloth simulator isn't a reason to buy the book. It is a reason to install the plugin, but it isn't a reason to buy the book.

As I thought about that, I wrote a script that I thought might help illustrate why people need to write scripts and plugins for Art of Illusion. If they understand that then it is easier to make the case for them to buy a book that will teach them how to write scripts and plugins.

The picture to the right is one of the images that I rendered from that script. The script adds 1000 sphere to the scene. Adding 1000 of anything isn't something you would want to do by hand. Not only would it take a long time but it would be impossible to get all of the positions exactly right. With a script you can let the computer do the heavy lifting. The Groovy script I used is below:

size = 0.2;
spacing = 4.0*size;
t = script.getTime();
maxTime = 10.0;
timeDelta = maxTime/1000.0;
myTime = 0.0;

for(k = 0; k < 10; k++){
 for(j = 0; j < 10; j++){
   for(i = 0; i < 10; i++){
   myTime += timeDelta;
   if(myTime > t) break;
   S = new Sphere(size, size, size);
   infoS = new ObjectInfo(S, new CoordinateSystem(), "Sphere");
   infoS.coords.setOrigin(new Vec3(spacing*(i-5.5), spacing*(j-5.5), spacing*(k-5.5)));
   script.addObject(infoS);
  }
  if(myTime > t) break;
 }
 if(myTime > t) break;
}


You would place this script in a Scripted Object. The code is simple enough, but how would you change it if you wanted to use Cubes instead of Spheres? What if you wanted the objects spinning or pointing at the same thing? Imagine a thousand eyeballs looking at the camera. How would you achieve that? This is why people who want to do scripting in Art of Illusion need the book. Not only does it provide plenty of examples but it provides a listing of every public interface in Art of Illusion. My name is on the cover and yet I found myself reaching for this book as I was writing this script. I could have looked up the functions I needed in the source code, but I found it preferable to have the book open in front of me. And it is my belief that if you are writing scripts for Art of Illusion you will find that beneficial as well.

You can buy the paperback from Amazon.com.
There is also a hardback version available.