Skip to content

Commit

Permalink
Lesson 83 - UV Mapped Icosphere
Browse files Browse the repository at this point in the history
  • Loading branch information
sketchpunk committed Mar 23, 2018
1 parent cfe550c commit cf0ae7e
Show file tree
Hide file tree
Showing 77 changed files with 12,359 additions and 0 deletions.
36 changes: 36 additions & 0 deletions lesson_083_uv_mapped_icosphere/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Fun with WebGL 2.0 - 083 - UV Mapped Icosphere / Icosahedron
**Description**:
Building an Icosphere is actually a pretty easy thing to do, but the trickiest part to get working is having it UV mapped procedurally. We explore setting up a new Geometry class with some support classes like Vertex to help us manage the data to produce the base shape of the Icosahedron then we explore how to do subdivision to a triangle that allows us to form it into a Icosphere. Then we continue on with an article that explains how to apply UV mapping and the steps to fix it so maps fairly well without a seam.


### Links of Interest

http://mft-dev.dk/uv-mapping-sphere/

http://wiki.unity3d.com/index.php/CreateIcoSphere

https://en.wikipedia.org/wiki/Icosahedron

https://medium.com/@peter_winslow/creating-procedural-planets-in-unity-part-1-df83ecb12e91

https://experilous.com/1/blog/post/procedural-planet-generation

http://donhavey.com/blog/tutorials/tutorial-3-the-icosahedron-sphere/

http://blog.andreaskahler.com/2009/06/creating-icosphere-mesh-in-code.html

http://kiwi.atmos.colostate.edu/BUGS/geodesic/text.html

http://vterrain.org/Textures/spherical.html

https://github.com/superwills/gtp/blob/master/gtp/geometry/Icosahedron.cpp

https://stackoverflow.com/questions/47441844/how-do-i-detect-the-vertices-on-the-seam-of-a-procedurally-generated-icosphere/49410688#49410688


