diff --git a/cesiumjs4gwt-main/src/main/java/org/cesiumjs/cs/scene/experimental/TextureUniform.java b/cesiumjs4gwt-main/src/main/java/org/cesiumjs/cs/scene/experimental/TextureUniform.java new file mode 100644 index 0000000000000000000000000000000000000000..148881395ca12c9981e247af66bcdc54e9e233a3 --- /dev/null +++ b/cesiumjs4gwt-main/src/main/java/org/cesiumjs/cs/scene/experimental/TextureUniform.java @@ -0,0 +1,76 @@ +/* + * Copyright 2021 iserge, Gis4Fun. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cesiumjs.cs.scene.experimental; + +import com.google.gwt.typedarrays.shared.Uint8Array; +import jsinterop.annotations.JsConstructor; +import jsinterop.annotations.JsOverlay; +import jsinterop.annotations.JsProperty; +import jsinterop.annotations.JsType; +import org.cesiumjs.cs.core.Resource; +import org.cesiumjs.cs.core.enums.PixelDatatype; +import org.cesiumjs.cs.core.enums.PixelFormat; +import org.cesiumjs.cs.scene.experimental.options.TextureUniformOptions; + +/** + * A simple struct that serves as a value of a sampler2D-valued uniform. + * This is used with {@link CustomShader} and {@link TextureManager}. + */ +@JsType(isNative = true, namespace = "Cesium", name = "TextureUniform") +public class TextureUniform { + /** + * A typed array storing the contents of a texture. Values are stored in row-major order. Since WebGL uses a y-up + * convention for textures, rows are listed from bottom to top. + */ + @JsProperty(name = "typedArray") + public native Uint8Array typedArray(); + /** + * The width of the image. Required when {@link this#typedArray} is present. + */ + @JsProperty(name = "width") + public native Number width(); + /** + * The height of the image. Required when {@link this#typedArray} is present. + */ + @JsProperty(name = "height") + public native Number height(); + /** + * When {@link this#typedArray} is defined, this is used to determine the pixel format of the texture + * Default: {@link PixelFormat#RGBA()} + */ + @JsProperty(name = "pixelFormat") + public native Number pixelFormat(); + /** + * When {@link this#typedArray} is defined, this is the data type of pixel values in the typed array. + * Default: {@link PixelDatatype#UNSIGNED_BYTE()} + */ + @JsProperty(name = "pixelDatatype") + public native Number pixelDatatype(); + + @JsConstructor + public TextureUniform(TextureUniformOptions options) {} + + @JsOverlay + public static TextureUniform create(String url) { + return new TextureUniform(new TextureUniformOptions().setUrl(url)); + } + + @JsOverlay + public static TextureUniform create(Resource url) { + return new TextureUniform(new TextureUniformOptions().setUrl(url)); + } +} diff --git a/cesiumjs4gwt-main/src/main/java/org/cesiumjs/cs/scene/experimental/options/ModelExperimentalFromGltfOptions.java b/cesiumjs4gwt-main/src/main/java/org/cesiumjs/cs/scene/experimental/options/ModelExperimentalFromGltfOptions.java index 39a6545292cb9866fa89a2f0f961891d37679e7e..8ff192fb68366842cffc65fd61d2ab60aecdd28f 100644 --- a/cesiumjs4gwt-main/src/main/java/org/cesiumjs/cs/scene/experimental/options/ModelExperimentalFromGltfOptions.java +++ b/cesiumjs4gwt-main/src/main/java/org/cesiumjs/cs/scene/experimental/options/ModelExperimentalFromGltfOptions.java @@ -161,28 +161,28 @@ public class ModelExperimentalFromGltfOptions { @JsConstructor private ModelExperimentalFromGltfOptions() {} - @JsMethod + @JsOverlay public static ModelExperimentalFromGltfOptions create(String gltf) { ModelExperimentalFromGltfOptions options = new ModelExperimentalFromGltfOptions(); options.url = gltf; return options; } - @JsMethod + @JsOverlay public static ModelExperimentalFromGltfOptions create(Resource gltf) { ModelExperimentalFromGltfOptions options = new ModelExperimentalFromGltfOptions(); options.gltf = gltf; return options; } - @JsMethod + @JsOverlay public static ModelExperimentalFromGltfOptions create(Uint8Array gltf) { ModelExperimentalFromGltfOptions options = new ModelExperimentalFromGltfOptions(); options.uarray = gltf; return options; } - @JsMethod + @JsOverlay public static ModelExperimentalFromGltfOptions create(Object gltf) { ModelExperimentalFromGltfOptions options = new ModelExperimentalFromGltfOptions(); options.gltfObj = gltf; diff --git a/cesiumjs4gwt-main/src/main/java/org/cesiumjs/cs/scene/experimental/options/ModelExperimentalOptions.java b/cesiumjs4gwt-main/src/main/java/org/cesiumjs/cs/scene/experimental/options/ModelExperimentalOptions.java index 9afa016f92076f2e0cb3e455f31b432587655229..5a41915abf6bd247b7d7bf69fd97c0209420ca3f 100644 --- a/cesiumjs4gwt-main/src/main/java/org/cesiumjs/cs/scene/experimental/options/ModelExperimentalOptions.java +++ b/cesiumjs4gwt-main/src/main/java/org/cesiumjs/cs/scene/experimental/options/ModelExperimentalOptions.java @@ -16,10 +16,7 @@ package org.cesiumjs.cs.scene.experimental.options; -import jsinterop.annotations.JsConstructor; -import jsinterop.annotations.JsPackage; -import jsinterop.annotations.JsProperty; -import jsinterop.annotations.JsType; +import jsinterop.annotations.*; import org.cesiumjs.cs.core.Color; import org.cesiumjs.cs.core.Matrix4; import org.cesiumjs.cs.core.Resource; @@ -111,6 +108,7 @@ public class ModelExperimentalOptions { @JsConstructor private ModelExperimentalOptions() {} + @JsOverlay public static ModelExperimentalOptions create(Resource resource) { ModelExperimentalOptions options = new ModelExperimentalOptions(); options.resource = resource; diff --git a/cesiumjs4gwt-main/src/main/java/org/cesiumjs/cs/scene/experimental/options/TextureUniformOptions.java b/cesiumjs4gwt-main/src/main/java/org/cesiumjs/cs/scene/experimental/options/TextureUniformOptions.java new file mode 100644 index 0000000000000000000000000000000000000000..7e86ab4460854c437dcdb8778e486b9c0db52bf3 --- /dev/null +++ b/cesiumjs4gwt-main/src/main/java/org/cesiumjs/cs/scene/experimental/options/TextureUniformOptions.java @@ -0,0 +1,163 @@ +/* + * Copyright 2021 iserge, Gis4Fun. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cesiumjs.cs.scene.experimental.options; + +import com.google.gwt.typedarrays.shared.Uint8Array; +import jsinterop.annotations.*; +import org.cesiumjs.cs.core.Resource; +import org.cesiumjs.cs.core.enums.PixelDatatype; +import org.cesiumjs.cs.core.enums.PixelFormat; +import org.cesiumjs.cs.scene.enums.TextureMagnificationFilter; +import org.cesiumjs.cs.scene.enums.TextureMinificationFilter; + +/** + * Options for {@link org.cesiumjs.cs.scene.experimental.TextureUniform}. + */ +@JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "Object") +public class TextureUniformOptions { + /** + * A typed array storing the contents of a texture. Values are stored in row-major order. Since WebGL uses a y-up + * convention for textures, rows are listed from bottom to top. + */ + @JsProperty + public Uint8Array typedArray; + /** + * The width of the image. Required when {@link this#typedArray} is present. + */ + @JsProperty + public int width; + /** + * The height of the image. Required when {@link this#typedArray} is present. + */ + @JsProperty + public int height; + /** + * A URL string or resource pointing to a texture image. + */ + @JsProperty + public String url; + /** + * A URL resource or resource pointing to a texture image. + */ + @JsProperty(name = "url") + public Resource urlRes; + /** + * When defined, the texture sampler will be set to wrap in both directions. + * Default: true + */ + @JsProperty + public boolean repeat; + /** + * When {@link this#typedArray} is defined, this is used to determine the pixel format of the texture + * Default: {@link PixelFormat#RGBA()} + */ + @JsProperty + public Number pixelFormat; + /** + * When {@link this#typedArray} is defined, this is the data type of pixel values in the typed array. + * Default: {@link PixelDatatype#UNSIGNED_BYTE()} + */ + @JsProperty + public Number pixelDatatype; + /** + * The minification filter of the texture sampler. + * Default: {@link TextureMinificationFilter#LINEAR()} + */ + @JsProperty + public Number minificationFilter; + /** + * The magnification filter of the texture sampler. + * Default: {@link TextureMagnificationFilter#LINEAR()} + */ + @JsProperty + public Number magnificationFilter; + /** + * The maximum anisotropy of the texture sampler. + * Default: 1.0 + */ + @JsProperty + public Number maximumAnisotropy; + + @JsConstructor + public TextureUniformOptions() {} + + @JsOverlay + public final TextureUniformOptions setTypedArray(Uint8Array typedArray) { + this.typedArray = typedArray; + return this; + } + + @JsOverlay + public final TextureUniformOptions setWidth(int width) { + this.width = width; + return this; + } + + @JsOverlay + public final TextureUniformOptions setHeight(int height) { + this.height = height; + return this; + } + + @JsOverlay + public final TextureUniformOptions setUrl(String url) { + this.url = url; + return this; + } + + @JsOverlay + public final TextureUniformOptions setUrl(Resource url) { + this.urlRes = url; + return this; + } + + @JsOverlay + public final TextureUniformOptions setRepeat(boolean repeat) { + this.repeat = repeat; + return this; + } + + @JsOverlay + public final TextureUniformOptions setPixelFormat(Number pixelFormat) { + this.pixelFormat = pixelFormat; + return this; + } + + @JsOverlay + public final TextureUniformOptions setPixelDatatype(Number pixelDatatype) { + this.pixelDatatype = pixelDatatype; + return this; + } + + @JsOverlay + public final TextureUniformOptions setMinificationFilter(Number minificationFilter) { + this.minificationFilter = minificationFilter; + return this; + } + + @JsOverlay + public final TextureUniformOptions setMagnificationFilter(Number magnificationFilter) { + this.magnificationFilter = magnificationFilter; + return this; + } + + @JsOverlay + public final TextureUniformOptions setMaximumAnisotropy(Number maximumAnisotropy) { + this.maximumAnisotropy = maximumAnisotropy; + return this; + } +} diff --git a/cesiumjs4gwt-main/src/main/java/org/cesiumjs/cs/scene/experimental/package-info.java b/cesiumjs4gwt-main/src/main/java/org/cesiumjs/cs/scene/experimental/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..3b1c96a7e14ce67b454a383074953402a46313da --- /dev/null +++ b/cesiumjs4gwt-main/src/main/java/org/cesiumjs/cs/scene/experimental/package-info.java @@ -0,0 +1,25 @@ +/* + * Copyright 2021 iserge, Gis4Fun. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Experimental features must still uphold Cesium's quality standards. Here are some guidelines: + * + * Experimental features must have high unit test coverage like any other feature. + * Experimental features are intended for large features where there is benefit of merging some of the code sooner (e.g. to avoid long-running staging branches) + * Experimental flags should be short-lived. Make it clear in the PR what it would take to promote the feature to a regular feature. + * To avoid cluttering the code, check the flag in as few places as possible. Ideally this would be a single place. + */ +package org.cesiumjs.cs.scene.experimental; diff --git a/cesiumjs4gwt-showcase/src/main/java/org/cleanlogic/cesiumjs4gwt/showcase/config/InjectorModule.java b/cesiumjs4gwt-showcase/src/main/java/org/cleanlogic/cesiumjs4gwt/showcase/config/InjectorModule.java index e7ce024ff469cafc407227b96831f41b1674c78b..e9ab55135f045f1340d72b715a187d3024c9e630 100644 --- a/cesiumjs4gwt-showcase/src/main/java/org/cleanlogic/cesiumjs4gwt/showcase/config/InjectorModule.java +++ b/cesiumjs4gwt-showcase/src/main/java/org/cleanlogic/cesiumjs4gwt/showcase/config/InjectorModule.java @@ -119,5 +119,6 @@ public class InjectorModule extends AbstractGinModule { bind(Tiles3DNextPhotogrammetryClassification.class).asEagerSingleton(); bind(CustomShaders3DTiles.class).asEagerSingleton(); + bind(CustomShadersModels.class).asEagerSingleton();; } } diff --git a/cesiumjs4gwt-showcase/src/main/java/org/cleanlogic/cesiumjs4gwt/showcase/examples/CustomShadersModels.java b/cesiumjs4gwt-showcase/src/main/java/org/cleanlogic/cesiumjs4gwt/showcase/examples/CustomShadersModels.java new file mode 100644 index 0000000000000000000000000000000000000000..22819ae69b25a3c1890eef6ef361fdcc777fb306 --- /dev/null +++ b/cesiumjs4gwt-showcase/src/main/java/org/cleanlogic/cesiumjs4gwt/showcase/examples/CustomShadersModels.java @@ -0,0 +1,360 @@ +/* + * Copyright 2021 iserge, Gis4Fun. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cleanlogic.cesiumjs4gwt.showcase.examples; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.typedarrays.client.Uint8ArrayNative; +import com.google.gwt.typedarrays.shared.Uint8Array; +import com.google.gwt.user.client.ui.*; +import org.cesiumjs.cs.Cesium; +import org.cesiumjs.cs.core.*; +import org.cesiumjs.cs.core.HeadingPitchRoll; +import org.cesiumjs.cs.core.enums.PixelFormat; +import org.cesiumjs.cs.core.enums.ScreenSpaceEventType; +import org.cesiumjs.cs.core.events.MouseClickEvent; +import org.cesiumjs.cs.core.events.MouseMoveEvent; +import org.cesiumjs.cs.scene.enums.TextureMagnificationFilter; +import org.cesiumjs.cs.scene.enums.TextureMinificationFilter; +import org.cesiumjs.cs.scene.experimental.CustomShader; +import org.cesiumjs.cs.scene.experimental.ModelExperimental; +import org.cesiumjs.cs.scene.experimental.TextureUniform; +import org.cesiumjs.cs.scene.experimental.enums.UniformType; +import org.cesiumjs.cs.scene.experimental.options.CustomShaderOptions; +import org.cesiumjs.cs.scene.experimental.options.ModelExperimentalFromGltfOptions; +import org.cesiumjs.cs.scene.experimental.options.TextureUniformOptions; +import org.cesiumjs.cs.scene.options.CameraFlyToOptions; +import org.cesiumjs.cs.widgets.ViewerPanel; +import org.cesiumjs.cs.widgets.options.ViewerOptions; +import org.cleanlogic.cesiumjs4gwt.showcase.basic.AbstractExample; +import org.cleanlogic.cesiumjs4gwt.showcase.components.store.ShowcaseExampleStore; + +import javax.inject.Inject; + +public class CustomShadersModels extends AbstractExample { + private ViewerPanel csVPanel; + private Cartesian3 position; + private HeadingPitchRoll hpr; + private Transforms.LocalFrameToFixedFrame fixedFrameTransform; + + private final String balloon = GWT.getModuleBaseURL() + "SampleData/models/CesiumBalloon/CesiumBalloon.glb"; + private final String drone = GWT.getModuleBaseURL() + "SampleData/models/CesiumDrone/CesiumDrone.glb"; + private final String pawns = GWT.getModuleBaseURL() + "SampleData/models/CesiumDrone/Pawns.glb"; + private final String milkTruck = GWT.getModuleBaseURL() + "SampleData/models/CesiumMilkTruck/CesiumMilkTruck.glb"; + private final String groundVehicle = GWT.getModuleBaseURL() + "SampleData/models/GroundVehicle/GroundVehicle.glb"; + + private CustomShader expandModelShader; + private CustomShader textureUniformShader; + private CustomShader checkerboardMaskShader; + private CustomShader checkerboardAlphaShader; + private CustomShader checkerboardHolesShader; + private CustomShader gradientShader; + private CustomShader modifyPbrShader; + + private boolean needsDrag = false; + private boolean dragActive = false; + + @Inject + public CustomShadersModels(ShowcaseExampleStore store) { + super("Custom Shaders Models", + "Create 3D models using glTF 3D Tiles Next", + new String[]{"Showcase", "Cesium", "3d", "Viewer", "experimental"}, store, "1.87.1"); + } + + @Override + public void buildPanel() { + Cesium.ExperimentalFeatures.enableModelExperimental = true; + + ViewerOptions options = new ViewerOptions(); + options.orderIndependentTranslucency = false; + csVPanel = new ViewerPanel(options); + + csVPanel.getViewer().clock().currentTime = JulianDate.fromIso8601("2021-11-09T20:27:37.016064475348684937Z"); + + // Model positioning =============================================== + + position = Cartesian3.fromDegrees(-123.0744619, 44.0503706, 0); + hpr = new HeadingPitchRoll(0, 0, 0); + fixedFrameTransform = Transforms.localFrameToFixedFrameGenerator("north", "west"); + + // Custom Shader Definitions ======================================== + + // Dragging the mouse will expand/shrink the model. + expandModelShader = new CustomShader(new CustomShaderOptions() + .addUniform("u_drag", UniformType.VEC2(), new Cartesian2(0.0, 0.0)) + .setVertexShaderText(String.join("\n", new String[] { + // If the mouse is dragged to the right, the model grows + // If the mouse is dragged to the left, the model shrinks + "void vertexMain(VertexInput vsInput, inout vec3 positionMC)", + "{", + " positionMC += 0.01 * u_drag.x * vsInput.attributes.normalMC;", + "}", + }))); + + textureUniformShader = new CustomShader(new CustomShaderOptions() + .addUniform("u_time", UniformType.FLOAT(), 0) + .addUniform("u_stripes", UniformType.SAMPLER_2D(), TextureUniform.create(GWT.getModuleBaseURL() + "SampleData/cesium_stripes.png")) + .setFragmentShaderText(String.join("\n", new String[] { + "void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)", + "{", + " vec2 texCoord = fsInput.attributes.texCoord_0 + 0.1 * vec2(u_time, 0.0);", + " material.diffuse = texture2D(u_stripes, texCoord).rgb;", + "}", + }))); + + TextureUniform checkerboardTexture = makeCheckerboardTexture(8); + Cesium.log(checkerboardTexture.typedArray()); + Cesium.log(checkerboardTexture.height()); + Cesium.log(checkerboardTexture.pixelDatatype()); + + // Use the checkerboard red channel as a mask + checkerboardMaskShader = new CustomShader(new CustomShaderOptions() + .addUniform("u_checkerboard", UniformType.SAMPLER_2D(), checkerboardTexture) + .setFragmentShaderText(String.join("\n", new String[] { + "void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)", + "{", + " vec2 texCoord = fsInput.attributes.texCoord_0;", + " vec4 checkerboard = texture2D(u_checkerboard, texCoord);", + " material.diffuse = mix(material.diffuse, vec3(0.0), checkerboard.r);", + "}", + }))); + + // Color like a checkerboard but make the transparency vary with + // the diagonal + checkerboardAlphaShader = new CustomShader(new CustomShaderOptions() + .addUniform("u_checkerboard", UniformType.SAMPLER_2D(), checkerboardTexture) + .setTranslucent(true) + .setFragmentShaderText(String.join("\n", new String[] { + "void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)", + "{", + " vec2 texCoord = fsInput.attributes.texCoord_0;", + " vec4 checkerboard = texture2D(u_checkerboard, texCoord);", + " material.diffuse = checkerboard.rgb;", + " material.alpha = checkerboard.a;", + "}", + }))); + + // Use the checkerboard to cut holes in the model + checkerboardHolesShader = new CustomShader(new CustomShaderOptions() + .addUniform("u_checkerboard", UniformType.SAMPLER_2D(), checkerboardTexture) + .setFragmentShaderText(String.join("\n", new String[] { + "void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)", + "{", + " vec2 texCoord = fsInput.attributes.texCoord_0;", + " vec4 checkerboard = texture2D(u_checkerboard, texCoord);", + " if (checkerboard.r > 0.0) {", + " discard;", + " }", + "}", + }))); + + TextureUniform gradientTexture = makeGradientTexture(); + // Color the texture along its UV coordinates. + gradientShader = new CustomShader(new CustomShaderOptions() + .addUniform("u_gradient", UniformType.SAMPLER_2D(), gradientTexture) + .setFragmentShaderText(String.join("\n", new String[] { + "void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)", + "{", + " material.diffuse = texture2D(u_gradient, fsInput.attributes.texCoord_0).rgb;", + "}", + }))); + + // Dragging the mouse will modify the PBR values + modifyPbrShader = new CustomShader(new CustomShaderOptions() + .addUniform("u_drag", UniformType.VEC2(), new Cartesian2(0., 0.)) + .setFragmentShaderText(String.join("\n", new String[] { + "void fragmentMain(FragmentInput vsInput, inout czm_modelMaterial material)", + "{", + " float dragDistance = length(u_drag);", + " float variation = smoothstep(0.0, 300.0, dragDistance);", + // variation adds an golden tint to the specular highlights + " material.specular = mix(material.specular, vec3(0.8, 0.5, 0.1), variation);", + // variation makes the material glossier + " material.roughness = clamp(1.0 - variation, 0.01, 1.0);", + // variation mixes some red into the diffuse color + " material.diffuse += vec3(0.5, 0.0, 0.0) * variation;", + "}", + }))); + + + + // Event handlers ===================================================== + double startTime = performanceNow(); + csVPanel.getViewer().scene().postUpdate().addEventListener((Event.Listener) o -> textureUniformShader.setUniform("u_time", (performanceNow() - startTime) / 1000)); + + Cartesian2 dragCenter = new Cartesian2(); + csVPanel.getViewer().screenSpaceEventHandler().setInputAction(event -> { + if (!needsDrag) { + return; + } + MouseClickEvent movement = (MouseClickEvent) event; + + PickedObject pickedFeature = csVPanel.getViewer().scene().pick(movement.position); + if (!Cesium.defined(pickedFeature)) { + return; + } + + csVPanel.getViewer().scene().screenSpaceCameraController().enableInputs = false; + + // set the new drag center + dragActive = true; + movement.position.clone(dragCenter); + }, ScreenSpaceEventType.LEFT_DOWN()); + + Cartesian2 scratchDrag = new Cartesian2(); + csVPanel.getViewer().screenSpaceEventHandler().setInputAction(event -> { + if (!needsDrag) { + return; + } + MouseMoveEvent movement = (MouseMoveEvent) event; + + if (dragActive) { + // get the mouse position relative to the center of the screen + Cartesian2 drag = Cartesian2.subtract(movement.endPosition, dragCenter, scratchDrag); + + // Update uniforms + expandModelShader.setUniform("u_drag", drag); + modifyPbrShader.setUniform("u_drag", drag); + } + }, ScreenSpaceEventType.MOUSE_MOVE()); + + csVPanel.getViewer().screenSpaceEventHandler().setInputAction(event -> { + if (!needsDrag) { + return; + } + + csVPanel.getViewer().scene().screenSpaceCameraController().enableInputs = true; + + dragActive = false; + }, ScreenSpaceEventType.LEFT_UP()); + + ListBox listBox = new ListBox(); + listBox.addItem("Custom Texture"); + listBox.addItem("Procedural Texture"); + listBox.addItem("Translucent materials"); + listBox.addItem("Use Texture as Mask"); + listBox.addItem("Procedural Gradient Texture"); + listBox.addItem("Modify PBR values via Mouse Drag"); + listBox.addItem("Expand Model via Mouse Drag"); + listBox.addChangeHandler(event -> { + String value = ((ListBox) event.getSource()).getSelectedItemText(); + switch (value) { + case "Custom Texture": selectModel(groundVehicle, textureUniformShader); needsDrag = false; break; + case "Procedural Texture": selectModel(balloon, checkerboardMaskShader); needsDrag = false; break; + case "Translucent materials": selectModel(balloon, checkerboardAlphaShader); needsDrag = false; break; + case "Use Texture as Mask": selectModel(balloon, checkerboardHolesShader); needsDrag = false; break; + case "Procedural Gradient Texture": selectModel(balloon, gradientShader); needsDrag = false; break; + case "Modify PBR values via Mouse Drag": selectModel(groundVehicle, modifyPbrShader); needsDrag = true; break; + case "Expand Model via Mouse Drag": selectModel(milkTruck, expandModelShader); needsDrag = true; break; + default: break; + } + }); + + HorizontalPanel hPanel = new HorizontalPanel(); + hPanel.add(listBox); + + AbsolutePanel aPanel = new AbsolutePanel(); + aPanel.add(csVPanel); + aPanel.add(hPanel, 20, 20); + + contentPanel.add(new HTML( + "

Create 3D models using glTF 3D Tiles Next.

")); + contentPanel.add(aPanel); + + initWidget(contentPanel); + + selectModel(groundVehicle, textureUniformShader); + } + + @Override + public String[] getSourceCodeURLs() { + String[] sourceCodeURLs = new String[1]; + sourceCodeURLs[0] = GWT.getModuleBaseURL() + "examples/" + "CustomShadersModels.txt"; + return sourceCodeURLs; + } + + // make a checkerboard texture with an alpha that increases with the + // diagonal number + private TextureUniform makeCheckerboardTexture(int textureSize) { + Uint8Array checkerboard = Uint8ArrayNative.create(4 * textureSize * textureSize); + + int maxDiagonal = 2 * (textureSize - 1); + for (int i = 0; i < textureSize; i++) { + for (int j = 0; j < textureSize; j++) { + int index = i * textureSize + j; + // Checking the parity of the diagonal number gives a checkerboard + // pattern. + int diagonal = i + j; + if (diagonal % 2 == 0) { + // set the square red. We only need to set the red channel! + checkerboard.set(4 * index, 255); + } + // otherwise we'd set the square to black. But arrays are already + // initialized to 0s so nothing needed here. + + // for the alpha channel, map the diagonal number to [0, 255] + checkerboard.set(4 * index + 3, (255 * diagonal) / maxDiagonal); + } + } + return new TextureUniform(new TextureUniformOptions() + .setTypedArray(checkerboard) + .setWidth(textureSize) + .setHeight(textureSize) + .setMinificationFilter(TextureMinificationFilter.NEAREST()) + .setMagnificationFilter(TextureMagnificationFilter.NEAREST())); + } + + // This example is to demonstrate the conventions used for orienting + // the texture. +x is to the right and +y is from **bottom to top**. + // This is to be consistent with WebGL conventions. + // + // This example also demonstrates how to use a different pixel format, + // in this case, RGB. + private TextureUniform makeGradientTexture() { + int size = 256; + Uint8Array typedArray = Uint8ArrayNative.create(3 * size * size); + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + int index = i * size + j; + // red increases in the +x direction (to the right) + typedArray.set(3 * index + 0, j); + // Green increases in the +y direction (from bottom to top) + typedArray.set(3 * index + 1, i); + // blue is 0 so it is omitted. + } + } + + return new TextureUniform(new TextureUniformOptions() + .setTypedArray(typedArray) + .setWidth(size) + .setHeight(size) + .setPixelFormat(PixelFormat.RGB())); + } + + private void selectModel(String url, CustomShader customShader) { + csVPanel.getViewer().scene().primitives().removeAll(); + ModelExperimentalFromGltfOptions options = ModelExperimentalFromGltfOptions.create(url); + options.customShader = customShader; + options.modelMatrix = Transforms.headingPitchRollToFixedFrame(position, hpr, Ellipsoid.WGS84(), fixedFrameTransform); + ModelExperimental model = (ModelExperimental) csVPanel.getViewer().scene().primitives().add(ModelExperimental.fromGltf(options)); + + model.readyPromise().then(value -> csVPanel.getViewer().camera.flyToBoundingSphere(value.boundingSphere(), new CameraFlyToOptions().setDuration(0.5))); + } + + private static native double performanceNow() /*-{ + return performance.now(); + }-*/; +} \ No newline at end of file diff --git a/cesiumjs4gwt-showcase/src/main/resources/org/cleanlogic/cesiumjs4gwt/public/examples/Custom Shaders Models.jpg b/cesiumjs4gwt-showcase/src/main/resources/org/cleanlogic/cesiumjs4gwt/public/examples/Custom Shaders Models.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d2e8b64a39ec03201ddbf8f2eabea1bc7ff15865 Binary files /dev/null and b/cesiumjs4gwt-showcase/src/main/resources/org/cleanlogic/cesiumjs4gwt/public/examples/Custom Shaders Models.jpg differ diff --git a/cesiumjs4gwt-showcase/src/main/resources/org/cleanlogic/cesiumjs4gwt/public/examples/CustomShadersModels.txt b/cesiumjs4gwt-showcase/src/main/resources/org/cleanlogic/cesiumjs4gwt/public/examples/CustomShadersModels.txt new file mode 100644 index 0000000000000000000000000000000000000000..346c1905887795b9b40e5619c3fac901f2e1f646 --- /dev/null +++ b/cesiumjs4gwt-showcase/src/main/resources/org/cleanlogic/cesiumjs4gwt/public/examples/CustomShadersModels.txt @@ -0,0 +1,341 @@ +package org.cleanlogic.cesiumjs4gwt.showcase.examples; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.typedarrays.client.Uint8ArrayNative; +import com.google.gwt.typedarrays.shared.Uint8Array; +import com.google.gwt.user.client.ui.*; +import org.cesiumjs.cs.Cesium; +import org.cesiumjs.cs.core.*; +import org.cesiumjs.cs.core.HeadingPitchRoll; +import org.cesiumjs.cs.core.enums.PixelFormat; +import org.cesiumjs.cs.core.enums.ScreenSpaceEventType; +import org.cesiumjs.cs.core.events.MouseClickEvent; +import org.cesiumjs.cs.core.events.MouseMoveEvent; +import org.cesiumjs.cs.scene.enums.TextureMagnificationFilter; +import org.cesiumjs.cs.scene.enums.TextureMinificationFilter; +import org.cesiumjs.cs.scene.experimental.CustomShader; +import org.cesiumjs.cs.scene.experimental.ModelExperimental; +import org.cesiumjs.cs.scene.experimental.TextureUniform; +import org.cesiumjs.cs.scene.experimental.enums.UniformType; +import org.cesiumjs.cs.scene.experimental.options.CustomShaderOptions; +import org.cesiumjs.cs.scene.experimental.options.ModelExperimentalFromGltfOptions; +import org.cesiumjs.cs.scene.experimental.options.TextureUniformOptions; +import org.cesiumjs.cs.scene.options.CameraFlyToOptions; +import org.cesiumjs.cs.widgets.ViewerPanel; +import org.cesiumjs.cs.widgets.options.ViewerOptions; +import org.cleanlogic.cesiumjs4gwt.showcase.basic.AbstractExample; +import org.cleanlogic.cesiumjs4gwt.showcase.components.store.ShowcaseExampleStore; + +import javax.inject.Inject; + +public class CustomShadersModels extends AbstractExample { + private ViewerPanel csVPanel; + private Cartesian3 position; + private HeadingPitchRoll hpr; + private Transforms.LocalFrameToFixedFrame fixedFrameTransform; + + private final String balloon = GWT.getModuleBaseURL() + "SampleData/models/CesiumBalloon/CesiumBalloon.glb"; + private final String drone = GWT.getModuleBaseURL() + "SampleData/models/CesiumDrone/CesiumDrone.glb"; + private final String pawns = GWT.getModuleBaseURL() + "SampleData/models/CesiumDrone/Pawns.glb"; + private final String milkTruck = GWT.getModuleBaseURL() + "SampleData/models/CesiumMilkTruck/CesiumMilkTruck.glb"; + private final String groundVehicle = GWT.getModuleBaseURL() + "SampleData/models/GroundVehicle/GroundVehicle.glb"; + + private CustomShader expandModelShader; + private CustomShader textureUniformShader; + private CustomShader checkerboardMaskShader; + private CustomShader checkerboardAlphaShader; + private CustomShader checkerboardHolesShader; + private CustomShader gradientShader; + private CustomShader modifyPbrShader; + + private boolean needsDrag = false; + private boolean dragActive = false; + + @Inject + public CustomShadersModels(ShowcaseExampleStore store) { + super("Custom Shaders Models", + "Create 3D models using glTF 3D Tiles Next", + new String[]{"Showcase", "Cesium", "3d", "Viewer", "experimental"}, store, "1.87.1"); + } + + @Override + public void buildPanel() { + Cesium.ExperimentalFeatures.enableModelExperimental = true; + + ViewerOptions options = new ViewerOptions(); + options.orderIndependentTranslucency = false; + csVPanel = new ViewerPanel(options); + + csVPanel.getViewer().clock().currentTime = JulianDate.fromIso8601("2021-11-09T20:27:37.016064475348684937Z"); + + // Model positioning =============================================== + + position = Cartesian3.fromDegrees(-123.0744619, 44.0503706, 0); + hpr = new HeadingPitchRoll(0, 0, 0); + fixedFrameTransform = Transforms.localFrameToFixedFrameGenerator("north", "west"); + + // Custom Shader Definitions ======================================== + + // Dragging the mouse will expand/shrink the model. + expandModelShader = new CustomShader(new CustomShaderOptions() + .addUniform("u_drag", UniformType.VEC2(), new Cartesian2(0.0, 0.0)) + .setVertexShaderText(String.join("\n", new String[] { + // If the mouse is dragged to the right, the model grows + // If the mouse is dragged to the left, the model shrinks + "void vertexMain(VertexInput vsInput, inout vec3 positionMC)", + "{", + " positionMC += 0.01 * u_drag.x * vsInput.attributes.normalMC;", + "}", + }))); + + textureUniformShader = new CustomShader(new CustomShaderOptions() + .addUniform("u_time", UniformType.FLOAT(), 0) + .addUniform("u_stripes", UniformType.SAMPLER_2D(), TextureUniform.create(GWT.getModuleBaseURL() + "SampleData/cesium_stripes.png")) + .setFragmentShaderText(String.join("\n", new String[] { + "void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)", + "{", + " vec2 texCoord = fsInput.attributes.texCoord_0 + 0.1 * vec2(u_time, 0.0);", + " material.diffuse = texture2D(u_stripes, texCoord).rgb;", + "}", + }))); + + TextureUniform checkerboardTexture = makeCheckerboardTexture(8); + + // Use the checkerboard red channel as a mask + checkerboardMaskShader = new CustomShader(new CustomShaderOptions() + .addUniform("u_checkerboard", UniformType.SAMPLER_2D(), checkerboardTexture) + .setFragmentShaderText(String.join("\n", new String[] { + "void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)", + "{", + " vec2 texCoord = fsInput.attributes.texCoord_0;", + " vec4 checkerboard = texture2D(u_checkerboard, texCoord);", + " material.diffuse = mix(material.diffuse, vec3(0.0), checkerboard.r);", + "}", + }))); + + // Color like a checkerboard but make the transparency vary with + // the diagonal + checkerboardAlphaShader = new CustomShader(new CustomShaderOptions() + .addUniform("u_checkerboard", UniformType.SAMPLER_2D(), checkerboardTexture) + .setTranslucent(true) + .setFragmentShaderText(String.join("\n", new String[] { + "void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)", + "{", + " vec2 texCoord = fsInput.attributes.texCoord_0;", + " vec4 checkerboard = texture2D(u_checkerboard, texCoord);", + " material.diffuse = checkerboard.rgb;", + " material.alpha = checkerboard.a;", + "}", + }))); + + // Use the checkerboard to cut holes in the model + checkerboardHolesShader = new CustomShader(new CustomShaderOptions() + .addUniform("u_checkerboard", UniformType.SAMPLER_2D(), checkerboardTexture) + .setFragmentShaderText(String.join("\n", new String[] { + "void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)", + "{", + " vec2 texCoord = fsInput.attributes.texCoord_0;", + " vec4 checkerboard = texture2D(u_checkerboard, texCoord);", + " if (checkerboard.r > 0.0) {", + " discard;", + " }", + "}", + }))); + + TextureUniform gradientTexture = makeGradientTexture(); + // Color the texture along its UV coordinates. + gradientShader = new CustomShader(new CustomShaderOptions() + .addUniform("u_gradient", UniformType.SAMPLER_2D(), gradientTexture) + .setFragmentShaderText(String.join("\n", new String[] { + "void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)", + "{", + " material.diffuse = texture2D(u_gradient, fsInput.attributes.texCoord_0).rgb;", + "}", + }))); + + // Dragging the mouse will modify the PBR values + modifyPbrShader = new CustomShader(new CustomShaderOptions() + .addUniform("u_drag", UniformType.VEC2(), new Cartesian2(0., 0.)) + .setFragmentShaderText(String.join("\n", new String[] { + "void fragmentMain(FragmentInput vsInput, inout czm_modelMaterial material)", + "{", + " float dragDistance = length(u_drag);", + " float variation = smoothstep(0.0, 300.0, dragDistance);", + // variation adds an golden tint to the specular highlights + " material.specular = mix(material.specular, vec3(0.8, 0.5, 0.1), variation);", + // variation makes the material glossier + " material.roughness = clamp(1.0 - variation, 0.01, 1.0);", + // variation mixes some red into the diffuse color + " material.diffuse += vec3(0.5, 0.0, 0.0) * variation;", + "}", + }))); + + + + // Event handlers ===================================================== + double startTime = performanceNow(); + csVPanel.getViewer().scene().postUpdate().addEventListener((Event.Listener) o -> textureUniformShader.setUniform("u_time", (performanceNow() - startTime) / 1000)); + + Cartesian2 dragCenter = new Cartesian2(); + csVPanel.getViewer().screenSpaceEventHandler().setInputAction(event -> { + if (!needsDrag) { + return; + } + MouseClickEvent movement = (MouseClickEvent) event; + + PickedObject pickedFeature = csVPanel.getViewer().scene().pick(movement.position); + if (!Cesium.defined(pickedFeature)) { + return; + } + + csVPanel.getViewer().scene().screenSpaceCameraController().enableInputs = false; + + // set the new drag center + dragActive = true; + movement.position.clone(dragCenter); + }, ScreenSpaceEventType.LEFT_DOWN()); + + Cartesian2 scratchDrag = new Cartesian2(); + csVPanel.getViewer().screenSpaceEventHandler().setInputAction(event -> { + if (!needsDrag) { + return; + } + MouseMoveEvent movement = (MouseMoveEvent) event; + + if (dragActive) { + // get the mouse position relative to the center of the screen + Cartesian2 drag = Cartesian2.subtract(movement.endPosition, dragCenter, scratchDrag); + + // Update uniforms + expandModelShader.setUniform("u_drag", drag); + modifyPbrShader.setUniform("u_drag", drag); + } + }, ScreenSpaceEventType.MOUSE_MOVE()); + + csVPanel.getViewer().screenSpaceEventHandler().setInputAction(event -> { + if (!needsDrag) { + return; + } + + csVPanel.getViewer().scene().screenSpaceCameraController().enableInputs = true; + + dragActive = false; + }, ScreenSpaceEventType.LEFT_UP()); + + ListBox listBox = new ListBox(); + listBox.addItem("Custom Texture"); + listBox.addItem("Procedural Texture"); + listBox.addItem("Translucent materials"); + listBox.addItem("Use Texture as Mask"); + listBox.addItem("Procedural Gradient Texture"); + listBox.addItem("Modify PBR values via Mouse Drag"); + listBox.addItem("Expand Model via Mouse Drag"); + listBox.addChangeHandler(event -> { + String value = ((ListBox) event.getSource()).getSelectedItemText(); + switch (value) { + case "Custom Texture": selectModel(groundVehicle, textureUniformShader); needsDrag = false; break; + case "Procedural Texture": selectModel(balloon, checkerboardMaskShader); needsDrag = false; break; + case "Translucent materials": selectModel(balloon, checkerboardAlphaShader); needsDrag = false; break; + case "Use Texture as Mask": selectModel(balloon, checkerboardHolesShader); needsDrag = false; break; + case "Procedural Gradient Texture": selectModel(balloon, gradientShader); needsDrag = false; break; + case "Modify PBR values via Mouse Drag": selectModel(groundVehicle, modifyPbrShader); needsDrag = true; break; + case "Expand Model via Mouse Drag": selectModel(milkTruck, expandModelShader); needsDrag = true; break; + default: break; + } + }); + + HorizontalPanel hPanel = new HorizontalPanel(); + hPanel.add(listBox); + + AbsolutePanel aPanel = new AbsolutePanel(); + aPanel.add(csVPanel); + aPanel.add(hPanel, 20, 20); + + contentPanel.add(new HTML( + "

Create 3D models using glTF 3D Tiles Next.

")); + contentPanel.add(aPanel); + + initWidget(contentPanel); + + selectModel(groundVehicle, textureUniformShader); + } + + @Override + public String[] getSourceCodeURLs() { + String[] sourceCodeURLs = new String[1]; + sourceCodeURLs[0] = GWT.getModuleBaseURL() + "examples/" + "CustomShadersModels.txt"; + return sourceCodeURLs; + } + + // make a checkerboard texture with an alpha that increases with the + // diagonal number + private TextureUniform makeCheckerboardTexture(int textureSize) { + Uint8Array checkerboard = Uint8ArrayNative.create(4 * textureSize * textureSize); + + int maxDiagonal = 2 * (textureSize - 1); + for (int i = 0; i < textureSize; i++) { + for (int j = 0; j < textureSize; j++) { + int index = i * textureSize + j; + // Checking the parity of the diagonal number gives a checkerboard + // pattern. + int diagonal = i + j; + if (diagonal % 2 == 0) { + // set the square red. We only need to set the red channel! + checkerboard.set(4 * index, 255); + } + // otherwise we'd set the square to black. But arrays are already + // initialized to 0s so nothing needed here. + + // for the alpha channel, map the diagonal number to [0, 255] + checkerboard.set(4 * index + 3, (255 * diagonal) / maxDiagonal); + } + } + return new TextureUniform(new TextureUniformOptions() + .setTypedArray(checkerboard) + .setWidth(textureSize) + .setHeight(textureSize) + .setMinificationFilter(TextureMinificationFilter.NEAREST()) + .setMagnificationFilter(TextureMagnificationFilter.NEAREST())); + } + + // This example is to demonstrate the conventions used for orienting + // the texture. +x is to the right and +y is from **bottom to top**. + // This is to be consistent with WebGL conventions. + // + // This example also demonstrates how to use a different pixel format, + // in this case, RGB. + private TextureUniform makeGradientTexture() { + int size = 256; + Uint8Array typedArray = Uint8ArrayNative.create(3 * size * size); + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + int index = i * size + j; + // red increases in the +x direction (to the right) + typedArray.set(3 * index + 0, j); + // Green increases in the +y direction (from bottom to top) + typedArray.set(3 * index + 1, i); + // blue is 0 so it is omitted. + } + } + + return new TextureUniform(new TextureUniformOptions() + .setTypedArray(typedArray) + .setWidth(size) + .setHeight(size) + .setPixelFormat(PixelFormat.RGB())); + } + + private void selectModel(String url, CustomShader customShader) { + csVPanel.getViewer().scene().primitives().removeAll(); + ModelExperimentalFromGltfOptions options = ModelExperimentalFromGltfOptions.create(url); + options.customShader = customShader; + options.modelMatrix = Transforms.headingPitchRollToFixedFrame(position, hpr, Ellipsoid.WGS84(), fixedFrameTransform); + ModelExperimental model = (ModelExperimental) csVPanel.getViewer().scene().primitives().add(ModelExperimental.fromGltf(options)); + + model.readyPromise().then(value -> csVPanel.getViewer().camera.flyToBoundingSphere(value.boundingSphere(), new CameraFlyToOptions().setDuration(0.5))); + } + + private static native double performanceNow() /*-{ + return performance.now(); + }-*/; +} \ No newline at end of file