diff --git a/modules/engine/src/geometry/geometry.js b/modules/engine/src/geometry/geometry.js index b1bd41d59b..e8bd9cd21e 100644 --- a/modules/engine/src/geometry/geometry.js +++ b/modules/engine/src/geometry/geometry.js @@ -35,16 +35,6 @@ export default class Geometry { this._setAttributes(attributes, indices); this.vertexCount = vertexCount || this._calculateVertexCount(this.attributes, this.indices); - - // stubRemovedMethods(this, [ - // 'setNeedsRedraw', 'needsRedraw', 'setAttributes' - // ], 'Immutable'); - - // stubRemovedMethods(this, [ - // 'hasAttribute', 'getAttribute', 'getArray' - // ], 'Use geometry.attributes and geometry.indices'); - - // deprecateMethods(this, ['getAttributes']) } get mode() { diff --git a/modules/engine/src/index.js b/modules/engine/src/index.js index 8954481187..009804fa65 100644 --- a/modules/engine/src/index.js +++ b/modules/engine/src/index.js @@ -20,3 +20,4 @@ export {KeyFrames} from './animation/key-frames'; // Utils export {default as ClipSpace} from './utils/clip-space'; +export {calculateTangents} from './utils/tangent'; diff --git a/modules/engine/src/utils/tangent.js b/modules/engine/src/utils/tangent.js new file mode 100644 index 0000000000..a8f00ec4c6 --- /dev/null +++ b/modules/engine/src/utils/tangent.js @@ -0,0 +1,64 @@ +import {Vector2, Vector3} from 'math.gl'; + +const scratchPos0 = new Vector3(); +const scratchPos1 = new Vector3(); +const scratchPos2 = new Vector3(); + +const scratchUV0 = new Vector2(); +const scratchUV1 = new Vector2(); +const scratchUV2 = new Vector2(); + +const scratchT1 = new Vector3(); +const scratchT2 = new Vector3(); +const scratchB1 = new Vector3(); +const scratchB2 = new Vector3(); + +export function calculateTangents(positions, texCoords, indices) { + const tangents = new Float32Array(positions.length); + const bitangents = new Float32Array(positions.length); + const length = indices ? indices.length : positions.length / 3; + + for (let i = 0; i < length; i += 3) { + const locations = indices ? [indices[i], indices[i + 1], indices[i + 2]] : [i, i + 1, i + 2]; + const [v0, v1, v2] = locations; + scratchPos0.set(positions[v0 * 3], positions[v0 * 3 + 1], positions[v0 * 3 + 2]); + scratchUV0.set(texCoords[v0 * 2], texCoords[v0 * 2 + 1]); + + scratchPos1.set(positions[v1 * 3], positions[v1 * 3 + 1], positions[v1 * 3 + 2]); + scratchUV1.set(texCoords[v1 * 2], texCoords[v1 * 2 + 1]); + + scratchPos2.set(positions[v2 * 3], positions[v2 * 3 + 1], positions[v2 * 3 + 2]); + scratchUV2.set(texCoords[v2 * 2], texCoords[v2 * 2 + 1]); + + const deltaPos1 = scratchPos1.subtract(scratchPos0); + const deltaPos2 = scratchPos2.subtract(scratchPos0); + + const deltaUV1 = scratchUV1.subtract(scratchUV0); + const deltaUV2 = scratchUV2.subtract(scratchUV0); + + const r = 1 / (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x); + + // vec3 tangent = (deltaPos1 * deltaUV2.y - deltaPos2 * deltaUV1.y)*r; + // vec3 bitangent = (deltaPos2 * deltaUV1.x - deltaPos1 * deltaUV2.x)*r; + const t1 = scratchT1.set(deltaPos1.x, deltaPos1.y, deltaPos1.z).multiplyScalar(deltaUV2.y); + const t2 = scratchT2.set(deltaPos2.x, deltaPos2.y, deltaPos2.z).multiplyScalar(deltaUV1.y); + const tangent = t1.subtract(t2).multiplyScalar(r); + + const b1 = scratchB1.set(deltaPos2.x, deltaPos2.y, deltaPos2.z).multiplyScalar(deltaUV1.x); + const b2 = scratchB2.set(deltaPos1.x, deltaPos1.y, deltaPos1.z).multiplyScalar(deltaUV2.x); + const bitangent = b1.subtract(b2).multiplyScalar(r); + + for (const v of locations) { + const index = v * 3; + tangents[index] += tangent.x; + tangents[index + 1] += tangent.y; + tangents[index + 2] += tangent.z; + + bitangents[index] += bitangent.x; + bitangents[index + 1] += bitangent.y; + bitangents[index + 2] += bitangent.z; + } + } + + return {tangents, bitangents}; +} diff --git a/test/apps/tangents/app.js b/test/apps/tangents/app.js new file mode 100644 index 0000000000..bdca9f6265 --- /dev/null +++ b/test/apps/tangents/app.js @@ -0,0 +1,229 @@ +import {AnimationLoop, Model, CylinderGeometry, calculateTangents} from '@luma.gl/engine'; +import {Buffer, clear} from '@luma.gl/webgl'; +import {setParameters} from '@luma.gl/gltools'; +import {Matrix4} from '@math.gl/core'; +import {Vector3, radians} from '@math.gl/core'; +import Controller from './controller'; + +const vs = `\ + #define SHADER_NAME simpleVs; + + attribute vec3 positions; + + uniform mat4 u_ModelMatrix; + uniform mat4 u_ViewMatrix; + uniform mat4 u_ProjectionMatrix; + + void main(void) { + gl_Position = u_ProjectionMatrix * u_ViewMatrix * u_ModelMatrix * vec4(positions, 1.0); + } +`; + +const fs = `\ + #define SHADER_NAME simpleFs; + + precision highp float; + + uniform vec4 u_Color; + + void main(void) { + gl_FragColor = u_Color; + } +`; + +const tangentVs = `\ + #define SHADER_NAME simpleVs; + + attribute vec3 positions; + attribute vec3 colors; + attribute vec3 instancePositions; + attribute vec3 instanceNormals; + attribute vec3 instanceTangents; + attribute vec3 instanceBitangents; + + uniform mat4 u_ModelMatrix; + uniform mat4 u_ViewMatrix; + uniform mat4 u_ProjectionMatrix; + + varying vec3 vColor; + + void main(void) { + vColor = colors; + mat3 instanceMatrix = mat3(instanceTangents, instanceBitangents, instanceNormals); + + vec4 pos = vec4(normalize(instanceMatrix * positions) * 0.2, 1.0); + pos.xyz += instancePositions; + pos = u_ModelMatrix * pos; + + gl_Position = u_ProjectionMatrix * u_ViewMatrix * pos; + } +`; + +const tangentFs = `\ + precision highp float; + + varying vec3 vColor; + void main(void) { + gl_FragColor = vec4(vColor, 1.0); + } +`; + +const loop = new AnimationLoop({ + onInitialize({gl}) { + this._controller = new Controller(gl.canvas); + setParameters(gl, { + depthTest: true, + depthFunc: gl.LEQUAL, + [gl.LINE_WIDTH]: 2 + }); + + const modelMatrix = new Matrix4().rotateX(radians(30)).rotateY(30); + const projectionMatrix = new Matrix4(); + + const geometry = new CylinderGeometry({ + radius: 1, + nradial: 10, + nvertical: 10 + }); + + const { + attributes: {NORMAL, TEXCOORD_0, POSITION}, + indices + } = geometry; + + const {tangents, bitangents} = calculateTangents( + POSITION.value, + TEXCOORD_0.value, + indices.value + ); + + const normal = NORMAL.value; + const position = POSITION.value; + const scratchP = new Vector3(); + const scratchT = new Vector3(); + const scratchB = new Vector3(); + const scratchN = new Vector3(); + + const count = position.length / 3; + const instanceTangents = new Float32Array(count * 3); + const instanceBitangents = new Float32Array(count * 3); + + for (let i = 0; i < count; i++) { + const index = i * 3; + scratchP.set(position[index], position[index + 1], position[index + 2]); + scratchT.set(tangents[index], tangents[index + 1], tangents[index + 2]); + scratchB.set(bitangents[index], bitangents[index + 1], bitangents[index + 2]); + scratchN.set(normal[index], normal[index + 1], normal[index + 2]); + + scratchT.normalize(); + scratchB.normalize(); + + instanceTangents[i * 3 + 0] = scratchT.x; + instanceTangents[i * 3 + 1] = scratchT.y; + instanceTangents[i * 3 + 2] = scratchT.z; + instanceBitangents[i * 3 + 0] = scratchB.x; + instanceBitangents[i * 3 + 1] = scratchB.y; + instanceBitangents[i * 3 + 2] = scratchB.z; + } + + const cubeModel = new Model(gl, { + vs, + fs, + geometry + }); + + const tangentModel = new Model(gl, { + vs: tangentVs, + fs: tangentFs, + attributes: { + positions: new Buffer( + gl, + new Float32Array([ + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ]) + ), + colors: new Buffer( + gl, + new Float32Array([ + 1.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 1.0 + ]) + ), + instanceNormals: new Buffer(gl, NORMAL.value), + instancePositions: new Buffer(gl, POSITION.value), + instanceTangents: new Buffer(gl, instanceTangents), + instanceBitangents: new Buffer(gl, instanceBitangents) + }, + vertexCount: 6, + instanceCount: count + }); + + return { + cubeModel, + tangentModel, + modelMatrix, + projectionMatrix + }; + }, + + onRender({gl, aspect, tangentModel, cubeModel, modelMatrix, projectionMatrix}) { + const {viewMatrix} = this._controller.getMatrices(); + projectionMatrix.perspective({fov: Math.PI / 3, aspect}); + + clear(gl, {color: [0, 0, 0, 1]}); + + cubeModel + .setUniforms({ + u_ModelMatrix: modelMatrix, + u_ViewMatrix: viewMatrix, + u_ProjectionMatrix: projectionMatrix, + u_Color: [1.0, 1.0, 1.0, 0.7] + }) + .setDrawMode(gl.LINES) + .draw(); + + tangentModel + .setUniforms({ + u_ModelMatrix: modelMatrix, + u_ViewMatrix: viewMatrix, + u_ProjectionMatrix: projectionMatrix + }) + .setDrawMode(gl.LINES) + .draw(); + } +}); + +loop.start(); diff --git a/test/apps/tangents/controller.js b/test/apps/tangents/controller.js new file mode 100644 index 0000000000..59c3b681ad --- /dev/null +++ b/test/apps/tangents/controller.js @@ -0,0 +1,61 @@ +import {Matrix4} from 'math.gl'; + +// Simple controller that keeps updating translation and rotation +export default class Controller { + constructor(canvas, {initialZoom = 3, onDrop = file => {}} = {}) { + this.mouse = { + lastX: 0, + lastY: 0 + }; + + this.translate = initialZoom; + this.rotation = [0, 0]; + this.rotationStart = [0, 0]; + + this._initializeEventHandling(canvas); + } + + getMatrices() { + const [pitch, roll] = this.rotation; + + const viewMatrix = new Matrix4() + .translate([0, 0, -this.translate]) + .rotateX(pitch) + .rotateY(roll); + + return { + viewMatrix + }; + } + + _initializeEventHandling(canvas) { + canvas.onwheel = e => { + this.translate += e.deltaY / 10; + if (this.translate < 0.1) { + this.translate = 0.1; + } + e.preventDefault(); + }; + + canvas.onpointerdown = e => { + this.mouse.lastX = e.clientX; + this.mouse.lastY = e.clientY; + + this.rotationStart[0] = this.rotation[0]; + this.rotationStart[1] = this.rotation[1]; + + canvas.setPointerCapture(e.pointerId); + e.preventDefault(); + }; + + canvas.onpointermove = e => { + if (e.buttons) { + const dX = e.clientX - this.mouse.lastX; + const dY = e.clientY - this.mouse.lastY; + + this.rotation[0] = this.rotationStart[0] + dY / 100; + this.rotation[1] = this.rotationStart[1] + dX / 100; + } + }; + } +} diff --git a/test/apps/tangents/index.html b/test/apps/tangents/index.html new file mode 100644 index 0000000000..c536b904c7 --- /dev/null +++ b/test/apps/tangents/index.html @@ -0,0 +1,22 @@ + + +
+ +