@@ -158,6 +158,13 @@
Augmenta × Three.js
+
+
+
+
diff --git a/src/connection.js b/src/connection.js
index e378ef1..434c141 100644
--- a/src/connection.js
+++ b/src/connection.js
@@ -157,6 +157,7 @@ export function createConnectionController({
let streamClouds;
let streamClusters;
let streamClusterPoints;
+ let useCompression;
try {
const settings = getSettings();
const targets = ensureTargetPlan(settings.address, settings.port);
@@ -166,6 +167,7 @@ export function createConnectionController({
streamClouds = settings.streamClouds !== false;
streamClusters = settings.streamClusters !== false;
streamClusterPoints = settings.streamClusterPoints !== false;
+ useCompression = settings.useCompression !== false;
} catch (error) {
wantsConnection = false;
retrying = false;
@@ -184,15 +186,17 @@ export function createConnectionController({
}
let decompressor;
- try {
- decompressor = await getZstdDecompressor();
- } catch (error) {
- if (attemptGeneration !== connectionAttemptGeneration || !wantsConnection) return;
- wantsConnection = false;
- retrying = false;
- const message = error instanceof Error ? error.message : String(error);
- publish('error', `Could not initialize Zstd decompression: ${message}`);
- return;
+ if (useCompression) {
+ try {
+ decompressor = await getZstdDecompressor();
+ } catch (error) {
+ if (attemptGeneration !== connectionAttemptGeneration || !wantsConnection) return;
+ wantsConnection = false;
+ retrying = false;
+ const message = error instanceof Error ? error.message : String(error);
+ publish('error', `Could not initialize Zstd decompression: ${message}`);
+ return;
+ }
}
if (attemptGeneration !== connectionAttemptGeneration || !wantsConnection) return;
@@ -209,11 +213,10 @@ export function createConnectionController({
streamClusters,
streamClusterPoints,
streamZonePoints: true,
- // Keep the realtime stream compressed by default, matching the Unity,
- // TouchDesigner and VVVV clients. This cuts WebSocket bandwidth and
- // backlog pressure; zstddec is initialized before the connection and
- // the JS SDK invokes it synchronously before parsing each binary frame.
- useCompression: true,
+ // Compression stays enabled by default, matching the Unity,
+ // TouchDesigner and VVVV clients, but Advanced can disable it for
+ // diagnostics or compatibility checks.
+ useCompression,
displayPointIntensity: true,
// Quaternions preserve Pleiades' exact OBB orientation. The viewer
// performs the left-handed -> right-handed basis reflection explicitly.
@@ -238,7 +241,7 @@ export function createConnectionController({
socketOpen = true;
publish(
'connected',
- `Connected to ${target.label}. Protocol V${activeVersion}; Zstd-compressed debug stream.`
+ `Connected to ${target.label}. Protocol V${activeVersion}; ${useCompression ? 'Zstd-compressed' : 'uncompressed'} debug stream.`
);
});
diff --git a/src/main.js b/src/main.js
index b8a26f4..ebaf062 100644
--- a/src/main.js
+++ b/src/main.js
@@ -37,7 +37,7 @@ function refreshFavicon() {
refreshFavicon();
const ui = {
- app: $('#app'), sidebar: $('#sidebar'), sidebarResizer: $('#sidebar-resizer'), serverAddress: $('#server-address'), port: $('#port'), protocol: $('#protocol'), downsample: $('#downsample'), connect: $('#connect'),
+ app: $('#app'), sidebar: $('#sidebar'), sidebarResizer: $('#sidebar-resizer'), serverAddress: $('#server-address'), port: $('#port'), protocol: $('#protocol'), compression: $('#compression'), downsample: $('#downsample'), connect: $('#connect'),
demo: $('#demo'), status: $('#status'), note: $('#connection-note'), summary: $('#summary'),
debug: $('#debug-content'), clear: $('#clear'), resetCamera: $('#reset-camera'), scenes: $('#scenes'),
sidebarToggle: $('#sidebar-toggle'), viewerTitle: $('.viewer-title'), connectionQrVisibility: $('.connection-qr-visibility'),
@@ -182,6 +182,7 @@ function applyConnectionSettings(settings) {
if (typeof settings.address === 'string') ui.serverAddress.value = settings.address;
if (typeof settings.port === 'string') ui.port.value = settings.port;
if (typeof settings.protocol === 'string') ui.protocol.value = settings.protocol;
+ if (typeof settings.compression === 'string') ui.compression.value = settings.compression;
if (typeof settings.downsample === 'string') ui.downsample.value = settings.downsample;
}
@@ -418,8 +419,9 @@ function updateDisplaySectionSummary() {
function updateConnectionAdvancedSummary() {
const protocolLabel = ui.protocol.selectedOptions[0]?.textContent?.split(' — ')[0]
?? ui.protocol.value;
+ const compressionLabel = ui.compression.selectedOptions[0]?.textContent ?? ui.compression.value;
ui.connectionAdvancedSummary.textContent =
- `${ui.port.value || '—'} · ${protocolLabel} · ×${ui.downsample.value || '—'}`;
+ `${ui.port.value || '—'} · ${protocolLabel} · ${compressionLabel} · ×${ui.downsample.value || '—'}`;
}
function syncSceneSelector() {
@@ -478,7 +480,9 @@ function getConnectionSettings() {
address: ui.serverAddress.value,
port: ui.port.value,
protocol: ui.protocol.value,
+ compression: ui.compression.value,
downsample: ui.downsample.value,
+ useCompression: ui.compression.value !== 'uncompressed',
streamClusters: ui.requestClusters.checked,
streamClusterPoints: ui.requestClusterPoints.checked,
streamClouds: ui.requestGeneralPoints.checked
@@ -747,6 +751,10 @@ ui.protocol.addEventListener('change', () => {
saveConnectionField('protocol', ui.protocol.value);
restartConnection('Protocol changed');
});
+ui.compression.addEventListener('change', () => {
+ saveConnectionField('compression', ui.compression.value);
+ restartConnection('Compression changed');
+});
ui.downsample.addEventListener('change', () => {
restartConnection('Downsample changed');
});
diff --git a/src/share-link.js b/src/share-link.js
index 606756e..2304f3d 100644
--- a/src/share-link.js
+++ b/src/share-link.js
@@ -1,4 +1,4 @@
-const CONNECTION_QUERY_KEYS = ['address', 'port', 'protocol', 'downsample'];
+const CONNECTION_QUERY_KEYS = ['address', 'port', 'protocol', 'compression', 'downsample'];
export function normalizeConnectionOptions(settings) {
const options = {};
@@ -16,6 +16,11 @@ export function normalizeConnectionOptions(settings) {
options.protocol = protocol;
}
+ const compression = String(settings?.compression ?? '');
+ if (['compressed', 'uncompressed'].includes(compression)) {
+ options.compression = compression;
+ }
+
const downsample = Number(settings?.downsample);
if (Number.isInteger(downsample) && downsample >= 1) {
options.downsample = String(downsample);
@@ -51,6 +56,7 @@ export function readConnectionOptionsFromUrl(urlValue) {
address: url.searchParams.get('address'),
port: url.searchParams.get('port'),
protocol: url.searchParams.get('protocol'),
+ compression: url.searchParams.get('compression'),
downsample: url.searchParams.get('downsample')
});
}
diff --git a/tests/share-link.test.mjs b/tests/share-link.test.mjs
index 6dc2cbf..31aa2d2 100644
--- a/tests/share-link.test.mjs
+++ b/tests/share-link.test.mjs
@@ -14,6 +14,7 @@ test('connection share URL preserves the page and embeds current connection opti
address: '192.168.1.42',
port: '6060',
protocol: '3',
+ compression: 'uncompressed',
downsample: '2'
}
);
@@ -24,6 +25,7 @@ test('connection share URL preserves the page and embeds current connection opti
assert.equal(parsed.searchParams.get('address'), '192.168.1.42');
assert.equal(parsed.searchParams.get('port'), '6060');
assert.equal(parsed.searchParams.get('protocol'), '3');
+ assert.equal(parsed.searchParams.get('compression'), 'uncompressed');
assert.equal(parsed.searchParams.get('downsample'), '2');
assert.equal(parsed.hash, '');
});
@@ -31,19 +33,20 @@ test('connection share URL preserves the page and embeds current connection opti
test('connection options in a shared URL are validated', () => {
assert.deepEqual(
readConnectionOptionsFromUrl(
- 'https://example.test/?address=augmenta-WA12031.local&port=6060&protocol=auto&downsample=4'
+ 'https://example.test/?address=augmenta-WA12031.local&port=6060&protocol=auto&compression=compressed&downsample=4'
),
{
address: 'augmenta-WA12031.local',
port: '6060',
protocol: 'auto',
+ compression: 'compressed',
downsample: '4'
}
);
assert.deepEqual(
readConnectionOptionsFromUrl(
- 'https://example.test/?address=%20&port=70000&protocol=9&downsample=0'
+ 'https://example.test/?address=%20&port=70000&protocol=9&compression=invalid&downsample=0'
),
{}
);
@@ -51,11 +54,12 @@ test('connection options in a shared URL are validated', () => {
test('regenerating a share URL replaces stale connection query values', () => {
const url = buildConnectionShareUrl(
- 'https://example.test/?address=old&port=1&protocol=2&downsample=99',
+ 'https://example.test/?address=old&port=1&protocol=2&compression=uncompressed&downsample=99',
{
address: '10.0.0.5',
port: 6060,
protocol: 'auto',
+ compression: 'compressed',
downsample: 1
}
);
@@ -64,6 +68,7 @@ test('regenerating a share URL replaces stale connection query values', () => {
address: '10.0.0.5',
port: '6060',
protocol: 'auto',
+ compression: 'compressed',
downsample: '1'
});
});
@@ -74,23 +79,27 @@ test('shared connection options override a local view without mutating local pre
address: 'augmenta-local.local',
port: '6060',
protocol: 'auto',
+ compression: 'compressed',
downsample: '1'
};
const shared = {
address: '192.168.1.42',
- protocol: '3'
+ protocol: '3',
+ compression: 'uncompressed'
};
assert.deepEqual(resolveConnectionOptions(local, shared), {
address: '192.168.1.42',
port: '6060',
protocol: '3',
+ compression: 'uncompressed',
downsample: '1'
});
assert.deepEqual(local, {
address: 'augmenta-local.local',
port: '6060',
protocol: 'auto',
+ compression: 'compressed',
downsample: '1'
});
});
@@ -100,11 +109,13 @@ test('connection option normalization keeps only valid local values', () => {
address: ' augmenta-server ',
port: 6060,
protocol: '2',
+ compression: 'uncompressed',
downsample: 4
}), {
address: 'augmenta-server',
port: '6060',
protocol: '2',
+ compression: 'uncompressed',
downsample: '4'
});
@@ -112,6 +123,7 @@ test('connection option normalization keeps only valid local values', () => {
address: ' ',
port: 0,
protocol: '7',
+ compression: 'invalid',
downsample: -1
}), {});
});
diff --git a/tests/websocket-compression.test.mjs b/tests/websocket-compression.test.mjs
index aab059e..b649fe7 100644
--- a/tests/websocket-compression.test.mjs
+++ b/tests/websocket-compression.test.mjs
@@ -37,12 +37,13 @@ test('bundled zstd decoder can decode Augmenta binary payloads synchronously aft
);
});
-test('Three.js runtime requests compression and ships the decoder in the Pages artifact', () => {
+test('Three.js runtime supports selectable compression and ships the decoder in the Pages artifact', () => {
const connection = readFileSync(new URL('../src/connection.js', import.meta.url), 'utf8');
const index = readFileSync(new URL('../index.html', import.meta.url), 'utf8');
const assemble = readFileSync(new URL('../scripts/assemble-site.mjs', import.meta.url), 'utf8');
- assert.match(connection, /useCompression:\s*true/);
+ assert.match(connection, /useCompression\s*=\s*settings\.useCompression\s*!==\s*false/);
+ assert.match(connection, /useCompression,/);
assert.match(connection, /decompressor,/);
assert.match(connection, /import\('zstddec'\)/);
assert.match(index, /zstddec@0\.3\.1\/\+esm/);