/**
 * Using the Storm 3D library to render simple geometry.
 */

use ui;
use ui:s3d;
use core:geometry;
use graphics;

/**
 * A simple vertex type to store vertex information.
 *
 * For this sample, we only store a 3D position and a color.
 */
vertex SimpleVertex {
	// 3D position.
	Vector pos;

	// Color.
	Color color;
}

/**
 * We also need a vertex type that describes the output from the vertex shader and the input to the
 * pixel shader.
 *
 * Since the output from the vertex shader is interpolated before it is used as an input to the
 * pixel shader, we need to specify which item is the vertex position, and we can also specify how
 * to interpolate points.
 */
vertex SimpleIntermediate {
	// Screen position. 'perspective' is interpolation and can be omitted.
	Vector4 pos : position, perspective;

	// Vertex color. Also interpolated.
	Color color : perspective;
}

/**
 * Finally we need a vertex type for outputs. Here, we only need a single color (marked "color") to
 * indicate which value should be used for the final pixel color. We may also mark a single element
 * as "depth" if we wish to alter the depth value.
 */
vertex SimpleOutput {
	// Output color.
	Color color : color;
}

/**
 * Now we can create a pipeline. It describes the rendering pipeline, and defines any uniforms we
 * need when rendering.
 */
pipeline SimplePipeline {
	// The flow of data through the pipeline:
	// (input parameters) -> vertex shader output -> pixel shader output
	// Note that some of the input parameters can be marked 'uniform'.
	data (SimpleVertex point, uniform Float time) -> SimpleIntermediate -> SimpleOutput;

	// Additional members become uniforms automatically, but are set globally rather than passed to
	// the function.
	Transform camera;
}

// Vertex shader: transform from model space to camera space.
vertex shader for SimplePipeline: SimpleIntermediate simpleVertexShader(SimpleVertex point) {
	// Note: 'camera' is a uniform and is available to us.
	Vector4 pos = Vector4(point.pos) * camera;

	// Try to use the 'time' uniform to alter the position:
	// pos.x += sin(360.0 deg * time);

	// Note: 'return' is optional, just as in Basic Storm.
	return SimpleIntermediate(pos, point.color);
}

// Pixel shader: determine the color of the pixel from the contents of the intermediate vertex.
pixel shader for SimplePipeline: SimpleOutput simplePixelShader(SimpleIntermediate point) {
	// Here, we just return the output color.
	return SimpleOutput(point.color);
}


/**
 * Painter. We attach this to a window to be able to render things.
 *
 * Note: You would usually just inherit from Painter3D. The SamplePainter3D class contains some
 * additional helpers.
 */
class SimplePainter extends SamplePainter3D {
	// The pipeline itself.
	SimplePipeline pipeline;

	// Vertex buffer.
	VertexBuffer<SimpleVertex> vertices;

	// Index buffer to avoid duplicate vertices.
	IndexBuffer indices;

	// Camera transform.
	Transform cameraPos;

	// Create.
	init() {
		init {
			// Create the pipeline and attach shaders to it.
			pipeline = SimplePipeline:create(simpleVertexShader, simplePixelShader);

			// Create a default camera transform.
			cameraPos = rotateX(-30 deg) * translate(Vector(0, 0, 3));
		}

		// Create a cube. First, create one vertex for each corner.
		vertices << SimpleVertex(Vector(-1, 1, 1), red);
		vertices << SimpleVertex(Vector(1, 1, 1), green);
		vertices << SimpleVertex(Vector(-1, -1, 1), blue);
		vertices << SimpleVertex(Vector(1, -1, 1), yellow);
		vertices << SimpleVertex(Vector(-1, 1, -1), red);
		vertices << SimpleVertex(Vector(1, 1, -1), green);
		vertices << SimpleVertex(Vector(-1, -1, -1), blue);
		vertices << SimpleVertex(Vector(1, -1, -1), yellow);

		// Then connect them using the index buffer.

		// Back face:
		indices << 0 << 2 << 1;
		indices << 1 << 2 << 3;

		// Top face:
		indices << 0 << 1 << 4;
		indices << 1 << 5 << 4;

		// Bottom face:
		indices << 2 << 6 << 3;
		indices << 3 << 6 << 7;

		// Left face:
		indices << 0 << 4 << 2;
		indices << 4 << 6 << 2;

		// Right face:
		indices << 1 << 3 << 5;
		indices << 5 << 3 << 7;

		// Front face:
		indices << 4 << 5 << 6;
		indices << 5 << 7 << 6;
	}

	// Render. This function is called to render a single frame.
	Bool render(Size size, Graphics3D g) : override {
		// Get a time value. Increases linearly from 0 to 1. See SamplePainter3D in common.bs for
		// the implementation.
		Float time = animationTime(5 s);

		// Clear the old buffer.
		g.clearDepth();
		g.fill(black);

		// Set up the camera. Note: this sets the uniform in the pipeline.
		pipeline.camera = rotateY(360 deg * time) * cameraPos * perspectiveProject(100 deg, size.aspect, 0.1, 100);

		// Then we are ready to render. Note that the 'time' parameter is passed as a parameter to
		// 'render' rather than by setting pipeline state since it was declared as a parameter.
		pipeline.render(g, indices, vertices, time);

		// Return true to tell the rendering system we want to redraw continuously.
		true;
	}
}

// Main function. See common.bs for the implementation of 'runSample'.
void simple() {
	runSample("Simple 3D", SimplePainter());
}
