Created
May 8, 2015 11:01
-
-
Save Wasabi2007/a6ede960773d3b3f378e to your computer and use it in GitHub Desktop.
Slice9Sprite.js for PlayCanvas
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pc.script.attribute('textureAsset', 'asset', [], { | |
type: 'texture', | |
max: 1 | |
}); | |
pc.script.attribute('x', 'number'); | |
pc.script.attribute('y', 'number'); | |
pc.script.attribute('width', 'number'); | |
pc.script.attribute('height', 'number'); | |
pc.script.attribute('depth', 'number', 1) | |
pc.script.attribute('pixelXMinSlice', 'number'); | |
pc.script.attribute('pixelYMinSlice', 'number'); | |
pc.script.attribute('pixelXMaxSlice', 'number'); | |
pc.script.attribute('pixelYMaxSlice', 'number'); | |
pc.script.attribute('margin', 'number',0.001); | |
pc.script.attribute('repeatX', 'boolean',true); | |
pc.script.attribute('repeatY', 'boolean',true); | |
pc.script.attribute('anchor', 'enumeration', 0, { | |
enumerations: [{ | |
name: 'topLeft', | |
value: 0 | |
}, { | |
name: 'top', | |
value: 1 | |
}, { | |
name: 'topRight', | |
value: 2 | |
}, { | |
name: 'left', | |
value: 3 | |
}, { | |
name: 'center', | |
value: 4 | |
}, { | |
name: 'right', | |
value: 5 | |
}, { | |
name: 'bottomLeft', | |
value: 6 | |
}, { | |
name: 'bottom', | |
value: 7 | |
}, { | |
name: 'bottomRight', | |
value: 8 | |
}] | |
}); | |
pc.script.attribute('pivot', 'enumeration', 0, { | |
enumerations: [{ | |
name: 'topLeft', | |
value: 0 | |
}, { | |
name: 'top', | |
value: 1 | |
}, { | |
name: 'topRight', | |
value: 2 | |
}, { | |
name: 'left', | |
value: 3 | |
}, { | |
name: 'center', | |
value: 4 | |
}, { | |
name: 'right', | |
value: 5 | |
}, { | |
name: 'bottomLeft', | |
value: 6 | |
}, { | |
name: 'bottom', | |
value: 7 | |
}, { | |
name: 'bottomRight', | |
value: 8 | |
}] | |
}); | |
pc.script.attribute('tint', 'rgba', [1,1,1,1]); | |
pc.script.attribute('maxResHeight', 'number', 720); | |
pc.script.create('sprite', function (app) { | |
var shader = null; | |
var vertexFormat = null; | |
var resolution = new pc.Vec2(); | |
var Sprite = function (entity) { | |
this.entity = entity; | |
}; | |
Sprite.prototype = { | |
initialize: function () { | |
var canvas = document.getElementById('application-canvas'); | |
this.userOffset = new pc.Vec2(); | |
this.offset = new pc.Vec2(); | |
this.scaling = new pc.Vec2(); | |
this.anchorOffset = new pc.Vec2(); | |
this.pivotOffset = new pc.Vec2(); | |
this.wasPressed = false; | |
this.touchid = -1; | |
this.uPercentage = 1; | |
this.vPercentage = 1; | |
// Create shader | |
var gd = app.graphicsDevice; | |
if (!shader) { | |
var shaderDefinition = { | |
attributes: { | |
aPosition: pc.SEMANTIC_POSITION, | |
aUv0: pc.SEMANTIC_TEXCOORD0 | |
}, | |
vshader: [ | |
"attribute vec2 aPosition;", | |
"attribute vec2 aUv0;", | |
"varying vec2 vUv0;", | |
"uniform vec2 uResolution;", | |
"uniform vec2 uOffset;", | |
"uniform vec2 uScale;", | |
"", | |
"void main(void)", | |
"{", | |
" gl_Position = vec4(2.0 * ((uScale * aPosition.xy + uOffset) / uResolution ) - 1.0, -0.9, 1.0);", | |
" vUv0 = aUv0;", | |
"}" | |
].join("\n"), | |
fshader: [ | |
"precision " + gd.precision + " float;", | |
"", | |
"varying vec2 vUv0;", | |
"", | |
"uniform vec4 vTint;", | |
"", | |
"uniform float margin;", | |
"", | |
"uniform vec4 uUvMinMax0;", | |
"", | |
"uniform vec2 uScale;", | |
"", | |
"uniform sampler2D uColorMap;", | |
"", | |
"void main(void)", | |
"{", | |
" vec2 vUV = vUv0.xy-uUvMinMax0.xy;", | |
" vec4 uUvMinMax = uUvMinMax0;", | |
" uUvMinMax.zw = uUvMinMax.zw-uUvMinMax0.xy;", | |
" vUV.x = clamp(mod(max(vUV.x,0.0),uUvMinMax.z+margin+min(vUV.x,0.0))+uUvMinMax0.x,0.0,1.0);", | |
" vUV.y = clamp(mod(max(vUV.y,0.0),uUvMinMax.w+margin+min(vUV.y,0.0))+uUvMinMax0.y,0.0,1.0);", | |
" vec4 color = texture2D(uColorMap, vUV);", | |
" gl_FragColor = vec4(color.rgb * vTint.rgb, color.a * vTint.a);", | |
"}" | |
].join("\n") | |
}; | |
shader = new pc.Shader(gd, shaderDefinition); | |
} | |
// Create the vertex format | |
if (!vertexFormat) { | |
vertexFormat = new pc.VertexFormat(gd, [ | |
{ semantic: pc.SEMANTIC_POSITION, components: 2, type: pc.ELEMENTTYPE_FLOAT32 }, | |
{ semantic: pc.SEMANTIC_TEXCOORD0, components: 2, type: pc.ELEMENTTYPE_FLOAT32 } | |
]); | |
} | |
// Load font assets | |
var assets = [ | |
app.assets.getAssetById(this.textureAsset), | |
]; | |
app.assets.load(assets).then(function (resources) { | |
this.texture = resources[0]; | |
if(this.pixelXMaxSlice<0){ | |
this.pixelXMaxSlice += this.texture.width; | |
} | |
if(this.pixelYMaxSlice<0){ | |
this.pixelYMaxSlice += this.texture.height; | |
} | |
// Create a vertex buffer | |
this.vertexBuffers = []; | |
for(var slice= 0;slice < 9; slice ++){ | |
this.vertexBuffers.push({"vbo":new pc.VertexBuffer(gd, vertexFormat, 6, pc.BUFFER_DYNAMIC),"ibo":new pc.IndexBuffer(gd, pc.INDEXFORMAT_UINT16, 6),"repatInfo":new pc.Vec4(0,0,1,1)}); | |
} | |
this.updateSprite(); | |
var command = new pc.Command(pc.LAYER_HUD, pc.BLEND_NORMAL, function () { | |
if (this.entity.enabled) { | |
// Set the shader | |
gd.setShader(shader); | |
gd.setBlending(true); | |
gd.setBlendFunction(pc.BLENDMODE_SRC_ALPHA, pc.BLENDMODE_ONE_MINUS_SRC_ALPHA); | |
gd.setDepthWrite(false); | |
gd.setDepthTest(false); | |
resolution.set(canvas.offsetWidth, canvas.offsetHeight); | |
gd.scope.resolve("uResolution").setValue(resolution.data); | |
gd.scope.resolve("uScale").setValue(this.calculateScaling().data); | |
gd.scope.resolve("uOffset").setValue(this.calculateOffset().data); | |
gd.scope.resolve("uColorMap").setValue(this.texture); | |
gd.scope.resolve("vTint").setValue(this.tint.data); | |
gd.scope.resolve("margin").setValue(this.margin); | |
for(var slice= 0;slice < 9; slice ++){ | |
// Set the vertex buffer | |
gd.setVertexBuffer(this.vertexBuffers[slice].vbo, 0); | |
//gd.setIndexBuffer(this.vertexBuffers[slice].ibo); | |
gd.scope.resolve("uUvMinMax0").setValue(this.vertexBuffers[slice].repatInfo.data); | |
gd.draw({ | |
type: pc.PRIMITIVE_TRIANGLES, | |
base: 0, | |
count: 6, | |
indexed: false | |
}); | |
} | |
} | |
}.bind(this)); | |
this.command = command; | |
command.key = this.depth; | |
app.scene.drawCalls.push(command); | |
}.bind(this)); | |
app.mouse.on('mousedown', this.onMouseDown, this); | |
if (app.touch) { | |
app.touch.on('touchstart', this.onTouchDown, this); | |
} | |
app.mouse.on('mouseup', this.onMouseUp, this); | |
if (app.touch) { | |
app.touch.on('touchend', this.onTouchUp, this); | |
} | |
}, | |
onMouseDown: function (e) { | |
if (!this.eventsEnabled) { | |
return; | |
} | |
this.onDown(e); | |
}, | |
onTouchDown: function (e) { | |
if (!this.eventsEnabled || this.touchid != -1) { | |
return; | |
} | |
this.touchid = e.changedTouches[0].id; | |
this.onDown(e.changedTouches[0]); | |
}, | |
onMouseUp: function (e) { | |
if (!this.eventsEnabled) { | |
return; | |
} | |
this.onUp(e); | |
}, | |
onTouchUp: function (e) { | |
if (!this.eventsEnabled || e.changedTouches[0].id != this.touchid) { | |
return; | |
} | |
this.onUp(e.changedTouches[0]); | |
this.touchid = -1; | |
}, | |
/** | |
* Calculates if the click has happened inside the rect of this | |
* sprite and fires 'click' event if it has | |
*/ | |
//onClick: function (cursor) { | |
// this.fire('click'); | |
//}, | |
onDown: function (cursor){ | |
if(this.isCourserInside(cursor)){ | |
this.wasPressed = true; | |
this.fire('down'); | |
} | |
}, | |
onUp: function (cursor){ | |
if(this.isCourserInside(cursor)){ | |
this.fire('up'); | |
if(this.wasPressed){ | |
this.fire("click"); | |
} | |
} | |
this.wasPressed = false; | |
}, | |
isCourserInside:function(cursor){ | |
var canvas = app.graphicsDevice.canvas; | |
var tlx, tly, brx, bry, mx, my; | |
var scaling = this.scaling; | |
var offset = this.offset; | |
tlx = 2.0 * (scaling.x * 0 + offset.x) / resolution.x - 1.0; | |
tly = 2.0 * (scaling.y * 0 + offset.y) / resolution.y - 1.0; | |
brx = 2.0 * (scaling.x * this.width + offset.x) / resolution.x - 1.0; | |
bry = 2.0 * (scaling.y * (- this.height) + offset.y) / resolution.y - 1.0; | |
mx = (2.0 * cursor.x / canvas.offsetWidth) - 1; | |
my = (2.0 * (canvas.offsetHeight - cursor.y) / canvas.offsetHeight) - 1; | |
if (mx >= tlx && mx <= brx && | |
my <= tly && my >= bry) { | |
return true; | |
} | |
return false; | |
}, | |
onAttributeChanged: function (name, oldValue, newValue) { | |
this.eventsEnabled = false; | |
if (name === 'depth') { | |
this.command.key = newValue; | |
} | |
else if (name === 'width' || | |
name === 'height' || | |
name === 'uPercentage' || | |
name === 'vPercentage') { | |
this.updateSprite(); | |
} | |
}, | |
updateSprite: function () { | |
if (!this.vertexBuffers) { | |
return; | |
} | |
var canvas = app.graphicsDevice.canvas; | |
/* | |
9 Slice Mesh | |
0 1 | |
h3 *---*---*---* 1 | |
| | | | | |
h2 *---*---*---* | |
| | | | | |
h1 *---*---*---* | |
| | | | | |
h0 *---*---*---* 0 | |
w0 w1 w2 w3 | |
*/ | |
var h0 = -this.height; // vertex Position | |
var uvh0 = 0; // streched UV Position | |
var uvrh0 = 0; // UV limit for repeating | |
var h1 = -(this.height-(this.texture.height-this.pixelYMaxSlice)); | |
var uvh1 = (this.texture.height-this.pixelYMaxSlice)/this.texture.height; | |
var uvrh1 = (this.texture.height-this.pixelYMaxSlice)/this.texture.height; | |
var h2 = -this.pixelYMinSlice; | |
var uvh2 = uvh1 + (this.repeatY?((this.height-(this.texture.height-(this.pixelYMaxSlice - this.pixelYMinSlice)))/(this.pixelYMaxSlice-this.pixelYMinSlice)):(this.pixelYMaxSlice-this.pixelYMinSlice)/this.texture.height); | |
var uvrh2 = (this.texture.height-this.pixelYMinSlice)/this.texture.height; | |
var h3 = 0; | |
var uvh3 = uvh2 + this.pixelYMinSlice/this.texture.height; | |
var uvrh3 = 1; | |
var heights = [h0,h1,h2,h3]; | |
var heightsUV = [uvh0,uvh1,uvh2,uvh3]; | |
var heightsRUV = [uvrh0,uvrh1,uvrh2,uvrh3]; | |
var w0 = 0; | |
var uvw0 = 0; | |
var uvrw0 = 0; | |
var w1 = this.pixelXMinSlice; | |
var uvw1 = this.pixelXMinSlice/this.texture.width; | |
var uvrw1 = this.pixelXMinSlice/this.texture.width; | |
var w2 = this.width-(this.texture.width-this.pixelXMaxSlice); | |
var uvw2 = uvw1+ (this.repeatX?((this.width-(this.texture.width-(this.pixelXMaxSlice-this.pixelXMinSlice)))/(this.pixelXMaxSlice-this.pixelXMinSlice)):(this.pixelXMaxSlice-this.pixelXMinSlice)/this.texture.width); | |
var uvrw2 = this.pixelXMaxSlice/this.texture.width; | |
var w3 = this.width; | |
var uvw3 = uvw2 + (this.texture.width-this.pixelXMaxSlice)/this.texture.width; | |
var uvrw3 = 1.0; | |
var widths = [w0,w1,w2,w3]; | |
var widthsUV = [uvw0,uvw1,uvw2,uvw3]; | |
var widthsRUV = [uvrw0,uvrw1,uvrw2,uvrw3]; | |
var count = 0; | |
for(y = 0;y<3;y++){ | |
for(x = 0;x<3;x++){ | |
this.fillVertexBuffer(this.vertexBuffers[count],[widths[x],heights[y],widths[x+1],heights[y+1]], | |
[(x<2?widthsUV[x]:widthsRUV[x]),(y<2?heightsUV[y]:heightsRUV[y]),(x<2?widthsUV[x+1]:widthsRUV[x+1]),(y<2?heightsUV[y+1]:heightsRUV[y+1])], | |
[widthsRUV[x],heightsRUV[y],widthsRUV[x+1],heightsRUV[y+1]]); | |
count++; | |
} | |
} | |
}, | |
fillVertexBuffer:function(objectInfo,vertexInfo,uvInfo,repatInfo){ | |
objectInfo.vbo.lock(); | |
var canvas = app.graphicsDevice.canvas; | |
var iterator = new pc.VertexIterator(objectInfo.vbo); | |
iterator.element[pc.SEMANTIC_POSITION].set(vertexInfo[0], vertexInfo[1]); | |
iterator.element[pc.SEMANTIC_TEXCOORD0].set(uvInfo[0], uvInfo[1]); | |
iterator.next(); | |
iterator.element[pc.SEMANTIC_POSITION].set(vertexInfo[2], vertexInfo[1]); | |
iterator.element[pc.SEMANTIC_TEXCOORD0].set(uvInfo[2], uvInfo[1]); | |
iterator.next(); | |
iterator.element[pc.SEMANTIC_POSITION].set(vertexInfo[0], vertexInfo[3]); | |
iterator.element[pc.SEMANTIC_TEXCOORD0].set(uvInfo[0], uvInfo[3]); | |
iterator.next(); | |
iterator.element[pc.SEMANTIC_POSITION].set(vertexInfo[0], vertexInfo[3]); | |
iterator.element[pc.SEMANTIC_TEXCOORD0].set(uvInfo[0], uvInfo[3]); | |
iterator.next(); | |
iterator.element[pc.SEMANTIC_POSITION].set(vertexInfo[2], vertexInfo[1]); | |
iterator.element[pc.SEMANTIC_TEXCOORD0].set(uvInfo[2], uvInfo[1]); | |
iterator.next(); | |
iterator.element[pc.SEMANTIC_POSITION].set(vertexInfo[2], vertexInfo[3]); | |
iterator.element[pc.SEMANTIC_TEXCOORD0].set(uvInfo[2], uvInfo[3]); | |
//iterator.next(); | |
objectInfo.vbo.unlock(); | |
objectInfo.repatInfo.x = repatInfo[0]; | |
objectInfo.repatInfo.y = repatInfo[1]; | |
objectInfo.repatInfo.z = repatInfo[2]; | |
objectInfo.repatInfo.w = repatInfo[3]; | |
var indices = new Uint8Array(objectInfo.ibo.lock()); | |
var indexArray = []; | |
indexArray.push(0,1,2,2,1,3); | |
indices.set(indexArray); | |
objectInfo.ibo.unlock(); | |
}, | |
calculateOffset: function () { | |
var canvas = app.graphicsDevice.canvas; | |
this.calculateAnchorOffset(); | |
this.calculatePivotOffset(); | |
this.offset.set(this.x * this.scaling.x, this.y * this.scaling.y) | |
.add(this.userOffset) | |
.add(this.anchorOffset) | |
.add(this.pivotOffset); | |
this.offset.y += canvas.offsetHeight; | |
return this.offset; | |
}, | |
calculateScaling: function () { | |
var canvas = app.graphicsDevice.canvas; | |
var scale = canvas.offsetHeight / this.maxResHeight; | |
this.scaling.set(scale*this.entity.getLocalScale().x, scale*this.entity.getLocalScale().y); | |
return this.scaling; | |
}, | |
calculateAnchorOffset: function () { | |
var canvas = app.graphicsDevice.canvas; | |
var width = canvas.offsetWidth; | |
var height = canvas.offsetHeight; | |
switch (this.anchor) { | |
// top left | |
case 0: | |
this.anchorOffset.set(0,0); | |
break; | |
// top | |
case 1: | |
this.anchorOffset.set(width * 0.5, 0); | |
break; | |
// top right | |
case 2: | |
this.anchorOffset.set(width, 0); | |
break; | |
// left | |
case 3: | |
this.anchorOffset.set(0, -height * 0.5); | |
break; | |
// center | |
case 4: | |
this.anchorOffset.set(width * 0.5, -height * 0.5); | |
break; | |
// right | |
case 5: | |
this.anchorOffset.set(width, -height * 0.5); | |
break; | |
// bottom left | |
case 6: | |
this.anchorOffset.set(0, -height); | |
break; | |
// bottom | |
case 7: | |
this.anchorOffset.set(width/2, -height); | |
break; | |
// bottom right | |
case 8: | |
this.anchorOffset.set(width, -height); | |
break; | |
default: | |
console.error('Wrong anchor: ' + this.anchor); | |
break; | |
} | |
return this.anchorOffset; | |
}, | |
calculatePivotOffset: function () { | |
var width = this.width * this.scaling.x; | |
var height = this.height * this.scaling.y; | |
switch (this.pivot) { | |
// top left | |
case 0: | |
this.pivotOffset.set(0,0); | |
break; | |
// top | |
case 1: | |
this.pivotOffset.set(-width * 0.5, 0); | |
break; | |
// top right | |
case 2: | |
this.pivotOffset.set(-width, 0); | |
break; | |
// left | |
case 3: | |
this.pivotOffset.set(0, height * 0.5); | |
break; | |
// center | |
case 4: | |
this.pivotOffset.set(-width * 0.5, height * 0.5); | |
break; | |
// right | |
case 5: | |
this.pivotOffset.set(-width, height * 0.5); | |
break; | |
// bottom left | |
case 6: | |
this.pivotOffset.set(0, height); | |
break; | |
// bottom | |
case 7: | |
this.pivotOffset.set(-width/2, height); | |
break; | |
// bottom right | |
case 8: | |
this.pivotOffset.set(-width, height); | |
break; | |
default: | |
console.error('Wrong pivot: ' + this.pivot); | |
break; | |
} | |
return this.pivotOffset; | |
}, | |
onEnable: function () { | |
this.eventsEnabled = false; | |
}, | |
onDisable: function () { | |
this.eventsEnabled = false; | |
}, | |
update: function (dt) { | |
this.eventsEnabled = true; | |
if(this.wasPressed){ | |
this.fire("pressed"); | |
} | |
}, | |
destroy: function () { | |
// remove draw call | |
if (this.command) { | |
var i = app.scene.drawCalls.indexOf(this.command); | |
if (i >= 0) { | |
app.scene.drawCalls.splice(i, 1); | |
} | |
} | |
} | |
}; | |
return Sprite; | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment