Drawing a Trapezoid with Stage3d

3

Posted by Colin Northway | Posted in Development, technical | Posted on 03-06-2013

Tags:

brokenGround

The game I’m working on now has nice round corners that we surround with fuzzy grass or hard crystals or any number of pleasently tactile textures. Unfortunately until yesterday they were being drawn with horrible jaggy distortion (click on the image for a better view).

This happens because I draw the circles out of trapezoids and the trapezoids out of triangles, and then the graphics card gets confused when it stretches the texture across the triangles. Here is a clear example of what it does look like vs. what it should look like:


trapezoidBoth

 

Obviously the one on the left is bad. It looks ridiculous at this scale and when you put it into the game it gives that jaggy broken look. It does show you clearly where the two triangles are, one on the top with a width of 400 pixels and one on the bottom with a width of 100 pixels. That width disparity is what drives the problem. I pass in UV texture coordinates for each point in each triangle but because the top triangle doesn’t know anything about the bottom right point it can’t know it’s being streched onto a trapedzoid so it just always assumes its being stretched onto a square.

Important! If you are using OpenGL or a graphics library that uses 4d texture coordiantes then you have an easy fix for this as described here.

Unfortunately Stage3d only has 2d texture coordinates so we have to fix it in the shader. Here is what a normal, shader might look like, one that results in a broken trapezoid:

Vertex Shader:
m44 vt0, va0, vc0
mov v1, va2 
mov op, vt0
Pixel Shader:
tex ft1, v1, fs0 <2d,linear,repeat,mipnearest>
mov oc, ft1

If you don’t know what this is then you can go read my posts on writing shaders for Stage3d.

Here is an image with some important information about our trapezoid. On right right is the texture coordiantes. It’s important to know that 0,0 is the top left and not the bottom left.

trapezoidDetails

The colourful image on the left is showing what shapes the graphics card is trying to texture. The bizarre slanting of the textures makes more sense if you think about what shape the triangle thinks it’s a part of. You can see the top triangle is trying to draw a big rhombus and the bottom triangle is trying to draw a much smaller one.

If you think of it like this it becomes pretty clear that we just want to divide the x coordinate by 1-y coordinate. The top pixels are correct, and they get more wrong as they approach the bottom. Dividing them by 1-y will divide them by the range 1-0 which isn’t strictly correct unless the bottom trapezoid is zero length, but it’s much closer than what it’s doing now. Instead of the range 1-0 we really we want to divide by the range 1-.25 because the bottom is 100 pixels and top is 400 pixels. 100/400 = .25 and since it’s trying to draw a bottom that is 400 pixels wide multiplying by .25 will result in a 100 pixel bottom. (remember, since we’re working in UV coords bigger numbers result in the texture being squeezed more so if you want to decrease the  size you divide by .25 instead of multiply).

Now we could do that with this pixel shader:

mov ft2 v1 //v1 contains our UV coords
mov ft3 v1 //ft3 will contain our modified y coord
mul ft3 ft3 fc4 //multiply by 1-ratio of bottom to top. In this case fc4 contains .75
sub ft3 fc1 ft3 //y = 1-y Now ft3.y contains values from 1 to .25 (fc1 contains 1)
rcp ft3 ft3 //y = 1/y there is no divide operator so we multiply by the reciprocal
mul ft2.x ft2.x ft3.y //do the real work
tex ft1, ft2, fs0 <2d,linear,repeat,mipnearest> //sample the texture
mov oc, ft1 //output

inShaderWhich results in the image on the right. Pretty cool eh? The top triangle is now correct! Unfortunately the bottom one is fucked. Plus we got here by passing the bottom/top ratio through a shader constant which isn’t going to work with variable shaped trapezoids unless we draw them one triangle at a time. Which is not going to fly.

So we need to generalise this to the bottom triangle as well as figure out a way to pass the shader the needed information. Obviously it’s time to fire up the VertexBuffers!

First lets take a quick peek at how we’re passing in the points and the UV coordinates. Here is some code:

triangles.push(new point(0, 0), new point(150, 300), new point(400, 0));
triangles.push(new point(150, 300), new point(400, 0), new point(250, 300));
for (var i:int = 0; i < triangles.length; i += 6) {
  mVertexData.setTexCoords(i, 0, 0);
  mVertexData.setTexCoords(i+1, 0, 1);
  mVertexData.setTexCoords(i+2, 1, 0);
  mVertexData.setTexCoords(i+3, 0, 1);
  mVertexData.setTexCoords(i+4, 1, 0);
  mVertexData.setTexCoords(i+5, 1, 1);
}

pointNum

