Skip to content

Instantly share code, notes, and snippets.

@Wasabi2007
Created May 8, 2015 11:01
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 Wasabi2007/a6ede960773d3b3f378e to your computer and use it in GitHub Desktop.
Save Wasabi2007/a6ede960773d3b3f378e to your computer and use it in GitHub Desktop.
Slice9Sprite.js for PlayCanvas
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