Skip to content

Commit d8043eb

Browse files
committed
Better culling
1 parent abc08a5 commit d8043eb

1 file changed

Lines changed: 172 additions & 66 deletions

File tree

extensions/reviewed/SpotLight3D.json

Lines changed: 172 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -40,23 +40,16 @@
4040
" return;",
4141
"}",
4242
"",
43+
"/**",
44+
" * @typedef {gdjs.RuntimeObject & {__cameraDistance: number, __spotLight: THREE.SpotLight, _getIsCastingShadow: () => boolean}} SpotLightRuntimeObject",
45+
" */",
46+
"",
4347
"const game = runtimeScene.getGame();",
48+
"const isInGameEdition = game.isInGameEdition && game.isInGameEdition();",
4449
"",
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-
"}",
5850
"",
5951
"class SpotLight3DRenderer extends gdjs.CustomRuntimeObject3DRenderer {",
52+
"",
6053
" constructor(",
6154
" object,",
6255
" instanceContainer,",
@@ -92,35 +85,30 @@
9285
"",
9386
" threeObject3D.visible = !this._object.hidden;",
9487
"",
88+
" /** @type {THREE.SpotLight} */",
89+
" //@ts-ignore",
90+
" const spotLight = object.__spotLight;",
91+
"",
9592
" const editor = game.getInGameEditor ? game.getInGameEditor() : null;",
9693
"",
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;",
108102
" 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+
" }",
109111
" }",
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 {",
124112
" let rootObject = object;",
125113
" while (rootObject.getInstanceContainer()",
126114
" //@ts-ignore",
@@ -129,29 +117,148 @@
129117
" //@ts-ignore",
130118
" .getOwner();",
131119
" }",
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+
" }",
141132
" //@ts-ignore",
142133
" 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;",
145135
" if (isWithInRange) {",
146-
" visibleLightCount++;",
136+
" getSpotLightManager(",
137+
" //@ts-ignore",
138+
" runtimeScene",
139+
" ).applyVisibilityAndShadow(object, distanceSq);",
147140
" }",
141+
"",
148142
" }",
149143
"",
150144
" // Force the visibility to be checked every frame",
151145
" this._isContainerDirty = true;",
152146
" }",
153147
"}",
154148
"",
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+
"",
155262
"const coneLength = 64;",
156263
"",
157264
"class SpotLightHelper extends THREE.Object3D {",
@@ -333,7 +440,7 @@
333440
" * @param isCastingShadow {boolean}",
334441
" */",
335442
" setCastingShadow(isCastingShadow) {",
336-
" this.spotLight.castShadow = isCastingShadow;",
443+
" // This is applied by applyVisibilityAndShadow",
337444
" }",
338445
"",
339446
" /**",
@@ -343,14 +450,14 @@
343450
" let size = 512;",
344451
" switch (shadowQuality) {",
345452
" case \"Low\":",
346-
" size = 256;",
347-
" break;",
453+
" size = 256;",
454+
" break;",
348455
" case \"Medium\":",
349-
" size = 512;",
350-
" break;",
456+
" size = 512;",
457+
" break;",
351458
" case \"High\":",
352-
" size = 1024;",
353-
" break;",
459+
" size = 1024;",
460+
" break;",
354461
" }",
355462
" this.spotLight.shadow.mapSize.width = size;",
356463
" this.spotLight.shadow.mapSize.height = size;",
@@ -582,7 +689,7 @@
582689
"const game = runtimeScene.getGame();",
583690
"",
584691
"// 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.",
586693
"const layer = object.getInstanceContainer().getLayer(object.getLayer());",
587694
"const group = object.getRenderer()._threeGroup;",
588695
"layer.getRenderer().remove3DRendererObject(group);",
@@ -603,14 +710,13 @@
603710
"object._renderer = spotLight3DRenderer;",
604711
"if (game.isInGameEdition && game.isInGameEdition()) {",
605712
" const spotLightHelper = new SpotLightHelper(object);",
713+
" spotLightHelper.rotation.order = 'ZYX';",
606714
" spotLightHelper.add(spotLight);",
607715
" spotLight3DRenderer._threeGroup = spotLightHelper;",
608716
" layer.getRenderer().add3DRendererObject(spotLightHelper);",
609717
"}",
610718
"else {",
611719
" spotLight3DRenderer._threeGroup = spotLight;",
612-
" // The light is added in Three.js scene from doStepPostEvents",
613-
" spotLight3DRenderer._threeGroup = spotLight;",
614720
" layer.getRenderer().add3DRendererObject(spotLight);",
615721
"}",
616722
"",
@@ -1135,7 +1241,7 @@
11351241
"objectGroups": []
11361242
},
11371243
{
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).",
11391245
"fullName": "Smoothing",
11401246
"functionType": "ExpressionAndCondition",
11411247
"name": "Smoothing",
@@ -1361,7 +1467,7 @@
13611467
"const object = objects[0];\r",
13621468
"const value = eventsFunctionContext.getArgument(\"Value\");\r",
13631469
"\r",
1364-
"object.__spotLightAdapter.setSmoothing(value);"
1470+
"object.__spotLightAdapter.setDecay(value);"
13651471
],
13661472
"parameterObjects": "Object",
13671473
"useStrict": true,
@@ -1996,7 +2102,7 @@
19962102
"value": "",
19972103
"type": "Resource",
19982104
"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.",
20002106
"group": "Shadow",
20012107
"extraInformation": [
20022108
"image"
@@ -2013,9 +2119,9 @@
20132119
"name": "IsCastingShadow"
20142120
},
20152121
{
2016-
"value": "1000",
2122+
"value": "2000",
20172123
"type": "Number",
2018-
"label": "Visibilty distance",
2124+
"label": "Visibility distance",
20192125
"name": "VisibilityDistanceMax"
20202126
},
20212127
{
@@ -2040,7 +2146,7 @@
20402146
"name": "ShadowQuality"
20412147
},
20422148
{
2043-
"value": "100",
2149+
"value": "20",
20442150
"type": "Number",
20452151
"unit": "Pixel",
20462152
"label": "Shadow camera near plane",

0 commit comments

Comments
 (0)