On the right I’ve numbered our  points as well as reminded us all what the UV coords for those points are. The biggest thing we need to do now is to start passing the Bottom/Top ratio to the shader. We’re going to do that by passing them into the shader through a vertex buffer. I’m using Starling so I modified the starling VertexData class to hold my data but however you usually pass in your vertex buffer is cool. The important thing is that somewhere you have a line that looks kind of like this:

context.setVertexBufferAt(1, mVertexBuffer, VertexData.PERSPECTIVE_OFFSET,    Context3DVertexBufferFormat.FLOAT_3);

Note that I named the collection of variables I’m about to pass in Perspective variables. Because it’s kind of a perspective transform we’re doing.

We could, at this point, just pass in Bottom/Top which would look something like this (remember, your mVertexData has no .setPerpectiveCoords, I added that):

var w1:Number = mTriangles[i + 2].x - mTriangles[i].x
var w2:Number = mTriangles[i + 5].x - mTriangles[i+3].x
mVertexData.setPerpectiveCoords(i, w2/w1);

But we can actually do better than that. We can do some of the math for the shader, which will be way faster than doing it for every pixel. Lets see what that looks like:

//Set the UV coords
mVertexData.setTexCoords(i, 0, 0);
mVertexData.setTexCoords(i+1, 0, 1);
mVertexData.setTexCoords(i+2, 1, 0);

//prep our perspective vals. We want this to go from 1->.25
var val0:Number = 0;
var val1:Number = 1;
val0 *= (w2/w1)-1; //0
val1 *= (w2/w1)-1; //-.75

val0 = val0 + 1; //1
val1 = val1 + 1; //.25
mVertexData.setPerpectiveCoords(i, 1, val0, 0);
mVertexData.setPerpectiveCoords(i+1, 1, val1, 0);
mVertexData.setPerpectiveCoords(i+2, 1, val0, 0);

This is actually pretty clever. Remember that anything passed from the vertex shader to the pixel shader is linearly interpolated. We use that by passing in 1 to the top points and .25 to the bottom point and let the video card do the work for us. Unfortunately we can’t also do the divide because the graph of 1/x is not a linear graph and so the transformation gets effed. That means our shaders now look like this:

Vertex:
m44 vt0, va0, vc0
mov v0, va1 //pass in our perpective data
mov v1, va2 //pass in the UV Coords
mov op, vt0
Pixel:
rcp ft3 v0 //y = 1/y
mul ft2.xyzw v1.xyzw ft3.yxzw //multiply TexCoords.x by 1/y
add ft2.x ft2.x v0.z //translate by some amount (we haven't discussed this yet)
tex ft1, ft2, fs0 <2d,linear,repeat,mipnearest> //grab the pixel
mov oc, ft1 //out

Magic! Our top triangle is now tops! Time to move on to the bottom one. The only tricky think about the bottom one is that its x and y coords are the reverse of what we want. The top one was easy because 0,0 was always correct but for the bottom triangle 1,1 is always correct and 0,0 is the most wrong. The solution? Pass in the UV coords upsidedown! Do the math on the upsidedown coords and then flip them back.

Here is what that looks like:

//Old UV coords
//mVertexData.setTexCoords(i+3, 0, 1);
//mVertexData.setTexCoords(i+4, 1, 0);
//mVertexData.setTexCoords(i+5, 1, 1);*/
//fliped Coords
mVertexData.setTexCoords(i+3, 1, 0);
mVertexData.setTexCoords(i+4, 0, 1);
mVertexData.setTexCoords(i + 5, 0, 0);

val0 = 0;
val1 = 1;
val0 *= (w1/w2)-1; // w1/w2 instead of w2/w1
val1 *= (w1/w2)-1;
val0 = val0 + 1;
val1 = val1 + 1;
val0 = -val0; //Flip the sign so that when the x is multiplied by 1/y it will flip the sign of x
val1 = -val1;
mVertexData.setPerpectiveCoords(i+3, -1, val0, 1);
mVertexData.setPerpectiveCoords(i+4, -1, val1, 1);
mVertexData.setPerpectiveCoords(i+5, -1, val0, 1);

Note -1 in the perpective coords. If you go back and look at the pixel shader you will see we multiply the y value by perspectiveCoords.x. By passing in -1 we flip the y axis. We flip the x axis by passing in a negative perspectiveCoords.y value.

ALMOST DONE!

The bottom triangle is fixed and this works for arbitrarily shaped trapezoids. The only problem is that it only works for texture coords from 0 to 1. But in real life our texture coords are usually things like 4.5 to 4.8 or 2.2 to 5.8 (in my application the y is always 0 to 1 though). That’s what this shader line is about:

add ft2.x ft2.x v0.z //translate by some amount (we haven't discussed this yet)

We just do all our math from 0 and then add back however much we want. If you look at these lines:

mVertexData.setPerpectiveCoords(i+5, -1, val0, 1);

