Skip to content

Commit

Permalink
feat(PNTSLoader): support NORMAL and NORMAL_OCT16P semantics (#594) (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
sguimmara authored Jan 2, 2025
1 parent b5fc595 commit 82d89b1
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 2 deletions.
32 changes: 30 additions & 2 deletions src/three/loaders/PNTSLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
Color,
} from 'three';
import { rgb565torgb } from '../../utilities/rgb565torgb.js';
import { decodeOctNormal } from '../../utilities/decodeOctNormal.js';

const DRACO_ATTRIBUTE_MAP = {
RGB: 'color',
Expand Down Expand Up @@ -82,6 +83,8 @@ export class PNTSLoader extends PNTSLoaderBase {
// handle non compressed case
const POINTS_LENGTH = featureTable.getData( 'POINTS_LENGTH' );
const POSITION = featureTable.getData( 'POSITION', POINTS_LENGTH, 'FLOAT', 'VEC3' );
const NORMAL = featureTable.getData( 'NORMAL', POINTS_LENGTH, 'FLOAT', 'VEC3' );
const NORMAL_OCT16P = featureTable.getData( 'NORMAL', POINTS_LENGTH, 'UNSIGNED_BYTE', 'VEC2' );
const RGB = featureTable.getData( 'RGB', POINTS_LENGTH, 'UNSIGNED_BYTE', 'VEC3' );
const RGBA = featureTable.getData( 'RGBA', POINTS_LENGTH, 'UNSIGNED_BYTE', 'VEC4' );
const RGB565 = featureTable.getData( 'RGB565', POINTS_LENGTH, 'UNSIGNED_SHORT', 'SCALAR' );
Expand Down Expand Up @@ -116,6 +119,33 @@ export class PNTSLoader extends PNTSLoaderBase {

}

if ( NORMAL !== null ) {

geometry.setAttribute( 'normal', new BufferAttribute( NORMAL, 3, false ) );

} else if ( NORMAL_OCT16P !== null ) {

const decodedNormals = new Float32Array( POINTS_LENGTH * 3 );

const n = new Vector3();

for ( let i = 0; i < POINTS_LENGTH; i ++ ) {

const x = NORMAL_OCT16P[ i * 2 ];
const y = NORMAL_OCT16P[ i * 2 + 1 ];

const normal = decodeOctNormal( x, y, n );

decodedNormals[ i * 3 ] = normal.x;
decodedNormals[ i * 3 + 1 ] = normal.y;
decodedNormals[ i * 3 + 2 ] = normal.z;

}

geometry.setAttribute( 'normal', new BufferAttribute( decodedNormals, 3, false ) );

}

if ( RGBA !== null ) {

geometry.setAttribute( 'color', new BufferAttribute( RGBA, 4, true ) );
Expand Down Expand Up @@ -164,8 +194,6 @@ export class PNTSLoader extends PNTSLoaderBase {

[
'BATCH_LENGTH',
'NORMAL',
'NORMAL_OCT16P',
].forEach( ( feature ) => {

if ( feature in featureTable.header ) {
Expand Down
47 changes: 47 additions & 0 deletions src/utilities/decodeOctNormal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Vector2, MathUtils, Vector3 } from 'three';

const f = new Vector2();

/**
* Decode an octahedron-encoded normal (as a pair of 8-bit unsigned numbers) into a Vector3.
*
* Resources:
* - https://stackoverflow.com/a/74745666/2704779
* - https://knarkowicz.wordpress.com/2014/04/16/octahedron-normal-vector-encoding/
* @param {number} x The unsigned 8-bit X coordinate on the projected octahedron.
* @param {number} y The unsigned 8-bit Y coordinate on the projected octahedron.
* @param {Vector3} [target] The target vector.
*/
export function decodeOctNormal( x, y, target = new Vector3() ) {

f.set( x, y ).divideScalar( 256 ).multiplyScalar( 2 ).subScalar( 1 );

target.set( f.x, f.y, 1 - Math.abs( f.x ) - Math.abs( f.y ) );

const t = MathUtils.clamp( - target.z, 0, 1 );

if ( target.x >= 0 ) {

target.setX( target.x - t );

} else {

target.setX( target.x + t );

}

if ( target.y >= 0 ) {

target.setY( target.y - t );

} else {

target.setY( target.y + t );

}

target.normalize();

return target;

}
39 changes: 39 additions & 0 deletions test/decodeOctNormal.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { decodeOctNormal } from '../src/utilities/decodeOctNormal';

const PRECISION = 0.005;

describe( 'decodeOctNormal', () => {

it.each( [
// 4 corners of the projected octahedron
[ 0, 0, 0, 0, - 1 ],
[ 255, 255, 0, 0, - 1 ],
[ 0, 255, 0, 0, - 1 ],
[ 255, 0, 0, 0, - 1 ],

// Center of the projected octahedron
[ 127, 127, 0, 0, 1 ],

// Midpoint of left edge of the projected octahedron
[ 0, 127, - 1, 0, 0 ],

// Midpoint of right edge of the projected octahedron
[ 255, 127, 1, 0, 0 ],

// Midpoint of top edge of the projected octahedron
[ 127, 255, 0, 1, 0 ],

// Midpoint of bottom edge of the projected octahedron
[ 127, 0, 0, - 1, 0 ],
] )( 'should decode (%s, %s) into [%s, %s, %s]', ( encX, encY, x, y, z ) => {

const result = decodeOctNormal( encX, encY );

expect( result.length() ).toBeCloseTo( 1, PRECISION );
expect( result.x ).toBeCloseTo( x, PRECISION );
expect( result.y ).toBeCloseTo( y, PRECISION );
expect( result.z ).toBeCloseTo( z, PRECISION );

} );

} );

0 comments on commit 82d89b1

Please sign in to comment.