Skip to content

Commit a71e211

Browse files
authored
Procedural Mesh API (playcanvas#1960)
* Procedural Mesh API
1 parent db95ded commit a71e211

26 files changed

+2212
-565
lines changed

build/dependencies.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
../src/scene/materials/standard-material-validator.js
7878
../src/scene/materials/standard-material-options-builder.js
7979
../src/scene/mesh.js
80+
../src/scene/mesh-instance.js
8081
../src/scene/skin.js
8182
../src/scene/skin-partition.js
8283
../src/scene/morph.js
6.54 KB
Loading

examples/examples.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ var categories = [
2121
"lights-baked",
2222
"loader-obj",
2323
"material-anisotropic",
24+
"mesh-decals",
25+
"mesh-deformation",
26+
"mesh-generation",
2427
"material-clear-coat",
2528
"material-physical",
2629
"model-asset",
@@ -29,6 +32,7 @@ var categories = [
2932
"model-shapes",
3033
"model-textured-box",
3134
"point-cloud",
35+
"point-cloud-simulation",
3236
"portal",
3337
"particles-anim-index",
3438
"particles-random-sprites",

examples/graphics/mesh-decals.html

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>PlayCanvas Mesh Decals</title>
5+
<meta charset="utf-8">
6+
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
7+
<link rel="icon" type="image/png" href="../playcanvas-favicon.png" />
8+
<script src="../../build/output/playcanvas.js"></script>
9+
<style>
10+
body {
11+
margin: 0;
12+
overflow-y: hidden;
13+
}
14+
</style>
15+
</head>
16+
17+
<body>
18+
<!-- The canvas element -->
19+
<canvas id="application-canvas"></canvas>
20+
21+
<!-- The script -->
22+
<script>
23+
var canvas = document.getElementById("application-canvas");
24+
25+
// Create the application
26+
var app = new pc.Application(canvas);
27+
28+
// Set the canvas to fill the window and automatically change resolution to be the same as the canvas size
29+
app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW);
30+
app.setCanvasResolution(pc.RESOLUTION_AUTO);
31+
32+
window.addEventListener("resize", function () {
33+
app.resizeCanvas(canvas.width, canvas.height);
34+
});
35+
36+
app.scene.ambientLight = new pc.Color(0.2, 0.2, 0.2);
37+
38+
// create plane primitive
39+
var primitive = new pc.Entity();
40+
primitive.addComponent('model', {
41+
type: "plane"
42+
});
43+
44+
// set position and scale and add it to scene
45+
primitive.setLocalScale(new pc.Vec3(20, 20, 20));
46+
primitive.setLocalPosition(new pc.Vec3(0, -0.01, 0));
47+
app.root.addChild(primitive);
48+
49+
// Create an Entity with a point light component
50+
var light = new pc.Entity();
51+
light.addComponent("light", {
52+
type: "point",
53+
color: new pc.Color(0.2, 0.2, 0.2),
54+
range: 30,
55+
castShadows : true
56+
});
57+
light.translate(0, 8, 0);
58+
app.root.addChild(light);
59+
60+
// Create an Entity with a camera component
61+
var camera = new pc.Entity();
62+
camera.addComponent("camera", {
63+
clearColor: new pc.Color(0.2, 0.2, 0.2)
64+
});
65+
66+
// Add the camera to the hierarchy
67+
app.root.addChild(camera);
68+
69+
// Position the camera
70+
camera.translate(0, 10, 20);
71+
camera.lookAt(pc.Vec3.ZERO);
72+
73+
// Create bounding call model and add it to hierarchy
74+
this.ball = new pc.Entity();
75+
this.ball.addComponent("model", {
76+
type: "sphere",
77+
});
78+
app.root.addChild(this.ball);
79+
80+
// Allocate space for decals. Each decal is a quad with 4 verticies
81+
this.numDecals = 500;
82+
var numDecalVertices = 4 * this.numDecals;
83+
84+
// Allocate storage for vertex positions, vertex stores x, y and z
85+
this.positions = new Float32Array(3 * numDecalVertices);
86+
87+
// Allocate storage for colors, each vertex stores r, g, b and a
88+
this.colors = new Uint8ClampedArray(4 * numDecalVertices);
89+
90+
// Allocate storage for uvs, each vertex stores u and v. And fill them up to display whole texture
91+
this.uvs = [];
92+
var i;
93+
for (i = 0; i < this.numDecals; i++)
94+
uvs.push(0, 0, 0, 1, 1, 1, 1, 0);
95+
96+
// Allocate and generate indices. Each quad is representing using 2 triangles, and uses 4 vertices
97+
var quadTriangles = [0, 1, 2, 2, 3, 0];
98+
this.indices = new Uint16Array(6 * this.numDecals);
99+
for (i = 0; i < this.numDecals; i++) {
100+
this.indices[6 * i + 0] = 4 * i + quadTriangles[0];
101+
this.indices[6 * i + 1] = 4 * i + quadTriangles[1];
102+
this.indices[6 * i + 2] = 4 * i + quadTriangles[2];
103+
this.indices[6 * i + 3] = 4 * i + quadTriangles[3];
104+
this.indices[6 * i + 4] = 4 * i + quadTriangles[4];
105+
this.indices[6 * i + 5] = 4 * i + quadTriangles[5];
106+
}
107+
108+
// Helper function to generate a decal with index i at position pos. It fills up information for all 4 vertices of a quad
109+
function createDecal(i, pos) {
110+
111+
// random size and rotation angle
112+
var size = 0.5 + Math.random();
113+
var angle = Math.random() * Math.PI;
114+
115+
// random color
116+
var r = Math.random() * 255;
117+
var g = Math.random() * 255;
118+
var b = Math.random() * 255;
119+
120+
var j;
121+
for (j = 0; j < 4; j++) {
122+
this.colors[i * 16 + j * 4 + 0] = r;
123+
this.colors[i * 16 + j * 4 + 1] = g;
124+
this.colors[i * 16 + j * 4 + 2] = b;
125+
this.colors[i * 16 + j * 4 + 3] = 0; // alpha is not used by shader
126+
}
127+
128+
// vertex positions to form a square quad with random rotation and size
129+
this.positions[12 * i + 0] = pos.x + size * Math.sin(angle); this.positions[12 * i + 1] = 0; this.positions[12 * i + 2] = pos.z + size * Math.cos(angle); angle += Math.PI * 0.5;
130+
this.positions[12 * i + 3] = pos.x + size * Math.sin(angle); this.positions[12 * i + 4] = 0; this.positions[12 * i + 5] = pos.z + size * Math.cos(angle); angle += Math.PI * 0.5;
131+
this.positions[12 * i + 6] = pos.x + size * Math.sin(angle); this.positions[12 * i + 7] = 0; this.positions[12 * i + 8] = pos.z + size * Math.cos(angle); angle += Math.PI * 0.5;
132+
this.positions[12 * i + 9] = pos.x + size * Math.sin(angle); this.positions[12 * i + 10] = 0; this.positions[12 * i + 11] = pos.z + size * Math.cos(angle); angle += Math.PI * 0.5;
133+
}
134+
135+
// helper function to update required vertex streams
136+
function updateMesh(mesh, updatePositions, updateColors, initAll) {
137+
138+
// update positions when needed
139+
if (updatePositions)
140+
self.mesh.setPositions(self.positions);
141+
142+
// update colors when needed
143+
if (updateColors)
144+
self.mesh.setColors32(self.colors);
145+
146+
// update indices and uvs only one time, as they never change
147+
if (initAll) {
148+
mesh.setIndices(self.indices);
149+
mesh.setUvs(0, self.uvs);
150+
}
151+
152+
mesh.update(pc.PRIMITIVE_TRIANGLES);
153+
}
154+
155+
// Create a mesh with dynamic vertex buffer and static index buffer
156+
this.mesh = new pc.Mesh(app.graphicsDevice);
157+
this.mesh.clear(true, false);
158+
updateMesh(this.mesh, true, true, true);
159+
160+
// create material
161+
this.material = new pc.StandardMaterial();
162+
this.material.useLighting = false; // turn off lighting - we use emissive texture only. Also, lighting needs normal maps which we don't generate
163+
this.material.diffuse = new pc.Color(0, 0, 0);
164+
this.material.emissiveVertexColor = true;
165+
this.material.blendType = pc.BLEND_ADDITIVE; // additive alpha blend
166+
this.material.depthWrite = false; // optimization - no need to write to depth buffer, as decals are part of the ground plane
167+
168+
// Create the mesh instance
169+
var node = new pc.GraphNode();
170+
var meshInstance = new pc.MeshInstance(node, this.mesh, this.material);
171+
172+
// Create a model and add the mesh instance to it
173+
var model = new pc.Model();
174+
model.graph = node;
175+
model.meshInstances = [ meshInstance ];
176+
177+
// Create Entity and add it to the scene
178+
this.entity = new pc.Entity();
179+
app.root.addChild(this.entity);
180+
181+
// Add a model compoonent
182+
app.systems.model.addComponent(this.entity, {
183+
type: 'asset',
184+
castShadows: false
185+
});
186+
this.entity.model.model = model;
187+
188+
// load a texture, create material and apply it to our MeshInstance
189+
app.assets.loadFromUrl("../assets/textures/spark.png", "texture", function (err, asset) {
190+
// update material with texture
191+
this.entity.model.meshInstances[0].material.emissiveMap = asset.resource;
192+
this.entity.model.meshInstances[0].material.update();
193+
194+
// start app here when all is ready
195+
app.start();
196+
});
197+
198+
199+
// Set an update function on the app's update event
200+
var self = this;
201+
var time = 0;
202+
var decalIndex = 0;
203+
app.on("update", function (dt) {
204+
205+
var previousTime = time;
206+
time += dt;
207+
208+
// Bounce the ball around in a circle with changing radius
209+
var radius = Math.abs(Math.sin(time * 0.55) * 9);
210+
var previousElevation = 2 * Math.cos(previousTime * 7);
211+
var elevation = 2 * Math.cos(time * 7);
212+
self.ball.setLocalPosition(new pc.Vec3(radius * Math.sin(time), 0.5 + Math.abs(elevation), radius * Math.cos(time)));
213+
214+
// When ball crossed the ground plane
215+
var positionsUpdated = false;
216+
var colorsUpdated = false;
217+
if ((previousElevation < 0 && elevation >= 0) || (elevation < 0 && previousElevation >= 0)) {
218+
219+
// create new decal at next index, and roll the index around if out of range
220+
createDecal(decalIndex, self.ball.getLocalPosition());
221+
decalIndex++;
222+
if (decalIndex >= self.numDecals)
223+
decalIndex = 0;
224+
225+
// both position and color streams were updated
226+
positionsUpdated = true;
227+
colorsUpdated = true;
228+
}
229+
230+
// fade out all vertex colors once a second
231+
if (Math.round(time) != Math.round(previousTime)) {
232+
for (i = 0; i < self.colors.length; i++)
233+
self.colors[i] -= 2;
234+
235+
// colors were updated
236+
colorsUpdated = true;
237+
}
238+
239+
// update mesh with the streams that were updated
240+
updateMesh(self.mesh, positionsUpdated, colorsUpdated);
241+
});
242+
243+
</script>
244+
</body>
245+
</html>

0 commit comments

Comments
 (0)