And replace the , 1); with , 4.5); or , 2.2); or whatever texture coord you want you will get your desired translation. To set your desired width you just pass the width in to the UV coords as usual:

mVertexData.setTexCoords(i, 0, 0);
mVertexData.setTexCoords(i+1, 0, 1);
mVertexData.setTexCoords(i+2, desiredLength, 0);

Now we really are truly finished and we are rewarded by beautiful globby circular level layouts like this:

final

Callooh! Callay!

Share

flattr this!

Rebuild 3: Interfaces & common resolutions in 2013

0

Posted by Sarah Northway | Posted in Development, Rebuild3, technical | Posted on 15-05-2013

Tags:

The target aspect ratio for Rebuild 3 is widescreen 16:9. Rebuild 1 and 2 were 4:3 which is pretty normal for Flash games that have to appear on sites like Kongregate with ads and stuff around them. But the PC/Mac and mobile versions of Rebuild 3 will support glorious fullscreen, where 16:9 is the new norm. Check it:

My dubious hobby/addiction to making colorful graphs

So the plan is to design an interface that fits snugly on 16:9 iPhone5s and wide PC screens, but scale and move around to fill more square screens. Here’s a few ways we might do it:

Concept art for Rebuild 3 at 16:9 and 4:3

Rebuild 3 concept menu

Alternate concept with broken sliding hud

Rebuild gui left menu concept

Concept with menu down the left, taller menus.

I hope my addiction to mockups leads to something useful. Adam’s doing the same thing on his end, finding inventive new ways to convey a ton of information without looking cluttered. I want the game to feel natural at any resolution, so you don’t feel so much like you’re buried in an endless stack of menus. Which… come to think of it, is not a totally inaccurate description of the game.

Share

flattr this!

Integrating PayPal for Digital Goods

3

Posted by Sarah Northway | Posted in technical | Posted on 13-07-2012

Let’s face it: PayPal may be an unethical, bullying monopoly, but it’s a necessary evil when selling online. When we used PayPal to sell Fantastic Contraption, they suddenly froze our account because they were worried our digital goods (the full version of the game) might *poof* and disappear causing thousands of players to demand a refund.

Incredipede will use micropayments to sell cool custom creature skins by different artists.

PayPal has since then joined the modern age and added a new payment solution specifically for digital goods. It has lower rates for payments under $15 (5% + 5 cents, you just have to apply), and includes an optional login-free 2-click interface. You still need access to a browser window running Javascript, but it seems PayPal is making strides to support smaller, faster purchases.

I just added a PayPal micropayment system to Incredipede. I used to implement payment methods professionally, but for my own projects I prefer to keep things simple. The Java PayPal APIs I found were all too complicated, didn’t work with Google App Engine, or were out of date and incompatible with Digital Goods. So I rolled my own simple system which uses PayPal’s Express Checkout API. It has three public methods:

  1. startPurchase – Begins the transaction and sends the user to PayPal
  2. validateDetails – Verifies the payment when the user returns
  3. finishPurchase – Completes the payment

I’ve simplified it down to one file for you with a bunch of inline constants and other bad practices. You may download PayPalManager.java (or copy & paste from below) and do as you will with it:

package com.incredipede.store;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.StringTokenizer;
import java.util.logging.Logger;

import javax.servlet.http.HttpServletResponse;

/**
 * Super simple implementation of Paypal for Digital Goods - Express Checkout API
 * Java NVP (name-value pairs) implementation (July 1012 version 69.0)
 * 
 * Using API and code samples from:
 * https://cms.paypal.com/cms_content/US/en_US/files/developer/PP_ExpressCheckout_IntegrationGuide_DG.pdf
 * https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_api_IntroducingExpressCheckoutDG
 * https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/howto_api_reference
 * https://www.paypal-labs.com/integrationwizard/ecpaypal/cart.php 
 *
 * Usage:
 *     When user presses a "Buy" button on your site:
 *         PayPalManager.startPurchase(2, 5, "Dapper Hat", A virtual item in the game Incredipede", "1.00", response);
 *     When user returns to RETURN_URL after authenitcating purchase on Paypal's website:
 *         String token = request.getParameter("token");
 *         int userId = Integer.parseInt(request.getParameter("userId"));
 *         int itemId = Integer.parseInt(request.getParameter("itemId"));
 *         // first check the status on paypal's system and make sure purchase is for reals
 *         String payerId = PayPalManager.validateDetails(token, userId, itemId);
 *         // next perform the purchase on your system to make sure it succeeds
 *         ...
 *         // finally take the user's money
 *         try {
 *             PayPalManager.finishPurchase(token, payerId, userId, itemId, "Dapper Hat", 
 *                 A virtual item in the game Incredipede", "1.00");
 *         } catch (Exception e) {
 *             // roll back the purchase on your system
 *             ...
 *         }
 * 
 * @author Sarah Northway
 */
