; BlitzPointer - Adding Pointers to Blitz. ; Copyright (C) 2015 Xaymar (Michael Fabian Dirks) ; ; This program is free software: you can redistribute it and/or modify ; it under the terms of the GNU Lesser General Public License as ; published by the Free Software Foundation, either version 3 of the ; License, or (at your option) any later version. ; ; This program is distributed in the hope that it will be useful, ; but WITHOUT ANY WARRANTY; without even the implied warranty of ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ; GNU General Public License for more details. ; ; You should have received a copy of the GNU Lesser General Public License ; along with this program. If not, see . ; ---------------------------------------------------------------------------- ; ; Example 7 - Callbacks ; ---------------------------------------------------------------------------- ; ; License: Creative Commons Attribution 2.0 ; Author: Michael Fabian Dirks ; Prerequisite: Example 5 ; In my opinion, Callbacks are one of the best things that people have figured ; out is useful. We can completely combine all our update logic into one single ; function call without ever having to worry about how to handle a new object. ; Well, unless you are a terrible planner anyways. Include "Example_Shared.bb" ExampleInit() SeedRnd MilliSecs() ; We'll begin by creating a basic Type that holds our generic object. By itself ; this generic object does nothing, but we define custom callbacks later on. ; For optimization reasons (saves some memory) we can use a manually defined ; index for our callbacks. ; Using an Index to store our Callbacks is more efficient that storing it for ; each entry. By doing this we can also have multiple functions that use the ; same structure (Update, PhysicsUpdate, Render, ...). It is wise to reserve ; the first index to check for incorrectly created objects. Dim CallbackIndex(10) ; Here we define our Type. Since each object has an Index, we Type TGeneric ; For optimization reasons and saving memory, we just store a callback index. Field Index ; We only need to store the pointer to our Type object, nothing else. Field Pointer End Type Function TGenericUpdate() Local This.TGeneric For This = Each TGeneric If This\Pointer <> 0 Then If BP_CallFunctionII(CallbackIndex(This\Index), This\Pointer) Then Delete This EndIf EndIf Next End Function ; TGeneric will call a function pointer with the following footprint: ; int FUNCTION(pointer) ; We will need to implement this function footprint in every callback. ; Our first Type will hold Cubes that moves up and down. Type TCube ; Our TCube is represented by an actual Cube in the world, so let's store it. Field Entity ; This is the original position of the Cube. Y is dynamically modified in code. Field PosX#, PosY#, PosZ# ; And finally, the time it was created. Field Time End Type ; Assign an index to our new Type (starting at 1) Const CALLBACK_INDEX_TCUBE% = 1 Function TCubeCreate.TCube(X#, Y#, Z#) ; Create our Object Local This.TCube = New TCube This\Entity = CreateCube(Example_SceneRoot) This\PosX = X This\PosY = Y This\PosZ = Z This\Time = MilliSecs() ; And now store it withing TGeneric Local TG.TGeneric = New TGeneric TG\Index = CALLBACK_INDEX_TCUBE ; To get the pointer we need (and can store), we just use Int() on the object. TG\Pointer = Int(This) ; That's all we had to do. Return This End Function Function TCubeCallback%(This.TCube) If CallbackIndex(CALLBACK_INDEX_TCUBE) = 0 Then CallbackIndex(CALLBACK_INDEX_TCUBE) = BP_GetFunctionPointer() Return EndIf ; Safeguard against stupidity (it affect everyone). If This = Null Then Return True Local TimeDiff = (MilliSecs() - This\Time) Local YOffset# = Cos((Cos(This\PosX) * 180) + (Sin(This\PosZ) * 180) + (TimeDiff / 10.0)) * 5 + 5 PositionEntity This\Entity, This\PosX, This\PosY + YOffset, This\PosZ EntityAlpha This\Entity, 1.0 - (TimeDiff / 5000.0) ; We will allow the object to exist for at most 5 seconds. If TimeDiff > 5000 Then TCubeDestroy(This) Return True EndIf Return False End Function Function TCubeDestroy(This.TCube) ; Safeguard against stupidity (it affect everyone). If This = Null Then Return FreeEntity This\Entity Delete This End Function ; And call the function once to automagically register it as a callback. TCubeCallback(Null) ; Next up is our second Type. It will hold a simple sphere that grows over time. Type TSphere ; Again, represented by an actual Sphere in the world. Field Entity ; And the time it was created. Field Time End Type ; And we will assign the second index to it. Const CALLBACK_INDEX_TSPHERE% = 2 Function TSphereCreate.TSphere(X#, Y#, Z#) ; Initialize our TSphere instance. Local This.TSphere = New TSphere This\Entity = CreateSphere(32, Example_SceneRoot) PositionEntity This\Entity, X, Y, Z This\Time = MilliSecs() ; And now store it withing TGeneric Local TG.TGeneric = New TGeneric TG\Index = CALLBACK_INDEX_TSPHERE TG\Pointer = Int(This) Return This End Function Function TSphereUpdate%(This.TSphere) If CallbackIndex(CALLBACK_INDEX_TSPHERE) = 0 Then CallbackIndex(CALLBACK_INDEX_TSPHERE) = BP_GetFunctionPointer() Return EndIf ; Safeguard against stupidity (it affect everyone). If This = Null Then Return True Local TimeDiff = (MilliSecs() - This\Time) Local TimePrc# = TimeDiff / 2500.0 Local Stp#, StpSize# Local R#, G#, B#, A#, Scale# ;Step StpSize# = 0.05:Stp# = 1.0 / (1.0 - StpSize) R = Interp(1, 1, TimePrc * Stp) G = Interp(1, 1, TimePrc * Stp) B = Interp(1, 0, TimePrc * Stp) ;Step StpSize# = 0.20:Stp# = 1.0 / (1.0 - StpSize) R = Interp(R, 1, (TimePrc - 0.05) * Stp) G = Interp(G, 0.75, (TimePrc - 0.05) * Stp) B = Interp(B, 0, (TimePrc - 0.05) * Stp) ;Step StpSize# = 0.25:Stp# = 1.0 / (1.0 - StpSize) R = Interp(R, 1, (TimePrc - 0.25) * Stp) G = Interp(G, 0, (TimePrc - 0.25) * Stp) B = Interp(B, 0, (TimePrc - 0.25) * Stp) ;Step StpSize# = 0.50:Stp# = 1.0 / (1.0 - StpSize) R = Interp(R, 0, (TimePrc - 0.50) * Stp) G = Interp(G, 0, (TimePrc - 0.50) * Stp) B = Interp(B, 0, (TimePrc - 0.50) * Stp) ;AlphaStop 1 at (0.8 of 1.0) A = Interp(1, 0, (TimePrc - 0.8) * 5) Scale = Interp(1, 20, Sin(TimePrc * 90)) ScaleEntity This\Entity, Scale,Scale,Scale EntityColor This\Entity, R * 255, G * 255, B * 255 EntityAlpha This\Entity, A ; We will allow the object to exist for at most 2.5 seconds. If TimeDiff > 2500 Then TSphereDestroy(This) Return True EndIf Return False End Function TSphereUpdate(Null) Function TSphereDestroy(This.TSphere) ; Safeguard against stupidity (it affect everyone). If This = Null Then Return FreeEntity This\Entity Delete This End Function ; And finally, a terrain that we can pick a spawning position from. Local Terrain = CreateTerrain(256, Example_SceneRoot) ScaleEntity Terrain, 1 / 256.0, 64, 1 / 256.0 MoveEntity Terrain, -100, 0, -100 ScaleEntity Terrain, 1 / 256.0 * 201, 64, 1 / 256.0 * 201 EntityColor Terrain, 25, 92, 25 TerrainShading Terrain, True TerrainDetail Terrain, 32767, False EntityPickMode Terrain, 2, True RandomTerrain(Terrain, 256, 8, 8) MoveEntity Example_Camera, 0, 0, -125 ;HideEntity Example_Floor While Not KeyHit(1) TGenericUpdate() ; Space to randomize Terrain If KeyHit(57) Then RandomTerrain(Terrain, 256, Rand(1,8), Rand(1,8)) EndIf Local ZDelta = MouseZSpeed() If KeyHit(2) Or ZDelta > 0 Then CameraPick(Example_Camera, MouseX(), MouseY()) TCubeCreate(PickedX(), PickedY(), PickedZ()) EndIf If KeyHit(3) Or ZDelta < 0 Then CameraPick(Example_Camera, MouseX(), MouseY()) TSphereCreate(PickedX(), PickedY(), PickedZ()) EndIf ExampleUpdate() ExampleLoop() UpdateWorld() Wend Function RandomTerrain(Terrain, GridSize, CellsX = 1, CellsZ = 1) Local CellW, CellH, CellX, CellZ, TerX, TerZ, CellPrcX#, CellPrcZ# Local HTL#, HTR#, HBL#, HBR#, HCC# CellW = Floor(GridSize / Float(CellsX)) CellH = Floor(GridSize / Float(CellsZ)) Local RowSize = CellsZ + 1 Local TempBankSize = (CellsX + 1) * RowSize * 4 Local TempBank = CreateBank(TempBankSize) For CellX = 0 To CellsX For CellZ = 0 To CellsZ PokeFloat TempBank, 4 * (CellX * RowSize + CellZ), Rnd(0, 1) Next Next Local OCellX, OCellZ:OCellX = -1: OCellZ = -1 For TerX = 1 To (GridSize - 1) CellX = Floor(TerX / Float(CellW)) CellPrcX = (TerX Mod CellW) / Float(CellW - 1) For TerZ = 1 To (GridSize - 1) CellZ = Floor(TerZ / Float(CellH)) CellPrcZ = (TerZ Mod CellH) / Float(CellH - 1) ;DebugLog CellPrcZ If CellX <> OCellX Or CellZ <> OCellZ Then HTL = PeekFloat(TempBank, 4 * (((CellX + 0) * RowSize + (CellZ + 0)))) HTR = PeekFloat(TempBank, 4 * (((CellX + 1) * RowSize + (CellZ + 0)))) HBL = PeekFloat(TempBank, 4 * (((CellX + 0) * RowSize + (CellZ + 1)))) HBR = PeekFloat(TempBank, 4 * (((CellX + 1) * RowSize + (CellZ + 1)))) OCellX = CellX:OCellZ = CellZ EndIf Local PrcX#, PrcZ# ; Normally, you'd use a bezier curve for this. PrcX = (Sin(-90 + CellPrcX*180) * 0.5 + 0.5) PrcZ = (Sin(-90 + CellPrcZ*180) * 0.5 + 0.5) Local Height# = Interp(Interp(HTL, HTR, PrcX), Interp(HBL, HBR, PrcX), PrcZ) ModifyTerrain Terrain, TerX, TerZ, Height Next Next FreeBank TempBank End Function Function Min#(A#, B#) If B < A Then Return B Return A End Function Function Max#(A#, B#) If B > A Then Return B Return A End Function Function Interp#(L#, R#, Lerp#) Lerp = Min(Max(Lerp, 0.0), 1.0) Return L * (1.0 - Lerp) + R * Lerp End Function ;~IDEal Editor Parameters: ;~C#Blitz3D