-
Notifications
You must be signed in to change notification settings - Fork 303
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(core): allow layer to specify which elements should be updatable #789
Conversation
This needs a little more documentation, on what is expected to be returned from |
src/Core/MainLoop.js
Outdated
@@ -68,7 +68,19 @@ function updateElements(context, geometryLayer, elements) { | |||
// update attached layers | |||
for (const attachedLayer of geometryLayer._attachedLayers) { | |||
if (attachedLayer.ready) { | |||
attachedLayer.update(context, attachedLayer, element); | |||
// Attached layers expect to receive the visual representation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An example would be needed to understand the use case.
it's necessary to add a new update?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can find an example in the https://github.com/peppsac/itowns2/tree/colorisation branch.
It uses the ColorTextureProcessing to colorize a 3dtiles pointcloud.
But 3dTilesProcessing update function process metadata objects (= the content of the tileset.json file) while ColorTextureProcessing expects objects with a material (because it was initially built for TileMesh).
src/Provider/3dTilesProvider.js
Outdated
@@ -62,6 +62,21 @@ function $3dTilesIndex(tileset, baseURL) { | |||
function preprocessDataLayer(layer, view, scheduler) { | |||
layer.sseThreshold = layer.sseThreshold || 16; | |||
layer.cleanupDelay = layer.cleanupDelay || 1000; | |||
layer.metadataToElements = (meta) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
metadataToElements
shouldn't be exposed in layer. We must avoid putting processing in the provider (especially that the providers should disappear)
why not do it in the attachedLayer.update
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
metadataToElements shouldn't be exposed in layer
We must avoid putting processing in the provider
Why?
(especially that the providers should disappear)
I don't think so. While currently their scope/responsability is unclear, I don't think they'll ever disappear. For instance, in the 3dtiles case you'll always need "something" that understands the tileset.json structure and parse/prepare it for the processing code to operate on.
The purpose of this metadataToElements
function is to allow Processing code to be independant of the providers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We already had the same discussion for the picking feature where no good conclusion was reached.
This confirms that the Provider/Processing boundary isn't good enough, but IMHO this shouldn't prevent merging this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As presented in #798, this PR proposes, not to couple the provider and the processing, as it makes the architecture more rigid and less modular. We must try to place this code in the processing not to increase this coupling
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You missed the point again and again.
I won't repeat myself please read my previous comments.
@zarov I shuffled the code a bit and moved the comment to |
src/Core/MainLoop.js
Outdated
if (sub) { | ||
if (__DEBUG__) { | ||
if (!(sub.isObject && sub.material)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure about this ? It will always throw an error here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No the correct code is if (!(sub.element.isObject3D && sub.element.material)) {
.
I'll push it, but first I need to resolve a bug that this assert shows.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
src/Core/MainLoop.js
Outdated
|
||
if (sub) { | ||
if (__DEBUG__) { | ||
if (!(sub.element.isObject3D && sub.element.material)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some objects still go through this condition, for example the Scene
of iTowns. Thus on the 3dtiles
example I can't navigate and move at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It may actually not really a bug.
Do we really want to pass all the root of 3dtiles scene to attached layer? (in which case the attached layer needs to be aware that it may receive a hierarchy, rather than a single object). Or do we want to pass the drawable elements? (in this case the code in getObjectToUpdateForAttachedLayers should collect all objects in content
that have a material).
I think I prefer the second option. WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since I'm not sure about this, I removed the sub.element.material
check for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll go with the second option as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I implemented it. Note that I had to adapt 3dTilesDebug to reflect this change - this part is a bit tedious due to the way we handle gltfUpAxis (= we rotate the matrix of the model but the boundingVolume must not be affected by this rotation).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When do you need the parent ?
src/Core/MainLoop.js
Outdated
Invalid object for attached layer to update. | ||
Must be a THREE.Object and have a THREE.Material`); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the above does not depend on attachedLayer and could be moved outside the loop
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I missed this, thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
e.g: for initializing a texture layer from the parent's texture = every usage of node.parent in |
767b0cb
to
a9791bb
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm currently reviewing this PR
but first you have to add some tests
attachedLayer.update(context, attachedLayer, sub.element, sub.parent); | ||
} | ||
} | ||
} else if (sub.elements) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getObjectToUpdateForAttachedLayers
can always return an Array?
it would simplify this part
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This part is internal only (= no user of iTowns will have to write the same code in its application), so I don't mind having a bit more complicated code if it performs better.
I'll check, and simplify if possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel that it is more of a problem of structure of our elements. We should not complicate this part of the process to be able to adapt to all structures. I'll go deeper and I'll come back on the subject
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @gchoqueux, even if it is hidden, it should always return elements
as an array.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keep in mind that this piece of code is executed multiple times during each update loop.
So creating an array by default, even if it's for a single element might pressure the GC or cause a perf hit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, that's a good point. We still have duplicated code though, could you move both part into another function ?
src/Provider/PointCloudProvider.js
Outdated
@@ -174,6 +175,23 @@ export default { | |||
layer.update = PointCloudProcessing.update; | |||
layer.postUpdate = PointCloudProcessing.postUpdate; | |||
|
|||
// override the default method, since updated objects are metadata in this case | |||
layer.getObjectToUpdateForAttachedLayers = (meta) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should move this function in the processing, and assign it as above (same think for 3dtiles)
layer.getObjectToUpdateForAttachedLayers = PointCloudProcessing.getObjectToUpdateForAttachedLayers;
the provider.preprocess
it looks like a layer's constructor. 3dTiles
and pointCloud
should inherit geometryLayer
and overload its processing functions (if that is necessary)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should move this function in the processing, and assign it as above
Where? Processing have no code that run once for a layer, while Provider have a preprocess step.
And processing code should try to be as independant as possible from providers so moving these functions that are highly dependent on the protocol/provider makes very little sense.
One of my mid-term goal is to have a single processing module for pointclouds, that would work with all kind of pointcloud sources.
To be able to do this, the provider of the said sources has to format/prepare the layer to meet the expectations of this processing module.
Basically: the processing code defines a contract, and will work with every layer that implements it.
the provider.preprocess it looks like a layer's constructor
Can you explain what you mean?
3dTiles and pointCloud should inherit geometryLayer and overload its processing functions (if that is necessary)
I assume that you meant 3dtiles layer and pointcloud layer?
What would be the benefits of inheriting + overriding GeometryLayer
over the current situation?
(as there are no processing functions in GeometryLayer so I don't understand this either)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as there are no processing functions in GeometryLayer so I don't understand this either
geometryLayer.getObjectToUpdateForAttachedLayers
is processing function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I don't want to change this because I simply don't agree.
The problem currently is not this code, but the fact that Provider and Processing have strong coupling.
I and others are working on improving this and I would appreciate that you don't undermine this effort by bringing this topic on each review.
Ok, thanks.
This part is central in iTowns, so it's exercised by all functionnal tests. |
attachedLayer.update(context, attachedLayer, sub.element, sub.parent); | ||
} | ||
} | ||
} else if (sub.elements) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel that it is more of a problem of structure of our elements. We should not complicate this part of the process to be able to adapt to all structures. I'll go deeper and I'll come back on the subject
src/Provider/3dTilesProvider.js
Outdated
@@ -62,6 +62,28 @@ function $3dTilesIndex(tileset, baseURL) { | |||
function preprocessDataLayer(layer, view, scheduler) { | |||
layer.sseThreshold = layer.sseThreshold || 16; | |||
layer.cleanupDelay = layer.cleanupDelay || 1000; | |||
// override the default method, since updated objects are metadata in this case | |||
layer.getObjectToUpdateForAttachedLayers = (meta) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you should add a test will verify the function work as expected
and others clarify / document the data structure used for 3dTiles (as for example, the object
, the userData
and the content
are structured)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I reverted part of the userData change.
The point was to store our objects in this dict created by three.js instead of patching the constructed objects (mainly because it's a good pratice and it might improve performance).
See this comment for a more info.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK fine,
but what is the structure of the data?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is the structure of the data?
I don't understand your question sorry.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you clarify the structure of objects used for 3dTiles, in itowns
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't change any the structure or its behavior in this PR. Why are you requesting this?
(also the structure is exactly the one from the 3dtiles spec)
src/Provider/PointCloudProvider.js
Outdated
@@ -174,6 +175,23 @@ export default { | |||
layer.update = PointCloudProcessing.update; | |||
layer.postUpdate = PointCloudProcessing.postUpdate; | |||
|
|||
// override the default method, since updated objects are metadata in this case | |||
layer.getObjectToUpdateForAttachedLayers = (meta) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you should add a test will verify the function work as expected
and others clarify / document the data structure used for cloudPoint (as for example, what is the difference this structure geometryLayer
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test getObjectToUpdateForAttachedLayers
is added, is it testing for geometryLayer
?
What is data structure used for cloudPoint (as for example, what is the difference this structure geometryLayer
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getObjectToUpdateForAttachedLayers
always return the same thing, as it's a contract between MainLoop and Layer.
The description is here https://github.com/iTowns/itowns/pull/789/files#diff-61e012c3e00bdc510be33d9eac684b2aR83, and the comment / ref implementation explains the returned types (it's not using jsdoc since it's not exposed anyway).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can not find an explanation of data structure used for cloudPoind, in itowns
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See #789 (comment)
src/Provider/PointCloudProvider.js
Outdated
@@ -174,6 +175,23 @@ export default { | |||
layer.update = PointCloudProcessing.update; | |||
layer.postUpdate = PointCloudProcessing.postUpdate; | |||
|
|||
// override the default method, since updated objects are metadata in this case | |||
layer.getObjectToUpdateForAttachedLayers = (meta) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as there are no processing functions in GeometryLayer so I don't understand this either
geometryLayer.getObjectToUpdateForAttachedLayers
is processing function
Pushed a commit to address the reviews + added some tests. |
af61677
to
e8c2b32
Compare
@gchoqueux can you update your review please? |
for this PR:
tileA // (THREE.Mesh->itowns.tileMesh)
{
children: [tileB, tileC] // (Array) tile children
parent: tile0 // (THREE.Mesh->itowns.tileMesh) tile parent
obb: // (itowns.OBB) oriented bouding box
} in This information is important to understand the
As seen in the PR #798 , it is an error to place processing functions in the provider, to go in this direction we have to strive to place in the processing like |
I disagree for the 1000th times. You got the logic backward: processing code should only depend on the data type (points or tiles or whatever). And 3dTilesProcessing should disappear, not 3dTilesProvider. To achieve this, the Provider must hide away the specific of each protocols. And this is exactly the purpose of getObjectToUpdateForAttachedLayers. With this logic, I'm able to implement pointcloud coloring using a WMS server (aka = I have a branch where it works). And the pointcloud texture selection uses the same code than the tile texturing code. Do you have something similar with your branch? If not, then maybe you could let this PR in, so I can submit the next PR that depends on it. And then you might realize that you're wrong. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I disagree for the 1000th times. You got the logic backward: processing code should only depend on the data type (points or tiles or whatever). And 3dTilesProcessing should disappear, not 3dTilesProvider.
And I disagree too. Both should disappear, data type has nothing to do with the provider. It makes more sense to me to add this processing code in 3dTilesProcessing
, which is more relevant to the 3dtiles format and data type.
Anyway, what @gchoqueux is saying (if I understand correctly) is that we should avoid adding methods like this if both are meant to disappear in the future.
gchoqueux 7 days ago Member
Can you clarify the structure of objects used for 3dTiles, in itowns
@peppsac
peppsac 5 days ago Member
I didn't change any the structure or its behavior in this PR. Why are you requesting this?
(also the structure is exactly the one from the 3dtiles spec)
Concerning this, I don't think the documentation of data structure is relevant to this PR, even if it could be good to have it.
} | ||
} | ||
} else if (sub.elements) { | ||
for (let i = 0; i < sub.elements.length; i++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use for (let element of sub.elements)
instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I dislike more and more the for of
syntax because it's harder to debug (lots of indirection). I'm also wondering on the perf cost of this (for the same reason: lots of indirection)...
Anyway I'll change it because for now we settled on using it everywhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah it seems that performance wise it is the slowest (as tested quickly here http://jsben.ch/iEYTg)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And this test misses one of the cost: in itowns we never run native for of
loops, but only transpiled for of
.
For instance a simple:
for (const word of words) {
console.log(word);
}
becomes:
"use strict";
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = words[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var word = _step.value;
console.log(word);
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
attachedLayer.update(context, attachedLayer, sub.element, sub.parent); | ||
} | ||
} | ||
} else if (sub.elements) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @gchoqueux, even if it is hidden, it should always return elements
as an array.
The function here has no expectation on the data type. It only depends on the 3dtiles protocol (metadata would be more accurate). To be clear: 3dtiles is not a data type, it's a protocol (in itowns current semantics at least which mix protocol and metadata). So the provider is aware of the metadata, and build a data structure understood by itowns / the processing functions. This structure will evolve, but the point of this PR (and the next one) is to iterate on its definition (at the very least: In the long term: the 3dtilesprocessing module should be gone, and be replaced either by a single processing.js module (see #674 for something that goes in this direction) or several datatypeprocessing.js (pointcloudprocessing.js, tileprocessing.js ...). But the 3dtilesprovider must remain (even if its renamed 3dtilessource): because we need a place in itowns that is aware of the protocol/metadata of each supported sources. Is it clearer? |
It is clearer thanks, I was confused with the fact that 3dtiles is a protocol indeed. Could we have a glimpse of the next PR ? Otherwise it seems good now to me, maybe @mbredif can give his opinion too ? I'm not the most aware contributor on 3dtiles. |
Already posted elsewhere: https://github.com/peppsac/itowns2/commits/colorisation My recent PRs are a split of the work done there. At the end the point is to be able to reuse the 'texture update processing' code to colorize pointclouds. (and I have another feature built on top of this branch that leverage the cache to reduce the latency in itowns... but this will have to wait until all the previous PRs are merged) |
Thanks ! I only took a quick look but I think there are some things I might disagree on later ;) Anyway, could you rebase this PR ? |
e8c2b32
to
3d8dfc4
Compare
Sure :)
Done |
3d8dfc4
to
f5290a2
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay let's merge this, rebase and test before ! Thanks
f5290a2
to
e0081e5
Compare
Some layers use separated objects for metadata and drawable objects. For instance PointCloudProvider or 3dTilesProvider. Attached layers (e.g texture layers) expect to receive a drawable object, in their update function. So this commit adds an optional metadataToElements function to layers, allowing them to returns the to-be-updated object from metadata, and its parent.
e0081e5
to
b4a880b
Compare
Some layers use separated objects for metadata and drawable objects.
For instance PointCloudProvider or 3dTilesProvider.
Attached layers (e.g texture layers) expect to receive a drawable object,
in their update function.
So this commit adds an optional metadataToElements function to layers,
allowing them to returns the to-be-updated object from metadata, and
its parent.