public class PayPalManager
{
	// production creds
//	protected static String API_USERNAME = "xxxxxxxxxxxxxxxxxxxxxxxx";
//	protected static String API_PASSWORD = "XXXXXXXXXXXXX";
//	protected static String API_SIGNATURE = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
//	protected static String API_URL = "https://api-3t.paypal.com/nvp";
//	protected static String REDIRECT_URL = "https://www.paypal.com/cgibin/webscr?cmd=_express-checkout";
	
	// sandbox creds
	protected static String API_USERNAME = "xxxxxxxxxxxxxxxxxxxxxxxx";
	protected static String API_PASSWORD = "XXXXXXXXXXXXX";
	protected static String API_SIGNATURE = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
	protected static String API_URL = "https://api-3t.sandbox.paypal.com/nvp";
	protected static String REDIRECT_URL = "https://www.sandbox.paypal.com/webscr?cmd=_express-checkout";
	
	/** User will return to this page after the sale is successful */
	protected static String RETURN_URL = "http://127.0.0.1:8888/return;
	
	/** User will return here if they hit the cancel button during purchase */
	protected static String CANCEL_URL = "http://127.0.0.1:8888/cancel;

	protected final static Logger log = Logger.getLogger(PayPalManager.class.getName());
	
	/**
	 * Step 1: SetExpressCheckout
	 * 
	 * The first step of Express Checkout for Digital Goods: send a SetExpressCheckout
	 * request to PayPal and receive a token in response. Redirect the user to Paypal,
	 * then wait for their return through either the returnUrl or cancelUrl.
	 * 
	 * As of version 69.0, digital payments must set L_PAYMENTREQUEST_0_ITEMCATEGORY0=Digital, 
	 * must specify NOSHIPPING=1 and REQCONFIRMSHIPPING=0, 
	 * must use both AMT and ITEMAMT, and must have exactly one 
	 * payment (PAYMENTREQUEST_0_[...]) and one item (L_PAYMENTREQUEST_0_[...]0).
	 * https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_api_nvp_r_SetExpressCheckout
	 *
	 * @param userId The user's unique id in our system
	 * @param itemId The unique id in our system for the thing being bought
	 * @param itemName Shown in paypal as the name of the thing being bought eg "Dapper hat"
	 * @param itemDescription Shown in paypal beneath the item name eg "A virtual item in the game Incredipede"
	 * @param itemPriceDollars String price in USD must include decimal and two digits after eg "10.00"
	 */
	public static void startPurchase (int userId, int itemId, String itemName, String itemDescription, 
		String itemPriceDollars, HttpServletResponse resp)
	{
		// include the userId and itemId in the return urls so we can access them later
		String returnUrl = encodeValue(RETURN_URL + "&userId=" + userId + "&itemId=" + itemId);
		String cancelUrl = encodeValue(CANCEL_URL + "&userId=" + userId + "&itemId=" + itemId);
		
		String data = 
			"METHOD=SetExpressCheckout" +
			getAuthenticationData() +
			"&REQCONFIRMSHIPPING = 0" +
			"&NOSHIPPING = 1" +
			"&ALLOWNOTE = 0" +
			"&PAYMENTREQUEST_0_PAYMENTACTION=Sale" +
			"&PAYMENTREQUEST_0_CURRENCYCODE=USD" + 
			getPurchaseData(userId, itemId, itemName, itemDescription, itemPriceDollars) + 
			"&RETURNURL=" + returnUrl + 
			"&CANCELURL=" + cancelUrl +
			"";

		// tell paypal we want to start a purchase
		HashMap results = doServerCall(data);
		
		// forward the user on to payapal's site with the token identifying this transaction
		try {
			String token = results.get("TOKEN");
			String redirectUrl = resp.encodeRedirectURL(REDIRECT_URL + "&token=" + token);
			log.info("Sending user to paypal: " + redirectUrl);
			resp.sendRedirect(redirectUrl);
		} catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException("Failed to open PayPal link: " + e.getMessage());
		}
	}
	
	/**
	 * Step 2: GetExpressCheckoutDetails
	 * 
	 * Second step, performed when the user returns from paypal to validate the transaction
	 * details. If we cared about shipping info, the user's name etc it would be fetched here.
	 * Throws an exception if userId or purchase details don't match paypal's values, or if
	 * there's a problem with the purchase itself.
	 * https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_api_nvp_r_GetExpressCheckoutDetails
	 * 
	 * @param token The token created and returned by Paypal in step 1 (from the return url)
	 * @param userId The user's unique id in our system (from the return url)
	 * @param itemId The unique id in our system for the thing being bought (from the return url)
	 * @return Returns the user's paypal PayerId for use in the last step
	 */
	public static String validateDetails(String token, int userId, int itemId)
	{
		String data = 
			"METHOD=GetExpressCheckoutDetails" +
			getAuthenticationData() +
			"&TOKEN=" + encodeValue(token) +
			"";
	
		HashMap results = doServerCall(data);

		int resultsUserId = Integer.parseInt(results.get("PAYMENTREQUEST_0_CUSTOM"));
		if (resultsUserId != userId) {
			throw new RuntimeException("UserId does not match.");
		}
		
		int resultsItemId = Integer.parseInt(results.get("PAYMENTREQUEST_0_INVNUM"));
		if (resultsItemId != itemId) {
			throw new RuntimeException("ItemId does not match.");
		}
		
		String payerId = results.get("PAYERID");
		if (payerId == null || payerId.trim().length() == 0) {
			throw new RuntimeException("Payment has not been initiated by the user.");
		}
		
		return payerId;
	}
	
	/**
	 * Step 3: DoExpressCheckoutPayment
	 * 
	 * Completes the payment that has already been started and authorized by the user
	 * via the paypal website. Requires passing in purchase information again.
	 * https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_api_nvp_r_DoExpressCheckoutPayment
	 * 
	 * @param userId The user's unique id in our system
	 * @param itemId The unique id in our system for the thing being bought
	 * @param itemName Shown in paypal as the name of the thing being bought eg "Dapper hat"
	 * @param itemDescription Shown in paypal beneath the item name eg "A virtual item in the game Incredipede"
	 * @param itemPriceDollars String price in USD must include decimal and two digits after eg "10.00"
	 */
	public static void finishPurchase(String token, String payerId, int userId, int itemId, String itemName, 
		String itemDescription, String itemPriceDollars)
	{
		try {
			String data = 
				"METHOD=DoExpressCheckoutPayment" +
				getAuthenticationData() +
				"&TOKEN=" + encodeValue(token) +
				"&PAYERID=" + encodeValue(payerId) +
				getPurchaseData(userId, itemId, itemName, itemDescription, itemPriceDollars) + 
				"";
		
			HashMap results = doServerCall(data);
			
			// warn if transaction type isn't completed or on the way to completed
			String status = results.get("PAYMENTINFO_0_PAYMENTSTATUS");
			if (status == null || !(status.equalsIgnoreCase("Completed") 
				|| status.equalsIgnoreCase("In-Progress")
				|| status.equalsIgnoreCase("Processed") 
				|| status.equalsIgnoreCase("Completed-Funds-Held"))) {
				ActionHandler.log.warning("Unexpected paypal purchase status: " + status 
					+ " for userId=" + userId + ", paypal payerId=" + payerId 
					+ ", transaction=" + results.get("PAYMENTINFO_0_TRANSACTIONID"));
			}
			
		// must rollback purchase if anything happens here, so make sure we catch them all
		} catch (RuntimeException e) {
			e.printStackTrace();
			throw new RuntimeException(e.getMessage());
		}
	}
	
	/**
	 * Return the name-value-pair parameters required for SetExpressCheckout and 
	 * DoExpressCheckoutPayment steps.
	 *
	 * @param userId The user's unique id in our system
	 * @param itemId The unique id in our system for the thing being bought
	 * @param itemName Shown in paypal as the name of the thing being bought eg "Dapper hat"
	 * @param itemDescription Shown in paypal beneath the item name eg "A virtual item in the game Incredipede"
	 * @param itemPriceDollars String price in USD must include decimal and two digits after eg "10.00"
	 */
	protected static String getPurchaseData(int userId, int itemId, String itemName, 
		String itemDescription, String itemPriceDollars)
	{
		return 
			"&PAYMENTREQUEST_0_AMT=" + itemPriceDollars + 
			"&PAYMENTREQUEST_0_ITEMAMT=" + itemPriceDollars + 
			"&PAYMENTREQUEST_0_DESC=" + itemDescription + 
			"&PAYMENTREQUEST_0_CUSTOM=" + userId + 
			"&PAYMENTREQUEST_0_INVNUM=" + itemId + 
			"&L_PAYMENTREQUEST_0_NAME0=" + itemName + 
			"&L_PAYMENTREQUEST_0_DESC0=" + itemDescription + 
			"&L_PAYMENTREQUEST_0_AMT0=" + itemPriceDollars + 
			"&L_PAYMENTREQUEST_0_QTY0=" + 1 + 
			"&L_PAYMENTREQUEST_0_ITEMCATEGORY0=Digital" +
			"";
	}

	/**
	 * Return the name-value-pair parameters required for all paypal api calls 
	 * to authenticate the seller account.
	 */
	protected static String getAuthenticationData()
	{
		return 
			"&VERSION=69.0" +
			"&USER=" + API_USERNAME +
			"&PWD=" + API_PASSWORD +
			"&SIGNATURE=" + API_SIGNATURE +
			"";
	}
	
	/**
	 * Send off the given data to PayPal's API and return the result in key-value pairs.
	 * Validate the ACK return value from paypal and throw an exception if it isn't "Success".
	 */
	protected static HashMap doServerCall (String data)
	{
		log.info("Sending data to paypal: " + data);
	
		String response = "";
		try {
			URL postURL = new URL(API_URL);
			HttpURLConnection conn = (HttpURLConnection)postURL.openConnection();
			conn.setDoInput(true);
			conn.setDoOutput(true);
			conn.setConnectTimeout(3000);
			conn.setReadTimeout(7000);
			conn.setRequestMethod("POST");
			
			DataOutputStream output = new DataOutputStream(conn.getOutputStream());
			output.writeBytes(data);
			output.flush();
			output.close();

			// Read input from the input stream.
			int responseCode = conn.getResponseCode();
			if (responseCode != HttpURLConnection.HTTP_OK) {
				throw new RuntimeException("Error " + responseCode + ": " + conn.getResponseMessage());
			}
			
			BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
			String line = null;
			while(((line = reader.readLine()) !=null)) {
				response = response + line;
			}
			reader.close();
		} catch(IOException e) {
			e.printStackTrace();
			throw new RuntimeException(e.getMessage());
		}

		log.info("Got response from paypal: " + response);
		if(response.length() <= 0) {
			throw new RuntimeException("Received empty response");
		}
		HashMap results = parsePaypalResponse(response);
		
		// first check for the new version (PAYMENTINFO_0_ACK)
		String ackString = results.get("PAYMENTINFO_0_ACK");
		if (ackString == null || !(ackString.equalsIgnoreCase("Success") || ackString.equalsIgnoreCase("SuccessWithWarning"))) {
			String errorCode = results.get("PAYMENTINFO_0_ERRORCODE");
			String errorLongMsg = results.get("PAYMENTINFO_0_LONGMESSAGE");
			if (errorCode != null && errorCode.trim().length() > 0) {
				throw new RuntimeException("Purchase Failed (code " + errorCode + "): " + errorLongMsg);
			}
			
			// sometimes API returns old version ACK instead of PAYMENTINFO_0_ACK
			ackString = results.get("ACK");
			if (ackString == null || !(ackString.equalsIgnoreCase("Success") || ackString.equalsIgnoreCase("SuccessWithWarning"))) {
				errorCode = results.get("L_ERRORCODE0");
				errorLongMsg = results.get("L_LONGMESSAGE0");
				throw new RuntimeException("Purchase Failed (code " + errorCode + "): " + errorLongMsg);
			}
		}
		
		return results;
	}
	
	/**
	 * Parse results from PayPal to a map of name/value pairs. Their format looks like: 
	 * "TOKEN=EC%2d80X519901R8632201&TIMESTAMP=2012%2d07%2d13T09%3a57%3a44Z&ACK=Success"
	 */
	protected static HashMap parsePaypalResponse (String data)
	{
		HashMap results = new HashMap();
		StringTokenizer tokenizer = new StringTokenizer(data, "&");
		while (tokenizer.hasMoreTokens()) {
			StringTokenizer tokenizer2 = new StringTokenizer(tokenizer.nextToken(), "=");
			if (tokenizer2.countTokens() != 2) {
				continue;
			}
			String key = decodeValue(tokenizer2.nextToken());
			String value = decodeValue(tokenizer2.nextToken());
			results.put(key.toUpperCase(), value);
		}
		return results;
	}
	
	/**
	 * Prepare a given string for transmission via HTTP. Spaces become %20, etc.
	 */
	protected static String encodeValue(String value)
	{
		try {
			return URLEncoder.encode(value, "UTF-8");
		} catch (Exception e) {
			e.printStackTrace();
			return "";
		}
	}
	
	/**
	 * Undo encoding of string that was sent via HTTP. %20 becomes a space, etc.
	 */
	protected static String decodeValue(String value)
	{
		try {
			return URLDecoder.decode(value, "UTF-8");
		} catch (Exception e) {
			e.printStackTrace();
			return "";
		}
	}
}


