9/28/2015
1
Shader Compiling/Linking
Linking Shaders with Application
• Read shaders
• Compile shaders
• Create a program object
• Link everything together
• Link variables in application with variables in shaders
– Vertex attributes
– Uniform variables
2Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
9/28/2015
2
Program Object
• Container for shaders
– Can contain multiple shaders
– Other GLSL functions
var program = gl.createProgram();
gl.attachShader( program, vertShdr ); gl.attachShader( program, fragShdr ); gl.linkProgram( program );
3Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
Reading a Shader
• Shaders are added to the program object and compiled
• Usual method of passing a shader is as a null‐terminated string using the function
• gl.shaderSource( fragShdr, fragElem.text );
• If shader is in HTML file, we can get it into application by getElementById method
• If the shader is in a file, we can write a reader to convert the file to a string
4Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
9/28/2015
3
Adding a Vertex Shader
var vertShdr;var vertElem = document.getElementById( vertexShaderId );
vertShdr = gl.createShader( gl.VERTEX_SHADER );
gl.shaderSource( vertShdr, vertElem.text );gl.compileShader( vertShdr );
// after program object createdgl.attachShader( program, vertShdr );
5Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
Shader Reader
• Following code may be a security issue with some browsers if you try to run it locally
– Cross Origin Request
6
function getShader(gl, shaderName, type) {var shader = gl.createShader(type);shaderScript = loadFileAJAX(shaderName);if (!shaderScript) {alert("Could not find shader source:
"+shaderName);}
}
Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
9/28/2015
4
Precision Declaration
• In GLSL for WebGL we must specify desired precision in fragment shaders– artifact inherited from OpenGL ES
– ES must run on very simple embedded devices that may not support 32‐bit floating point
– All implementations must support mediump
– No default for float in fragment shader
• Can use preprocessor directives (#ifdef) to check if highp supported and, if not, default to mediump
7Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
Pass Through Fragment Shader
#ifdef GL_FRAGMENT_SHADER_PRECISION_HIGHprecision highp float;#elseprecision mediump float;#endif
varying vec4 fcolor;void main(void){
gl_FragColor = fcolor;}
8Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
9/28/2015
5
Execution in Browser
Event Loop
• Remember that the sample program specifies a render function which is a event listener orcallback function
– Every program should have a render callback
– For a static application we need only execute the render function once
– In a dynamic application, the render function can call itself recursively but each redrawing of the display must be triggered by an event
9/28/2015
6
Lack of Object Orientation
• All versions of OpenGL are not object oriented so that there are multiple functions for a given logical function
• Example: sending values to shaders
– gl.uniform3f
– gl.uniform2i
– gl.uniform3dv
• Underlying storage mode is the same
WebGL function format
gl.uniform3f(x,y,z)
belongs to WebGL canvas
function name
x,y,z are variables
gl.uniform3fv(p)
p is an array
dimension
9/28/2015
7
WebGL constants
• Most constants are defined in the canvas object
– In desktop OpenGL, they were in #include files such as gl.h
• Examples– desktop OpenGL
• glEnable(GL_DEPTH_TEST);
– WebGL• gl.enable(gl.DEPTH_TEST)
– gl.clear(gl.COLOR_BUFFER_BIT)
WebGL and JS
9/28/2015
8
Coding in WebGL
• Can run WebGL on any recent browser
– Chrome
– Firefox
– Safari
– IE
• Code written in JavaScript
• JS runs within browser
– Use local resources
WebGL
• Five steps
– Describe page (HTML file)
• request WebGL Canvas
• read in necessary files
– Define shaders (HTML file)
• could be done with a separate file (browser dependent)
– Compute or specify data (JS file)
– Send data to GPU (JS file)
– Render data (JS file)
Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
9/28/2015
9
square.html
<!DOCTYPE html><html><head><script id="vertex‐shader" type="x‐shader/x‐vertex">
attribute vec4 vPosition;void main(){gl_Position = vPosition;
}</script>
<script id="fragment‐shader" type="x‐shader/x‐fragment">
precision mediump float;
void main(){gl_FragColor = vec4( 1.0, 1.0, 1.0, 1.0 );
}</script>
Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
Shaders
• We assign names to the shaders that we can use in the JS file
• These are trivial pass‐through (do nothing) shaders that which set the two required built‐in variables– gl_Position– gl_FragColor
• Note both shaders are full programs• Note vector type vec2• Must set precision in fragment shader
Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
9/28/2015
10
square.html (cont)
<script type="text/javascript" src="../Common/webgl‐utils.js"></script><script type="text/javascript" src="../Common/initShaders.js"></script><script type="text/javascript" src="../Common/MV.js"></script><script type="text/javascript" src="square.js"></script></head>
<body><canvas id="gl‐canvas" width="512" height="512">Oops ... your browser doesn't support the HTML5 canvas element</canvas></body></html>
Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
Files
• ../Common/webgl-utils.js: Standard utilities for setting up WebGL context in Common directory on website
• ../Common/initShaders.js: contains JS and WebGL code for reading, compiling and linking the shaders
• ../Common/MV.js: our matrix‐vector package
• square.js: the application file
Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
9/28/2015
11
square.js
var gl;var points;
window.onload = function init(){var canvas = document.getElementById( "gl‐canvas" );
gl = WebGLUtils.setupWebGL( canvas );if ( !gl ) { alert( "WebGL isn't available" );
}// Four Vertices
var vertices = [vec2( ‐0.5, ‐0.5 ),vec2( ‐0.5, 0.5 ),vec2( 0.5, 0.5 ),vec2( 0.5, ‐0.5)
];
Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
Notes
• onload: determines where to start execution when all code is loaded
• canvas gets WebGL context from HTML file
• vertices use vec2 type in MV.js
• JS array is not the same as a C or Java array
– object with methods
– vertices.length // 4
• Values in clip coordinates
Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
9/28/2015
12
square.js (cont)
// Configure WebGL
gl.viewport( 0, 0, canvas.width, canvas.height );gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
// Load shaders and initialize attribute buffers
var program = initShaders( gl, "vertex‐shader", "fragment‐shader" );gl.useProgram( program );
// Load the data into the GPU
var bufferId = gl.createBuffer();gl.bindBuffer( gl.ARRAY_BUFFER, bufferId );gl.bufferData( gl.ARRAY_BUFFER, flatten(vertices), gl.STATIC_DRAW );
// Associate out shader variables with our data buffer
var vPosition = gl.getAttribLocation( program, "vPosition" );gl.vertexAttribPointer( vPosition, 2, gl.FLOAT, false, 0, 0 );gl.enableVertexAttribArray( vPosition );
Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
Notes
• initShaders used to load, compile and link shaders to form a program object
• Load data onto GPU by creating a vertex buffer object on the GPU– Note use of flatten() to convert JS array to an array of float32’s
• Finally we must connect variable in program with variable in shader– need name, type, location in buffer
Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
9/28/2015
13
square.js (cont)
render();};
function render() {gl.clear( gl.COLOR_BUFFER_BIT );gl.drawArrays( gl.TRIANGLE_FAN, 0, 4 );
}
0
1 2
3
Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
Triangles, Fans or Strips
gl.drawArrays( gl.TRIANGLES, 0, 6 ); // 0, 1, 2, 0, 2, 3
0
1 2
3
gl.drawArrays( gl.TRIANGLE_STRIP, 0, 4 ); // 0, 1, 3, 2
gl.drawArrays( gl.TRIANGLE_FAN, 0, 4 ); // 0, 1 , 2, 3
0
1 2
3
Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
9/28/2015
14
JavaScript Notes
• JavaScript (JS) is the language of the Web
– All browsers will execute JS code
– JavaScript is an interpreted object‐oriented language
• References
– Flanagan, JavaScript: The Definitive Guide, O’Reilly
– Crockford, JavaScript, The Good Parts, O’Reilly
–Many Web tutorials
JS Notes
• Is JS slow?– JS engines in browsers are getting much faster
– Not a key issues for graphics since once we get the data to the GPU it doesn’t matter how we got the data there
• JS is a (too) big language–We don’t need to use it all
– Choose parts we want to use
– Don’t try to make your code look like C or Java
9/28/2015
15
JS Notes
• Very few native types:– numbers– strings– booleans
• Only one numerical type: 32 bit float– var x = 1;– var x = 1.0; // same– potential issue in loops– two operators for equality == and ===
• Dynamic typing
Scoping
• Different from other languages
• Function scope
• variables are hoisted within a function
– can use a variable before it is declared
• Note functions are first class objects in JS
9/28/2015
16
JS Arrays
• JS arrays are objects
– inherit methods
– var a = [1, 2, 3];
is not the same as in C++ or Java
– a.length // 3
– a.push(4); // length now 4
– a.pop(); // 4
– avoids use of many loops and indexing
– Problem for WebGL which expects C‐style arrays
Typed Arrays
JS has typed arrays that are like C arrays
var a = new Float32Array(3)
var b = new Uint8Array(3)
Generally, we prefer to work with standard JS arrays and convert to typed arrays only when we need to send data to the GPU with the flatten function in MV.js
9/28/2015
17
A Minimalist Approach
• We will use only core JS and HTML– no extras or variants
• No additional packages– CSS
– JQuery
• Focus on graphics– examples may lack beauty
• You are welcome to use other variants as long as I can run them from your URL
2D Gasket in Points
9/28/2015
18
35
A Simple Program in pseudocode
main() # Draw a sequence of points{
initialize_system();p = initial_point();
for (some number of points){
q = generate_a_point_based_on(p);display_point(q);p = q;
}cleanup();
}
36
Alternative
main() # Draw a sequence of points{
initialize_system();p = initial_point();
for (some number of points){
q = generate_a_point_based_on(p);store_point(q);p = q;
}display_all_points();cleanup();
}
9/28/2015
19
37
Alternative #2
main() # Draw a sequence of points{
initialize_system();p = initial_point();
for (some number of points) {
q = generate_a_point_based_on(p);store_point(q);p = q;
}send_all_points_to_GPU();display_points_on_GPU();cleanup();
}
38
Chaos Game
Chaos Game is due to Michael Barnsley
Given a starting dot and a goal triangle (green)Goal is to move dot into green triangle in fewest number of moves
http://math.bu.edu/DYSYS/applets/chaos‐game.htmlJohanna Voolich and Robert L. Devaney
9/28/2015
20
39
Chaos Gamehttp://math.bu.edu/DYSYS/applets/chaos-game.html
40
gasket1.html
<script … src="../Common/webgl-utils.js"></script><script … src="../Common/initShaders.js"></script><script … src="../Common/Angel.js"></script><script … src="gasket1.js"></script>
<body><canvas id="gl-canvas" width="512"" height="512"Oops ... your browser doesn't support the HTML5 canvas element</canvas>
</body></html>
9/28/2015
21
41
gasket1.js
var gl;var points;
const NumPoints = 5000;
window.onload = function init(){
canvas = document.getElementById( "gl-canvas" );
gl = WebGLUtils.setupWebGL( canvas );if ( !gl ) { alert( "WebGL isn't available" ); }
42
gasket1.js
//// Initialize our data for the Sierpinski Gasket
// Initialize corners of our gasket with 3 points.var vertices = [
vec2( -1, -1 ),vec2( 0, 1 ),vec2( 1, -1 )
];
var u = add( vertices[0], vertices[1] );var v = add( vertices[0], vertices[2] );var p = scale( 0.5, add( u, v ) );
9/28/2015
22
43
gasket1.js
// Add randomly chosen point into array of points
points = [ p ];
for ( var i = 0; points.length < NumPoints; ++i ) {var j = Math.floor(Math.random() * 3);
p = add( points[i], vertices[j] );p = scale( 0.5, p );points.push( p );
}
44
gasket1.js
//// Configure WebGL//gl.viewport( 0, 0, canvas.width, canvas.height );gl.clearColor( 1.0, 1.0, 1.0, 1.0 );
// Load shaders and initialize attribute buffersvar program = initShaders( gl, "vertex-shader",
"fragment-shader" );gl.useProgram( program );
// Load the data into the GPUvar bufferId = gl.createBuffer();
9/28/2015
23
45
gasket1.js
// Load the data into the GPUvar bufferId = gl.createBuffer();gl.bindBuffer( gl.ARRAY_BUFFER, bufferId );gl.bufferData( gl.ARRAY_BUFFER, flatten(points),
gl.STATIC_DRAW );
var vPos = gl.getAttribLocation( program, "vPosition" );
gl.vertexAttribPointer( vPos, 2, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( vPos );
render();}
46
gasket1.js
render();}
function render(){
gl.clear( gl.COLOR_BUFFER_BIT );gl.drawArrays( gl.POINTS, 0, points.length
);}
9/28/2015
24
47
Picking the points
points = [ p ];
for ( var i = 0; points.length < NumPoints; ++i )
{
var j = Math.floor(Math.random() * 3);
p = add( points[i], vertices[j] );
p = scale( 0.5, p );
points.push( p );
}
48
Add some comments
points = [ p ];
// Compute and store N-1 new pointsfor ( var i = 0; points.length < NumPoints; ++i ) {
// Pick one of three corners at random var j = Math.floor(Math.random() * 3);
// Compute point halfway between vertex and prior point p = add( points[i], vertices[j] );p = scale( 0.5, p );
// Add it to the listpoints.push( p );
}
Starting point
Second point
Second Random Vertex
9/28/2015
25
49
Why does this work?
• In my version the starting point is in the center of the middle triangle
• Each time we move halfway towards a corner, we move into a blank triangle
• We will never land in a shaded triangle
• So why does the resulting set look the the Sierpinski Gasket?
50
Why does this work?
Imagine our random vertex is on the north, so we map all points north
At each step, our point is in the center of a blank triangle
But at every step, the new triangle is half as big, so point is closer to gasket
In the limit, point lies within epsilon of the Sierpinski gasket
If the choice of next vertex is not random, we do not get the full setA
B C
9/28/2015
26
2D Gasket in Polygons
52
Three‐dimensional Applications
• In WebGL, two‐dimensional applications are a special case of three‐dimensional graphics
• Going to 3D– Not much changes
– Use vec3, gl.uniform3f– Have to worry about the order in which primitives are rendered or use hidden‐surface removal
Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
9/28/2015
27
53
Sierpinski Gasket (2D)
• Start with a triangle
• Connect bisectors of sides and remove central triangle
• Repeat
Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
54
Example
• Five subdivisions
Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
9/28/2015
28
55
The gasket as a fractal
• Consider the filled area (black) and the perimeter (the length of all the lines around the filled triangles)
• As we continue subdividing– the area goes to zero
– but the perimeter goes to infinity
• This is not an ordinary geometric object– It is neither two‐ nor three‐dimensional
• It is a fractal (fractional dimension) object
Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
Gasket Program
• HTML file
– Same as in other examples
– Pass through vertex shader
– Fragment shader sets color
– Read in JS file
56Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
9/28/2015
29
57
Gasket Program
var points = [];var NumTimesToSubdivide = 5;
/* initial triangle */
var vertices = [vec2( -1, -1 ),vec2( 0, 1 ),vec2( 1, -1 )
];
divideTriangle( vertices[0],vertices[1],vertices[2], NumTimesToSubdivide);
Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
58
Draw one triangle
/* display one triangle */
function triangle( a, b, c ){points.push( a, b, c );
}
Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
9/28/2015
30
59
Triangle Subdivision
function divideTriangle( a, b, c, count ){// check for end of recursion
if ( count === 0 ) {triangle( a, b, c );}else {
//bisect the sidesvar ab = mix( a, b, 0.5 );var ac = mix( a, c, 0.5 );var bc = mix( b, c, 0.5 );--count;
// three new trianglesdivideTriangle( a, ab, ac, count-1 );divideTriangle( c, ac, bc, count-1 );divideTriangle( b, bc, ab, count-1 );}
}
Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
60
init()
var program = initShaders( gl, "vertex-shader", "fragment-shader" );
gl.useProgram( program );var bufferId = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, bufferId ) gl.bufferData( gl.ARRAY_BUFFER, flatten(points), gl.STATIC_DRAW );
var vPosition = gl.getAttribLocation( program, "vPosition" );
gl.vertexAttribPointer( vPosition, 2, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( vPosition );render();
Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
9/28/2015
31
61
Render Function
function render(){gl.clear( gl.COLOR_BUFFER_BIT );gl.drawArrays( gl.TRIANGLES, 0, points.length );
}
Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
3D Gasket in Tets
9/28/2015
32
63
Moving to 3D
• We can easily make the program three‐dimensional by using three dimensional points and starting with a tetrahedron
var vertices = [
vec3( 0.0000, 0.0000, ‐1.0000 ), vec3( 0.0000, 0.9428, 0.3333 ), vec3( ‐0.8165, ‐0.4714, 0.3333 ), vec3( 0.8165, ‐0.4714, 0.3333 )
];
subdivide each face
Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
64
3D Gasket
• We can subdivide each of the four faces
• Appears as if we remove a solid tetrahedron from the center leaving four smaller tetrahedra
• Code almost identical to 2D example
Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
9/28/2015
33
65
Almost Correct
• Because the triangles are drawn in the order they are specified in the program, the front triangles are not always rendered in front of triangles behind them
get this
want this
Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
66
Hidden‐Surface Removal
• We want to see only those surfaces in front of other surfaces
• OpenGL uses a hidden‐surfacemethod called the z‐buffer algorithm that saves depth information as objects are rendered so that only the front objects appear in the image
Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
9/28/2015
34
67
Using the z‐buffer algorithm
• The algorithm uses an extra buffer, the z‐buffer, to store depth information as geometry travels down the pipeline
• Depth buffer is required to be available in WebGL
• It must be
– Enabled• gl.enable(gl.DEPTH_TEST)
– Cleared in for each render• gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
68
Surface vs Volume Subdvision
• In our example, we divided the surface of each face
• We could also divide the volume using the same midpoints
• The midpoints define four smaller tetrahedrons, one for each vertex
• Keeping only these tetrahedrons removes a volume in the middle
• See text for code
Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
9/28/2015
35
69
Volume Subdivision
Angel and Shreiner: Interactive Computer Graphics 7E © Addison‐Wesley 2015
Input Positions
• Learn to use the mouse to give locations
–Must convert from position on canvas to position in application
• Respond to window events such as reshapes triggered by the mouse
9/28/2015
36
Window Coordinates
w
h
(0, 0)
(w ‐1, h‐1)
(xw, yw)
Window to Clip Coordinates
x 12* wx
w
y 12* w(h y )
h
(0,h) (1,1)
(w,0) (1,1)
9/28/2015
37
Returning Position from Click Event
Canvas specified in HTML file of size canvas.widthx canvas.height
Returned window coordinates are event.clientXand event.clientY
// add a vertex to GPU for each clickcanvas.addEventListener("click", function() {gl.bindBuffer(gl.ARRAY_BUFFER, vBuffer);var t = vec2(‐1 + 2*event.clientX/canvas.width,‐1 + 2*(canvas.height‐event.clientY)/canvas.height);gl.bufferSubData(gl.ARRAY_BUFFER,sizeof[’vec2’]*index, t);index++;
});
CAD‐like Examples
www.cs.unm.edu/~angel/WebGL/7E/03
square.html: puts a colored square at location of each mouse
click
triangle.html: first three mouse clicks define first triangle of
triangle strip. Each succeeding mouse clicks adds a new triangle at end of strip
cad1.html: draw a rectangle for each two successive mouse clicks
cad2.html: draws arbitrary polygons
9/28/2015
38
Window Events
• Events can be generated by actions that affect the canvas window
– moving or exposing a window
– resizing a window
– opening a window
– iconifying/deiconifying a window a window
• Note that events generated by other application that use the canvas can affect the WebGL canvas
– There are default callbacks for some of these events
Reshape Events
• Suppose we use the mouse to change the size of our canvas
• Must redraw the contents
• Options
– Display the same objects but change size
– Display more or fewer objects at the same size
• Almost always want to keep proportions
9/28/2015
39
onresize Event
• Returns size of new canvas is available through window.innerHeight and window. innerWidth
• Use innerHeight and innerWidth to change canvas.height and canvas.width
• Example (next slide): maintaining a square display
Keeping Square Proportions
window.onresize = function() {var min = innerWidth;if (innerHeight < min) {min = innerHeight;}if (min < canvas.width || min < canvas.height) {gl.viewport(0, canvas.height‐min, min, min);
}};
9/28/2015
40
Picking
• How do we identify objects on the display
• Overview three methods
– selection
– using an off‐screen buffer and color
– bounding boxes
Why is Picking Difficult?
• Given a point in the canvas how do map this point back to an object?
• Lack of uniqueness
• Forward nature of pipeline
• Take into account difficulty of getting an exact position with a pointing device
9/28/2015
41
Selection
• Supported by fixed function OpenGL pipeline
• Each primitive is given an id by the application indicating to which object it belongs
• As the scene is rendered, the id’s of primitives that render near the mouse are put in a hit list
• Examine the hit list after the rendering
Selection
• Implement by creating a window that corresponds to small area around mouse– We can track whether or not a primitive renders to this window
– Do not want to display this rendering
– Render off‐screen to an extra color buffer or user back buffer and don’t do a swap
• Requires a rendering which puts depths into hit record
• Possible to implement with WebGL
9/28/2015
42
Picking with Color
• We can use gl.readPixels to get the color at any location in window
• Idea is to use color to identify object but–Multiple objects can have the same color
– A shaded object will display many colors
• Solution: assign a unique color to each object and render off‐screen– Use gl.readPixels to get color at mouse location
– Use a table to map this color to an object
Picking with Bounding Boxes
• Both previous methods require an extra rendering each time we do a pick
• Alternative is to use a table of (axis‐aligned) bounding boxes
• Map mouse location to object through table
inside bounding boxoutside triangle
inside bounding boxinside triangle
outside bounding boxoutside triangle