2017-09-08 09:24:16 -07:00
//
// nitroRender.js
//--------------------
// Provides an interface with which NSBMD models can be drawn to a fst canvas.
// by RHY3756547
//
// includes: gl-matrix.js (glMatrix 2.0)
// /formats/nitro.js --passive requirement from other nitro formats
// /formats/nsbmd.js
// /formats/nsbta.js
// /formats/nsbtx.js
//
window . nitroRender = new function ( ) {
var gl , frag , vert , nitroShader ;
var cVec , color , texCoord , norm ;
2019-06-10 15:06:38 -07:00
var vecMode , vecPos , vecNorm , vecTx , vecCol , vecNum , vecMat , curMat , stripAlt ;
2017-09-08 09:24:16 -07:00
var texWidth , texHeight , alphaMul = 1 ;
2019-06-10 15:06:38 -07:00
var t = this ;
2017-09-08 09:24:16 -07:00
this . cullModes = [ ] ;
this . billboardID = 0 ; //incrememts every time billboards need to be updated. cycles &0xFFFFFF to avoid issues
this . lastMatStack = null ; //used to check if we need to send the matStack again. will be used with a versioning system in future.
this . last = { } ; //obj: the last vertex buffers drawn
var optimiseTriangles = true ; //improves draw performance by >10x on most models.
var modelBuffer ;
var shaders = [ ] ;
this . renderDispList = renderDispList ;
this . setAlpha = setAlpha ;
this . getViewWidth = getViewWidth ;
this . getViewHeight = getViewHeight ;
this . flagShadow = false ;
2019-06-10 15:06:38 -07:00
this . forceFlatNormals = false ; //generate flat normals for this mesh. Used for course model for better shadows.
2017-09-08 09:24:16 -07:00
var parameters = {
0 : 0 ,
0x10 : 1 , 0x11 : 0 , 0x12 : 1 , 0x13 : 1 , 0x14 : 1 , 0x15 : 0 , 0x16 : 16 , 0x17 : 12 , 0x18 : 16 , 0x19 : 12 , 0x1A : 9 , 0x1B : 3 , 0x1C : 3 , //matrix commands
0x20 : 1 , 0x21 : 1 , 0x22 : 1 , 0x23 : 2 , 0x24 : 1 , 0x25 : 1 , 0x26 : 1 , 0x27 : 1 , 0x28 : 1 , 0x29 : 1 , 0x2A : 1 , 0x2B : 1 , //vertex commands
0x30 : 1 , 0x31 : 1 , 0x32 : 1 , 0x33 : 1 , 0x34 : 32 , //material param
0x40 : 1 , 0x41 : 0 , //begin or end vertices
0x50 : 1 , //swap buffers
0x60 : 1 , //viewport
0x70 : 3 , 0x71 : 2 , 0x72 : 1 //tests
}
var instructions = { } ;
instructions [ 0x14 ] = function ( view , off ) { //restore to matrix, used constantly for bone transforms
curMat = view . getUint8 ( off ) ;
}
instructions [ 0x20 ] = function ( view , off ) { //color
var dat = view . getUint16 ( off , true ) ;
color [ 0 ] = ( dat & 31 ) / 31 ;
color [ 1 ] = ( ( dat >> 5 ) & 31 ) / 31 ;
color [ 2 ] = ( ( dat >> 10 ) & 31 ) / 31 ;
}
instructions [ 0x21 ] = function ( view , off ) { //normal
var dat = view . getUint32 ( off , true ) ;
norm [ 0 ] = tenBitSign ( dat ) ;
norm [ 1 ] = tenBitSign ( dat >> 10 ) ;
norm [ 2 ] = tenBitSign ( dat >> 20 ) ;
}
instructions [ 0x22 ] = function ( view , off ) { //texcoord
texCoord [ 0 ] = ( view . getInt16 ( off , true ) / 16 ) / texWidth ;
texCoord [ 1 ] = ( view . getInt16 ( off + 2 , true ) / 16 ) / texHeight ;
}
instructions [ 0x23 ] = function ( view , off ) { //xyz 16 bit
cVec [ 0 ] = view . getInt16 ( off , true ) / 4096 ;
cVec [ 1 ] = view . getInt16 ( off + 2 , true ) / 4096 ;
cVec [ 2 ] = view . getInt16 ( off + 4 , true ) / 4096 ;
pushVector ( ) ;
}
instructions [ 0x24 ] = function ( view , off ) { //xyz 10 bit
var dat = view . getUint32 ( off , true ) ;
cVec [ 0 ] = tenBitSign ( dat ) ;
cVec [ 1 ] = tenBitSign ( dat >> 10 ) ;
cVec [ 2 ] = tenBitSign ( dat >> 20 ) ;
pushVector ( ) ;
}
instructions [ 0x25 ] = function ( view , off ) { //xy 16 bit
cVec [ 0 ] = view . getInt16 ( off , true ) / 4096 ;
cVec [ 1 ] = view . getInt16 ( off + 2 , true ) / 4096 ;
pushVector ( ) ;
}
instructions [ 0x26 ] = function ( view , off ) { //xz 16 bit
cVec [ 0 ] = view . getInt16 ( off , true ) / 4096 ;
cVec [ 2 ] = view . getInt16 ( off + 2 , true ) / 4096 ;
pushVector ( ) ;
}
instructions [ 0x27 ] = function ( view , off ) { //yz 16 bit
cVec [ 1 ] = view . getInt16 ( off , true ) / 4096 ;
cVec [ 2 ] = view . getInt16 ( off + 2 , true ) / 4096 ;
pushVector ( ) ;
}
instructions [ 0x28 ] = function ( view , off ) { //xyz 10 bit relative
var dat = view . getUint32 ( off , true ) ;
cVec [ 0 ] += relativeSign ( dat ) ;
cVec [ 1 ] += relativeSign ( dat >> 10 ) ;
cVec [ 2 ] += relativeSign ( dat >> 20 ) ;
pushVector ( ) ;
}
instructions [ 0x40 ] = function ( view , off ) { //begin vtx
var dat = view . getUint32 ( off , true ) ;
vecMode = dat ;
if ( ! optimiseTriangles ) {
vecPos = [ ] ;
vecNorm = [ ] ;
vecTx = [ ] ;
vecCol = [ ] ;
vecMat = [ ] ;
}
vecNum = 0 ;
2019-06-10 15:06:38 -07:00
stripAlt = 0 ;
2017-09-08 09:24:16 -07:00
}
instructions [ 0x41 ] = function ( view , off ) { //end vtx
if ( ! optimiseTriangles ) pushStrip ( ) ;
}
function setAlpha ( alpha ) { //for fading specific things out or whatever
alphaMul = alpha ;
}
function getViewWidth ( ) {
return gl . viewportWidth ;
}
function getViewHeight ( ) {
return gl . viewportHeight ;
}
function pushStrip ( ) { //push the last group of triangles to the buffer. Should do this on matrix change... details fourthcoming
var modes = ( optimiseTriangles ) ? [ gl . TRIANGLES , gl . TRIANGLES , gl . TRIANGLES , gl . TRIANGLES ] : [ gl . TRIANGLES , gl . TRIANGLES , gl . TRIANGLE _STRIP , gl . TRIANGLE _STRIP ] ;
var pos = gl . createBuffer ( ) ;
var col = gl . createBuffer ( ) ;
var tx = gl . createBuffer ( ) ;
var mat = gl . createBuffer ( ) ;
var norm = gl . createBuffer ( ) ;
var posArray = new Float32Array ( vecPos ) ;
2019-06-10 15:06:38 -07:00
if ( t . forceFlatNormals && modes [ vecMode ] == gl . TRIANGLES ) {
//calculate new flat normals for each triangle
for ( var i = 0 ; i < vecPos . length ; i += 9 ) {
var v1 = [ vecPos [ i ] , vecPos [ i + 1 ] , vecPos [ i + 2 ] ] ;
var v2 = [ vecPos [ i + 3 ] , vecPos [ i + 4 ] , vecPos [ i + 5 ] ] ;
var v3 = [ vecPos [ i + 6 ] , vecPos [ i + 7 ] , vecPos [ i + 8 ] ] ;
vec3 . sub ( v2 , v2 , v1 ) ;
vec3 . sub ( v3 , v3 , v1 ) ;
var newNorm = vec3 . cross ( [ ] , v2 , v3 ) ;
vec3 . normalize ( newNorm , newNorm ) ;
for ( var j = 0 ; j < 3 ; j ++ ) {
for ( var k = 0 ; k < 3 ; k ++ ) {
vecNorm [ i + ( j * 3 ) + k ] = newNorm [ k ] ;
}
}
}
}
2017-09-08 09:24:16 -07:00
gl . bindBuffer ( gl . ARRAY _BUFFER , pos ) ;
gl . bufferData ( gl . ARRAY _BUFFER , posArray , gl . STATIC _DRAW ) ;
gl . bindBuffer ( gl . ARRAY _BUFFER , tx ) ;
gl . bufferData ( gl . ARRAY _BUFFER , new Float32Array ( vecTx ) , gl . STATIC _DRAW ) ;
gl . bindBuffer ( gl . ARRAY _BUFFER , col ) ;
gl . bufferData ( gl . ARRAY _BUFFER , new Float32Array ( vecCol ) , gl . STATIC _DRAW ) ;
gl . bindBuffer ( gl . ARRAY _BUFFER , mat ) ;
gl . bufferData ( gl . ARRAY _BUFFER , new Float32Array ( vecMat ) , gl . STATIC _DRAW ) ;
gl . bindBuffer ( gl . ARRAY _BUFFER , norm ) ;
gl . bufferData ( gl . ARRAY _BUFFER , new Float32Array ( vecNorm ) , gl . STATIC _DRAW ) ;
modelBuffer . strips . push ( {
posArray : posArray ,
vPos : pos ,
vTx : tx ,
vCol : col ,
vMat : mat ,
vNorm : norm ,
verts : vecPos . length / 3 ,
mode : modes [ vecMode ]
} )
}
function pushVector ( ) {
if ( vecMode == 1 && vecNum % 4 == 3 ) { //quads - special case
vecPos = vecPos . concat ( vecPos . slice ( vecPos . length - 9 , vecPos . length - 6 ) ) . concat ( vecPos . slice ( vecPos . length - 3 ) ) ;
vecNorm = vecNorm . concat ( vecNorm . slice ( vecNorm . length - 9 , vecNorm . length - 6 ) ) . concat ( vecNorm . slice ( vecNorm . length - 3 ) ) ;
vecTx = vecTx . concat ( vecTx . slice ( vecTx . length - 6 , vecTx . length - 4 ) ) . concat ( vecTx . slice ( vecTx . length - 2 ) ) ;
vecCol = vecCol . concat ( vecCol . slice ( vecCol . length - 12 , vecCol . length - 8 ) ) . concat ( vecCol . slice ( vecCol . length - 4 ) ) ;
vecMat = vecMat . concat ( vecMat . slice ( vecMat . length - 3 , vecMat . length - 2 ) ) . concat ( vecMat . slice ( vecMat . length - 1 ) ) ;
}
if ( optimiseTriangles && ( vecMode > 1 ) && ( vecNum > 2 ) ) { //convert tri strips to individual triangles so we get one buffer per polygon
2019-06-10 15:06:38 -07:00
var b = vecMat . length - ( ( ( stripAlt % 2 ) == 0 ) ? 1 : 3 ) ;
var s2 = vecMat . length - ( ( ( stripAlt % 2 ) == 0 ) ? 2 : 1 ) ;
vecPos = vecPos . concat ( vecPos . slice ( b * 3 , b * 3 + 3 ) ) . concat ( vecPos . slice ( s2 * 3 , s2 * 3 + 3 ) ) ;
vecNorm = vecNorm . concat ( vecNorm . slice ( b * 3 , b * 3 + 3 ) ) . concat ( vecNorm . slice ( s2 * 3 , s2 * 3 + 3 ) ) ;
vecTx = vecTx . concat ( vecTx . slice ( b * 2 , b * 2 + 2 ) ) . concat ( vecTx . slice ( s2 * 2 , s2 * 2 + 2 ) ) ;
vecCol = vecCol . concat ( vecCol . slice ( b * 4 , b * 4 + 4 ) ) . concat ( vecCol . slice ( s2 * 4 , s2 * 4 + 4 ) ) ;
vecMat = vecMat . concat ( vecMat . slice ( b , b + 1 ) ) . concat ( vecMat . slice ( s2 , s2 + 1 ) ) ;
stripAlt ++ ;
2017-09-08 09:24:16 -07:00
}
vecNum ++ ;
vecPos = vecPos . concat ( cVec ) ;
vecTx = vecTx . concat ( texCoord ) ;
vecCol = vecCol . concat ( color ) ;
vecNorm = vecNorm . concat ( norm ) ;
vecMat . push ( curMat ) ;
}
function tenBitSign ( val ) {
val &= 1023 ;
if ( val & 512 ) return ( val - 1024 ) / 64 ;
else return val / 64 ;
}
function relativeSign ( val ) {
val &= 1023 ;
if ( val & 512 ) return ( val - 1024 ) / 4096 ;
else return val / 4096 ;
}
this . init = function ( ctx ) {
gl = ctx ;
this . gl = gl ;
2019-06-09 15:50:55 -07:00
this . billboardMat = mat4 . create ( ) ;
this . yBillboardMat = mat4 . create ( ) ;
2017-09-08 09:24:16 -07:00
shaders = nitroShaders . compileShaders ( gl ) ;
this . nitroShader = shaders [ 0 ] ;
this . cullModes = [ gl . FRONT _AND _BACK , gl . FRONT , gl . BACK ] ;
}
this . prepareShader = function ( ) {
//prepares the shader so no redundant calls have to be made. Should be called upon every program change.
gl . enable ( gl . BLEND ) ;
2019-06-09 15:50:55 -07:00
gl . blendFunc ( gl . ONE , gl . ONE _MINUS _SRC _ALPHA ) ; //gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
2017-09-08 09:24:16 -07:00
this . last = { } ;
gl . activeTexture ( gl . TEXTURE0 ) ;
gl . uniform1i ( this . nitroShader . samplerUniform , 0 ) ;
}
2019-06-10 15:06:38 -07:00
this . setShadowMode = function ( sTex , fsTex , sMat , fsMat , dir ) {
2017-09-08 09:24:16 -07:00
this . nitroShader = shaders [ 1 ] ;
var shader = shaders [ 1 ] ;
gl . useProgram ( shader ) ;
2019-06-10 15:06:38 -07:00
vec3 . normalize ( dir , dir ) ;
gl . uniform3fv ( shader . lightDirUniform , dir ) ;
2017-09-08 09:24:16 -07:00
gl . uniformMatrix4fv ( shader . shadowMatUniform , false , sMat ) ;
gl . uniformMatrix4fv ( shader . farShadowMatUniform , false , fsMat ) ;
2018-03-24 16:50:10 -07:00
gl . uniform1f ( shader . lightIntensityUniform , 0.3 ) ;
2017-09-08 09:24:16 -07:00
2018-03-24 16:50:10 -07:00
this . resetShadOff ( ) ;
2019-06-10 15:06:38 -07:00
this . setNormalFlip ( 1 ) ;
2017-09-08 09:24:16 -07:00
gl . activeTexture ( gl . TEXTURE1 ) ;
gl . bindTexture ( gl . TEXTURE _2D , sTex ) ;
gl . uniform1i ( shader . lightSamplerUniform , 1 ) ;
gl . activeTexture ( gl . TEXTURE2 ) ;
gl . bindTexture ( gl . TEXTURE _2D , fsTex ) ;
gl . uniform1i ( shader . farLightSamplerUniform , 2 ) ;
this . setColMult ( [ 1 , 1 , 1 , 1 ] ) ;
this . prepareShader ( ) ;
}
2019-06-10 15:06:38 -07:00
this . setLightIntensities = function ( intensity , shadIntensity ) {
if ( intensity == null ) intensity = 0.3 ;
if ( shadIntensity == null ) shadIntensity = 1 ;
var shader = this . nitroShader ;
gl . useProgram ( this . nitroShader ) ;
gl . uniform1f ( shader . lightIntensityUniform , intensity ) ;
gl . uniform1f ( shader . shadLightenUniform , 1 - shadIntensity ) ;
}
2019-06-09 15:50:55 -07:00
this . setShadBias = function ( bias ) {
2019-06-10 15:06:38 -07:00
var shader = this . nitroShader ;
gl . useProgram ( this . nitroShader ) ;
2019-06-09 15:50:55 -07:00
gl . uniform1f ( shader . shadOffUniform , bias ) ;
gl . uniform1f ( shader . farShadOffUniform , bias ) ;
}
2019-06-10 15:06:38 -07:00
this . setNormalFlip = function ( flip ) {
var shader = this . nitroShader ;
gl . useProgram ( this . nitroShader ) ;
gl . uniform1f ( shader . normalFlipUniform , flip ) ;
}
2018-03-24 16:50:10 -07:00
this . resetShadOff = function ( ) {
2019-06-10 15:06:38 -07:00
var shader = this . nitroShader ;
gl . useProgram ( this . nitroShader ) ;
gl . uniform1f ( shader . shadOffUniform , 0.0005 + ( ( mobile ) ? 0.0005 : 0 ) ) ;
gl . uniform1f ( shader . farShadOffUniform , 0.0020 ) ;
2018-03-24 16:50:10 -07:00
}
2017-09-08 09:24:16 -07:00
this . unsetShadowMode = function ( ) {
this . nitroShader = shaders [ 0 ] ;
gl . useProgram ( this . nitroShader ) ;
this . setColMult ( [ 1 , 1 , 1 , 1 ] ) ;
this . prepareShader ( ) ;
}
2019-05-28 17:08:48 -07:00
var paused = false ;
this . pauseShadowMode = function ( ) {
this . nitroShader = shaders [ 0 ] ;
if ( this . nitroShader == shaders [ 1 ] ) paused = true ;
gl . useProgram ( this . nitroShader ) ;
this . setColMult ( [ 1 , 1 , 1 , 1 ] ) ;
this . prepareShader ( ) ;
}
this . unpauseShadowMode = function ( ) {
if ( ! paused ) return ;
this . nitroShader = shaders [ 1 ] ;
gl . useProgram ( this . nitroShader ) ;
this . setColMult ( [ 1 , 1 , 1 , 1 ] ) ;
this . prepareShader ( ) ;
}
2017-09-08 09:24:16 -07:00
this . setColMult = function ( color ) {
gl . useProgram ( this . nitroShader ) ;
gl . uniform4fv ( this . nitroShader . colMultUniform , color ) ;
}
this . updateBillboards = function ( view ) {
this . billboardID = ( this . billboardID + 1 ) % 0xFFFFFF ;
var nv = mat4 . clone ( view ) ;
nv [ 12 ] = 0 ;
nv [ 13 ] = 0 ;
nv [ 14 ] = 0 ; //nullify translation
var nv2 = mat4 . clone ( nv ) ;
this . billboardMat = mat4 . invert ( nv , nv ) ;
nv2 [ 4 ] = 0 ;
nv2 [ 5 ] = 1 ; //do not invert y axis view
nv2 [ 6 ] = 0 ;
this . yBillboardMat = mat4 . invert ( nv2 , nv2 ) ;
}
function renderDispList ( disp , tex , startStack ) { //renders the display list to a form of vertex buffer. The idea is that NSBTA and NSBCA can still be applied to the buffer at little performance cost. (rather than recompiling the model)
modelBuffer = {
strips : [ ]
/ * s t r i p e n t r y f o r m a t :
vPos : glBuffer ,
vTx : glBuffer ,
vCol : glBuffer ,
verts : int count of vertices ,
mode : ( eg . gl . TRIANGLES , gl . TRIANGLESTRIP )
mat : transformation matrix to apply . unused atm as matrix functions are unimplemented
* /
} //the nitroModel will store this and use it for rendering instead of the display list in future.
curMat = startStack ; //start on root bone
var shader = nitroRender . nitroShader ;
var gl = nitroRender . gl ;
var off = 0 ;
var view = new DataView ( disp ) ;
texWidth = tex . width ;
texHeight = tex . height ;
cVec = [ 0 , 0 , 0 ] ;
norm = [ 0 , 1 , 0 ] ;
texCoord = [ 0 , 0 ] ;
color = [ 1 , 1 , 1 , alphaMul ] ; //todo: polygon attributes
vecMode = 0 ;
vecNum = 0 ;
2019-06-10 15:06:38 -07:00
stripAlt = 0 ;
2017-09-08 09:24:16 -07:00
vecPos = [ ] ;
vecNorm = [ ] ;
vecTx = [ ] ;
vecCol = [ ] ;
vecMat = [ ] ;
while ( off < disp . byteLength ) {
var ioff = off ;
off += 4 ;
for ( var i = 0 ; i < 4 ; i ++ ) {
var inst = view . getUint8 ( ioff ++ ) ;
if ( instructions [ inst ] != null ) {
instructions [ inst ] ( view , off ) ;
} else {
if ( inst != 0 ) alert ( "invalid instruction 0x" + ( inst . toString ( 16 ) ) ) ;
}
var temp = parameters [ inst ] ;
off += ( temp == null ) ? 0 : temp * 4 ;
}
}
if ( optimiseTriangles ) pushStrip ( ) ;
return modelBuffer ;
}
} ;
2019-06-09 15:50:55 -07:00
function nitroModel ( bmd , btx ) {
2017-09-08 09:24:16 -07:00
var bmd = bmd ;
this . bmd = bmd ;
var thisObj = this ;
var loadedTex ;
var texCanvas ;
var tex ;
var texAnim ;
2019-05-28 17:08:48 -07:00
var texPAnim ;
2017-09-08 09:24:16 -07:00
var texFrame ;
var modelBuffers ;
var collisionModel = [ ] ;
var matBufEmpty = new Float32Array ( 31 * 16 ) ;
var temp = mat4 . create ( ) ;
var off = 0 ;
for ( var i = 0 ; i < 31 ; i ++ ) {
matBufEmpty . set ( temp , off ) ;
off += 16 ;
}
temp = null ;
var texMap = { tex : { } , pal : { } } ;
//var matStack;
this . draw = draw ;
this . drawPoly = externDrawPoly ;
this . drawModel = externDrawModel ;
2019-06-09 15:50:55 -07:00
this . getBoundingCollisionModel = getBoundingCollisionModel ;
2017-09-08 09:24:16 -07:00
this . getCollisionModel = getCollisionModel ;
2019-06-09 15:50:55 -07:00
this . baseMat = mat4 . create ( ) ;
2017-09-08 09:24:16 -07:00
modelBuffers = [ ]
this . modelBuffers = modelBuffers ;
var matBuf = [ ] ;
for ( var i = 0 ; i < bmd . modelData . objectData . length ; i ++ ) {
modelBuffers . push ( new Array ( bmd . modelData . objectData [ i ] . polys . objectData . length ) ) ;
matBuf . push ( { built : false , dat : new Float32Array ( 31 * 16 ) } ) ;
}
if ( btx != null ) {
loadTexture ( btx ) ;
} else if ( bmd . tex != null ) {
loadTexture ( bmd . tex )
} else {
loadWhiteTex ( ) ;
}
2019-05-28 17:08:48 -07:00
function loadWhiteTex ( btx ) { //examines the materials in the loaded model and generates textures for each.
2017-09-08 09:24:16 -07:00
var gl = nitroRender . gl ; //get gl object from nitro render singleton
loadedTex = btx ;
texCanvas = [ ] ;
tex = [ ] ;
var models = bmd . modelData . objectData ;
for ( var j = 0 ; j < models . length ; j ++ ) {
var model = models [ j ] ;
var mat = model . materials . objectData
for ( var i = 0 ; i < mat . length ; i ++ ) {
var m = mat [ i ] ;
var fC = document . createElement ( "canvas" ) ;
fC . width = 2 ;
fC . height = 2 ;
var ctx = fC . getContext ( "2d" )
ctx . fillStyle = "black" ;
ctx . globalAlpha = 0.33 ;
ctx . fillRect ( 0 , 0 , 2 , 2 ) ;
texCanvas . push ( fC ) ;
var t = loadTex ( fC , gl , ! m . repeatX , ! m . repeatY ) ;
t . realWidth = 2 ;
t . realHeight = 2 ;
tex . push ( t ) ;
}
}
}
function loadTexture ( btx ) { //examines the materials in the loaded model and generates textures for each.
var gl = nitroRender . gl ; //get gl object from nitro render singleton
loadedTex = btx ;
texCanvas = [ ] ;
tex = [ ] ;
var models = bmd . modelData . objectData ;
for ( var j = 0 ; j < models . length ; j ++ ) {
var model = models [ j ] ;
var mat = model . materials . objectData
for ( var i = 0 ; i < mat . length ; i ++ ) {
2019-06-09 15:50:55 -07:00
mat [ i ] . texInd = tex . length ;
loadMatTex ( mat [ i ] , btx ) ;
}
}
}
2017-09-08 09:24:16 -07:00
2019-06-09 15:50:55 -07:00
function loadMatTex ( mat , btx , matReplace ) {
var m = mat ;
if ( matReplace ) m = matReplace ;
var texI = m . texName ;
var palI = m . palName ;
if ( texI == null || palI == null ) {
debugger ;
console . warn ( "WARNING: material " + i + " in model could not be assigned a texture." ) ;
/ *
var fC = document . createElement ( "canvas" ) ;
fC . width = 2 ;
fC . height = 2 ;
var ctx = fC . getContext ( "2d" )
ctx . fillStyle = "white" ;
ctx . fillRect ( 0 , 0 , 2 , 2 ) ;
texCanvas . push ( fC ) ;
var t = loadTex ( fC , gl , ! mat . repeatX , ! mat . repeatY ) ;
t . realWidth = 2 ;
t . realHeight = 2 ;
tex . push ( t ) ;
* /
2017-09-08 09:24:16 -07:00
2019-06-09 15:50:55 -07:00
return ;
2019-05-28 17:08:48 -07:00
}
2019-06-09 15:50:55 -07:00
var truetex = loadedTex . textureInfo . nameToIndex [ "$" + texI ] || 0 ;
var truepal = loadedTex . paletteInfo . nameToIndex [ "$" + palI ] || 0 ;
var cacheID = truetex + ":" + truepal ;
var cached = btx . cache [ cacheID ] ;
tex [ mat . texInd ] = cacheTex ( btx , truetex , truepal , mat ) ;
2019-05-28 17:08:48 -07:00
}
function cacheTex ( btx , truetex , truepal , m ) {
var cacheID = truetex + ":" + truepal ;
var cached = btx . cache [ cacheID ] ;
if ( cached == null ) {
var canvas = btx . readTexWithPal ( truetex , truepal ) ;
if ( m . flipX || m . flipY ) {
var fC = document . createElement ( "canvas" ) ;
var ctx = fC . getContext ( "2d" ) ;
fC . width = ( m . flipX ) ? canvas . width * 2 : canvas . width ;
fC . height = ( m . flipY ) ? canvas . height * 2 : canvas . height ;
ctx . drawImage ( canvas , 0 , 0 ) ;
ctx . save ( ) ;
if ( m . flipX ) {
ctx . translate ( 2 * canvas . width , 0 ) ;
ctx . scale ( - 1 , 1 ) ;
ctx . drawImage ( canvas , 0 , 0 ) ;
ctx . restore ( ) ;
ctx . save ( ) ;
}
if ( m . flipY ) {
ctx . translate ( 0 , 2 * canvas . height ) ;
ctx . scale ( 1 , - 1 ) ;
ctx . drawImage ( fC , 0 , 0 ) ;
ctx . restore ( ) ;
2017-09-08 09:24:16 -07:00
}
2019-05-28 17:08:48 -07:00
texCanvas . push ( fC ) ;
var t = loadTex ( fC , gl , ! m . repeatX , ! m . repeatY ) ;
t . realWidth = canvas . width ;
t . realHeight = canvas . height ;
btx . cache [ cacheID ] = t ;
return t ;
} else {
texCanvas . push ( canvas ) ;
var t = loadTex ( canvas , gl , ! m . repeatX , ! m . repeatY ) ;
t . realWidth = canvas . width ;
t . realHeight = canvas . height ;
btx . cache [ cacheID ] = t ;
return t ;
2017-09-08 09:24:16 -07:00
}
2019-05-28 17:08:48 -07:00
} else {
return cached ;
2017-09-08 09:24:16 -07:00
}
}
this . loadTexAnim = function ( bta ) {
texAnim = bta ;
texFrame = 0 ;
}
2019-05-28 17:08:48 -07:00
this . loadTexPAnim = function ( btp ) {
texPAnim = btp ;
}
2017-09-08 09:24:16 -07:00
this . setFrame = function ( frame ) {
texFrame = frame ;
}
2019-06-09 15:50:55 -07:00
this . setBaseMat = function ( mat ) {
thisObj . baseMat = mat ;
thisObj . billboardID = - 1 ;
}
2017-09-08 09:24:16 -07:00
function externDrawModel ( mv , project , mdl ) {
var models = bmd . modelData . objectData ;
drawModel ( models [ mdl ] , mv , project , mdl ) ;
}
function externDrawPoly ( mv , project , modelind , poly , matStack ) {
var models = bmd . modelData . objectData ;
var model = models [ modelind ] ;
var polys = model . polys . objectData ;
var matStack = matStack ;
if ( matStack == null ) {
matStack = matBuf [ modelind ] ;
if ( ( ( thisObj . billboardID != nitroRender . billboardID ) && bmd . hasBillboards ) || ( ! matStack . built ) ) {
nitroRender . lastMatStack = null ;
generateMatrixStack ( model , matStack . dat ) ;
matStack . built = true ;
thisObj . billboardID = nitroRender . billboardID ;
}
}
var shader = nitroRender . nitroShader ;
2019-06-09 15:50:55 -07:00
//var mv = mat4.scale([], mv, [model.head.scale, model.head.scale, model.head.scale]);
2017-09-08 09:24:16 -07:00
gl . uniformMatrix4fv ( shader . mvMatrixUniform , false , mv ) ;
gl . uniformMatrix4fv ( shader . pMatrixUniform , false , project ) ;
if ( matStack != nitroRender . lastMatStack ) {
gl . uniformMatrix4fv ( shader . matStackUniform , false , matStack . dat ) ;
nitroRender . lastMatStack = matStack ;
}
drawPoly ( polys [ poly ] , modelind , poly ) ;
}
function draw ( mv , project , matStack ) {
var models = bmd . modelData . objectData ;
for ( var j = 0 ; j < models . length ; j ++ ) {
drawModel ( models [ j ] , mv , project , j , matStack ) ;
}
}
2019-06-09 15:50:55 -07:00
function getBoundingCollisionModel ( modelind , polyind ) { //simple func to get collision model for a model. used when I'm too lazy to define my own... REQUIRES TRI MODE ACTIVE!
var model = bmd . modelData . objectData [ modelind ] ;
var poly = model . polys . objectData [ polyind ] ;
if ( modelBuffers [ modelind ] [ polyind ] == null ) modelBuffers [ modelind ] [ polyind ] = nitroRender . renderDispList ( poly . disp , tex [ poly . mat ] , ( poly . stackID == null ) ? model . lastStackID : poly . stackID ) ;
var tris = modelBuffers [ modelind ] [ polyind ] . strips [ 0 ] . posArray ;
var tC = tris . length / 3 ;
var off = 0 ;
var min = [ Infinity , Infinity , Infinity ] ;
var max = [ - Infinity , - Infinity , - Infinity ] ;
for ( var i = 0 ; i < tC ; i ++ ) {
var tri = [ tris [ off ++ ] , tris [ off ++ ] , tris [ off ++ ] ] ;
for ( var j = 0 ; j < 3 ; j ++ ) {
if ( tri [ j ] < min [ j ] ) min [ j ] = tri [ j ] ;
if ( tri [ j ] > max [ j ] ) max [ j ] = tri [ j ] ;
}
}
//create the bounding box
out = [
{ //top
Vertices : [ [ max [ 0 ] , max [ 1 ] , max [ 2 ] ] , [ max [ 0 ] , max [ 1 ] , min [ 2 ] ] , [ min [ 0 ] , max [ 1 ] , min [ 2 ] ] ] ,
Normal : [ 0 , 1 , 0 ]
} ,
{
Vertices : [ [ min [ 0 ] , max [ 1 ] , min [ 2 ] ] , [ min [ 0 ] , max [ 1 ] , max [ 2 ] ] , [ max [ 0 ] , max [ 1 ] , max [ 2 ] ] ] ,
Normal : [ 0 , 1 , 0 ]
} ,
{ //bottom
Vertices : [ [ min [ 0 ] , min [ 1 ] , min [ 2 ] ] , [ max [ 0 ] , min [ 1 ] , min [ 2 ] ] , [ max [ 0 ] , min [ 1 ] , max [ 2 ] ] ] ,
Normal : [ 0 , - 1 , 0 ]
} ,
{
Vertices : [ [ max [ 0 ] , min [ 1 ] , max [ 2 ] ] , [ min [ 0 ] , min [ 1 ] , max [ 2 ] ] , [ min [ 0 ] , min [ 1 ] , min [ 2 ] ] ] ,
Normal : [ 0 , - 1 , 0 ]
} ,
{ //back (farthest z)
Vertices : [ [ max [ 0 ] , max [ 1 ] , max [ 2 ] ] , [ max [ 0 ] , min [ 1 ] , max [ 2 ] ] , [ min [ 0 ] , min [ 1 ] , max [ 2 ] ] ] ,
Normal : [ 0 , 0 , 1 ]
} ,
{
Vertices : [ [ min [ 0 ] , min [ 1 ] , max [ 2 ] ] , [ min [ 0 ] , max [ 1 ] , max [ 2 ] ] , [ max [ 0 ] , max [ 1 ] , max [ 2 ] ] ] ,
Normal : [ 0 , 0 , 1 ]
} ,
{ //front (closest z)
Vertices : [ [ min [ 0 ] , min [ 1 ] , min [ 2 ] ] , [ max [ 0 ] , min [ 1 ] , min [ 2 ] ] , [ max [ 0 ] , max [ 1 ] , min [ 2 ] ] ] ,
Normal : [ 0 , 0 , - 1 ]
} ,
{
Vertices : [ [ max [ 0 ] , max [ 1 ] , min [ 2 ] ] , [ min [ 0 ] , max [ 1 ] , min [ 2 ] ] , [ min [ 0 ] , min [ 1 ] , min [ 2 ] ] ] ,
Normal : [ 0 , 0 , - 1 ]
} ,
{ //right (pos x)
Vertices : [ [ max [ 0 ] , max [ 1 ] , max [ 2 ] ] , [ max [ 0 ] , min [ 1 ] , max [ 2 ] ] , [ max [ 0 ] , min [ 1 ] , min [ 2 ] ] ] ,
Normal : [ 1 , 0 , 0 ]
} ,
{
Vertices : [ [ max [ 0 ] , min [ 1 ] , min [ 2 ] ] , [ max [ 0 ] , max [ 1 ] , min [ 2 ] ] , [ max [ 0 ] , max [ 1 ] , max [ 2 ] ] ] ,
Normal : [ 1 , 0 , 0 ]
} ,
{ //left (neg x)
Vertices : [ [ - max [ 0 ] , min [ 1 ] , min [ 2 ] ] , [ - max [ 0 ] , min [ 1 ] , max [ 2 ] ] , [ - max [ 0 ] , max [ 1 ] , max [ 2 ] ] ] ,
Normal : [ - 1 , 0 , 0 ]
} ,
{
Vertices : [ [ - max [ 0 ] , max [ 1 ] , max [ 2 ] ] , [ - max [ 0 ] , max [ 1 ] , min [ 2 ] ] , [ - max [ 0 ] , min [ 1 ] , min [ 2 ] ] ] ,
Normal : [ - 1 , 0 , 0 ]
} ,
]
out . push ( )
return { dat : out , scale : model . head . scale } ;
}
function getCollisionModel ( modelind , polyind , colType ) { //simple func to get collision model for a model. used when I'm too lazy to define my own... REQUIRES TRI MODE ACTIVE!
2017-09-08 09:24:16 -07:00
if ( collisionModel [ modelind ] == null ) collisionModel [ modelind ] = [ ] ;
if ( collisionModel [ modelind ] [ polyind ] != null ) return collisionModel [ modelind ] [ polyind ] ;
var model = bmd . modelData . objectData [ modelind ] ;
var poly = model . polys . objectData [ polyind ] ;
if ( modelBuffers [ modelind ] [ polyind ] == null ) modelBuffers [ modelind ] [ polyind ] = nitroRender . renderDispList ( poly . disp , tex [ poly . mat ] , ( poly . stackID == null ) ? model . lastStackID : poly . stackID ) ;
var tris = modelBuffers [ modelind ] [ polyind ] . strips [ 0 ] . posArray ;
var out = [ ] ;
var tC = tris . length / 9 ;
var off = 0 ;
for ( var i = 0 ; i < tC ; i ++ ) {
var t = { }
2019-06-09 15:50:55 -07:00
t . Vertices = [ ] ;
t . Vertices [ 0 ] = [ tris [ off ++ ] , tris [ off ++ ] , tris [ off ++ ] ] ;
t . Vertices [ 1 ] = [ tris [ off ++ ] , tris [ off ++ ] , tris [ off ++ ] ] ;
t . Vertices [ 2 ] = [ tris [ off ++ ] , tris [ off ++ ] , tris [ off ++ ] ] ;
2017-09-08 09:24:16 -07:00
//calculate normal
2019-06-09 15:50:55 -07:00
var v = vec3 . sub ( [ ] , t . Vertices [ 1 ] , t . Vertices [ 0 ] ) ;
var w = vec3 . sub ( [ ] , t . Vertices [ 2 ] , t . Vertices [ 0 ] ) ;
t . Normal = vec3 . cross ( [ ] , v , w ) ;
t . CollisionType = colType ;
2017-09-08 09:24:16 -07:00
vec3 . normalize ( t . Normal , t . Normal ) ;
out . push ( t ) ;
}
collisionModel [ modelind ] [ polyind ] = { dat : out , scale : model . head . scale } ;
return collisionModel [ modelind ] [ polyind ] ;
}
function drawModel ( model , mv , project , modelind , matStack ) {
var polys = model . polys . objectData ;
var matStack = matStack ;
if ( matStack == null ) {
matStack = matBuf [ modelind ] ;
if ( ( ( thisObj . billboardID != nitroRender . billboardID ) && bmd . hasBillboards ) || ( ! matStack . built ) ) {
nitroRender . lastMatStack = null ;
generateMatrixStack ( model , matStack . dat ) ;
matStack . built = true ;
thisObj . billboardID = nitroRender . billboardID ;
}
}
var shader = nitroRender . nitroShader ;
2019-06-09 15:50:55 -07:00
//var mv = mat4.scale([], mv, [model.head.scale, model.head.scale, model.head.scale]);
2017-09-08 09:24:16 -07:00
gl . uniformMatrix4fv ( shader . mvMatrixUniform , false , mv ) ;
gl . uniformMatrix4fv ( shader . pMatrixUniform , false , project ) ;
if ( matStack != nitroRender . lastMatStack ) {
gl . uniformMatrix4fv ( shader . matStackUniform , false , matStack . dat ) ;
nitroRender . lastMatStack = matStack ;
}
for ( var i = 0 ; i < polys . length ; i ++ ) {
drawPoly ( polys [ i ] , modelind , i ) ;
}
}
function drawPoly ( poly , modelind , polyind ) {
var shader = nitroRender . nitroShader ;
var model = bmd . modelData . objectData [ modelind ] ;
var gl = nitroRender . gl ;
//texture 0 SHOULD be bound, assuming the nitrorender program has been prepared
2019-05-28 17:08:48 -07:00
var pmat = poly . mat ;
var matname = model . materials . names [ pmat ] ; //attach tex anim to mat with same name
if ( texPAnim != null ) {
var info = texPAnim . animData . objectData [ modelind ] ;
var anims = texPAnim . animData . objectData [ modelind ] . data ;
var animNum = anims . names . indexOf ( matname ) ;
if ( animNum != - 1 ) {
var offFrame = texFrame % info . duration ;
//we got a match! it's wonderful :')
var anim = anims . objectData [ animNum ] ;
//look thru frames for the approprate point in the animation
2019-06-09 15:50:55 -07:00
for ( var i = anim . frames . length - 1 ; i >= 0 ; i -- ) {
2019-05-28 17:08:48 -07:00
if ( offFrame >= anim . frames [ i ] . time ) {
2019-06-09 15:50:55 -07:00
loadMatTex ( model . materials . objectData [ pmat ] , btx == null ? bmd . tex : btx , anim . frames [ i ] ) ;
/ *
2019-05-28 17:08:48 -07:00
tex [ pmat ] = cacheTex ( btx == null ? bmd . tex : btx , anim . frames [ i ] . tex , anim . frames [ i ] . mat , model . materials . objectData [ pmat ] ) ;
2019-06-09 15:50:55 -07:00
* /
break ;
2019-05-28 17:08:48 -07:00
}
}
}
}
2019-06-10 15:06:38 -07:00
if ( nitroRender . last . tex != tex [ pmat ] ) {
gl . bindTexture ( gl . TEXTURE _2D , tex [ pmat ] ) ; //load up material texture
nitroRender . last . tex = tex [ pmat ] ;
}
2017-09-08 09:24:16 -07:00
2019-05-28 17:08:48 -07:00
var material = model . materials . objectData [ pmat ] ;
2019-06-09 15:50:55 -07:00
nitroRender . setAlpha ( material . alpha ) ;
2017-09-08 09:24:16 -07:00
if ( texAnim != null ) {
//generate and send texture matrix from data
2019-05-28 17:08:48 -07:00
var matname = model . materials . names [ pmat ] ; //attach tex anim to mat with same name
2017-09-08 09:24:16 -07:00
var anims = texAnim . animData . objectData [ modelind ] . data ;
var animNum = anims . names . indexOf ( matname ) ;
if ( animNum != - 1 ) {
//we got a match! it's wonderful :')
var anim = anims . objectData [ animNum ] ;
2019-06-09 15:50:55 -07:00
var mat = matAtFrame ( texFrame , anim ) ;
2017-09-08 09:24:16 -07:00
gl . uniformMatrix3fv ( shader . texMatrixUniform , false , mat ) ;
} else {
gl . uniformMatrix3fv ( shader . texMatrixUniform , false , material . texMat ) ;
}
} else gl . uniformMatrix3fv ( shader . texMatrixUniform , false , material . texMat ) ;
2019-06-10 15:06:38 -07:00
if ( modelBuffers [ modelind ] [ polyind ] == null ) modelBuffers [ modelind ] [ polyind ] = nitroRender . renderDispList ( poly . disp , tex [ poly . mat ] , ( poly . stackID == null ) ? model . lastStackID : poly . stackID ) ;
if ( material . cullMode < 3 ) {
gl . enable ( gl . CULL _FACE ) ;
gl . cullFace ( nitroRender . cullModes [ material . cullMode ] ) ;
} else {
if ( nitroRender . forceFlatNormals ) {
//dual side lighting model, course render mode essentially
gl . enable ( gl . CULL _FACE ) ;
gl . cullFace ( gl . BACK ) ;
drawModelBuffer ( modelBuffers [ modelind ] [ polyind ] , gl , shader ) ;
nitroRender . setNormalFlip ( - 1 ) ;
gl . cullFace ( gl . FRONT ) ;
drawModelBuffer ( modelBuffers [ modelind ] [ polyind ] , gl , shader ) ;
nitroRender . setNormalFlip ( 1 ) ;
return ;
}
gl . disable ( gl . CULL _FACE ) ;
}
drawModelBuffer ( modelBuffers [ modelind ] [ polyind ] , gl , shader ) ;
2017-09-08 09:24:16 -07:00
}
2019-06-09 15:50:55 -07:00
function frameLerp ( frame , step , values ) {
if ( values . length == 1 ) return values [ 0 ] ;
var i = ( frame / ( 1 << step ) ) % 1 ;
var len = values . length
if ( step > 0 ) len -= 1 ;
var frame1 = ( frame >> step ) % len ;
var from = values [ frame1 ] ;
var to = values [ frame1 + 1 ] || values [ frame1 ] ;
return to * i + from * ( 1 - i ) ;
}
function matAtFrame ( frame , anim ) {
var mat = mat3 . create ( ) ; //material texture mat is ignored
mat3 . scale ( mat , mat , [ frameLerp ( frame , anim . frameStep . scaleS , anim . scaleS ) , frameLerp ( frame , anim . frameStep . scaleT , anim . scaleT ) ] ) ;
mat3 . translate ( mat , mat , [ - frameLerp ( frame , anim . frameStep . translateS , anim . translateS ) , frameLerp ( frame , anim . frameStep . translateT , anim . translateT ) ] ) ;
return mat ;
}
2019-05-28 17:08:48 -07:00
function generateMatrixStack ( model , targ ) { //this generates a matrix stack with the default bones. use nitroAnimator to pass custom matrix stacks using nsbca animations.
2017-09-08 09:24:16 -07:00
var matrices = [ ] ;
var objs = model . objects . objectData ;
var cmds = model . commands ;
2019-06-09 15:50:55 -07:00
var curMat = mat4 . clone ( thisObj . baseMat ) ;
2017-09-08 09:24:16 -07:00
var lastStackID = 0 ;
2019-06-09 15:50:55 -07:00
var highestUsed = - 1 ;
2017-09-08 09:24:16 -07:00
for ( var i = 0 ; i < cmds . length ; i ++ ) {
var cmd = cmds [ i ] ;
2019-06-09 15:50:55 -07:00
if ( cmd . copy != null ) {
//copy this matrix to somewhere else, because it's bound and is going to be overwritten.
matrices [ cmd . dest ] = mat4 . clone ( matrices [ cmd . copy ] ) ;
continue ;
}
2017-09-08 09:24:16 -07:00
if ( cmd . restoreID != null ) curMat = mat4 . clone ( matrices [ cmd . restoreID ] ) ;
var o = objs [ cmd . obj ] ;
mat4 . multiply ( curMat , curMat , o . mat ) ;
if ( o . billboardMode == 1 ) mat4 . multiply ( curMat , curMat , nitroRender . billboardMat ) ;
if ( o . billboardMode == 2 ) mat4 . multiply ( curMat , curMat , nitroRender . yBillboardMat ) ;
2019-06-09 15:50:55 -07:00
2017-09-08 09:24:16 -07:00
if ( cmd . stackID != null ) {
matrices [ cmd . stackID ] = mat4 . clone ( curMat ) ;
lastStackID = cmd . stackID ;
2019-06-09 15:50:55 -07:00
if ( lastStackID > highestUsed ) highestUsed = lastStackID ;
2017-09-08 09:24:16 -07:00
} else {
matrices [ lastStackID ] = mat4 . clone ( curMat ) ;
}
}
model . lastStackID = lastStackID ;
2019-06-09 15:50:55 -07:00
var scale = [ model . head . scale , model . head . scale , model . head . scale ] ;
2017-09-08 09:24:16 -07:00
targ . set ( matBufEmpty ) ;
var off = 0 ;
2019-06-09 15:50:55 -07:00
for ( var i = 0 ; i <= highestUsed ; i ++ ) {
if ( matrices [ i ] != null ) {
mat4 . scale ( matrices [ i ] , matrices [ i ] , scale ) ;
targ . set ( matrices [ i ] , off ) ;
}
2017-09-08 09:24:16 -07:00
off += 16 ;
}
return targ ;
}
function drawModelBuffer ( buf , gl , shader ) {
for ( var i = 0 ; i < buf . strips . length ; i ++ ) {
var obj = buf . strips [ i ] ;
if ( obj != nitroRender . last . obj ) {
gl . bindBuffer ( gl . ARRAY _BUFFER , obj . vPos ) ;
gl . vertexAttribPointer ( shader . vertexPositionAttribute , 3 , gl . FLOAT , false , 0 , 0 ) ;
gl . bindBuffer ( gl . ARRAY _BUFFER , obj . vTx ) ;
gl . vertexAttribPointer ( shader . textureCoordAttribute , 2 , gl . FLOAT , false , 0 , 0 ) ;
gl . bindBuffer ( gl . ARRAY _BUFFER , obj . vCol ) ;
gl . vertexAttribPointer ( shader . colorAttribute , 4 , gl . FLOAT , false , 0 , 0 ) ;
gl . bindBuffer ( gl . ARRAY _BUFFER , obj . vMat ) ;
gl . vertexAttribPointer ( shader . matAttribute , 1 , gl . FLOAT , false , 0 , 0 ) ;
gl . bindBuffer ( gl . ARRAY _BUFFER , obj . vNorm ) ;
gl . vertexAttribPointer ( shader . normAttribute , 3 , gl . FLOAT , false , 0 , 0 ) ;
nitroRender . last . obj = obj ;
}
gl . drawArrays ( obj . mode , 0 , obj . verts ) ;
}
}
}
function loadTex ( img , gl , clampx , clampy ) { //general purpose function for loading an image into a texture.
var texture = gl . createTexture ( ) ;
gl . bindTexture ( gl . TEXTURE _2D , texture ) ;
gl . texImage2D ( gl . TEXTURE _2D , 0 , gl . RGBA , gl . RGBA , gl . UNSIGNED _BYTE , img ) ;
2019-05-28 17:08:48 -07:00
gl . texParameteri ( gl . TEXTURE _2D , gl . TEXTURE _MIN _FILTER , gl . LINEAR ) ;
2017-09-08 09:24:16 -07:00
gl . texParameteri ( gl . TEXTURE _2D , gl . TEXTURE _MAG _FILTER , gl . NEAREST ) ;
if ( clampx ) gl . texParameteri ( gl . TEXTURE _2D , gl . TEXTURE _WRAP _S , gl . CLAMP _TO _EDGE ) ;
if ( clampy ) gl . texParameteri ( gl . TEXTURE _2D , gl . TEXTURE _WRAP _T , gl . CLAMP _TO _EDGE ) ;
2019-05-28 17:08:48 -07:00
gl . generateMipmap ( gl . TEXTURE _2D ) ;
2017-09-08 09:24:16 -07:00
texture . width = img . width ;
texture . height = img . height ;
gl . bindTexture ( gl . TEXTURE _2D , null ) ;
return texture ;
}