Share

flattr this!

Rebuild: Porting to the PlayBook via Adobe AIR and FlashDevelop

29

Posted by Sarah Northway | Posted in Development, technical | Posted on 11-03-2012

Tags:

Pugs Luv Beats

I helped demo IGF nominees Faraway and the very musical Pugs Luv Beats.

Last week was my first GDC as an indie developer, and hoh boy were those goodtimes!

I helped Colin present in a talk about failure (Incredipede was the happy ending), and co-demoed Steph Thirion’s game Faraway in the Independent Games Festival. During the awards ceremony they gave us all BlackBerry PlayBooks, so I now have little excuse not to port Rebuild to it.

I sat down to get it running today and spent far too long stepping through RiM’s convoluted developer security setup, which took me even longer than Apple’s similarly obtuse system. There are several tutorials out there but some were out of date or assume you have Flash Builder (I use FlashDevelop). So as of March 2012, here’s what you do to get your SWF running as an app on your PlayBook:

Step 1: Request a CSJ code signing key from BlackBerry.com (takes a couple hours).

Step 2: Get the Flex SDK, AIR SDK, and BlackBerry AIR SDK. I had trouble with Flex4.6 + Air3.2 so I used Flex4.5 with Air 3.1.

Step 3: Start dev mode on your PlayBook in Options > Security > Development Mode.

