Wednesday, April 26


Since Microsoft are currently telling the world what a nice monster they are and how evil and arbitary the EU Competition supremos are, I think it's time to look back to when there actually was some freedom and choice in the software world - just before the appearance of Win 3.1, when you could choose between DR-DOS or MS-DOS, and how Microsoft killed that competition.

EDIT: The link mysteriously died. There don't seem to be too many copies of this doc floating on the web for some reason. Minds more paranoid than I may speculate

The rest, as they say, is history. There is just one thing I'd like to know: Who WAS AARD?

Thursday, April 6

Frame zero is a mythical creature.

I've decided that the entire last post was a result of semantic confusion. Keyframes and frames are different things; a frame is a measure of elapsed time, and a keyframe is a sample of animation at an instant in time, so it makes perfect sense to say an animation with four sampled keyframes actually has three frames; because frames are just seconds in disguise, just another way of expressing the elapsed time. The trick is to keep t accurate and all you have left is interpolation.

Wednesday, April 5

Frame zero. It drives me nuts.

What am I talking about? Well, for the sake of argument, lets suppose I have a 4 - frame, keyframed animation, with the simulation and the renderer stepped at 1/30sec per frame. The artist sees 4 frames in Maya/Max/Blender/WhizzosAtomicModeller

| | | | |
0 1 2 3 4

To play a complete animation, we have to render at 5 intervals, starting with t=0 and ending with t=4. Great, so thats what you do, after all, the artists want to see *all* of their lovely animation, right?

So along comes someone else, actually observing the behavior of the animation and says "hey, our x frame animation is playing over x+1 frames", and the guy/thing/car is overshooting their turn/cutscene spot/fov

At this point a number of things can happen.

1> We rejig the logic/calling order so that animation_update() always gets called before animation_render() and we never see the t=0 frame. Everything seems right. There's no "unstated state", the animation is always playing - that has to be right, no? along as none of the more bright artists realize and get upset (rightly) that they are losing a frame of their lovingly hand-crafted animation every time.

2> We add one to the number of frames of animation every time the AI asks what the frame count is going to be. Velocities, etc get scaled accordingly. This can annoy AI people, if things take a frame longer than they think they will, especially when doing things like cornering. The API/Interface has to be bulletproof.

3> The opposite of 1>. Have every animation signal its desire to terminate a frame before the end and never play t=4 except either to hold a pose, or blended in with another animation that is transitioning in.

I've worked in shops that have used each. My personal preference is probably 3, but I've met people quite vehement about 1. I have never had the chutzpah to pull 2, myself, but I've seen people do it successfully.

Before I write yet another animation engine (properly, this time, I always tell myself!) I'd like to sound out the opinions of the crowd. Anyone have any strong arguments pro and con for any of these three?

Saturday, April 1

Amazon Web Services With Lisp

I've just had a nice day banging my head on the wall trying to figure out why I can't talk to amazon web services with cl-trivial-http. Turns out that trivial-http doesn't actually implement HTTP to the letter. GET is supposed to send the local path and not the full URL to the server.

When you send Amazon the local path instead of the full URL, it wants to play. Just for reference, here is my test code. Hope it helps someone...

;; load up requirements
(asdf:oos 'asdf:load-op 'trivial-sockets)
(asdf:oos 'asdf:load-op 'trivial-http)
(asdf:oos 'asdf:load-op 's-xml)

;; Define parts of the REST request.
(defparameter *baseurl* "")
(defparameter *service* "AWSECommerceService")
(defparameter *accesskeyid* "098CMPJ4AGZ7C6T1DHG2")
(defparameter *operation* "ItemSearch")
(defparameter *searchindex* "Books")
(defparameter *default-response-group* "Request,Small")

(defparameter *search-indexes* '( "Apparel" "Automotive" "Baby" "Beauty" "Blended" "Books" "Classical" "DigitalMusic" "DVD" "Electronics" "ForeignBooks" "GourmetFood" "HealthPersonalCare" "Hobbies" "HomeGarden" "Jewelry" "Kitchen" "Magazines" "Merchants" "Miscellaneous" "Music" "MusicalInstruments" "MusicTracks" "OfficeProducts" "OutdoorLiving" "PCHardware" "PetSupplies" "Photo" "Restaurants" "Software" "SoftwareVideoGames" "SportingGoods" "Tools" "Toys" "VHS" "Video" "VideoGames" "Wireless" "WirelessAccessories" ) )

(defparameter *response-groups* '( "Accessories" "BrowseNodeInfo" "BrowseNodes" "Cart" "CartNewReleases" "CartTopSellers" "CartSimilarities" "CustomerFull" "CustomerInfo" "CustomerLists" "CustomerReviews" "EditorialReview" "Help" "Images" "ItemAttributes" "ItemIds" "Large" "ListFull" "ListInfo" "ListItems" "ListmaniaLists" "ListMinimum" "Medium" "NewReleases" "OfferFull" "Offers" "OfferSummary" "Request" "Reviews" "SalesRank" "SearchBins" "Seller" "SellerListing" "Similarities" "Small" "Subjects" "TopSellers" "Tracks" "TransactionDetails" "VariationMinimum" "Variations" "VariationImages" "VariationSummary" ))

(defun append-parameter-to-url (url name value)
(let ((result
(concatenate 'string url "&" name "=" (trivial-http:escape-url-query value))))

(defun make-amazon-item-search-url (search-index &key keywords power title author item-page (response-group *default-response-group*))
(if (not (member search-index *search-indexes* :test #'string=))
(error "~A is not a valid search index" search-index)
(let ((result
(concatenate 'string
*baseurl* "?Service=" *service* "&AWSAccessKeyId=" *accesskeyid*
"&Operation=ItemSearch&SearchIndex=" search-index "&ResponseGroup=" response-group
(if keywords
(setf result (append-parameter-to-url result "Keywords" keywords)))
(if author
(setf result (append-parameter-to-url result "Author" author)))
(if title
(setf result (append-parameter-to-url result "Title" author)))
(if power
(setf result (append-parameter-to-url result "Power" power)))
(if item-page
(setf result (append-parameter-to-url result "ItemPage" item-page)))

(defun url-path (url)
(assert (string-equal url "http://" :end1 7))
(let ((path-start (or (position #\/ url :start 7) (length url))))
(subseq url path-start (length url))))

(defun amazon-http-get (url)
(let* ((host (thttp::url-host url))
(port (thttp::url-port url))
(stream (trivial-sockets:open-stream host port)))
(format stream "GET ~A HTTP/1.0~AHost: ~A~AUser-Agent: Trivial HTTP for Common Lisp~A~A"
(url-path url) thttp::+crlf+ host thttp::+crlf+ thttp::+crlf+ thttp::+crlf+)
(force-output stream)
(thttp::response-read-code stream)
(thttp::response-read-headers stream)

(defun test-amazon ()
(destructuring-bind (response headers http-stream)
(amazon-http-get (make-amazon-item-search-url "Books" :keywords "Lisp" :item-page "25"))
(if (not (= response 200))
(error "response ~A from server" response)
(format t "response ~A~%" response)
(format t "headers ~A~%" headers)
(loop for line = (read-line http-stream nil nil)
while line do (format t "~A%" line))))))