Skip to content

Commit dcb732b

Browse files
committed
Extend immediate mode line drawing with support for custom material
1 parent 297aca6 commit dcb732b

File tree

5 files changed

+272
-15
lines changed

5 files changed

+272
-15
lines changed

examples/src/examples/graphics/lines.tsx

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,44 @@ import * as pc from '../../../../';
33
class LinesExample {
44
static CATEGORY = 'Graphics';
55
static NAME = 'Lines';
6+
static FILES = {
7+
'shader.vert': /* glsl */`
8+
attribute vec3 aPosition;
9+
attribute vec2 aTAndLength;
10+
attribute vec4 aColor;
11+
12+
uniform mat4 matrix_model;
13+
uniform mat4 matrix_viewProjection;
14+
15+
varying vec2 vTAndLength;
16+
varying vec4 vColor;
17+
18+
void main(void)
19+
{
20+
vTAndLength = aTAndLength;
21+
vColor = aColor;
22+
gl_Position = matrix_viewProjection * matrix_model * vec4(aPosition, 1.0);
23+
}`,
24+
'shader.frag': /* glsl */`
25+
precision mediump float;
26+
27+
varying vec2 vTAndLength;
28+
varying vec4 vColor;
29+
30+
uniform float uDashSize;
31+
uniform float uDashGap;
32+
33+
void main(void)
34+
{
35+
if (mod(vTAndLength.x * vTAndLength.y, uDashSize + uDashGap) > uDashSize) {
36+
discard;
37+
}
38+
gl_FragColor = vColor;
39+
}`
40+
};
41+
642

7-
example(canvas: HTMLCanvasElement): void {
43+
example(canvas: HTMLCanvasElement, files: { 'shader.vert': string, 'shader.frag': string }): void {
844

945
const assets = {
1046
'helipad': new pc.Asset('helipad-env-atlas', 'texture', { url: '/static/assets/cubemaps/helipad-env-atlas.png' }, { type: pc.TEXTURETYPE_RGBP })
@@ -46,6 +82,30 @@ class LinesExample {
4682
app.scene.envAtlas = assets.helipad.resource;
4783
app.scene.skyboxRotation = new pc.Quat().setFromEulerAngles(0, 30, 0);
4884

85+
// Create a shader definition and shader from the vertex and fragment shaders
86+
// for the dashed lines material
87+
const shaderDefinition = {
88+
attributes: {
89+
aPosition: pc.SEMANTIC_POSITION,
90+
aTAndLength: pc.SEMANTIC_TEXCOORD0,
91+
aColor: pc.SEMANTIC_COLOR
92+
},
93+
vshader: files['shader.vert'],
94+
fshader: files['shader.frag']
95+
};
96+
const shader = new pc.Shader(app.graphicsDevice, shaderDefinition);
97+
98+
// Create a new material with the new shader
99+
const material = new pc.Material();
100+
material.shader = shader;
101+
material.blend = true;
102+
material.blendType = pc.BLEND_NORMAL;
103+
material.depthTest = true;
104+
material.setParameter('uDashSize', 2);
105+
material.setParameter('uDashGap', 1);
106+
material.update();
107+
108+
49109
// Create an Entity with a camera component
50110
const camera = new pc.Entity();
51111
camera.addComponent("camera", {
@@ -179,8 +239,8 @@ class LinesExample {
179239
grayLineColors.push(pc.Color.GRAY, pc.Color.GRAY);
180240
}
181241

182-
// render all gray lines
183-
app.drawLines(grayLinePositions, grayLineColors);
242+
// render all gray lines
243+
app.drawLinesWithMaterial(material, grayLinePositions, grayLineColors);
184244
});
185245
});
186246
});

src/framework/app-base.js

Lines changed: 155 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1708,10 +1708,61 @@ class AppBase extends EventHandler {
17081708
* var worldLayer = app.scene.layers.getLayerById(pc.LAYERID_WORLD);
17091709
* app.drawLine(start, end, pc.Color.WHITE, true, worldLayer);
17101710
*/
1711-
drawLine(start, end, color, depthTest, layer) {
1711+
drawLine(start, end, color, depthTest, layer = this.scene.defaultDrawLayer) {
17121712
this.scene.drawLine(start, end, color, depthTest, layer);
17131713
}
17141714

1715+
/**
1716+
* Draws a single line using the given material. Line start and end coordinates are specified in world-space.
1717+
* We expose the following vertex attributes for the material:
1718+
* - aPosition: pc.Vec3 (vertex position)
1719+
* - aTAndLength: pc.Vec2 (x - 1D texture coordinate [0..1], y - line length)
1720+
* - aColor: pc.Vec4 (vertex color)
1721+
*
1722+
* @param {Material} material - The material used for rendering the line.
1723+
* @param {Vec3} start - The start world-space coordinate of the line.
1724+
* @param {Vec3} end - The end world-space coordinate of the line.
1725+
* @param {Color} [color] - The color of the line. It defaults to white if not specified.
1726+
* @param {Layer} [layer] - The layer to render the line into. Defaults to {@link LAYERID_IMMEDIATE}.
1727+
* @example
1728+
* // Render a 1-unit long white line
1729+
* const material = new pc.BasicMaterial();
1730+
* material.vertexColors = true;
1731+
* material.blend = true;
1732+
* material.blendType = BLEND_NORMAL;
1733+
* material.depthTest = true;
1734+
* material.update();
1735+
* const start = new pc.Vec3(0, 0, 0);
1736+
* const end = new pc.Vec3(1, 0, 0);
1737+
* app.drawLineWithMaterial(material, start, end);
1738+
* @example
1739+
* // Render a 1-unit long red line which is not depth tested and renders on top of other geometry
1740+
* const material = new pc.BasicMaterial();
1741+
* material.vertexColors = true;
1742+
* material.blend = true;
1743+
* material.blendType = BLEND_NORMAL;
1744+
* material.depthTest = false;
1745+
* material.update();
1746+
* const start = new pc.Vec3(0, 0, 0);
1747+
* const end = new pc.Vec3(1, 0, 0);
1748+
* app.drawLineWithMaterial(material, start, end, pc.Color.RED);
1749+
* @example
1750+
* // Render a 1-unit long white line into the world layer
1751+
* const material = new pc.BasicMaterial();
1752+
* material.vertexColors = true;
1753+
* material.blend = true;
1754+
* material.blendType = BLEND_NORMAL;
1755+
* material.depthTest = true;
1756+
* material.update();
1757+
* const start = new pc.Vec3(0, 0, 0);
1758+
* const end = new pc.Vec3(1, 0, 0);
1759+
* const worldLayer = app.scene.layers.getLayerById(pc.LAYERID_WORLD);
1760+
* app.drawLineWithMaterial(material, start, end, pc.Color.WHITE, worldLayer);
1761+
*/
1762+
drawLineWithMaterial(material, start, end, color, layer = this.scene.defaultDrawLayer) {
1763+
this.scene.drawLineWithMaterial(material, start, end, color, layer);
1764+
}
1765+
17151766
/**
17161767
* Renders an arbitrary number of discrete line segments. The lines are not connected by each
17171768
* subsequent point in the array. Instead, they are individual segments specified by two
@@ -1755,6 +1806,64 @@ class AppBase extends EventHandler {
17551806
this.scene.drawLines(positions, colors, depthTest, layer);
17561807
}
17571808

1809+
/**
1810+
* Renders an arbitrary number of discrete line segments with a given material.
1811+
* The lines are not connected by each subsequent point in the array. Instead, they are individual segments
1812+
* specified by two points. Therefore, the lengths of the supplied position and color arrays must be the same
1813+
* and also must be a multiple of 2. The colors of the ends of each line segment will be interpolated along
1814+
* the length of each line.
1815+
* We expose the following vertex attributes for the material:
1816+
* - aPosition: pc.Vec3 (vertex position)
1817+
* - aTAndLength: pc.Vec2 (x - 1D texture coordinate [0..1], y - line length)
1818+
* - aColor: pc.Vec4 (vertex color)
1819+
*
1820+
* @param {Material} material - The material used for rendering the lines.
1821+
* @param {Vec3[]} positions - An array of points to draw lines between. The length of the
1822+
* array must be a multiple of 2.
1823+
* @param {Color[]} colors - An array of colors to color the lines. This must be the same
1824+
* length as the position array. The length of the array must also be a multiple of 2.
1825+
* @param {Layer} [layer] - The layer to render the lines into. Defaults to {@link LAYERID_IMMEDIATE}.
1826+
* @example
1827+
* // Render a single line, with unique colors for each point
1828+
* const material = new pc.BasicMaterial();
1829+
* material.vertexColors = true;
1830+
* material.blend = true;
1831+
* material.blendType = BLEND_NORMAL;
1832+
* material.depthTest = false;
1833+
* material.update();
1834+
* const start = new pc.Vec3(0, 0, 0);
1835+
* const end = new pc.Vec3(1, 0, 0);
1836+
* app.drawLinesWithMaterial(material, [start, end], [pc.Color.RED, pc.Color.WHITE]);
1837+
* @example
1838+
* // Render 2 discrete line segments
1839+
* const material = new pc.BasicMaterial();
1840+
* material.vertexColors = true;
1841+
* material.blend = true;
1842+
* material.blendType = BLEND_NORMAL;
1843+
* material.depthTest = false;
1844+
* material.update();
1845+
* const points = [
1846+
* // Line 1
1847+
* new pc.Vec3(0, 0, 0),
1848+
* new pc.Vec3(1, 0, 0),
1849+
* // Line 2
1850+
* new pc.Vec3(1, 1, 0),
1851+
* new pc.Vec3(1, 1, 1)
1852+
* ];
1853+
* const colors = [
1854+
* // Line 1
1855+
* pc.Color.RED,
1856+
* pc.Color.YELLOW,
1857+
* // Line 2
1858+
* pc.Color.CYAN,
1859+
* pc.Color.BLUE
1860+
* ];
1861+
* app.drawLinesWithMaterial(material, points, colors);
1862+
*/
1863+
drawLinesWithMaterial(material, positions, colors, layer = this.scene.defaultDrawLayer) {
1864+
this.scene.drawLinesWithMaterial(material, positions, colors, layer);
1865+
}
1866+
17581867
/**
17591868
* Renders an arbitrary number of discrete line segments. The lines are not connected by each
17601869
* subsequent point in the array. Instead, they are individual segments specified by two
@@ -1791,6 +1900,51 @@ class AppBase extends EventHandler {
17911900
this.scene.drawLineArrays(positions, colors, depthTest, layer);
17921901
}
17931902

1903+
/**
1904+
* Renders an arbitrary number of discrete line segments with a given material.
1905+
* The lines are not connected by each subsequent point in the array. Instead, they are
1906+
* individual segments specified by two points.
1907+
* We expose the following vertex attributes for the material:
1908+
* - aPosition: pc.Vec3 (vertex position)
1909+
* - aTAndLength: pc.Vec2 (x - 1D texture coordinate [0..1], y - line length)
1910+
* - aColor: pc.Vec4 (vertex color)
1911+
*
1912+
* @param {Material} material - The material used for rendering the lines.
1913+
* @param {number[]} positions - An array of points to draw lines between. Each point is
1914+
* represented by 3 numbers - x, y and z coordinate.
1915+
* @param {number[]} colors - An array of colors to color the lines. This must be the same
1916+
* length as the position array. The length of the array must also be a multiple of 2.
1917+
* @param {Layer} [layer] - The layer to render the lines into. Defaults to {@link LAYERID_IMMEDIATE}.
1918+
* @example
1919+
* // Render 2 discrete line segments
1920+
* const material = new pc.BasicMaterial();
1921+
* material.vertexColors = true;
1922+
* material.blend = true;
1923+
* material.blendType = BLEND_NORMAL;
1924+
* material.depthTest = false;
1925+
* material.update();
1926+
* const points = [
1927+
* // Line 1
1928+
* 0, 0, 0,
1929+
* 1, 0, 0,
1930+
* // Line 2
1931+
* 1, 1, 0,
1932+
* 1, 1, 1
1933+
* ];
1934+
* const colors = [
1935+
* // Line 1
1936+
* 1, 0, 0, 1, // red
1937+
* 0, 1, 0, 1, // green
1938+
* // Line 2
1939+
* 0, 0, 1, 1, // blue
1940+
* 1, 1, 1, 1 // white
1941+
* ];
1942+
* app.drawLineArraysWithMaterial(material, points, colors);
1943+
*/
1944+
drawLineArraysWithMaterial(material, positions, colors, layer = this.scene.defaultDrawLayer) {
1945+
this.scene.drawLineArraysWithMaterial(material, positions, colors, layer);
1946+
}
1947+
17941948
/**
17951949
* Draws a wireframe sphere with center, radius and color.
17961950
*

src/scene/immediate/immediate-batch.js

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { Mat4 } from '../../core/math/mat4.js';
22

33
import { PRIMITIVE_LINES } from '../../platform/graphics/constants.js';
4-
5-
import { Mesh } from '../mesh.js';
6-
import { MeshInstance } from '../mesh-instance.js';
7-
import { GraphNode } from '../graph-node.js';
4+
import { Mesh } from '../../scene/mesh.js';
5+
import { MeshInstance } from '../../scene/mesh-instance.js';
6+
import { GraphNode } from '../../scene/graph-node.js';
7+
import { Vec3 } from '../../core/math/vec3.js';
88

99
const identityGraphNode = new GraphNode();
1010
identityGraphNode.worldTransform = Mat4.IDENTITY;
@@ -19,9 +19,16 @@ class ImmediateBatch {
1919
// line data, arrays of numbers
2020
this.positions = [];
2121
this.colors = [];
22+
// stores length of line for each vertex plus
23+
// a normalized t [0..1] to know where we are on the
24+
// line (more like a 1D UV)
25+
this.tAndLength = [];
2226

2327
this.mesh = new Mesh(device);
2428
this.meshInstance = null;
29+
30+
this.helperA = new Vec3();
31+
this.helperB = new Vec3();
2532
}
2633

2734
// add line positions and colors to the batch
@@ -30,10 +37,16 @@ class ImmediateBatch {
3037

3138
// positions
3239
const destPos = this.positions;
40+
const destTAndLength = this.tAndLength;
3341
const count = positions.length;
34-
for (let i = 0; i < count; i++) {
35-
const pos = positions[i];
36-
destPos.push(pos.x, pos.y, pos.z);
42+
for (let i = 0; i < count; i += 2) {
43+
const pos1 = positions[i];
44+
const pos2 = positions[i + 1];
45+
destPos.push(pos1.x, pos1.y, pos1.z);
46+
destPos.push(pos2.x, pos2.y, pos2.z);
47+
const length = pos2.distance(pos1);
48+
destTAndLength.push(0, length);
49+
destTAndLength.push(1, length);
3750
}
3851

3952
// colors
@@ -59,8 +72,18 @@ class ImmediateBatch {
5972

6073
// positions
6174
const destPos = this.positions;
62-
for (let i = 0; i < positions.length; i += 3) {
75+
const destTAndLength = this.tAndLength;
76+
for (let i = 0; i < positions.length; i += 6) {
6377
destPos.push(positions[i], positions[i + 1], positions[i + 2]);
78+
this.helperA.set(positions[i], positions[i + 1], positions[i + 2]);
79+
80+
destPos.push(positions[i + 3], positions[i + 4], positions[i + 5]);
81+
this.helperB.set(positions[i + 3], positions[i + 4], positions[i + 5]);
82+
83+
const length = this.helperB.distance(this.helperA);
84+
85+
destTAndLength.push(0, length);
86+
destTAndLength.push(1, length);
6487
}
6588

6689
// colors
@@ -85,6 +108,7 @@ class ImmediateBatch {
85108

86109
// update mesh vertices
87110
this.mesh.setPositions(this.positions);
111+
this.mesh.setUvs(0, this.tAndLength);
88112
this.mesh.setColors(this.colors);
89113
this.mesh.update(PRIMITIVE_LINES, false);
90114
if (!this.meshInstance) {
@@ -93,6 +117,7 @@ class ImmediateBatch {
93117

94118
// clear lines when after they were rendered as their lifetime is one frame
95119
this.positions.length = 0;
120+
this.tAndLength.length = 0;
96121
this.colors.length = 0;
97122

98123
// inject mesh instance into visible list to be rendered

src/scene/immediate/immediate.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,13 @@ class Immediate {
6666

6767
// returns a batch for rendering lines to a layer with required depth testing state
6868
getBatch(layer, depthTest) {
69+
// get batch for the material
70+
const material = depthTest ? this.materialDepth : this.materialNoDepth;
71+
return this.getBatchByMaterial(material, layer);
72+
}
6973

74+
// returns a batch for rendering lines to a layer with given material
75+
getBatchByMaterial(material, layer) {
7076
// get batches for the layer
7177
let batches = this.batchesMap.get(layer);
7278
if (!batches) {
@@ -76,9 +82,6 @@ class Immediate {
7682

7783
// add it for rendering
7884
this.allBatches.add(batches);
79-
80-
// get batch for the material
81-
const material = depthTest ? this.materialDepth : this.materialNoDepth;
8285
return batches.getBatch(material, layer);
8386
}
8487

0 commit comments

Comments
 (0)