Step 4: Edit & execute the following to install the debug token on your PlayBook (ten steps and five different passwords, seriously?):

@echo off

:: BlackBerry development token 
:: More information:
:: https://bdsc.webapps.blackberry.com/air/documentation/ww_air_testing/Create_a_debug_token_CMD_ms_1968147_11.html
:: http://www.hsharma.com/tutorials/10-easy-steps-to-package-and-sign-air-apps-for-playbook/
:: http://docs.blackberry.com/en/developers/deliverables/27280/Running_unsigned_apps_debug_tokens_1585072_11.jsp
:: http://openbbnews.wordpress.com/2011/11/16/installing-a-debug-token/
:: http://www.mellisdesigns.com/blog/?p=37

:: Path to Blackberry SDK
set PATH=%PATH%;C:\Program Files (x86)\Research in Motion\blackberry-tablet-sdk-2.0.0\bin

:: Path to Java
set PATH=%PATH%;C:\Program Files (x86)\Java\jre6\bin

echo First get CSJ from https://bdsc.webapps.blackberry.com/air/signingkeys
echo And start dev mode on PlayBook in Options > Security > Development Mode

:: begin setup
call blackberry-signer -csksetup -cskpass [YOUR_PASSWORD]

:: register CSJ locally
call blackberry-signer -register -csjpin [YOUR_PASSWORD] -cskpass [YOUR_PASSWORD] client-PBDT-[XXXXXXXXXX].csj

