From 018951c1e9f74a8da6cc8978e8cc3fc344baded1 Mon Sep 17 00:00:00 2001 From: Hexay <97907668+Hexay@users.noreply.github.com> Date: Sun, 8 Nov 2026 10:16:36 +0000 Subject: [PATCH 1/3] Batch shape markers into one fill and one border draw per style --- common/webapp/src/js/markers/MarkerSet.js | 16 ++ common/webapp/src/js/markers/ShapeMarker.js | 4 + .../webapp/src/js/markers/ShapeMarkerBatch.js | 178 ++++++++++++++++++ 3 files changed, 198 insertions(+) create mode 100644 common/webapp/src/js/markers/ShapeMarkerBatch.js diff --git a/common/webapp/src/js/markers/MarkerSet.js b/common/webapp/src/js/markers/MarkerSet.js index d4719dcb4..ddb207ff1 100644 --- a/common/webapp/src/js/markers/MarkerSet.js +++ b/common/webapp/src/js/markers/MarkerSet.js @@ -31,6 +31,7 @@ import {HtmlMarker} from "./HtmlMarker"; import {PoiMarker} from "./PoiMarker"; import {reactive} from "vue"; import {getLocalStorage, setLocalStorage} from "../Utils"; +import {ShapeMarkerBatch} from "./ShapeMarkerBatch"; export class MarkerSet extends Scene { @@ -46,6 +47,9 @@ export class MarkerSet extends Scene { /** @type {Map} */ this.markers = new Map(); + this.shapeMarkerBatch = new ShapeMarkerBatch(this); + this.shapeMarkerBatchOutdated = false; + this.data = reactive({ id: id, label: id, @@ -183,6 +187,16 @@ export class MarkerSet extends Scene { marker.updateFromData(data); } + invalidateShapeMarkerBatch() { + if (this.shapeMarkerBatchOutdated) return; + this.shapeMarkerBatchOutdated = true; + + queueMicrotask(() => { + this.shapeMarkerBatchOutdated = false; + this.shapeMarkerBatch.update(); + }); + } + /** * Removes all markers and marker-sets */ @@ -201,6 +215,7 @@ export class MarkerSet extends Scene { if (o.isMarker && !this.markers.has(o.data.id)) { this.markers.set(o.data.id, o); this.data.markers.push(o.data); + if (o.isShapeMarker) this.invalidateShapeMarkerBatch(); } } @@ -221,6 +236,7 @@ export class MarkerSet extends Scene { if (i > -1) this.data.markers.splice(i, 1); this.markers.delete(o.data.id); o.dispose(); + if (o.isShapeMarker) this.invalidateShapeMarkerBatch(); } } diff --git a/common/webapp/src/js/markers/ShapeMarker.js b/common/webapp/src/js/markers/ShapeMarker.js index 499fab373..972e17d6e 100644 --- a/common/webapp/src/js/markers/ShapeMarker.js +++ b/common/webapp/src/js/markers/ShapeMarker.js @@ -95,6 +95,8 @@ export class ShapeMarker extends ObjectMarker { * }} */ updateFromData(markerData) { + let changed = !deepEquals(markerData, this._markerData) || !deepEquals(this._markerData, markerData); + super.updateFromData(markerData); // update shape only if needed, based on last update-data @@ -136,6 +138,8 @@ export class ShapeMarker extends ObjectMarker { // save used marker data for next update this._markerData = markerData; + + if (changed && this.parent && this.parent.isMarkerSet) this.parent.invalidateShapeMarkerBatch(); } dispose() { diff --git a/common/webapp/src/js/markers/ShapeMarkerBatch.js b/common/webapp/src/js/markers/ShapeMarkerBatch.js new file mode 100644 index 000000000..9a5247159 --- /dev/null +++ b/common/webapp/src/js/markers/ShapeMarkerBatch.js @@ -0,0 +1,178 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +import {BufferAttribute, BufferGeometry, Matrix4, Mesh, Vector3} from "three"; +import {Line2} from "three/examples/jsm/lines/Line2"; +import {LineSegmentsGeometry} from "three/examples/jsm/lines/LineSegmentsGeometry"; + +const MIN_SHAPE_MARKERS = 64; +const HIDDEN_LAYER = 30; + +export class ShapeMarkerBatch { + + /** + * @param markerSet {MarkerSet} + */ + constructor(markerSet) { + this.markerSet = markerSet; + this.objects = []; + this.parts = []; + } + + update() { + this.clear(); + + let markers = [...this.markerSet.markers.values()].filter(marker => marker.isShapeMarker && marker.visible); + if (markers.length < MIN_SHAPE_MARKERS) return; + + this.markerSet.updateWorldMatrix(true, true); + let toSet = new Matrix4().copy(this.markerSet.matrixWorld).invert(); + + let groups = new Map(); + markers.forEach(marker => { + [marker.fill, marker.border].forEach(part => { + if (!part.visible) return; + let key = batchKey(part); + let group = groups.get(key); + if (group) group.push(part); + else groups.set(key, [part]); + }); + }); + + groups.forEach(parts => { + let object = parts[0].isLine2 ? createBorderBatch(parts, toSet) : createFillBatch(parts, toSet); + this.objects.push(object); + this.markerSet.add(object); + parts.forEach(part => { + part.layers.set(HIDDEN_LAYER); + this.parts.push(part); + }); + }); + } + + clear() { + this.objects.forEach(object => { + this.markerSet.remove(object); + object.dispose(); + }); + this.parts.forEach(part => part.layers.set(0)); + this.objects = []; + this.parts = []; + } + +} + +function batchKey(part) { + let color = part.color; + return [ + part.isLine2 ? "border" : "fill", + part.renderOrder, + part.depthTest, + color.r, color.g, color.b, + part.opacity, + part.isLine2 ? part.linewidth : 0, + part.fadeDistanceMin, + part.fadeDistanceMax + ].join("|"); +} + +function createFillBatch(parts, toSet) { + let vertexCount = 0; + let indexCount = 0; + parts.forEach(part => { + let position = part.geometry.attributes.position; + vertexCount += position.count; + indexCount += part.geometry.index ? part.geometry.index.count : position.count; + }); + + let positions = new Float32Array(vertexCount * 3); + let indices = new Uint32Array(indexCount); + let matrix = new Matrix4(); + let point = new Vector3(); + let v = 0; + let i = 0; + parts.forEach(part => { + matrix.multiplyMatrices(toSet, part.matrixWorld); + let position = part.geometry.attributes.position; + for (let k = 0; k < position.count; k++) { + point.fromBufferAttribute(position, k).applyMatrix4(matrix).toArray(positions, (v + k) * 3); + } + + let index = part.geometry.index; + if (index) { + for (let k = 0; k < index.count; k++) indices[i++] = v + index.getX(k); + } else { + for (let k = 0; k < position.count; k++) indices[i++] = v + k; + } + + v += position.count; + }); + + let geometry = new BufferGeometry(); + geometry.setAttribute("position", new BufferAttribute(positions, 3)); + geometry.setIndex(new BufferAttribute(indices, 1)); + + return createBatchObject(new Mesh(geometry, parts[0].material.clone()), parts[0]); +} + +function createBorderBatch(parts, toSet) { + let segmentCount = parts.reduce((sum, part) => sum + part.geometry.attributes.instanceStart.count, 0); + + let positions = new Float32Array(segmentCount * 6); + let matrix = new Matrix4(); + let point = new Vector3(); + let s = 0; + parts.forEach(part => { + matrix.multiplyMatrices(toSet, part.matrixWorld); + let start = part.geometry.attributes.instanceStart; + let end = part.geometry.attributes.instanceEnd; + for (let k = 0; k < start.count; k++, s++) { + point.fromBufferAttribute(start, k).applyMatrix4(matrix).toArray(positions, s * 6); + point.fromBufferAttribute(end, k).applyMatrix4(matrix).toArray(positions, s * 6 + 3); + } + }); + + let geometry = new LineSegmentsGeometry(); + geometry.setPositions(positions); + + let line = createBatchObject(new Line2(geometry, parts[0].material.clone()), parts[0]); + line.onBeforeRender = renderer => { + let resolution = line.material.resolution; + renderer.getSize(resolution); + if (parts[0].material.resolution.equals(resolution)) return; + parts.forEach(part => part.material.resolution.copy(resolution)); + }; + + return line; +} + +function createBatchObject(object, part) { + object.renderOrder = part.renderOrder; + object.raycast = () => {}; + object.dispose = () => { + object.geometry.dispose(); + object.material.dispose(); + }; + return object; +} From 7ba0148155ad692cc58b842e39dd49bda925029f Mon Sep 17 00:00:00 2001 From: Hexay <97907668+Hexay@users.noreply.github.com> Date: Sun, 8 Nov 2026 12:56:41 +0000 Subject: [PATCH 2/3] Batch extrude and line markers too --- common/webapp/src/js/markers/ExtrudeMarker.js | 3 ++ common/webapp/src/js/markers/LineMarker.js | 3 ++ .../{ShapeMarkerBatch.js => MarkerBatch.js} | 34 ++++++++++++++----- common/webapp/src/js/markers/MarkerSet.js | 20 +++++------ common/webapp/src/js/markers/ShapeMarker.js | 7 ++-- 5 files changed, 44 insertions(+), 23 deletions(-) rename common/webapp/src/js/markers/{ShapeMarkerBatch.js => MarkerBatch.js} (85%) diff --git a/common/webapp/src/js/markers/ExtrudeMarker.js b/common/webapp/src/js/markers/ExtrudeMarker.js index d4dbe7558..c7d9ef213 100644 --- a/common/webapp/src/js/markers/ExtrudeMarker.js +++ b/common/webapp/src/js/markers/ExtrudeMarker.js @@ -31,6 +31,7 @@ import {deepEquals} from "../util/Utils"; import {LineSegmentsGeometry} from "three/examples/jsm/lines/LineSegmentsGeometry"; import {ObjectMarker} from "./ObjectMarker"; import {lineShader} from "../util/LineShader"; +import {invalidateMarkerBatchOnChange} from "./MarkerBatch"; export class ExtrudeMarker extends ObjectMarker { @@ -137,6 +138,8 @@ export class ExtrudeMarker extends ObjectMarker { this.fill.fadeDistanceMin = minDist; this.fill.fadeDistanceMax = maxDist; + invalidateMarkerBatchOnChange(this, this._markerData, markerData); + // save used marker data for next update this._markerData = markerData; } diff --git a/common/webapp/src/js/markers/LineMarker.js b/common/webapp/src/js/markers/LineMarker.js index b5fcb6923..08807b6cb 100644 --- a/common/webapp/src/js/markers/LineMarker.js +++ b/common/webapp/src/js/markers/LineMarker.js @@ -29,6 +29,7 @@ import {Line2} from "three/examples/jsm/lines/Line2"; import {deepEquals} from "../util/Utils"; import {ObjectMarker} from "./ObjectMarker"; import {lineShader} from "../util/LineShader"; +import {invalidateMarkerBatchOnChange} from "./MarkerBatch"; export class LineMarker extends ObjectMarker { @@ -123,6 +124,8 @@ export class LineMarker extends ObjectMarker { this.line.fadeDistanceMin = minDist; this.line.fadeDistanceMax = maxDist; + invalidateMarkerBatchOnChange(this, this._markerData, markerData); + // save used marker data for next update this._markerData = markerData; } diff --git a/common/webapp/src/js/markers/ShapeMarkerBatch.js b/common/webapp/src/js/markers/MarkerBatch.js similarity index 85% rename from common/webapp/src/js/markers/ShapeMarkerBatch.js rename to common/webapp/src/js/markers/MarkerBatch.js index 9a5247159..58856bb5e 100644 --- a/common/webapp/src/js/markers/ShapeMarkerBatch.js +++ b/common/webapp/src/js/markers/MarkerBatch.js @@ -25,11 +25,27 @@ import {BufferAttribute, BufferGeometry, Matrix4, Mesh, Vector3} from "three"; import {Line2} from "three/examples/jsm/lines/Line2"; import {LineSegmentsGeometry} from "three/examples/jsm/lines/LineSegmentsGeometry"; +import {deepEquals} from "../util/Utils"; -const MIN_SHAPE_MARKERS = 64; +const MIN_BATCHABLE_MARKERS = 64; const HIDDEN_LAYER = 30; -export class ShapeMarkerBatch { +export function isBatchable(marker) { + return !!(marker.isShapeMarker || marker.isExtrudeMarker || marker.isLineMarker); +} + +export function invalidateMarkerBatchOnChange(marker, oldData, newData) { + if (!marker.parent || !marker.parent.isMarkerSet) return; + if (deepEquals(oldData, newData) && deepEquals(newData, oldData)) return; + marker.parent.invalidateMarkerBatch(); +} + +function batchableParts(marker) { + if (marker.isLineMarker) return [marker.line]; + return [marker.fill, marker.border]; +} + +export class MarkerBatch { /** * @param markerSet {MarkerSet} @@ -43,15 +59,15 @@ export class ShapeMarkerBatch { update() { this.clear(); - let markers = [...this.markerSet.markers.values()].filter(marker => marker.isShapeMarker && marker.visible); - if (markers.length < MIN_SHAPE_MARKERS) return; + let markers = [...this.markerSet.markers.values()].filter(marker => isBatchable(marker) && marker.visible); + if (markers.length < MIN_BATCHABLE_MARKERS) return; this.markerSet.updateWorldMatrix(true, true); let toSet = new Matrix4().copy(this.markerSet.matrixWorld).invert(); let groups = new Map(); markers.forEach(marker => { - [marker.fill, marker.border].forEach(part => { + batchableParts(marker).forEach(part => { if (!part.visible) return; let key = batchKey(part); let group = groups.get(key); @@ -61,7 +77,7 @@ export class ShapeMarkerBatch { }); groups.forEach(parts => { - let object = parts[0].isLine2 ? createBorderBatch(parts, toSet) : createFillBatch(parts, toSet); + let object = parts[0].isLine2 ? createLineBatch(parts, toSet) : createMeshBatch(parts, toSet); this.objects.push(object); this.markerSet.add(object); parts.forEach(part => { @@ -86,7 +102,7 @@ export class ShapeMarkerBatch { function batchKey(part) { let color = part.color; return [ - part.isLine2 ? "border" : "fill", + part.isLine2 ? "line" : "mesh", part.renderOrder, part.depthTest, color.r, color.g, color.b, @@ -97,7 +113,7 @@ function batchKey(part) { ].join("|"); } -function createFillBatch(parts, toSet) { +function createMeshBatch(parts, toSet) { let vertexCount = 0; let indexCount = 0; parts.forEach(part => { @@ -136,7 +152,7 @@ function createFillBatch(parts, toSet) { return createBatchObject(new Mesh(geometry, parts[0].material.clone()), parts[0]); } -function createBorderBatch(parts, toSet) { +function createLineBatch(parts, toSet) { let segmentCount = parts.reduce((sum, part) => sum + part.geometry.attributes.instanceStart.count, 0); let positions = new Float32Array(segmentCount * 6); diff --git a/common/webapp/src/js/markers/MarkerSet.js b/common/webapp/src/js/markers/MarkerSet.js index ddb207ff1..d1755a7e9 100644 --- a/common/webapp/src/js/markers/MarkerSet.js +++ b/common/webapp/src/js/markers/MarkerSet.js @@ -31,7 +31,7 @@ import {HtmlMarker} from "./HtmlMarker"; import {PoiMarker} from "./PoiMarker"; import {reactive} from "vue"; import {getLocalStorage, setLocalStorage} from "../Utils"; -import {ShapeMarkerBatch} from "./ShapeMarkerBatch"; +import {isBatchable, MarkerBatch} from "./MarkerBatch"; export class MarkerSet extends Scene { @@ -47,8 +47,8 @@ export class MarkerSet extends Scene { /** @type {Map} */ this.markers = new Map(); - this.shapeMarkerBatch = new ShapeMarkerBatch(this); - this.shapeMarkerBatchOutdated = false; + this.markerBatch = new MarkerBatch(this); + this.markerBatchOutdated = false; this.data = reactive({ id: id, @@ -187,13 +187,13 @@ export class MarkerSet extends Scene { marker.updateFromData(data); } - invalidateShapeMarkerBatch() { - if (this.shapeMarkerBatchOutdated) return; - this.shapeMarkerBatchOutdated = true; + invalidateMarkerBatch() { + if (this.markerBatchOutdated) return; + this.markerBatchOutdated = true; queueMicrotask(() => { - this.shapeMarkerBatchOutdated = false; - this.shapeMarkerBatch.update(); + this.markerBatchOutdated = false; + this.markerBatch.update(); }); } @@ -215,7 +215,7 @@ export class MarkerSet extends Scene { if (o.isMarker && !this.markers.has(o.data.id)) { this.markers.set(o.data.id, o); this.data.markers.push(o.data); - if (o.isShapeMarker) this.invalidateShapeMarkerBatch(); + if (isBatchable(o)) this.invalidateMarkerBatch(); } } @@ -236,7 +236,7 @@ export class MarkerSet extends Scene { if (i > -1) this.data.markers.splice(i, 1); this.markers.delete(o.data.id); o.dispose(); - if (o.isShapeMarker) this.invalidateShapeMarkerBatch(); + if (isBatchable(o)) this.invalidateMarkerBatch(); } } diff --git a/common/webapp/src/js/markers/ShapeMarker.js b/common/webapp/src/js/markers/ShapeMarker.js index 972e17d6e..cfd093a3e 100644 --- a/common/webapp/src/js/markers/ShapeMarker.js +++ b/common/webapp/src/js/markers/ShapeMarker.js @@ -30,6 +30,7 @@ import {Line2} from "three/examples/jsm/lines/Line2"; import {deepEquals} from "../util/Utils"; import {ObjectMarker} from "./ObjectMarker"; import {lineShader} from "../util/LineShader"; +import {invalidateMarkerBatchOnChange} from "./MarkerBatch"; import {LineSegmentsGeometry} from "three/examples/jsm/lines/LineSegmentsGeometry"; export class ShapeMarker extends ObjectMarker { @@ -95,8 +96,6 @@ export class ShapeMarker extends ObjectMarker { * }} */ updateFromData(markerData) { - let changed = !deepEquals(markerData, this._markerData) || !deepEquals(this._markerData, markerData); - super.updateFromData(markerData); // update shape only if needed, based on last update-data @@ -136,10 +135,10 @@ export class ShapeMarker extends ObjectMarker { this.fill.fadeDistanceMin = minDist; this.fill.fadeDistanceMax = maxDist; + invalidateMarkerBatchOnChange(this, this._markerData, markerData); + // save used marker data for next update this._markerData = markerData; - - if (changed && this.parent && this.parent.isMarkerSet) this.parent.invalidateShapeMarkerBatch(); } dispose() { From 70db8da9683f8b47a7ba845e1088faaaac1da58d Mon Sep 17 00:00:00 2001 From: Hexay <97907668+Hexay@users.noreply.github.com> Date: Sun, 8 Nov 2026 13:39:34 +0000 Subject: [PATCH 3/3] Split marker batches by map region, compare only drawn fields, keep overlapping fills visible --- common/webapp/src/js/markers/Marker.js | 7 +++- common/webapp/src/js/markers/MarkerBatch.js | 38 +++++++++++++++------ 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/common/webapp/src/js/markers/Marker.js b/common/webapp/src/js/markers/Marker.js index 2ee07ce33..1ad35948a 100644 --- a/common/webapp/src/js/markers/Marker.js +++ b/common/webapp/src/js/markers/Marker.js @@ -24,6 +24,7 @@ */ import {MathUtils, Object3D, Vector3} from "three"; import {reactive} from "vue"; +import {isBatchable} from "./MarkerBatch"; export class Marker extends Object3D { @@ -50,7 +51,11 @@ export class Marker extends Object3D { }); Object.defineProperty(this, "visible", { get() { return this.data.visible }, - set(value) { this.data.visible = value } + set(value) { + if (this.data.visible === value) return; + this.data.visible = value; + if (isBatchable(this) && this.parent && this.parent.isMarkerSet) this.parent.invalidateMarkerBatch(); + } }); } diff --git a/common/webapp/src/js/markers/MarkerBatch.js b/common/webapp/src/js/markers/MarkerBatch.js index 58856bb5e..37bf621c1 100644 --- a/common/webapp/src/js/markers/MarkerBatch.js +++ b/common/webapp/src/js/markers/MarkerBatch.js @@ -22,13 +22,17 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -import {BufferAttribute, BufferGeometry, Matrix4, Mesh, Vector3} from "three"; +import {BufferAttribute, BufferGeometry, Matrix4, Mesh, Vector2, Vector3} from "three"; import {Line2} from "three/examples/jsm/lines/Line2"; import {LineSegmentsGeometry} from "three/examples/jsm/lines/LineSegmentsGeometry"; -import {deepEquals} from "../util/Utils"; - const MIN_BATCHABLE_MARKERS = 64; const HIDDEN_LAYER = 30; +const CELL_SIZE = 4096; +const DRAWN_FIELDS = [ + "position", "shape", "holes", "line", "shapeY", "height", "shapeMinY", "shapeMaxY", + "depthTest", "lineWidth", "lineColor", "borderColor", "fillColor", "minDistance", "maxDistance", + "x", "y", "z", "r", "g", "b", "a" +]; export function isBatchable(marker) { return !!(marker.isShapeMarker || marker.isExtrudeMarker || marker.isLineMarker); @@ -36,7 +40,7 @@ export function isBatchable(marker) { export function invalidateMarkerBatchOnChange(marker, oldData, newData) { if (!marker.parent || !marker.parent.isMarkerSet) return; - if (deepEquals(oldData, newData) && deepEquals(newData, oldData)) return; + if (JSON.stringify(oldData, DRAWN_FIELDS) === JSON.stringify(newData, DRAWN_FIELDS)) return; marker.parent.invalidateMarkerBatch(); } @@ -66,18 +70,25 @@ export class MarkerBatch { let toSet = new Matrix4().copy(this.markerSet.matrixWorld).invert(); let groups = new Map(); + let position = new Vector3(); markers.forEach(marker => { + position.setFromMatrixPosition(marker.matrixWorld).applyMatrix4(toSet); + let cellX = Math.floor(position.x / CELL_SIZE); + let cellZ = Math.floor(position.z / CELL_SIZE); batchableParts(marker).forEach(part => { if (!part.visible) return; - let key = batchKey(part); + let key = batchKey(part) + "|" + cellX + "|" + cellZ; let group = groups.get(key); - if (group) group.push(part); - else groups.set(key, [part]); + if (group) group.parts.push(part); + else groups.set(key, {cellX, cellZ, parts: [part]}); }); }); - groups.forEach(parts => { - let object = parts[0].isLine2 ? createLineBatch(parts, toSet) : createMeshBatch(parts, toSet); + groups.forEach(({cellX, cellZ, parts}) => { + let origin = new Vector3((cellX + 0.5) * CELL_SIZE, 0, (cellZ + 0.5) * CELL_SIZE); + let toBatch = new Matrix4().makeTranslation(-origin.x, -origin.y, -origin.z).multiply(toSet); + let object = parts[0].isLine2 ? createLineBatch(parts, toBatch) : createMeshBatch(parts, toBatch); + object.position.copy(origin); this.objects.push(object); this.markerSet.add(object); parts.forEach(part => { @@ -149,7 +160,10 @@ function createMeshBatch(parts, toSet) { geometry.setAttribute("position", new BufferAttribute(positions, 3)); geometry.setIndex(new BufferAttribute(indices, 1)); - return createBatchObject(new Mesh(geometry, parts[0].material.clone()), parts[0]); + let material = parts[0].material.clone(); + material.depthWrite = false; + + return createBatchObject(new Mesh(geometry, material), parts[0]); } function createLineBatch(parts, toSet) { @@ -173,10 +187,12 @@ function createLineBatch(parts, toSet) { geometry.setPositions(positions); let line = createBatchObject(new Line2(geometry, parts[0].material.clone()), parts[0]); + let partsResolution = new Vector2(-1, -1); line.onBeforeRender = renderer => { let resolution = line.material.resolution; renderer.getSize(resolution); - if (parts[0].material.resolution.equals(resolution)) return; + if (partsResolution.equals(resolution)) return; + partsResolution.copy(resolution); parts.forEach(part => part.material.resolution.copy(resolution)); };