void testApp::generateVBOs() {
    // If the meshes have already been generated once, we need to clear their data
    vboMeshOriginal.clear();
    vboMeshMirrored.clear();

    // Shorthand to make the equations more readable
    //  r is the radius of a circle that is inscribed by the display window
    //  a is the current angle heading we will use to sample the image
    //  da is half the span of the triangle
    float r = minWindowDimension/2.0;
    float a = ofDegToRad(imageSectionHeading);
    float da = ofDegToRad(triangleAngularWidth/2);

    // Define the vertices of the triangular face
    ofVec3f triangleBottom(0, 0, 0);
    ofVec3f triangleLeft(r*cos(a+da), -r*sin(a+da), 0);     // Flip because of drawing
    ofVec3f triangleRight(r*cos(a-da), -r*sin(a-da), 0);    // Flip because of drawing

    float cx = imageSectionCenter.x;
    float cy = imageSectionCenter.y;
    r = minImageDimension/2.0;

    // Define the triangular section of the image that we want to draw on the face
    ofVec2f textureBottom(cx, cy);
    ofVec2f textureLeft(cx+r*cos(a+da), cy-r*sin(a+da));
    ofVec2f textureRight(cx+r*cos(a-da), cy-r*sin(a-da));

    // Add the vertices to the VBO mesh to form a triangle
    addFace(vboMeshOriginal, triangleBottom, triangleLeft, triangleRight);
    addFace(vboMeshMirrored, triangleBottom, triangleLeft, triangleRight);

    // Add the texture coordinates to the mesh
    addTexture(vboMeshOriginal, textureBottom, textureLeft, textureRight);
    addTexture(vboMeshMirrored, textureBottom, textureRight, textureLeft);
}
void UltraEyeRenderer::drawHenshinInstruction()
{
	XuSkeletonJointInfo jrh, jh, jre;
	m_henshinDetector->getUserDetector()->getSkeletonJointInfo(XU_SKEL_RIGHT_HAND, &jrh);
	m_henshinDetector->getUserDetector()->getSkeletonJointInfo(XU_SKEL_HEAD, &jh);
	m_henshinDetector->getUserDetector()->getSkeletonJointInfo(XU_SKEL_RIGHT_ELBOW, &jre);

	if (!isConfident(jrh) || !isConfident(jh) || !isConfident(jre)) return;

	const float CIRCLE_RADIUS = 100;

	XV3 fv(m_henshinDetector->getUserDetector()->getForwardVector());
	XV3 uv(m_henshinDetector->getUserDetector()->getUpVector());
	XV3 armDirection((jrh.position - jre.position).normalize());
	XV3 adjustedRightHand(jrh.position + armDirection * 30); // slightly move to the fingertip side
	XV3 adjustedHead(jh.position + fv * 100); // slightly move forward
	XV3 arrowTip(adjustedRightHand.interpolate(adjustedHead, 0.95f));
	XV3 arrowBottom(adjustedRightHand.interpolate(adjustedHead, 0.0f));
	float len = (arrowTip - arrowBottom).magnitude();
	XV3 triangleBottom(arrowBottom.interpolate(arrowTip, 0.8f));
	XV3 triangleOpening = (arrowTip - arrowBottom).cross(armDirection).normalize() * len * 0.1f;
	XV3 arrowPlaneNorm = (arrowTip - arrowBottom).cross(triangleOpening).normalize();
	XV3 triangleEnd1(triangleBottom + triangleOpening);
	XV3 triangleEnd2(triangleBottom - triangleOpening);

	float maxAlpha = cramp((len - 50.0f) / 150.0f, 0, 1);
	float blinkSpeed = 1000.0f / std::max(len - 100.0f, 100.f);
	m_phase += m_ticker.tick() * blinkSpeed;
	float alpha = square(std::sin(m_phase)) * maxAlpha;

	M3DVector4f arrowColor = { 0.7f, 0.0f, 0.0f, alpha };
	m_rctx->shaderMan->UseStockShader(GLT_SHADER_FLAT, m_rctx->transform.GetModelViewProjectionMatrix(), arrowColor);

	const float THICKNESS = 2;
	glDisable(GL_DEPTH_TEST);
	glLineWidth(getPointSize() * THICKNESS);
	glPointSize(getPointSize() * THICKNESS);

	glBegin(GL_LINES);
	if (len > CIRCLE_RADIUS) {
		glVertex3fv(XV3toM3D(arrowBottom + (arrowTip - arrowBottom).normalize() * CIRCLE_RADIUS));
		glVertex3fv(XV3toM3D(arrowTip));
	}
	glVertex3fv(XV3toM3D(arrowTip));
	glVertex3fv(XV3toM3D(triangleEnd1));
	glVertex3fv(XV3toM3D(arrowTip));
	glVertex3fv(XV3toM3D(triangleEnd2));
	glEnd();

	glBegin(GL_LINE_LOOP);
	XV3 r0((arrowTip - arrowBottom).normalize() * CIRCLE_RADIUS);
	GLFrame f;
	f.SetForwardVector(0, 0, 1); // invert Z
	const int SEGMENTS = 24;
	const float STEP_ANGLE = float(M_PI * 2 / SEGMENTS);
	for (int i = 0; i < SEGMENTS; i++) {
		f.RotateLocal(STEP_ANGLE, arrowPlaneNorm.X, arrowPlaneNorm.Y, arrowPlaneNorm.Z);
		M3DVector3f r;
		f.TransformPoint(XV3toM3D(r0), r);
		glVertex3fv(XV3toM3D(arrowBottom + r));
	}
	glEnd();

	if (m_isNewUser) {
		XV3 p(-0.95f, 0.80f, 0.0f), s(0.001f, 0.0015f, 1.0f);
		renderStrokeText(m_rctx, "Put your Ultra Eye On! Now!", p, s, 3.0f, arrowColor);
	}

	glEnable(GL_DEPTH_TEST);
}