:: create p12 file
call blackberry-keytool -genkeypair -keystore author.p12 -storepass [YOUR_PASSWORD] -dname "cn=YOUR_NAME" -alias author

:: register device against CSJ
call blackberry-debugtokenrequest -register -csjpin [YOUR_DEVICE_ID] -storepass [YOUR_PASSWORD] client-PBDT-[XXXXXXXXXX].csj

:: later calls expect the p12 here for some reason
call copy author.p12 "C:\Users\USER_NAME\AppData\Local\Research In Motion\author.p12"

:: create the debug token BAR
call blackberry-debugtokenrequest -storepass [YOUR_PASSWORD] -devicepin [YOUR_DEVICE_ID] debug_token.bar

:: sign bar with RIM (remote)
call blackberry-signer -verbose -cskpass [YOUR_PASSWORD] -keystore author.p12 -storepass [YOUR_PASSWORD] debug_token.bar PBDT

:: sign bar with developer (local)
call blackberry-signer -keystore author.p12 -storepass [YOUR_PASSWORD] debug_token.bar author

:: upload debug token to playbook (must be running in debug mode at this address & password)
call blackberry-deploy -installDebugToken debug_token.bar -device [DEVICE_IP_ADDRESS] -password [YOUR_PASSWORD]

:: echo important metadata
echo Add the following authorId to bar-descriptor.xml
call blackberry-airpackager -listManifest debug_token.bar

pause

Step 5: Download this FlashDevelop project adapted from Studio Chris’ BlackBerry template, and edit airplaybook.as3proj and airplaybookConfig.xml to point to your SDK locations.

Step 6: From FlashDevelop, hit F5 to build your SWF and test it locally using ADL.

Step 7: Edit & execute the following to package and install the app:

@echo off
:: AIR application packaging
:: More information:
:: http://livedocs.adobe.com/flex/3/html/help.html?content=CommandLineTools_5.html#1035959
:: http://www.hsharma.com/tutorials/10-easy-steps-to-package-and-sign-air-apps-for-playbook/

:: Path to Flex + AIR SDK
set PATH=%PATH%;C:\Program Files (x86)\Adobe\Flex451AIR31\bin

:: Path to Blackberry
set PATH=%PATH%;C:\Program Files (x86)\Research in Motion\blackberry-tablet-sdk-2.0.0\bin

:: Path to Java
set PATH=%PATH%;C:\Program Files (x86)\Java\jre6\bin

