|
40 | 40 | " return;", |
41 | 41 | "}", |
42 | 42 | "", |
| 43 | + "/**", |
| 44 | + " * @typedef {gdjs.RuntimeObject & {__cameraDistance: number, __spotLight: THREE.SpotLight, _getIsCastingShadow: () => boolean}} SpotLightRuntimeObject", |
| 45 | + " */", |
| 46 | + "", |
43 | 47 | "const game = runtimeScene.getGame();", |
| 48 | + "const isInGameEdition = game.isInGameEdition && game.isInGameEdition();", |
44 | 49 | "", |
45 | | - "const visibleLightCountMax = 10;", |
46 | | - "const editorVisibleLightCountMax = 4;", |
47 | | - "let visibleLightCount = 0;", |
48 | | - "gdjs.registerRuntimeScenePreEventsCallback(", |
49 | | - " runtimeScene => {", |
50 | | - " visibleLightCount = 0;", |
51 | | - " });", |
52 | | - "if (gdjs.registerInGameEditorPostStepCallback) {", |
53 | | - " gdjs.registerInGameEditorPostStepCallback(", |
54 | | - " runtimeScene => {", |
55 | | - " visibleLightCount = 0;", |
56 | | - " });", |
57 | | - "}", |
58 | 50 | "", |
59 | 51 | "class SpotLight3DRenderer extends gdjs.CustomRuntimeObject3DRenderer {", |
| 52 | + "", |
60 | 53 | " constructor(", |
61 | 54 | " object,", |
62 | 55 | " instanceContainer,", |
|
92 | 85 | "", |
93 | 86 | " threeObject3D.visible = !this._object.hidden;", |
94 | 87 | "", |
| 88 | + " /** @type {THREE.SpotLight} */", |
| 89 | + " //@ts-ignore", |
| 90 | + " const spotLight = object.__spotLight;", |
| 91 | + "", |
95 | 92 | " const editor = game.getInGameEditor ? game.getInGameEditor() : null;", |
96 | 93 | "", |
97 | | - " if (editor) {", |
98 | | - " const selectedObject = editor._selection.getLastSelectedObject();", |
99 | | - " let parentObject = object;", |
100 | | - " let isSelected = parentObject === selectedObject;", |
101 | | - " const isDirectlySelected = isSelected;", |
102 | | - " while (!isSelected && parentObject.getInstanceContainer()", |
103 | | - " //@ts-ignore", |
104 | | - " .getOwner) {", |
105 | | - " parentObject = parentObject.getInstanceContainer()", |
106 | | - " //@ts-ignore", |
107 | | - " .getOwner();", |
| 94 | + " spotLight.castShadow = false;", |
| 95 | + " spotLight.visible = false;", |
| 96 | + "", |
| 97 | + " if (!object.isHidden()) {", |
| 98 | + " let isSelected = false;", |
| 99 | + " if (editor) {", |
| 100 | + " const selectedObject = editor._selection.getLastSelectedObject();", |
| 101 | + " let parentObject = object;", |
108 | 102 | " isSelected = parentObject === selectedObject;", |
| 103 | + " while (!isSelected && parentObject.getInstanceContainer()", |
| 104 | + " //@ts-ignore", |
| 105 | + " .getOwner) {", |
| 106 | + " parentObject = parentObject.getInstanceContainer()", |
| 107 | + " //@ts-ignore", |
| 108 | + " .getOwner();", |
| 109 | + " isSelected = parentObject === selectedObject;", |
| 110 | + " }", |
109 | 111 | " }", |
110 | | - " /** @type {THREE.SpotLight} */", |
111 | | - " //@ts-ignore", |
112 | | - " const spotLight = object.__spotLight;", |
113 | | - " spotLight.visible = isDirectlySelected || (isSelected && visibleLightCount < editorVisibleLightCountMax);", |
114 | | - " if (spotLight.visible) {", |
115 | | - " visibleLightCount++;", |
116 | | - " }", |
117 | | - " }", |
118 | | - " else if (object.isHidden()) {", |
119 | | - " threeObject3D.visible = false;", |
120 | | - " }", |
121 | | - " else if (visibleLightCount >= visibleLightCountMax) {", |
122 | | - " threeObject3D.visible = false;", |
123 | | - " } else {", |
124 | 112 | " let rootObject = object;", |
125 | 113 | " while (rootObject.getInstanceContainer()", |
126 | 114 | " //@ts-ignore", |
|
129 | 117 | " //@ts-ignore", |
130 | 118 | " .getOwner();", |
131 | 119 | " }", |
132 | | - " const layerName = rootObject.getLayer();", |
133 | | - "", |
134 | | - " const runtimeScene = object.getRuntimeScene();", |
135 | | - " const cameraX = gdjs.evtTools.camera.getCameraX(runtimeScene, layerName, 0);", |
136 | | - " const cameraY = gdjs.evtTools.camera.getCameraY(runtimeScene, layerName, 0);", |
137 | | - " const cameraZ = gdjs.scene3d.camera.getCameraZ(runtimeScene, layerName, 0);", |
138 | | - " const deltaX = rootObject.getX() - cameraX;", |
139 | | - " const deltaY = rootObject.getY() - cameraY;", |
140 | | - " const deltaZ = rootObject.getZ() - cameraZ;", |
| 120 | + " const runtimeScene = rootObject.getRuntimeScene();", |
| 121 | + " let distanceSq = 0;", |
| 122 | + " if (!isSelected) {", |
| 123 | + " const layerName = rootObject.getLayer();", |
| 124 | + " const cameraX = gdjs.evtTools.camera.getCameraX(runtimeScene, layerName, 0);", |
| 125 | + " const cameraY = gdjs.evtTools.camera.getCameraY(runtimeScene, layerName, 0);", |
| 126 | + " const cameraZ = gdjs.scene3d.camera.getCameraZ(runtimeScene, layerName, 0);", |
| 127 | + " const deltaX = rootObject.getX() - cameraX;", |
| 128 | + " const deltaY = rootObject.getY() - cameraY;", |
| 129 | + " const deltaZ = rootObject.getZ() - cameraZ;", |
| 130 | + " distanceSq = deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ;", |
| 131 | + " }", |
141 | 132 | " //@ts-ignore", |
142 | 133 | " const distanceMax = object._getVisibilityDistanceMax();", |
143 | | - " const isWithInRange = deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ <= distanceMax * distanceMax;", |
144 | | - " threeObject3D.visible = isWithInRange;", |
| 134 | + " const isWithInRange = distanceSq <= distanceMax * distanceMax;", |
145 | 135 | " if (isWithInRange) {", |
146 | | - " visibleLightCount++;", |
| 136 | + " getSpotLightManager(", |
| 137 | + " //@ts-ignore", |
| 138 | + " runtimeScene", |
| 139 | + " ).applyVisibilityAndShadow(object, distanceSq);", |
147 | 140 | " }", |
| 141 | + "", |
148 | 142 | " }", |
149 | 143 | "", |
150 | 144 | " // Force the visibility to be checked every frame", |
151 | 145 | " this._isContainerDirty = true;", |
152 | 146 | " }", |
153 | 147 | "}", |
154 | 148 | "", |
| 149 | + "gdjs.registerRuntimeScenePreEventsCallback(", |
| 150 | + " runtimeScene => {", |
| 151 | + " getSpotLightManager(", |
| 152 | + " //@ts-ignore", |
| 153 | + " runtimeScene.getScene()", |
| 154 | + " ).clear();", |
| 155 | + " });", |
| 156 | + "if (gdjs.registerInGameEditorPostStepCallback) {", |
| 157 | + " gdjs.registerInGameEditorPostStepCallback(", |
| 158 | + " inGameEditor => {", |
| 159 | + " getSpotLightManager(", |
| 160 | + " //@ts-ignore", |
| 161 | + " inGameEditor.getEditedInstanceContainer().getScene()", |
| 162 | + " ).clear();", |
| 163 | + " });", |
| 164 | + "}", |
| 165 | + "", |
| 166 | + "/**", |
| 167 | + " * Get the platforms manager of an instance container.", |
| 168 | + " * @param {gdjs.RuntimeScene & {__spotLightManager: SpotLightManager}} runtimeScene", |
| 169 | + " */", |
| 170 | + "function getSpotLightManager(runtimeScene) {", |
| 171 | + " if (!runtimeScene.__spotLightManager) {", |
| 172 | + " // Create the shared manager if necessary.", |
| 173 | + " runtimeScene.__spotLightManager = isInGameEdition ?", |
| 174 | + " new SpotLightManager(4, 1) :", |
| 175 | + " new SpotLightManager(20, 4);", |
| 176 | + " }", |
| 177 | + " return runtimeScene.__spotLightManager;", |
| 178 | + "}", |
| 179 | + "", |
| 180 | + "/** @type {{isInserted: boolean, removedObject: SpotLightRuntimeObject | null}} */", |
| 181 | + "const sortResult = { isInserted: false, removedObject: null };", |
| 182 | + "", |
| 183 | + "/**", |
| 184 | + " * @param objects {Array<SpotLightRuntimeObject>}", |
| 185 | + " * @param maxCount {number}", |
| 186 | + " * @param object {SpotLightRuntimeObject}", |
| 187 | + " * @param distance {number}", |
| 188 | + " */", |
| 189 | + "function insertByDistance(objects, maxCount, object, distance) {", |
| 190 | + " sortResult.isInserted = false;", |
| 191 | + " sortResult.removedObject = null;", |
| 192 | + " let index = objects.length - 1;", |
| 193 | + " for (; index >= 0; index--) {", |
| 194 | + " const other = objects[index];", |
| 195 | + " const otherDistance = other.__cameraDistance;", |
| 196 | + " if (distance >= otherDistance) {", |
| 197 | + " break;", |
| 198 | + " }", |
| 199 | + " }", |
| 200 | + " if (index + 1 >= maxCount) {", |
| 201 | + " return sortResult;", |
| 202 | + " }", |
| 203 | + " object.__cameraDistance = distance", |
| 204 | + " objects.splice(index + 1, 0, object);", |
| 205 | + " sortResult.isInserted = true;", |
| 206 | + " if (objects.length > maxCount) {", |
| 207 | + " sortResult.removedObject = objects.pop();", |
| 208 | + " }", |
| 209 | + " return sortResult;", |
| 210 | + "}", |
| 211 | + "", |
| 212 | + "class SpotLightManager {", |
| 213 | + " /** @type {Array<SpotLightRuntimeObject>} */", |
| 214 | + " visibleObjects = new Array();", |
| 215 | + " /** @type {Array<SpotLightRuntimeObject>} */", |
| 216 | + " shadowObjects = new Array();", |
| 217 | + " /** @type {number} */", |
| 218 | + " maxCount;", |
| 219 | + " /** @type {number} */", |
| 220 | + " shadowCount;", |
| 221 | + "", |
| 222 | + " /**", |
| 223 | + " * @param maxCount {number}", |
| 224 | + " * @param shadowCount {number}", |
| 225 | + " */", |
| 226 | + " constructor(maxCount, shadowCount) {", |
| 227 | + " this.maxCount = maxCount;", |
| 228 | + " this.shadowCount = shadowCount;", |
| 229 | + " }", |
| 230 | + "", |
| 231 | + " clear() {", |
| 232 | + " this.visibleObjects.length = 0;", |
| 233 | + " this.shadowObjects.length = 0;", |
| 234 | + " }", |
| 235 | + "", |
| 236 | + " /**", |
| 237 | + " * @param object {SpotLightRuntimeObject}", |
| 238 | + " * @param distance {number}", |
| 239 | + " */", |
| 240 | + " applyVisibilityAndShadow(object, distance) {", |
| 241 | + " if (object._getIsCastingShadow()) {", |
| 242 | + " const { isInserted, removedObject } = insertByDistance(", |
| 243 | + " this.shadowObjects, this.shadowCount, object, distance);", |
| 244 | + " if (isInserted) {", |
| 245 | + " object.__spotLight.castShadow = true;", |
| 246 | + " }", |
| 247 | + " if (removedObject) {", |
| 248 | + " removedObject.__spotLight.castShadow = false;", |
| 249 | + " }", |
| 250 | + " }", |
| 251 | + " const { isInserted, removedObject } = insertByDistance(", |
| 252 | + " this.visibleObjects, this.maxCount, object, distance);", |
| 253 | + " if (isInserted) {", |
| 254 | + " object.__spotLight.visible = true;", |
| 255 | + " }", |
| 256 | + " if (removedObject) {", |
| 257 | + " removedObject.__spotLight.visible = false;", |
| 258 | + " }", |
| 259 | + " }", |
| 260 | + "}", |
| 261 | + "", |
155 | 262 | "const coneLength = 64;", |
156 | 263 | "", |
157 | 264 | "class SpotLightHelper extends THREE.Object3D {", |
|
333 | 440 | " * @param isCastingShadow {boolean}", |
334 | 441 | " */", |
335 | 442 | " setCastingShadow(isCastingShadow) {", |
336 | | - " this.spotLight.castShadow = isCastingShadow;", |
| 443 | + " // This is applied by applyVisibilityAndShadow", |
337 | 444 | " }", |
338 | 445 | "", |
339 | 446 | " /**", |
|
343 | 450 | " let size = 512;", |
344 | 451 | " switch (shadowQuality) {", |
345 | 452 | " case \"Low\":", |
346 | | - " size = 256;", |
347 | | - " break;", |
| 453 | + " size = 256;", |
| 454 | + " break;", |
348 | 455 | " case \"Medium\":", |
349 | | - " size = 512;", |
350 | | - " break;", |
| 456 | + " size = 512;", |
| 457 | + " break;", |
351 | 458 | " case \"High\":", |
352 | | - " size = 1024;", |
353 | | - " break;", |
| 459 | + " size = 1024;", |
| 460 | + " break;", |
354 | 461 | " }", |
355 | 462 | " this.spotLight.shadow.mapSize.width = size;", |
356 | 463 | " this.spotLight.shadow.mapSize.height = size;", |
|
582 | 689 | "const game = runtimeScene.getGame();", |
583 | 690 | "", |
584 | 691 | "// This is a hack that may break in future releases.", |
585 | | - "// Replace the group that would hold children objects by the emmiter.", |
| 692 | + "// Replace the group that would hold children objects by the light.", |
586 | 693 | "const layer = object.getInstanceContainer().getLayer(object.getLayer());", |
587 | 694 | "const group = object.getRenderer()._threeGroup;", |
588 | 695 | "layer.getRenderer().remove3DRendererObject(group);", |
|
603 | 710 | "object._renderer = spotLight3DRenderer;", |
604 | 711 | "if (game.isInGameEdition && game.isInGameEdition()) {", |
605 | 712 | " const spotLightHelper = new SpotLightHelper(object);", |
| 713 | + " spotLightHelper.rotation.order = 'ZYX';", |
606 | 714 | " spotLightHelper.add(spotLight);", |
607 | 715 | " spotLight3DRenderer._threeGroup = spotLightHelper;", |
608 | 716 | " layer.getRenderer().add3DRendererObject(spotLightHelper);", |
609 | 717 | "}", |
610 | 718 | "else {", |
611 | 719 | " spotLight3DRenderer._threeGroup = spotLight;", |
612 | | - " // The light is added in Three.js scene from doStepPostEvents", |
613 | | - " spotLight3DRenderer._threeGroup = spotLight;", |
614 | 720 | " layer.getRenderer().add3DRendererObject(spotLight);", |
615 | 721 | "}", |
616 | 722 | "", |
|
1135 | 1241 | "objectGroups": [] |
1136 | 1242 | }, |
1137 | 1243 | { |
1138 | | - "description": "the smoothing of the light . Percent of the spotlight cone that is attenuated due to penumbra (between 0 and 1).", |
| 1244 | + "description": "the smoothing of the light. Percent of the spotlight cone that is attenuated due to penumbra (between 0 and 1).", |
1139 | 1245 | "fullName": "Smoothing", |
1140 | 1246 | "functionType": "ExpressionAndCondition", |
1141 | 1247 | "name": "Smoothing", |
|
1361 | 1467 | "const object = objects[0];\r", |
1362 | 1468 | "const value = eventsFunctionContext.getArgument(\"Value\");\r", |
1363 | 1469 | "\r", |
1364 | | - "object.__spotLightAdapter.setSmoothing(value);" |
| 1470 | + "object.__spotLightAdapter.setDecay(value);" |
1365 | 1471 | ], |
1366 | 1472 | "parameterObjects": "Object", |
1367 | 1473 | "useStrict": true, |
|
1996 | 2102 | "value": "", |
1997 | 2103 | "type": "Resource", |
1998 | 2104 | "label": "Image", |
1999 | | - "description": "Shadow casting must be enable for the image to have any effect.", |
| 2105 | + "description": "Shadow casting must be enabled for the image to have any effect.", |
2000 | 2106 | "group": "Shadow", |
2001 | 2107 | "extraInformation": [ |
2002 | 2108 | "image" |
|
2013 | 2119 | "name": "IsCastingShadow" |
2014 | 2120 | }, |
2015 | 2121 | { |
2016 | | - "value": "1000", |
| 2122 | + "value": "2000", |
2017 | 2123 | "type": "Number", |
2018 | | - "label": "Visibilty distance", |
| 2124 | + "label": "Visibility distance", |
2019 | 2125 | "name": "VisibilityDistanceMax" |
2020 | 2126 | }, |
2021 | 2127 | { |
|
2040 | 2146 | "name": "ShadowQuality" |
2041 | 2147 | }, |
2042 | 2148 | { |
2043 | | - "value": "100", |
| 2149 | + "value": "20", |
2044 | 2150 | "type": "Number", |
2045 | 2151 | "unit": "Pixel", |
2046 | 2152 | "label": "Shadow camera near plane", |
|
0 commit comments