### Links
* [Lesson on Youtube](https://youtu.be/4u7HXv4b5-U)
* [Youtube Series PlayList](https://www.youtube.com/playlist?list=PLMinhigDWz6emRKVkVIEAaePW7vtIkaIF)
* [Fungi Git Change](https://github.com/sketchpunk/FunWithWebGL2/commit/cfe550c06c6187e83e7a012df51438887c25f5a3)
* [Twitter](https://twitter.com/SketchpunkLabs)
* [Patreon](https://www.patreon.com/sketchpunk)
85 changes: 85 additions & 0 deletions lesson_083_uv_mapped_icosphere/fungi/Fungi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import gl, { FBO } from "./gl.js";
import CameraOrbit from "./cameras/Orbit.js";
import GridFloor from "./primitives/GridFloor.js";
import Quad from "./primitives/Quad.js";
import Renderer from "./util/Renderer.js";
import RenderLoop from "./util/RenderLoop.js";
import VDebug from "./entities/VisualDebugger.js";
import {KBMCtrl, KBMCtrl_Viewport} from "./util/KBMCtrl.js"

export default{
render :Renderer, //Main Render Function
renderLoop :null, //Render loop
lblFPS :null, //Html Element reference to a tag to update text of FPS

mainCamera :null, //Main camera for the scene
ctrlCamera :null, //Keyboard and Mouse controls for the camera

debugLine :null, //Renderable used to help debug data or models
debugPoint :null, //Same but with points.

gridFloor :null, //Just a reference to the renderable for the grid floor.
scene :[], //Array that holds the heirarchy of transforms / renderables.

deltaTime :0,
sinceStart :0,

//Begin the GL Context
init:function(){ gl.set("FungiCanvas"); return this; },

//Build all the main objects needed to get a scene up and running
ready:function(renderHandler,opt=1){
this.mainCamera = new CameraOrbit().setPosition(0,0.5,2).setEulerDegrees(-15,10,0);
this.ctrlCamera = new KBMCtrl().addHandler("camera",new KBMCtrl_Viewport(this.mainCamera),true,true);

this.renderLoop = new RenderLoop(renderHandler);
this.lblFPS = document.getElementById("lblFPS");
setInterval(function(){ this.lblFPS.innerHTML = this.renderLoop.fps; }.bind(this),200);

if((opt & 1) == 1){
this.gridFloor = GridFloor();
this.scene.push(this.gridFloor);
}

//Setup Features
if(opt){
if((opt & 2) == 2) this.scene.push( this.debugLine = new VDebug() ); //DEBUG LINE RENDERER
if((opt & 4) == 4) this.scene.push( this.debugPoint = new VDebug().drawPoints() ); //DEBUG POINT RENDERER
}

return this;
},


//Lesson 61 has example of how to do Deferred Rendering with MultiSample Render Buffers then Blits to Texture Buffers for final render
setupDeferred:function(matName,onPre=null,onPost=null){
var fbo = new FBO(); //FBO Struct Builder
this.deferred = {
quad : Quad(-1,-1,1,1,matName,"postQuad").setOptions(true,false),
fboRender : fbo.create().texColorBuffer("bColor",0).texDepthBuffer().finalize("fboRender"),
onPreRender : ()=>{ FBO.clear(this.deferred.fboRender,false); },
onPostRender : ()=>{
FBO.deactivate();
this.render.prepareNext(this.deferred.quad).draw();
}
};

if(onPre != null) this.render.onPreRender = onPre;
if(onPost != null) this.render.onPostRender = onPost;
},


//Get a frame ready to be rendered.
update:function(){
this.mainCamera.update();

gl.UBOTransform.update(
"matCameraView",this.mainCamera.invertedLocalMatrix,
"posCamera",this.mainCamera.getWorldPosition(), //Because of Orbit, Position isn't true worldspace position, need to rotate , //this.mainCamera.position
"fTime",new Float32Array( [this.sinceStart] )
);

gl.clear();
return this;
}
}
151 changes: 151 additions & 0 deletions lesson_083_uv_mapped_icosphere/fungi/Maths.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import Vec3 from "./maths/Vec3.js";
import Mat3 from "./maths/Mat3.js";
import Mat4 from "./maths/Mat4.js";
import Quat from "./maths/Quat.js";
import DualQuat from "./maths/DualQuat.js";

const DEG2RAD = Math.PI/180;
const RAD2DEG = 180/Math.PI;

//Normalize x value to x range, then normalize to lerp the z range.
function map(x, xMin,xMax, zMin,zMax){ return (x - xMin) / (xMax - xMin) * (zMax-zMin) + zMin; }

function clamp(v,min,max){ return Math.max(min,Math.min(max,v)); }

function smoothStep(edge1, edge2, val){ //https://en.wikipedia.org/wiki/Smoothstep
var x = Math.max(0, Math.min(1, (val-edge1)/(edge2-edge1)));
return x*x*(3-2*x);
}

//Get a number between A and B from a normalized number.
function lerp(a,b,t){ return a + t * (b-a); }

//From a point in space, closest spot to a 2D line
function closestPointToLine2D(x0,y0,x1,y1,px,py){
var dx = x1 - x0,
dy = y1 - y0,
t = ((px-x0)*dx + (py-y0)*dy) / (dx*dx+dy*dy),
x = x0 + (dx * t), //Util.lerp(x0, x1, t),
y = y0 + (dy * t); //Util.lerp(y0, y1, t);
return [x,y]
}

//From a point in space, closest spot to a 3D line
function closestPointToLine3D(a,b,p,out){
if(out == undefined) out = new Vec3();
var dx = b.x - a.x,
dy = b.y - a.y,
dz = a.z - a.z,
t = ((p.x-a.x)*dx + (p.y-a.y)*dy + (p.z-a.z)*dz) / (dx*dx+dy*dy+dz*dz),
x = a.x + (dx * t),
y = a.y + (dy * t),
z = a.z + (dz * t);
return out.set(x,y,z);
}

//Return back the two points that are closes on two infinite lines
//http://geomalgorithms.com/a07-_distance.html
function closestpoint_2Lines(A0,A1,B0,B1){
var u = A1.clone().sub(A0),
v = B1.clone().sub(B0),
w = A0.clone().sub(B0),
a = Vec3.dot(u,u), // always >= 0
b = Vec3.dot(u,v),
c = Vec3.dot(v,v), // always >= 0
d = Vec3.dot(u,w),
e = Vec3.dot(v,w),
D = a*c - b*b, // always >= 0
tU, tV;
//compute the line parameters of the two closest points
if(D < 0.000001){ // the lines are almost parallel
tU = 0.0;
tV = (b>c ? d/b : e/c); // use the largest denominator
}else{
tU = (b*e - c*d) / D;
tV = (a*e - b*d) / D;
}

//Calc Length
//Vector vLen = w + (uT * u) - (vT * v); // = L1(sc) - L2(tc)
//Float len = sqrt( dot(vLen,vLen) );

return [ u.scale(tU).add(A0), v.scale(tV).add(B0) ];
}

//Return back the two points that are the closests but bound by the limit of two segments
//http://geomalgorithms.com/a07-_distance.html
function closestPointS_2Segments(A0,A1,B0,B1){
var u = A1.clone().sub(A0),
v = B1.clone().sub(B0),
w = A0.clone().sub(B0),
a = Vec3.dot(u,u), // always >= 0
b = Vec3.dot(u,v),
c = Vec3.dot(v,v), // always >= 0
d = Vec3.dot(u,w),
e = Vec3.dot(v,w),
D = a*c - b*b, // always >= 0
sc, sN, sD = D, // sc = sN / sD, default sD = D >= 0
tc, tN, tD = D; // tc = tN / tD, default tD = D >= 0

// compute the line parameters of the two closest points
if(D < 0.000001){ // the lines are almost parallel
sN = 0.0; // force using point P0 on segment S1
sD = 1.0; // to prevent possible division by 0.0 later
tN = e;
tD = c;
}else{ // get the closest points on the infinite lines
sN = (b*e - c*d);
tN = (a*e - b*d);
if(sN < 0.0){ // sc < 0 => the s=0 edge is visible
sN = 0.0;
tN = e;
tD = c;
}else if (sN > sD){ // sc > 1 => the s=1 edge is visible
sN = sD;
tN = e + b;
tD = c;
}
}

if (tN < 0.0){ // tc < 0 => the t=0 edge is visible
tN = 0.0;
// recompute sc for this edge
if (-d < 0.0) sN = 0.0;
else if (-d > a) sN = sD;
else{
sN = -d;
sD = a;
}
}else if(tN > tD){ // tc > 1 => the t=1 edge is visible
tN = tD;
// recompute sc for this edge
if((-d + b) < 0.0) sN = 0;
else if ((-d + b) > a) sN = sD;
else{
sN = (-d + b);
sD = a;
}
}

// finally do the division to get sc and tc
sc = (Math.abs(sN) < 0.000001 ? 0.0 : sN / sD);
tc = (Math.abs(tN) < 0.000001 ? 0.0 : tN / tD);

// get the difference of the two closest points
//Vector dP = w + (sc * u) - (tc * v); // = S1(sc) - S2(tc)

return [ u.scale(sc).add(A0), v.scale(tc).add(B0) ];
}


export default { DEG2RAD:DEG2RAD, RAD2DEG:RAD2DEG,
map:map,
clamp:clamp,
smoothStep:smoothStep,
closestPointToLine2D:closestPointToLine2D,
closestPointToLine3D:closestPointToLine3D,
closestpoint_2Lines:closestpoint_2Lines,
closestPointS_2Segments:closestPointS_2Segments,
}

export { Vec3, Mat3, Mat4, Quat, DualQuat, DEG2RAD, RAD2DEG }
106 changes: 106 additions & 0 deletions lesson_083_uv_mapped_icosphere/fungi/cameras/Orbit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import gl from "../gl.js";
import fungi from "../fungi.js";
import Transform from "../entities/Transform.js";
import {Vec3, Mat4, Quat, DEG2RAD} from "../Maths.js";

class Orbit extends Transform{
constructor(fov,near,far){
super();
//Setup the projection and invert matrices
this.projectionMatrix = new Float32Array(16);
this.invertedProjectionMatrix = new Float32Array(16);
this.invertedLocalMatrix = new Float32Array(16);

var ratio = gl.ctx.canvas.width / gl.ctx.canvas.height;
Mat4.perspective(this.projectionMatrix, fov || 45, ratio, near || 0.1, far || 100.0);
Mat4.invert(this.invertedProjectionMatrix, this.projectionMatrix); //Save Inverted version for Ray Casting.

gl.UBOTransform.update(
"matProjection",this.projectionMatrix,
"screenRes", new Float32Array( [ gl.width, gl.height ] )
); //Initialize The Transform UBO.

//Orbit Camera will control things based on euler, its cheating but not ready for quaternions
this.euler = new Vec3();
}

orthoProjection(zoom=1,near=-10,far=100){
var w = 1 * zoom,
h = gl.height / gl.width * zoom;

Mat4.ortho(this.projectionMatrix, -w, w, -h, h, near, far);
Mat4.invert(this.invertedProjectionMatrix, this.projectionMatrix); //Save Inverted version for Ray Casting.

gl.UBOTransform.update(
"matProjection",this.projectionMatrix,
"screenRes", new Float32Array( [ gl.width, gl.height ] )
); //Initialize The Transform UBO.

return this;
}

//Override how this transfer creates the localMatrix : Call Update, not this function in render loop.
updateMatrix(){
//Only Update the Matrix if its needed.
//if(!this.position.isModified && !this.rotation.isModified && !this.euler.isModified) return this.localMatrix;

Quat.setFromEuler(this.rotation,this.euler.x,this.euler.y,this.euler.z,"YXZ");
Mat4.fromQuaternion(this.localMatrix,this.rotation);
this.localMatrix.resetTranslation().translate(this.position);

//Set the modified indicator to false on all the transforms.
this.position.isModified = false;
this.rotation.isModified = false;
this.euler.isModified = false;
return this.localMatrix;
}

//Update the Matrices and UBO.
update(){
if(this.position.isModified || this.scale.isModified || this.euler.isModified){
this.updateMatrix();
Mat4.invert(this.invertedLocalMatrix,this.localMatrix);
}
}

setEulerDegrees(x,y,z){ this.euler.set(x * DEG2RAD,y * DEG2RAD,z * DEG2RAD); return this; }

getWorldPosition(){
//Because of how orbit works, position isn't in worldspace.
//Need to apply rotation to bring it into world splace
var ary = new Float32Array(3);
Quat.rotateVec3(this.rotation,this.position,ary);
return ary;
}

worldToScreen(vAry){
var mat = new Float32Array(16), // Matrix4 Holder
p = [0,0,0,0], // Vec4
rtn = []; // List of vec2 results

//Move Points from WorldSpace to -> View Space (View Matrix) -> ClipSpace (ProjMatrix)
Mat4.mult(mat,this.projectionMatrix,this.invertedLocalMatrix);

for(var i=0; i < vAry.length; i++){
Mat4.transformVec3(p, vAry[i], mat);

//Move from Clip Space to NDC Space (Normalized Device Coordinate Space) (-1 to 1 opengl viewport)
if(p[3] != 0){ //only if W is not zero,
p[0] = p[0] / p[3];
p[1] = p[1] / p[3];
}

//Then finally move the points to Screen Space
//Map points from -1 to 1 range into 0 to 1 range, Then multiple by canvas size
rtn.push( // Replaced /2 with *0.5
( p[0] + 1) * 0.5 * gl.width,
(-p[1] + 1) * 0.5 * gl.height
);
}

if(vAry.length == 1) return rtn[0]; //Just return the one point
return rtn; //Return all the points
}
}

export default Orbit;
Loading

0 comments on commit cf0ae7e

Please sign in to comment.