:: package swf and assets into a bar then install to device and run the app
call blackberry-airpackager -package airplaybook.bar -installApp -launchApp application.xml bar-descriptor.xml airplaybook.swf blackberry-tablet-icon.png landscape-splash.png portrait-splash.png -devMode -device [YOUR_DEVICE_IP] -password [YOUR_PASSWORD]

pause

If all goes well, you should see Main.as (a red square on a black background) appear on your BlackBerry. Chances are good that some parts of the debug token installation aren’t necessary but it got the job done.

Rebuild actually ran the first time, and quite well which was an unexpected surprise. Now I need to adjust the fonts and aspect ratio, then go through what promises to be another certification nightmare to package it for sale in the BlackBerry App World. It’ll probably take two or three days total, so no great loss if it bombs.

Rebuild should be out for the PlayBook by the end of the month!

Share

flattr this!

Debugging Shaders in Flash with PIX

7

Posted by Colin Northway | Posted in Development, technical | Posted on 20-01-2012

Tags:

** Update: It’s hard to tell but I think PIX is no longer a free app and is now part of Visual Studio :/ I have started using Intel’s profiler/debugger which is quite good but requires you to have an intel card you can use **

What the Fuck!

What the Fuck is it DOING!?

Why in the name of hell isn’t it working?

These are just a few of the fun phrases you can apply in the fun field of Shader programming with Flash’s new Stage3d API.

Shaders are cool as hell. In my last post I wrote up an example of a pixel shader that makes grass wave in the wind. It was a pretty simple 7 line shader and I managed to write it without a debugger of any kind. The real problem comes when you want to write something more complicated. Then the lack of any solid feedback will start to drive you crazy. You will begin to fantasize about introducing axes to graphics cards with great force. Fortunately Microsoft, in a rare show of magnanimity, has given us a way out.

There is a tool in DirectX Utilities called PIX which can be used to examine shader variables as well as step through their operation! In a perfect world that would be all the instruction required: use PIX. Unfortunately PIX has a bit of a learning curve and there are a few bends to the curve that are far from obvious so I’m going to tediously document every little step.

I’m going assume you have the standalone flash player. But you will need that if you don’t have it. This example is going to use my simple Shader Testbed which you can download from this post.

First go download DirectX and install it. *I don’t think this link will get you PIX anymore* Now run PIX which is one of the DirectX Utilities in your start menu. Inside PIX select file->New Experiment You will be presented with a window that looks like this:

Provide the path to your flash stand alone player in the Program path field. Select the “a single-frame capture of Direct3D whenever F12 is pressed” radio button. Now click the “more options” button and then the “Target Program” tab.

In the “Command-line arguments” field fill in the path to your swf. Now hit “Start Experiment”. If your swf doesn’t start running then you’ve got a filename wrong or something.

Ideally now your swf is running. Do whatever you have to do to get your shader running and the results on the screen. For my Shader Testbed example I’m going to paste in the wavy grass shader.

The moment has come. Now hit F-12. This will capture the state of the program on that frame. Close your swf. This will bring up PIX’s analysis window which is chock-a-block with stuff. You want to debug a pixel and look at watch the shader modify its value. To do that:

To get there:

  1. Select the frame you captured
  2. Select the render tab, this will bring up a screenshot of the frame in question
  3. Right click on the pixel you’re interested in and select “Debug This Pixel”

Now you’ll get a list of draw calls pertaining to that pixel. Unfortunately for some reason it won’t include the draw call you care about. Or it will, but it won’t include your shader… or something. For whatever reason you can’t debug your shader from here. You can debug some shader here but you can’t debug your shader here.

To debug your shader you have to select the specific draw call that displays your texture. You can find that by expanding the Frame and finding the draw call manually.

After expanding the Frame press the “Next Draw Call” button. It’s a capital D with an arrow next to it. Then make sure you have the “render” tab open on the right.

Watch the screenshot on the right and go from draw call to draw call until your texture shows up. Now stop hitting the “Next Draw Call” button or you’ll get back to the useless state. Ok, now go back to the Render window and right-click a pixel on your texture and select “Debug This Pixel”.

This time it will actually work! Hit the “Debug Pixel” link and you will get to step through your shader code and inspect the registers!

It’s not in AGAL, it’s in some other shader assembly but, you know, seen one shader assembly language you seen ‘em all really.

So, yeah, that did take a fair amount of work and yes, the turn around time between debug sessions isn’t great. But damn it shaders are super cool and if you want to do much with them then you’re going to have to be able to do some debugging.

Hope you found this useful. It took me a while to figure out. Also, if you have trouble finding the right draw call you can always use the “Objects” window to find the memory address of your particular shader, search the Frame for that address, and then you know the next draw call is the one you want.

Woo! Shaders!

Share

flattr this!