Skip to content

Instantly share code, notes, and snippets.

@Justinfront
Last active June 4, 2016 13:13
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Justinfront/0b7acb1a4e9213deaad5 to your computer and use it in GitHub Desktop.
Save Justinfront/0b7acb1a4e9213deaad5 to your computer and use it in GitHub Desktop.
Using TwoLines with Kha to draw 2D vector in 3d.
-cmd node Kha/make -t html5
-cmd node Kha/make --server
package;
// Modified from http://luboslenco.com/kha3d/
// see example 6.
import kha.Game;
import kha.Framebuffer;
import kha.Color;
import kha.Loader;
import kha.graphics4.Program;
import kha.graphics4.VertexStructure;
import kha.graphics4.VertexBuffer;
import kha.graphics4.IndexBuffer;
import kha.graphics4.FragmentShader;
import kha.graphics4.VertexShader;
import kha.graphics4.VertexData;
import kha.graphics4.Usage;
import kha.graphics4.ConstantLocation;
import kha.graphics4.CompareMode;
import kha.math.Matrix4;
import kha.math.Vector3;
import TwoLines;
class Empty extends Game {
// An array of vertices to form a cube
static var vertices:Array<Float> = [];
// Array of colors for each cube vertex
static var colors:Array<Float> = [];
var vertexBuffer:VertexBuffer;
var indexBuffer:IndexBuffer;
var program:Program;
var mvp:Matrix4;
var mvpID:ConstantLocation;
public function new() {
super("Empty");
}
override public function init() {
// Define vertex structure
var structure = new VertexStructure();
structure.add("pos", VertexData.Float3);
structure.add("col", VertexData.Float3);
// Save length - we store position and color data
var structureLength = 6;
// Load shaders - these are located in 'Sources/Shaders' directory
// and Kha includes them automatically
var fragmentShader = new FragmentShader(Loader.the.getShader("simple.frag"));
var vertexShader = new VertexShader(Loader.the.getShader("simple.vert"));
// Link program with fragment and vertex shaders we loaded
program = new Program();
program.setFragmentShader(fragmentShader);
program.setVertexShader(vertexShader);
program.link(structure);
// Get a handle for our "MVP" uniform
mvpID = program.getConstantLocation("MVP");
// Projection matrix: 45° Field of View, 4:3 ratio, display range : 0.1 unit <-> 100 units
var projection = Matrix4.perspectiveProjection(45.0, 4.0 / 3.0, 0.1, 100.0);
// Or, for an ortho camera
//var projection = Matrix4.orthogonalProjection(-10.0, 10.0, -10.0, 10.0, 0.0, 100.0); // In world coordinates
// Camera matrix
var view = Matrix4.lookAt(new Vector3(4, 3, 3), // Camera is at (4, 3, 3), in World Space
new Vector3(0, 0, 0), // and looks at the origin
new Vector3(0, -1, 0) // Head is up (set to (0, -1, 0) to look upside-down)
);
// Model matrix: an identity matrix (model will be at the origin)
var model = Matrix4.identity();
// Our ModelViewProjection: multiplication of our 3 matrices
// Remember, matrix multiplication is the other way around
mvp = Matrix4.identity();
mvp = mvp.multmat(projection);
mvp = mvp.multmat(view);
mvp = mvp.multmat(model);
var verticesLocal = vertices;
var colorsLocal = colors;
//TwoLines.testColors = true; // sets some default colors.
TwoLines.setupSingleColors( 0xff0000, 1, 0xff0000, 1 );
TwoLines.thickness = 50;
var twoLines = TwoLines;
var toRGBs = toRGB;
var z: Float = -1;
var adjScale: Float = 200;
// create the triangle drawing command.
TwoLines.drawTri = function( p0: Point, p1: Point, p2: Point
, col: Int, alpha: Float
, lineCol: Int, lineAlpha: Float ):Void {
verticesLocal.push( p0.x/adjScale - 2);
verticesLocal.push( p0.y/adjScale );
verticesLocal.push( -z );
verticesLocal.push( p1.x/adjScale - 2);
verticesLocal.push( p1.y/adjScale );
verticesLocal.push( -z );
verticesLocal.push( p2.x/adjScale - 2);
verticesLocal.push( p2.y/adjScale );
verticesLocal.push( -z );
var rgb = toRGBs( col );
colors.push( rgb.r );
colors.push( rgb.g );
colors.push( rgb.b );
colors.push( rgb.r );
colors.push( rgb.g );
colors.push( rgb.b );
colors.push( rgb.r );
colors.push( rgb.g );
colors.push( rgb.b );
//twoLines.thickness -= 0.05;
}
/*
// for reference.
var image = Image.createRenderTarget(256, 256);
image.g2.begin();
image.g2.fillRect(...);
image.g2.end();*/
// wavy line
z = -0.5;
// alpha and outline are not used only first color parameter.
TwoLines.setupSingleColors( 0xff0000, 1, 0xff0000, 1 );
TwoLines.createTriangles( TwoLines.horizontalWavePoints( 10, 5, 100, 50, 60, 3 ) );
// circles
z = 0.5;
TwoLines.setupSingleColors( 0xffff00, 1, 0xffff00, 1 );
TwoLines.createPolyTriangles( TwoLines.polyPoints( 150, 100, 100, 60 ) );
TwoLines.setupSingleColors( 0x0000ff, 1, 0x0000ff, 1 );
TwoLines.createPolyTriangles( TwoLines.polyPoints( 450, 100, 100, 60 ) );
TwoLines.setupSingleColors( 0x00ff00, 1, 0x00ff00, 1 );
TwoLines.createPolyTriangles( TwoLines.polyPoints( 750, 100, 100, 60 ) );
// polygons
z = 1;
TwoLines.thickness = 80;
TwoLines.setupSingleColors( 0xffcc00, 1, 0xffcc00, 1 );
TwoLines.createPolyTriangles( TwoLines.polyPoints( 850, 200, 100, 6 ) );
TwoLines.thickness = 30;
TwoLines.setupSingleColors( 0xff00ff, 1, 0xff00ff, 1 );
TwoLines.createPolyTriangles( TwoLines.boxPoints( 100, 100, 200, 200 ) );
TwoLines.thickness = 10;
TwoLines.setupSingleColors( 0x00ffff, 1, 0x00ffff, 1 );
TwoLines.createPolyTriangles( TwoLines.equalTriPoints( 570, 240, 100, 0 ) );
// quad curve
z = -0.7;
TwoLines.setupSingleColors( 0xF84525, 1, 0xF84525, 1 );
TwoLines.thickness = 30;
var cx = 300;
var cy = -300;
var heartScale = 7;
var pp: Array<Point> = [
{ x: cx - 27*heartScale, y: cy - 20*heartScale },
{ x: cx - 15*heartScale, y: cy - 30*heartScale },
{ x: cx + 1, y: cy - 15*heartScale },
{ x: cx + 15*heartScale, y: cy - 30*heartScale },
{ x: cx + 27*heartScale, y: cy - 20*heartScale },
{ x: cx + 34*heartScale, y: cy - 5*heartScale },
{ x: cx + 20*heartScale, y: cy + 6*heartScale },
{ x: cx + 25*heartScale, y: cy +1 },
{ x: cx + 1, y: cy + 30*heartScale },
{ x: cx - 25*heartScale, y: cy + 1},
{ x: cx - 20*heartScale, y: cy + 6*heartScale },
{ x: cx - 34*heartScale, y: cy - 5*heartScale },
{ x: cx - 28*heartScale, y: cy - 20*heartScale },
{ x: cx - 27*heartScale, y: cy - 20*heartScale }];
pp.reverse();
for( i in 0...(pp.length-2) ){
if( (i-1)%2 == 0 ){
TwoLines.createTriangles( TwoLines.quadCurve( pp[i], pp[i+1], pp[i+2] ) );
}
}
// Create vertex buffer
vertexBuffer = new VertexBuffer(
Std.int(vertices.length / 3), // Vertex count - 3 floats per vertex
structure, // Vertex structure
Usage.StaticUsage // Vertex data will stay the same
);
// Copy vertices and colors to vertex buffer
var vbData = vertexBuffer.lock();
for (i in 0...Std.int(vbData.length / structureLength)) {
vbData[i * structureLength] = vertices[i * 3];
vbData[i * structureLength + 1] = vertices[i * 3 + 1];
vbData[i * structureLength + 2] = vertices[i * 3 + 2];
vbData[i * structureLength + 3] = colors[i * 3];
vbData[i * structureLength + 4] = colors[i * 3 + 1];
vbData[i * structureLength + 5] = colors[i * 3 + 2];
}
vertexBuffer.unlock();
// A 'trick' to create indices for a non-indexed vertex data
var indices:Array<Int> = [];
for (i in 0...Std.int(vertices.length / 3)) {
indices.push(i);
}
// Create index buffer
indexBuffer = new IndexBuffer(
indices.length, // Number of indices for our cube
Usage.StaticUsage // Index data will stay the same
);
// Copy indices to index buffer
var iData = indexBuffer.lock();
for (i in 0...iData.length) {
iData[i] = indices[i];
}
indexBuffer.unlock();
}
public static inline function toRGB( int: Int ) : { r: Float, g: Float, b: Float } {
return {
r: ((int >> 16) & 255) / 255,
g: ((int >> 8) & 255) / 255,
b: (int & 255) / 255,
}
}
override public function render( frame: Framebuffer ) {
// A graphics object which lets us perform 3D operations
var g = frame.g4;
// Begin rendering
g.begin();
// Set depth mode
g.setDepthMode(true, CompareMode.Less);
// Clear screen
g.clear(Color.fromFloats(0.0, 0.0, 0.3), 1.0);
// Bind data we want to draw
g.setVertexBuffer(vertexBuffer);
g.setIndexBuffer(indexBuffer);
// Bind shader program we want to draw with
g.setProgram(program);
// Set our transformation to the currently bound shader, in the "MVP" uniform
g.setMatrix(mvpID, mvp);
// Draw!
g.drawIndexedVertices();
// End rendering
g.end();
}
}
package;
import kha.Starter;
class Main {
public static function main() {
var starter = new Starter();
starter.start(new Empty());
}
}
package;
typedef Point = { x: Float, y: Float }
class TwoLines {
var p0: Point;
var p1: Point;
var p2: Point;
public var p3: Point;
public var p4: Point;
var angleA: Float; // smallest angle between lines
var cosA: Float;
var b2: Float;
var c2: Float;
var a2: Float;
var b: Float; // first line length
var c: Float; // second line length
var a: Float;
var angleD: Float;
var halfA: Float;
var beta: Float;
var r: Float;
var _theta: Float;
var angle1: Float;
var angle2: Float;
public function new( p0_: Point, p1_: Point, p2_: Point, thick: Float ){
p0 = p0_;
p1 = p1_;
p2 = p2_;
b2 = dist2( p0, p1 );
c2 = dist2( p1, p2 );
a2 = dist2( p0, p2 );
b = Math.sqrt( b2 );
c = Math.sqrt( c2 );
a = Math.sqrt( a2 );
cosA = ( b2 + c2 - a2 )/ ( 2*b*c );
angleA = Math.acos( cosA );
angleD = Math.PI - angleA;
halfA = angleA/2;
beta = Math.PI/2 - halfA;
r = ( thick/2 )/Math.cos( beta );
calculateP3p4();
}
public inline function calculateP3p4(){
_theta = theta( p0, p1 );
if( _theta > 0 ){
if( halfA < 0 ){
angle2 = _theta + halfA + Math.PI/2;
angle1 = _theta - halfA;
}else {
angle1 = _theta + halfA - Math.PI;
angle2 = _theta + halfA;
}
} else {
if( halfA > 0 ){
angle1 = _theta + halfA - Math.PI;
angle2 = _theta + halfA;
} else {
angle2 = _theta + halfA + Math.PI/2;
angle1 = _theta - halfA;
}
}
p3 = { x: p1.x + r * Math.cos( angle1 ), y: p1.y + r * Math.sin( angle1 ) };
p4 = { x: p1.x + r * Math.cos( angle2 ), y: p1.y + r * Math.sin( angle2 ) };
}
public function rebuildAsPoly( p2_: Point ){
p0 = p1;
p1 = p2;
p2 = p2_;
calculateP3p4();
}
private function theta( p0: Point, p1: Point ): Float {
var dx: Float = p0.x - p1.x;
var dy: Float = p0.y - p1.y;
return Math.atan2( dy, dx );
}
private function dist2( p0: Point, p1: Point ): Float {
var dx: Float = p0.x - p1.x;
var dy: Float = p0.y - p1.y;
return dx*dx + dy*dy;
}
public static var thickness: Float;
private static var q0: Point;
private static var q1: Point;
public static var col: Int;
public static var alpha: Float;
public static var lineCol: Int;
public static var lineAlpha: Float;
public static var col2: Int;
public static var alpha2: Float;
public static var lineCol2: Int;
public static var lineAlpha2: Float;
public static function setupSingleColors( col_: Int, alpha_: Float, lineCol_: Int, lineAlpha_: Float ){
col = col_;
col2 = col_;
alpha = alpha_;
alpha2 = alpha_;
lineCol = lineCol_;
lineCol2 = lineCol_;
lineAlpha = lineAlpha_;
lineAlpha2 = lineAlpha_;
}
public static var testColors( null, set ):Bool;
public static function set_testColors( val: Bool ): Bool{
col = 0xffff00;
col2 = 0xff00ff;
lineCol = col;
lineCol2 = col2;
alpha = 0.5;
alpha2 = alpha;
lineAlpha = 0.7;
lineAlpha2 = lineAlpha;
return val;
}
// p1, p2, p3
// , colour, alpha default 1, optional line color depending on support, optional line alpha depending on support
public static var drawTri: Point -> Point -> Point -> Int -> Float -> Int -> Float -> Void;
public static inline function createPolyTriangles( p: Array<Point> ){
q0 = p[0];
q1 = p[0];
var twoLines: TwoLines = firstQuad( p, 0 );
for( i in 1...( p.length - 2 ) ) drawOtherQuad( p, twoLines, i );
}
public static inline function createTriangles( p: Array<Point> ){
q0 = p[0];
q1 = p[0];
for( i in 0...( p.length - 2 ) ) drawQuad( p, i );
}
private static inline function firstQuad( p: Array<Point>, i: Int ): TwoLines {
var twoLines = new TwoLines( p[ i ], p[ i + 1 ], p[ i + 2 ], thickness );
var q3 = twoLines.p3;
var q4 = twoLines.p4;
q0 = q3;
q1 = q4;
return twoLines;
}
// assumes that firstQuad is drawn.
private static inline function drawOtherQuad( p: Array<Point>, twoLines: TwoLines, i: Int ){
twoLines.rebuildAsPoly( p[ i + 2 ]);
var q3 = twoLines.p3;
var q4 = twoLines.p4;
drawTri( q0, q3, q1, col, alpha, lineCol, lineAlpha );
drawTri( q1, q3, q4, col2, alpha2, lineCol2, lineAlpha2 );
q0 = q3;
q1 = q4;
return twoLines;
}
private static inline function drawQuad( p: Array<Point>, i: Int ){
var twoLines = new TwoLines( p[ i ], p[ i + 1 ], p[ i + 2 ], thickness );
var q3 = twoLines.p3;
var q4 = twoLines.p4;
if( i != 0 ){
drawTri( q0, q3, q1, col, alpha, lineCol, lineAlpha );
drawTri( q1, q3, q4, col2, alpha2, lineCol2, lineAlpha2 );
}
q0 = q3;
q1 = q4;
return twoLines;
}
public static inline function boxPoints( x: Float,y: Float, wid: Float, hi: Float ): Array<Point>{
var p: Array<Point> = [ { x: x, y: y }
, { x: x+wid, y: y }
, { x: x+wid, y: y+hi }
, { x: x, y: y+hi }
, { x: x, y: y }
, { x: x+wid, y: y }
, { x: x+wid, y: y+hi }
];
p.reverse();
return p;
}
public static inline function equalTriPoints( dx: Float, dy: Float, radius: Float, ?rotation: Float = 0 ):Array<Point>{
var p: Array<Point> = new Array<Point>();
var angle: Float = 0;
var offset: Float = - 2.5*Math.PI*2/6 - Math.PI + rotation;
for( i in 0...6 ){
angle = i*(Math.PI*2)/3 - offset;
p.push( { x: dx + radius * Math.cos( angle ), y: dy + radius * Math.sin( angle ) });
}
p.reverse();
return p;
}
public static inline function polyPoints( dx: Float, dy: Float, radius: Float, sides: Int ):Array<Point>{
var p: Array<Point> = new Array<Point>();
var angle: Float = 0;
var angleInc: Float = (Math.PI*2)/sides;
for( i in 0...( sides + 3 ) ){
angle = i*angleInc;
p.push( { x: dx + radius * Math.cos( angle ), y: dy + radius * Math.sin( angle ) });
}
p.reverse();
return p;
}
public static inline function horizontalWavePoints( x_: Float, dx_: Float, y_: Float, amplitude: Float, sides: Int, repeats: Float ):Array<Point>{
var p: Array<Point> = new Array<Point>();
var dx: Float = 0;
var angleInc: Float = (Math.PI*2)/sides;
var len: Int = Std.int( sides*repeats );
for( i in 0...len ) p.push( { x: x_ + (dx+=dx_), y: y_ + amplitude * Math.sin( i*angleInc ) });
return p;
}
public static inline function quadCurve( p0, p1, p2 ): Array<Point> {
var p: Array<Point> = new Array<Point>();
var approxDistance = distance( p0, p1 ) + distance( p1, p2 );
var factor = 2;
var v: { x: Float, y: Float };
if( approxDistance == 0 ) approxDistance = 0.000001;
var step = Math.min( 1/( approxDistance*0.707 ), 0.2 );
var arr = [ p0, p1, p2 ];
var t = 0.0;
v = quadraticBezier( 0.0, arr );
p.push( { x: v.x, y: v.y } );
t += step;
while( t < 1 ){
v = quadraticBezier( t, arr );
p.push( { x: v.x, y: v.y } );
t += step;
}
v = quadraticBezier( 1.0, arr );
p.push( { x: v.x, y: v.y } );
//p.reverse();
return p;
}
public static inline function distance( p0: { x: Float, y: Float }
, p1: { x: Float, y: Float }
): Float {
var x = p0.x - p1.x;
var y = p0.y - p1.y;
return Math.sqrt( x*x + y*y );
}
// from divtastic3 and hxDeadalus wings.data.MathPoints
public inline static function quadraticBezier( t: Float
, arr: Array<{ x: Float, y: Float }>
): { x: Float,y: Float } {
return { x: _quadraticBezier( t, arr[0].x, arr[1].x, arr[2].x )
, y: _quadraticBezier( t, arr[0].y, arr[1].y, arr[2].y ) };
}
private inline static function _quadraticBezier ( t: Float
, startPoint: Float
, controlPoint: Float
, endPoint: Float
): Float {
var u = 1 - t;
return Math.pow( u, 2) * startPoint + 2 * u * t * controlPoint + Math.pow( t, 2 ) * endPoint;
}
public static inline function generateMidPoints( arr: Array<{ x: Float, y: Float }>
): Array<{ x: Float, y: Float }>{
var out: Array<{ x: Float, y: Float }> = [];
var a: { x: Float, y: Float };
var b: { x: Float, y: Float };
var len = arr.length - 2;
for( i in 0...len ){
a = arr[ i ];
b = arr[ i + 1 ];
out.push( { x: ( b.x + a.x )/2, y: ( b.y + a.y )/2 });
out.push( { x: b.x, y: b.y } );
}
a = arr[0];
out.unshift( { x: a.x, y: a.y } );
out.unshift( { x: a.x, y: a.y } );
b = arr[ arr.length - 1 ];
out.push( { x: b.x, y: b.y } );
out.push( { x: b.x, y: b.y } );
out.push( { x: b.x, y: b.y } );
return out;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment