; Blitz3D+ World Editor ; Unified scene, asset, and validation authoring for .world version 1 files. ; Compile in Extended language mode. Include "world_document.bb" Include "virtual_grid.bb" Include "asset_preview.bb" Include "asset_tiles.bb" Include "scene_workspace.bb" Include "fast_edit.bb" Include "live_workspace.bb" Include "live_assets.bb" Include "scene_viewports.bb" Include "selection_protection.bb" Include "validation_workspace.bb" Const EVENT_KEY_DOWN=$101 Const EVENT_MOUSE_DOWN=$201 Const EVENT_MOUSE_UP=$202 Const EVENT_MOUSE_MOVE=$203 Const EVENT_MOUSE_WHEEL=$204 Const EVENT_GADGET_ACTION=$401 Const EVENT_WINDOW_SIZE=$802 Const EVENT_WINDOW_CLOSE=$803 Const EVENT_MENU_ACTION=$1001 Const MENU_NEW=101 Const MENU_OPEN=102 Const MENU_SAVE=103 Const MENU_SAVE_AS=104 Const MENU_TEST=105 Const MENU_EXIT=109 Const MENU_ADD=201 Const MENU_REMOVE=202 Const MENU_UNDO=203 Const MENU_SELECT_ALL=204 Const MENU_CLEAR_FILTERS=301 Const MENU_WIREFRAME_PREVIEWS=302 Const MENU_ABOUT=401 Const TEST_IDLE=0 Const TEST_LOADING=1 Const TEST_READY=2 Const TEST_FAILED=3 Const TEST_CANCELLING=4 Global gAppWindow:Int=0 Global gAppToolbar:Int=0 Global gAppWorkspaceTabber:Int=0 Global gAppScenePanel:Int=0 Global gAppValidationPanel:Int=0 Global gAppWorkspace:Int=1 Global gAppFilterPanel:Int=0 Global gAppFilterCombo:Int=0 Global gAppSearchField:Int=0 Global gAppClearSearch:Int=0 Global gAppCountLabel:Int=0 Global gAppAddButton:Int=0 Global gAppRemoveButton:Int=0 Global gAppTestButton:Int=0 Global gAppPlaceButton:Int=0 Global gAppSplitter:Int=0 Global gAppGridPane:Int=0 Global gAppDetailPane:Int=0 Global gAppDetailSplitter:Int=0 Global gAppPreviewPane:Int=0 Global gAppInspectorPane:Int=0 Global gAppGridCanvas:Int=0 Global gAppGridSlider:Int=0 Global gAppDetailTitle:Int=0 Global gAppPreviewCanvas:Int=0 Global gAppFrameButton:Int=0 Global gAppResetButton:Int=0 Global gAppWireframeButton:Int=0 Global gAppPlayButton:Int=0 Global gAppAnimationLabel:Int=0 Global gAppAnimationCombo:Int=0 Global gAppAnimationRevision:Int=-1 Global gAppPlaybackState:Int=-1 Global gAppNameLabel:Int=0 Global gAppNameField:Int=0 Global gAppRenameButton:Int=0 Global gAppKindLabel:Int=0 Global gAppKindCombo:Int=0 Global gAppLoadingLabel:Int=0 Global gAppLoadingCombo:Int=0 Global gAppOptionLabel:Int=0 Global gAppOptionField:Int=0 Global gAppApplyOptions:Int=0 Global gAppInfoArea:Int=0 Global gAppTestPanel:Int=0 Global gAppTestTitle:Int=0 Global gAppTestStage:Int=0 Global gAppTestProgress:Int=0 Global gAppTestDetails:Int=0 Global gAppTestCancel:Int=0 Global gAppTestAgain:Int=0 Global gAppFileMenu:Int=0 Global gAppEditMenu:Int=0 Global gAppSaveMenu:Int=0 Global gAppSaveAsMenu:Int=0 Global gAppTestMenu:Int=0 Global gAppRemoveMenu:Int=0 Global gAppUndoMenu:Int=0 Global gAppWireframeMenu:Int=0 Global gAppPopup:Int=0 Global gAppSelected.WacAsset=Null Global gAppRunning:Int=True Global gAppSearchCache$="" Global gAppLastStatus:Int=0 Global gAppLastGridWidth:Int=0 Global gAppLastGridHeight:Int=0 Global gAppLastPreviewWidth:Int=0 Global gAppLastPreviewHeight:Int=0 Global gAppLastInspectorWidth:Int=0 Global gAppLastInspectorHeight:Int=0 Global gAppPreviewDrag:Int=0 Global gAppPreviewLastX:Int=0 Global gAppPreviewLastY:Int=0 Global gAppLastClickKey:Int=0 Global gAppLastClickTime:Int=0 Global gAppLastClickCanvas:Int=0 Global gAppLastClickX:Int=0 Global gAppLastClickY:Int=0 Global gAppTestState:Int=TEST_IDLE Global gAppTestWorld:Int=0 Global gAppTestStarted:Int=0 Global gAppTestReadyAt:Int=0 Global gAppTestLastFrame:Int=0 Global gAppTestLargestFrame:Int=0 Global gAppTestSpinner:Int=0 Global gAppTestResult$="" Global gPickerMode:Int=False Global gPickerRequestFile$="" Global gPickerResultFile$="" Global gPickerRequestId$="" Global gPickerCollection$="" Global gPickerResultWritten:Int=False Global gEditorAssetMode:Int=False Global gEditorInitialFile$="" Global gEditorLiveMode:Int=False Global gEditorSessionDescriptor$="" Function WacAppRefreshVisible() WacRebuildVisible SelectedGadgetItem(gAppFilterCombo),TextFieldText(gAppSearchField) gAppSearchCache=TextFieldText(gAppSearchField) SetGadgetText gAppCountLabel,gWacVisibleCount+" visible / "+gWacAssetCount+" total" WacGridConfigure() End Function Function WacAppChangeView() ; A filtered-out selection must never remain an invisible removal target. WacClearSelection() WacAppSetSelected Null WacAppRefreshVisible() End Function Function WacAppSetInspectorVisible(visible:Int) gAppPlaybackState=-1 If visible Then ShowGadget gAppFrameButton:ShowGadget gAppResetButton:ShowGadget gAppWireframeButton:ShowGadget gAppPlayButton ShowGadget gAppNameLabel:ShowGadget gAppNameField:ShowGadget gAppRenameButton ShowGadget gAppKindLabel:ShowGadget gAppKindCombo ShowGadget gAppLoadingLabel:ShowGadget gAppLoadingCombo ShowGadget gAppOptionLabel:ShowGadget gAppOptionField:ShowGadget gAppApplyOptions ShowGadget gAppInfoArea HideGadget gAppTestPanel Else HideGadget gAppFrameButton:HideGadget gAppResetButton:HideGadget gAppWireframeButton:HideGadget gAppPlayButton HideGadget gAppAnimationLabel:HideGadget gAppAnimationCombo HideGadget gAppNameLabel:HideGadget gAppNameField:HideGadget gAppRenameButton HideGadget gAppKindLabel:HideGadget gAppKindCombo HideGadget gAppLoadingLabel:HideGadget gAppLoadingCombo HideGadget gAppOptionLabel:HideGadget gAppOptionField:HideGadget gAppApplyOptions HideGadget gAppInfoArea ShowGadget gAppTestPanel EndIf End Function Function WacAppUpdateOptionEditor(asset.WacAsset) SetGadgetToolTip gAppOptionField,"" If asset=Null Then SetGadgetText gAppOptionLabel,"Kind option" SetGadgetText gAppOptionField,"" Return EndIf Select asset\kind Case WAC_KIND_MODEL SetGadgetText gAppOptionLabel,"Animated / hierarchy (0 or 1)" SetGadgetToolTip gAppOptionField,"1 preserves model nodes and animation, including static hierarchies. 0 loads a single collapsed mesh." SetGadgetText gAppOptionField,asset\animated Case WAC_KIND_TEXTURE SetGadgetText gAppOptionLabel,"Texture flags" SetGadgetText gAppOptionField,asset\textureFlags Case WAC_KIND_HEIGHTMAP SetGadgetText gAppOptionLabel,"Heightmap options" SetGadgetText gAppOptionField,"None in .world v1" Case WAC_KIND_SOUND SetGadgetText gAppOptionLabel,"Spatial (0 or 1)" SetGadgetText gAppOptionField,asset\spatialSound Case WAC_KIND_SHADER SetGadgetText gAppOptionLabel,"Configuration" SetGadgetText gAppOptionField,asset\shaderConfiguration End Select End Function Function WacAppSetSelected(asset.WacAsset) gAppSelected=asset WacPreviewSelect asset gAppAnimationRevision=-1 gAppLastInspectorHeight=0 WacAppUpdatePlaybackControls() If asset=Null Then SetGadgetText gAppDetailTitle,"Asset details" SetGadgetText gAppNameField,"" SetGadgetText gAppInfoArea,"Select an asset card to inspect it." DisableGadget gAppRenameButton:DisableGadget gAppApplyOptions Return EndIf SetGadgetText gAppDetailTitle,asset\id+" - "+WacKindText(asset\kind) SetGadgetText gAppNameField,asset\id SelectGadgetItem gAppKindCombo,asset\kind-1 SelectGadgetItem gAppLoadingCombo,asset\loading-1 WacAppUpdateOptionEditor asset EnableGadget gAppRenameButton:EnableGadget gAppApplyOptions EnableGadget gAppNameField:EnableGadget gAppKindCombo:EnableGadget gAppLoadingCombo:EnableGadget gAppOptionField If asset\liveProtected Then DisableGadget gAppRenameButton:DisableGadget gAppApplyOptions DisableGadget gAppNameField:DisableGadget gAppKindCombo:DisableGadget gAppLoadingCombo:DisableGadget gAppOptionField EndIf End Function Function WacAppInfoText$(asset.WacAsset) If asset=Null Then Return "Select an asset card to inspect it." Local newline$=Chr(13)+Chr(10) Local text$="Source: "+asset\source+newline text=text+"Class: "+WacKindText(asset\kind)+" ("+Upper(WacExtension(asset\source))+")"+newline text=text+"Loading: "+WacPolicyText(asset\loading)+newline If asset\liveProtected Then text=text+"Protected live-world asset (declaration is read-only)"+newline text=text+"Source file size: "+WacFormatBytes(asset\sourceBytes)+newline text=text+"Preview: "+WacGridState(asset)+newline If asset\error<>"" Then text=text+"Error: "+asset\error+newline Select asset\kind Case WAC_KIND_MODEL text=text+newline+"Model statistics"+newline text=text+"Children: "+asset\childCount+newline text=text+"Material slots (surfaces): "+asset\surfaceCount+newline text=text+"Vertices: "+asset\vertexCount+newline text=text+"Triangles: "+asset\triangleCount+newline text=text+"Animation sequences: "+asset\animationCount+newline Case WAC_KIND_TEXTURE text=text+newline+"Texture dimensions: "+asset\width+" x "+asset\height+newline text=text+"Texture flags: "+asset\textureFlags+newline If asset\height>0 Then text=text+"Aspect ratio: "+(Float(asset\width)/Float(asset\height))+newline Case WAC_KIND_HEIGHTMAP text=text+newline+"Terrain side: "+asset\terrainSide+newline If asset\terrainSide>1 Then Local triangleEstimate:Long=2L*Long(asset\terrainSide-1)*Long(asset\terrainSide-1) text=text+"Full-resolution triangle estimate: "+triangleEstimate+newline EndIf Case WAC_KIND_SOUND text=text+newline+"Spatial sound: "+asset\spatialSound+newline text=text+"Decoded duration: not exposed by this runtime"+newline Case WAC_KIND_SHADER text=text+newline+"Configuration: "+asset\shaderConfiguration+newline End Select text=text+newline+"Resident memory: not available per collection in this runtime"+newline text=text+"Engine texture total: "+RenderTextureMemoryKB()+" KiB"+newline text=text+"Engine geometry total: "+RenderGeometryMemoryKB()+" KiB" Return text End Function Function WacAppUpdatePlaybackControls() If gAppTestState<>TEST_IDLE Then Return If gAppAnimationRevision<>gWacAnimationRevision Then ClearGadgetItems gAppAnimationCombo For animation.WacPreviewAnimation=Each WacPreviewAnimation AddGadgetItem gAppAnimationCombo,animation\label,animation=gWacAnimationSelected Next gAppAnimationRevision=gWacAnimationRevision EndIf Local state:Int=0 If gAppSelected<>Null Then If gAppSelected\kind=WAC_KIND_MODEL Then state=1 If gWacDisplayEntity<>0 Then state=2 If gWacAnimationSelected<>Null Then state=3 If Animating(gWacAnimationSelected\entity) Then state=4 EndIf ElseIf gAppSelected\kind=WAC_KIND_SOUND And gWacDisplaySound<>0 Then state=5 EndIf EndIf ; Do not hide, disable, or rebuild an open sequence dropdown on each tick. If state=gAppPlaybackState Then Return gAppPlaybackState=state If state>=1 And state<=4 Then ShowGadget gAppAnimationLabel:ShowGadget gAppAnimationCombo If state>=3 Then EnableGadget gAppAnimationCombo:EnableGadget gAppPlayButton If state=4 Then SetGadgetText gAppPlayButton,"Stop animation" Else SetGadgetText gAppPlayButton,"Play animation" EndIf Else DisableGadget gAppAnimationCombo:DisableGadget gAppPlayButton If state=1 Then SetGadgetText gAppPlayButton,"Preview pending" Else SetGadgetText gAppPlayButton,"No animations" EndIf Else HideGadget gAppAnimationLabel:HideGadget gAppAnimationCombo If state=5 Then SetGadgetText gAppPlayButton,"Play / Stop sound" EnableGadget gAppPlayButton Else SetGadgetText gAppPlayButton,"Playback unavailable" DisableGadget gAppPlayButton EndIf EndIf End Function Function WacAppTogglePlayback() If gAppSelected=Null Then Return If gAppSelected\kind=WAC_KIND_MODEL Then WacPreviewToggleAnimation() If gAppSelected\kind=WAC_KIND_SOUND Then WacPreviewToggleSound() WacAppUpdatePlaybackControls() End Function Function WacAppUpdateInspector() If gAppTestState<>TEST_IDLE Then Return WacAppUpdatePlaybackControls() If gAppSelected=Null Then Return SetGadgetText gAppDetailTitle,gAppSelected\id+" - "+WacKindText(gAppSelected\kind) SetGadgetText gAppInfoArea,WacAppInfoText(gAppSelected) End Function Function WacAppUpdateCommands() Local editable:Int=Not gEditorLiveMode If gEditorLiveMode Then editable=gLiveAssetsReady And gLiveAssetsRequest=0 And WacLiveCanEdit() Local selected:Int=WacSelectedCount() Local removable:Int=editable And selected>0 For asset.WacAsset=Each WacAsset If asset\selected And asset\liveProtected Then removable=False Next If removable Then EnableGadget gAppRemoveButton:EnableToolBarItem gAppToolbar,4:EnableMenu gAppRemoveMenu Else DisableGadget gAppRemoveButton:DisableToolBarItem gAppToolbar,4:DisableMenu gAppRemoveMenu EndIf If editable Then EnableGadget gAppAddButton:EnableToolBarItem gAppToolbar,2:EnableToolBarItem gAppToolbar,3:EnableMenu gAppEditMenu EnableMenu gAppSaveMenu:EnableMenu gAppSaveAsMenu Else DisableGadget gAppAddButton:DisableToolBarItem gAppToolbar,2:DisableToolBarItem gAppToolbar,3 DisableMenu gAppSaveMenu If gEditorLiveMode Then EnableMenu gAppSaveAsMenu Else DisableMenu gAppSaveAsMenu EndIf If gWacUndoCount>0 And editable Then EnableMenu gAppUndoMenu Else DisableMenu gAppUndoMenu If gEditorLiveMode Then SetGadgetText gAppTestButton,"Refresh live assets" If gLiveAssetsCapable And gLiveAssetsRequest=0 Then EnableGadget gAppTestButton:EnableToolBarItem gAppToolbar,5:EnableMenu gAppTestMenu Else DisableGadget gAppTestButton:DisableToolBarItem gAppToolbar,5:DisableMenu gAppTestMenu EndIf If WacLiveCanEdit() Then EnableMenu gAppSaveMenu:EnableMenu gAppSaveAsMenu:EnableToolBarItem gAppToolbar,2 ElseIf gWacFile<>"" And gAppTestState=TEST_IDLE Then EnableGadget gAppTestButton:EnableToolBarItem gAppToolbar,5:EnableMenu gAppTestMenu Else DisableGadget gAppTestButton:DisableToolBarItem gAppToolbar,5:DisableMenu gAppTestMenu EndIf If gPickerMode Then If gAppSelected=Null Then DisableGadget gAppPlaceButton ElseIf gAppSelected\kind=WAC_KIND_MODEL And gWacDirty=False And gWacFile<>"" Then EnableGadget gAppPlaceButton Else DisableGadget gAppPlaceButton EndIf EndIf UpdateWindowMenu gAppWindow End Function Function WacAppUpdateStatus() If gEditorLiveMode Then SetGadgetText gAppWindow,"Blitz3D+ World Editor - Live World" Local liveStatus$="Live DebugWorld session | changes are temporary program state" If gAppWorkspace=1 Then If gLiveAssetsRequest<>0 Then liveStatus=liveStatus+" | Reading supported live assets..." ElseIf gLiveAssetsReady Then liveStatus=liveStatus+" | "+gWacVisibleCount+"/"+gWacAssetCount+" supported assets" Else liveStatus=liveStatus+" | Live assets not loaded" EndIf ElseIf gSceneStatus<>"" Then liveStatus=liveStatus+" | "+gSceneStatus EndIf SetStatusText gAppWindow,liveStatus WacAppUpdateCommands() Return EndIf Local name$=WacFileName(gWacFile) If name="" Then name="Untitled.world" Local marker$="" If gWacDirty Then marker=" *" SetGadgetText gAppWindow,"Blitz3D+ World Editor - "+name+marker Local access$="Editable asset-only world" If gWacEntityCount>0 Then access="Editable scene world ("+gWacEntityCount+" entities)" Local status$=access+" | "+gWacVisibleCount+"/"+gWacAssetCount+" assets | Sources "+WacFormatBytes(WacTotalSourceBytes()) status=status+" | Engine textures "+RenderTextureMemoryKB()+" KiB, geometry "+RenderGeometryMemoryKB()+" KiB" status=status+" | "+gWacPreviewStatus If gWacPreviewWireframe Then status=status+" | Wireframe preview" If gWacWarning<>"" Then status=status+" | "+gWacWarning SetStatusText gAppWindow,status WacAppUpdateCommands() End Function Function WacAppSetWireframePreview(enabled:Int) gWacPreviewWireframe=enabled<>0 If gAppWireframeButton<>0 Then SetButtonState gAppWireframeButton,gWacPreviewWireframe If gAppWireframeMenu<>0 Then If gWacPreviewWireframe Then CheckMenu gAppWireframeMenu Else UncheckMenu gAppWireframeMenu EndIf If gAppWindow<>0 Then UpdateWindowMenu gAppWindow WacAppUpdateStatus() End Function Function WacAppShape(gadget:Int,x:Int,y:Int,width:Int,height:Int) ; This workspace owns its layout explicitly. Prevent the native default ; centre anchors from moving controls before this layout pass commits. SetGadgetLayout gadget,1,0,1,0 SetGadgetShape gadget,x,y,width,height End Function Function WacAppLayout() Local gridWidth:Int=ClientWidth(gAppGridPane) Local gridHeight:Int=ClientHeight(gAppGridPane) If gridWidth<>gAppLastGridWidth Or gridHeight<>gAppLastGridHeight Then WacAppShape gAppGridCanvas,0,0,gridWidth-20,gridHeight WacAppShape gAppGridSlider,gridWidth-20,0,20,gridHeight gAppLastGridWidth=gridWidth:gAppLastGridHeight=gridHeight WacGridConfigure() EndIf Local previewWidth:Int=ClientWidth(gAppPreviewPane) Local previewHeight:Int=ClientHeight(gAppPreviewPane) If previewWidth<>gAppLastPreviewWidth Or previewHeight<>gAppLastPreviewHeight Then Local canvasWidth:Int=previewWidth-24 Local canvasHeight:Int=previewHeight-50 If canvasWidth<1 Then canvasWidth=1 If canvasHeight<1 Then canvasHeight=1 WacAppShape gAppDetailTitle,12,10,canvasWidth,24 WacAppShape gAppPreviewCanvas,12,38,canvasWidth,canvasHeight gAppLastPreviewWidth=previewWidth:gAppLastPreviewHeight=previewHeight EndIf Local width:Int=ClientWidth(gAppInspectorPane) Local height:Int=ClientHeight(gAppInspectorPane) If width=gAppLastInspectorWidth And height=gAppLastInspectorHeight Then Return Local row:Int=8 Local flexibleWidth:Int=width-316 If flexibleWidth<1 Then flexibleWidth=1 WacAppShape gAppFrameButton,12,row,74,27 WacAppShape gAppResetButton,92,row,74,27 WacAppShape gAppWireframeButton,172,row,124,27 WacAppShape gAppPlayButton,304,row,flexibleWidth,27 row=row+35 If gAppSelected<>Null Then If gAppSelected\kind=WAC_KIND_MODEL Then flexibleWidth=width-110 If flexibleWidth<1 Then flexibleWidth=1 WacAppShape gAppAnimationLabel,12,row+4,82,22 WacAppShape gAppAnimationCombo,98,row,flexibleWidth,26 row=row+34 EndIf EndIf flexibleWidth=width-196 If flexibleWidth<1 Then flexibleWidth=1 WacAppShape gAppNameLabel,12,row+4,82,22 WacAppShape gAppNameField,98,row,flexibleWidth,26 WacAppShape gAppRenameButton,width-92,row,80,26 row=row+34 flexibleWidth=width-340 If flexibleWidth<1 Then flexibleWidth=1 WacAppShape gAppKindLabel,12,row+4,82,22 WacAppShape gAppKindCombo,98,row,150,26 WacAppShape gAppLoadingLabel,260,row+4,64,22 WacAppShape gAppLoadingCombo,328,row,flexibleWidth,26 row=row+34 flexibleWidth=width-244 If flexibleWidth<1 Then flexibleWidth=1 WacAppShape gAppOptionLabel,12,row+4,130,22 WacAppShape gAppOptionField,146,row,flexibleWidth,26 WacAppShape gAppApplyOptions,width-92,row,80,26 row=row+36 Local infoWidth:Int=width-24 Local infoHeight:Int=height-row-20 If infoWidth<1 Then infoWidth=1 If infoHeight<1 Then infoHeight=1 WacAppShape gAppInfoArea,12,row,infoWidth,infoHeight Local testPanelHeight:Int=height-24 If testPanelHeight<1 Then testPanelHeight=1 WacAppShape gAppTestPanel,12,4,infoWidth,testPanelHeight WacAppShape gAppTestTitle,12,12,width-52,26 WacAppShape gAppTestStage,12,46,width-52,26 WacAppShape gAppTestProgress,12,78,width-52,22 Local testDetailsHeight:Int=testPanelHeight-172 If testDetailsHeight<1 Then testDetailsHeight=1 Local testButtonY:Int=testPanelHeight-48 If testButtonY<0 Then testButtonY=0 WacAppShape gAppTestDetails,12,108,width-52,testDetailsHeight WacAppShape gAppTestCancel,12,testButtonY,100,28 WacAppShape gAppTestAgain,120,testButtonY,100,28 gAppLastInspectorWidth=width:gAppLastInspectorHeight=height End Function Function WacAppSelectWorkspace(workspace:Int) WacFastCancel():gFastFocus=0 If gEditorLiveMode And workspace=2 Then workspace=0 If workspace<0 Or workspace>2 Then workspace=1 gAppWorkspace=workspace SelectGadgetItem gAppWorkspaceTabber,workspace If workspace=0 Then ShowGadget gAppScenePanel:HideGadget gAppFilterPanel:HideGadget gAppSplitter:HideGadget gAppValidationPanel WacSceneDocumentChanged() ElseIf workspace=1 Then HideGadget gAppScenePanel:ShowGadget gAppFilterPanel:ShowGadget gAppSplitter:HideGadget gAppValidationPanel If gEditorLiveMode And (gLiveAssetsReady=False Or gLiveAssetsDirty) Then WacLiveAssetsRefresh() Else HideGadget gAppScenePanel:HideGadget gAppFilterPanel:HideGadget gAppSplitter:ShowGadget gAppValidationPanel WacValidationRefresh() EndIf End Function Function WacAppSave:Int(saveAs:Int=False) If gEditorLiveMode Then If WorldEditorSessionState(gLiveSession)<>WAC_SESSION_PAUSED Then Notify "Pause the program before saving a world snapshot.",False:Return False Local scope:Int=WacLiveChooseSaveScope() If scope<0 Then Return False Local defaultSnapshot$="Runtime Snapshot.world":If scope=1 Then defaultSnapshot="World Additions.world" Local snapshotName$=RequestFile("Save live world snapshot","world,*",True,defaultSnapshot) If snapshotName="" Then Return False Return WacLiveSaveSnapshot(snapshotName,scope=1) EndIf Local destination$=gWacFile If saveAs Or destination="" Then Local defaultName$=destination If defaultName="" Then defaultName="Untitled.world" destination=RequestFile("Save world","world,*",True,defaultName) If destination="" Then Return False EndIf If WacSaveDocument(destination)=False Then Notify "Unable to save world:"+Chr(13)+Chr(10)+gWacError,True:Return False If gWacWarning<>"" Then Notify "World saved."+Chr(13)+Chr(10)+gWacWarning,False WacPreviewSchedule() Return True End Function Function WacAppCanReplace:Int() If gEditorLiveMode Then Return True If gWacDirty=False Then Return True Local response:Int=Proceed("Save changes to the current world?",False) If response<0 Then Return False If response=1 Then Return WacAppSave(False) Return True End Function Function WacAppNew() If gEditorLiveMode Then Return If WacAppCanReplace()=False Then Return WacPreviewRetireCollection() WacNewDocument("Untitled Collection") WacAppRefreshVisible() WacAppSetSelected Null WacPreviewSchedule(50) End Function Function WacAppOpen() If gEditorLiveMode Then Return If WacAppCanReplace()=False Then Return Local file$=RequestFile("Open .world","world,*",False,gWacFile) If file="" Then Return WacPreviewRetireCollection() If WacLoadDocument(file)=False Then Notify "Unable to open world:"+Chr(13)+Chr(10)+gWacError,True:WacNewDocument():Return WacAppRefreshVisible() WacAppSetSelected Null WacPreviewSchedule(50) End Function Function WacAppChooseKind:Int() Local chooser:Int=CreateWindow("Choose asset class",250,180,390,170,gAppWindow,13) CreateLabel "Asset class",18,20,90,24,chooser Local combo:Int=CreateComboBox(112,16,244,28,chooser) AddGadgetItem combo,"Auto-detect",True AddGadgetItem combo,"Model" AddGadgetItem combo,"Texture" AddGadgetItem combo,"Terrain / heightmap" AddGadgetItem combo,"Sound" AddGadgetItem combo,"Shader" Local okButton:Int=CreateButton("Choose files...",172,68,116,30,chooser) Local cancelButton:Int=CreateButton("Cancel",296,68,60,30,chooser) ActivateWindow chooser Local result:Int=-1 While result=-1 Local eventID:Int=WaitEvent() If eventID=EVENT_WINDOW_CLOSE And EventSource()=chooser Then result=0 If eventID=EVENT_GADGET_ACTION Then If EventSource()=cancelButton Then result=0 If EventSource()=okButton Then result=SelectedGadgetItem(combo)+1 EndIf Wend If result=0 Then result=-1 Else result=result-1 EndIf FreeGadget chooser ActivateWindow gAppWindow Return result End Function Function WacAppAssetFilter$(kind:Int) Select kind Case WAC_KIND_MODEL Return "b3d;3ds;x;gltf;glb,*" Case WAC_KIND_TEXTURE Return "png;bmp;jpg;jpeg;tif;tiff;gif;dds;tga;ktx2,*" Case WAC_KIND_HEIGHTMAP Return "png;bmp,*" Case WAC_KIND_SOUND Return "wav,*" Case WAC_KIND_SHADER Return "b3shader,*" End Select Return "b3d;3ds;x;gltf;glb,png;bmp;jpg;jpeg;tif;tiff;gif;dds;tga;ktx2,wav,b3shader,*" End Function Function WacAppAdd() If gEditorLiveMode And (gLiveAssetsReady=False Or WacLiveCanEdit()=False) Then gSceneStatus="Pause and load the live Assets view before adding assets.":Return Local kind:Int=WacAppChooseKind() If kind<0 Then Return Local files$=RequestFiles("Add assets",WacAppAssetFilter(kind),"") If files="" Then Return Local start:Int=1 Local separator:Int Local file$ Local asset.WacAsset Local lastAsset.WacAsset=Null Local added:Int=0,failed:Int=0 Local firstError$="" While start<=Len(files) separator=Instr(files,Chr(10),start) If separator=0 Then file=Mid(files,start) Else file=Mid(files,start,separator-start) EndIf If Right(file,1)=Chr(13) Then file=Left(file,Len(file)-1) If file<>"" Then asset=WacAddAsset(file,kind) If asset=Null Then failed=failed+1 If firstError="" Then firstError=WacFileName(file)+": "+gWacError Else If added=0 Then WacClearSelection() asset\selected=True lastAsset=asset added=added+1 EndIf EndIf If separator=0 Then Exit start=separator+1 Wend If added=0 Then Notify "Unable to add the selected assets:"+Chr(13)+Chr(10)+firstError,True Return EndIf WacAppRefreshVisible() Local index:Int For index=0 To gWacVisibleCount-1 If gWacVisible(index)=lastAsset Then gWacGridFocusIndex=index:gWacGridAnchorIndex=index:WacGridEnsureVisible index Next WacAppSetSelected lastAsset WacPreviewSchedule() If failed>0 Then Notify "Added "+added+" assets; "+failed+" could not be added."+Chr(13)+Chr(10)+firstError,True End Function Function WacAppRemove() Local count:Int=WacSelectedCount() If count=0 Then Return If count>1 Then If Confirm("Remove "+count+" asset records from this collection?"+Chr(13)+Chr(10)+"Source files will remain on disk.",True)=False Then Return EndIf Local focus:Int=gWacGridFocusIndex If gAppSelected<>Null Then If gAppSelected\selected Then WacAppSetSelected Null EndIf If WacRemoveSelected()=0 Then Notify gWacError,True:Return WacAppRefreshVisible() If focus>=gWacVisibleCount Then focus=gWacVisibleCount-1 If focus>=0 Then WacAppSetSelected(WacGridSelectIndex(focus,False,False)) Else WacAppSetSelected Null WacPreviewSchedule() End Function Function WacAppUndo() If WacUndoRemove()=0 Then Notify gWacError,False:Return WacAppRefreshVisible() Local index:Int For index=0 To gWacVisibleCount-1 If gWacVisible(index)\selected Then gWacGridFocusIndex=index:gWacGridAnchorIndex=index:WacAppSetSelected(gWacVisible(index)):Exit Next WacPreviewSchedule() End Function Function WacAppRename() If gAppSelected=Null Then Return If WacRenameAsset(gAppSelected,TextFieldText(gAppNameField))=False Then Notify gWacError,True:Return WacAppRefreshVisible() WacAppSetSelected gAppSelected WacPreviewSchedule() End Function Function WacAppApplyOptions() If gAppSelected=Null Then Return Local kind:Int=SelectedGadgetItem(gAppKindCombo)+1 Local loading:Int=SelectedGadgetItem(gAppLoadingCombo)+1 Local animated:Int=gAppSelected\animated Local flags:Int=gAppSelected\textureFlags Local spatial:Int=gAppSelected\spatialSound Local configuration$=gAppSelected\shaderConfiguration Local value$=Trim(TextFieldText(gAppOptionField)) Select kind Case WAC_KIND_MODEL animated=Int(value)<>0 Case WAC_KIND_TEXTURE flags=Int(value) Case WAC_KIND_SOUND spatial=Int(value)<>0 Case WAC_KIND_SHADER configuration=value End Select If WacSetAssetOptions(gAppSelected,kind,loading,animated,flags,spatial,configuration)=False Then Notify gWacError,True:Return WacAppRefreshVisible() WacAppSetSelected gAppSelected WacPreviewSchedule() End Function Function WacAppChangeLoading() If gAppSelected=Null Then Return Local loading:Int=SelectedGadgetItem(gAppLoadingCombo)+1 If loading=gAppSelected\loading Then Return If WacSetAssetLoadingPolicy(gAppSelected,loading)=False Then Notify gWacError,True SelectGadgetItem gAppLoadingCombo,gAppSelected\loading-1 Return EndIf WacAppRefreshVisible() If WacFilterMatches(gAppSelected,SelectedGadgetItem(gAppFilterCombo)) And WacSearchMatches(gAppSelected,TextFieldText(gAppSearchField)) Then WacAppSetSelected gAppSelected Else WacClearSelection() WacAppSetSelected Null EndIf End Function Function WacTestShowResult(message$) gAppTestResult=message SetGadgetText gAppTestDetails,message ShowGadget gAppTestAgain SetGadgetText gAppTestCancel,"Close" End Function Function WacTestStart() If gEditorLiveMode Then WacLiveAssetsRefresh():Return If gAppTestState<>TEST_IDLE Then Return If gWacDirty Then If Confirm("Test loads the exact saved .world file. Save this collection now?",False)=False Then Return If WacAppSave(False)=False Then Return EndIf If gWacFile="" Then If WacAppSave(True)=False Then Return EndIf If WacValidate()=False Then Notify "Cannot test this world:"+Chr(13)+Chr(10)+gWacError,True:Return WacPreviewRetireCollection() SetWorld gWacDisplayWorld gAppTestSpinner=CreateCube() EntityColor gAppTestSpinner,74,144,226 ScaleEntity gAppTestSpinner,.7,.7,.7 gWacDisplayEntity=gAppTestSpinner gWacDisplayRadius=1.3 WacPreviewResetCamera() gAppTestWorld=LoadWorldAsync(gWacFile) gWacDisplayMessage="Loading "+gWacWorldName gAppTestStarted=MilliSecs():gAppTestLastFrame=gAppTestStarted gAppTestReadyAt=0:gAppTestLargestFrame=0 gAppTestState=TEST_LOADING SetGadgetText gAppDetailTitle,"LoadWorldAsync test" SetGadgetText gAppTestTitle,"Testing "+gWacWorldName SetGadgetText gAppTestStage,"Submitting asynchronous load..." SetGadgetText gAppTestDetails,"The animated preview remains responsive while LoadWorldAsync runs." SetGadgetText gAppTestCancel,"Cancel" HideGadget gAppTestAgain UpdateProgBar gAppTestProgress,0 WacAppSetInspectorVisible(False) End Function Function WacTestClose() If gAppTestWorld<>0 Then FreeWorld gAppTestWorld:gAppTestWorld=0 If gAppTestSpinner<>0 Then FreeEntity gAppTestSpinner:gAppTestSpinner=0:gWacDisplayEntity=0 gAppTestState=TEST_IDLE gWacDisplayMessage="" WacAppSetInspectorVisible(True) WacPreviewSchedule(40) WacAppSetSelected gAppSelected End Function Function WacTestCancel() If gAppTestState=TEST_LOADING Then If WorldReady(gAppTestWorld) Then WacTestClose() Else CancelWorldLoad gAppTestWorld gAppTestState=TEST_CANCELLING SetGadgetText gAppTestStage,"Cancelling..." EndIf Else WacTestClose() EndIf End Function Function WacTestUpdate() If gAppTestState=TEST_IDLE Then Return UpdateAsyncLoads .9 Local now:Int=MilliSecs() Local frame:Int=now-gAppTestLastFrame gAppTestLastFrame=now If frame>gAppTestLargestFrame Then gAppTestLargestFrame=frame If gAppTestSpinner<>0 Then TurnEntity gAppTestSpinner,.5,1.0,.25 If gAppTestWorld=0 Then Return Local state:Int=WorldLoadState(gAppTestWorld) UpdateProgBar gAppTestProgress,Float(WorldLoadProgress(gAppTestWorld))/1000.0 SetGadgetText gAppTestStage,WorldLoadStage(gAppTestWorld)+" "+(WorldLoadProgress(gAppTestWorld)/10.0)+"% | "+(now-gAppTestStarted)+" ms" gWacDisplayMessage="LoadWorldAsync: "+WorldLoadStage(gAppTestWorld)+" "+(WorldLoadProgress(gAppTestWorld)/10.0)+"%" If state=WORLD_LOAD_FAILED Then gAppTestState=TEST_FAILED WacTestShowResult "Load failed after "+(now-gAppTestStarted)+" ms"+Chr(13)+Chr(10)+WorldLoadError(gAppTestWorld)+Chr(13)+Chr(10)+"Largest observed UI frame: "+gAppTestLargestFrame+" ms"+Chr(13)+Chr(10)+"In-process; caches may be warm." ElseIf state=WORLD_LOAD_CANCELLED Then gAppTestState=TEST_FAILED WacTestShowResult "Load cancelled after "+(now-gAppTestStarted)+" ms."+Chr(13)+Chr(10)+"Largest observed UI frame: "+gAppTestLargestFrame+" ms" ElseIf WorldReady(gAppTestWorld) Then If gAppTestReadyAt=0 Then gAppTestReadyAt=now gAppTestState=TEST_READY EndIf Local streaming$="Streaming complete" If WorldStreaming(gAppTestWorld) Then streaming="Streaming "+(WorldStreamingProgress(gAppTestWorld)/10.0)+"%" WacTestShowResult "Required assets ready in "+(gAppTestReadyAt-gAppTestStarted)+" ms"+Chr(13)+Chr(10)+streaming+Chr(13)+Chr(10)+"Largest observed UI frame: "+gAppTestLargestFrame+" ms"+Chr(13)+Chr(10)+"In-process; caches may be warm." EndIf End Function Function WacAppHandleGridMouse(button:Int,index:Int=-1,eventCanvas:Int=0) If eventCanvas=0 Then eventCanvas=gAppGridCanvas If index<0 Then index=WacGridHitIndex(CanvasMouseX(gAppGridCanvas),CanvasMouseY(gAppGridCanvas)) If index<0 Then gAppLastClickKey=0:Return If button=2 Then gAppLastClickKey=0 If gWacVisible(index)\selected=False Then WacAppSetSelected(WacGridSelectIndex(index,False,False)) ShowPopupMenu gAppPopup,eventCanvas,EventX(),EventY() Return EndIf If button<>1 Then gAppLastClickKey=0:Return Local control:Int=KeyDown(29) Or KeyDown(157) Local shift:Int=KeyDown(42) Or KeyDown(54) Local asset.WacAsset=WacGridSelectIndex(index,control,shift) WacAppSetSelected asset ActivateGadget gAppGridCanvas WacAppPreviewClick asset,eventCanvas,CanvasMouseX(eventCanvas),CanvasMouseY(eventCanvas),control Or shift End Function Function WacAppPreviewClick(asset.WacAsset,canvas:Int,x:Int,y:Int,modified:Int=False) If asset=Null Or modified Then gAppLastClickKey=0:Return Local now:Int=MilliSecs() If asset\editKey=gAppLastClickKey And canvas=gAppLastClickCanvas And now-gAppLastClickTime>=0 And now-gAppLastClickTime<420 And Abs(x-gAppLastClickX)<=WacGridPx(4) And Abs(y-gAppLastClickY)<=WacGridPx(4) Then gAppLastClickKey=0 WacAppPlaceAsset asset Return EndIf gAppLastClickKey=asset\editKey:gAppLastClickTime=now:gAppLastClickCanvas=canvas gAppLastClickX=x:gAppLastClickY=y End Function Function WacAppPlaceAsset(asset.WacAsset) If gProtectFocusActive Then gSceneStatus="Unfocus all before adding objects":Return If asset=Null Then Return If asset\kind<>WAC_KIND_MODEL Then Notify "Only model assets can be placed directly as scene objects. Other asset types are resources used by scene components.",False Return EndIf If gPickerMode Then WacPickerPlace():Return If gEditorLiveMode Then WacLivePlaceAsset asset:Return WacSceneCreateEntity "model",asset\id WacAppSelectWorkspace 0 End Function Function WacAppHandleMenu(command:Int) Select command Case MENU_NEW WacAppNew() Case MENU_OPEN WacAppOpen() Case MENU_SAVE WacAppSave(False) Case MENU_SAVE_AS WacAppSave(True) Case MENU_TEST WacTestStart() Case MENU_EXIT If WacAppCanReplace() Then If gPickerMode Then If WacPickerCancel() Then gAppRunning=False Else gAppRunning=False EndIf EndIf Case MENU_ADD WacAppAdd() Case MENU_REMOVE WacAppRemove() Case MENU_UNDO WacAppUndo() Case MENU_SELECT_ALL WacGridSelectAllVisible() If gWacVisibleCount>0 Then WacAppSetSelected(gWacVisible(0)) Case MENU_CLEAR_FILTERS SelectGadgetItem gAppFilterCombo,0:SetGadgetText gAppSearchField,"":WacAppChangeView() Case MENU_WIREFRAME_PREVIEWS WacAppSetWireframePreview Not gWacPreviewWireframe Case MENU_ABOUT Notify "Blitz3D+ World Editor"+Chr(13)+Chr(10)+"One Blitz3D+ tool for scene, asset, and validation authoring."+Chr(13)+Chr(10)+"All edits use the public WorldDocument transaction API.",False End Select End Function Function WacPickerArgument$(name$) Local command$=CommandLine$() Local lowerCommand$=Lower(command) Local token$=Lower(name) Local position:Int=Instr(lowerCommand,token) If position=0 Then Return "" Local start:Int=position+Len(token) While start<=Len(command) And (Mid(command,start,1)=" " Or Mid(command,start,1)=Chr(9)) start=start+1 Wend If start>Len(command) Then Return "" Local finish:Int If Mid(command,start,1)=Chr(34) Then finish=Instr(command,Chr(34),start+1) If finish=0 Then Return "" Return Mid(command,start+1,finish-start-1) EndIf finish=start While finish<=Len(command) And Mid(command,finish,1)<>" " And Mid(command,finish,1)<>Chr(9) finish=finish+1 Wend Return Mid(command,start,finish-start) End Function Function WacPickerReadRequest:Int() If Instr(Lower(CommandLine$()),"--pick-model")=0 Then Return True gPickerMode=True gPickerRequestFile=WacPickerArgument("--request") gPickerResultFile=WacPickerArgument("--result") If gPickerRequestFile="" Or gPickerResultFile="" Then WacSetError("Picker mode requires --request and --result paths") Return False EndIf Local request:Int=LoadJson(gPickerRequestFile) If request=0 Then WacSetError("Unable to read picker request: "+JsonError()):Return False If JsonKind(request)<>JSON_OBJECT Then FreeJson request:WacSetError("Picker request must be a JSON object"):Return False Local protocol$=WacJsonText(request,"protocol",True) Local version:Long=WacJsonInteger(request,"version",True,0L) gPickerRequestId=WacJsonText(request,"requestId",True) Local mode$=WacJsonText(request,"mode",True) gPickerCollection=WacJsonText(request,"collection",False,"") FreeJson request If gWacError<>"" Then Return False If protocol<>"blitz3d.world-asset-picker" Or version<>1L Or mode<>"select-model" Or gPickerRequestId="" Then WacSetError("Unsupported or incomplete world asset picker request") Return False EndIf Return True End Function Function WacEditorFirstArgument$() Local command$=Trim(CommandLine$()) If command="" Then Return "" If Left(command,1)=Chr(34) Then Local finish:Int=Instr(command,Chr(34),2) If finish>1 Then Return Mid(command,2,finish-2) Return "" EndIf Local space:Int=Instr(command," ") If space=0 Then Return command Return Left(command,space-1) End Function Function WacEditorReadLaunch() Local command$=Trim(CommandLine$()) Local lowerCommand$=Lower(command) If Left(lowerCommand,9)="--session" Then gEditorLiveMode=True gEditorSessionDescriptor=WacPickerArgument("--session") Return EndIf If Left(lowerCommand,8)="--assets" Then gEditorAssetMode=True command=Trim(Mid(command,9)) If command<>"" Then If Left(command,1)=Chr(34) Then Local finish:Int=Instr(command,Chr(34),2) If finish>1 Then gEditorInitialFile=Mid(command,2,finish-2) Else gEditorInitialFile=command EndIf EndIf Return EndIf If Left(lowerCommand,2)="--" Then Return gEditorInitialFile=WacEditorFirstArgument() End Function Function WacPickerWriteResult:Int(status$,asset.WacAsset) Local result:Int=CreateJsonObject() WacJsonSetString result,"protocol","blitz3d.world-asset-picker" WacJsonSetInteger result,"version",1L WacJsonSetString result,"requestId",gPickerRequestId WacJsonSetString result,"status",status If gWacFile<>"" Then WacJsonSetString result,"collection",gWacFile Else WacJsonSetString result,"collection",gPickerCollection If status="selected" Then If asset<>Null Then WacJsonSetString result,"collectionSha256",Sha256File(gWacFile) WacJsonSetString result,"assetId",asset\id WacJsonSetString result,"kind","model" EndIf EndIf Local saved:Int=SaveJsonAtomic(result,gPickerResultFile,2) FreeJson result If saved=0 Then WacSetError("Unable to write picker result: "+FilesystemError()):Return False gPickerResultWritten=True Return True End Function Function WacPickerPlace:Int() If gAppSelected=Null Then Notify "Select a model asset to place in the world.",False Return False EndIf If gAppSelected\kind<>WAC_KIND_MODEL Then Notify "Select a model asset to place in the world.",False Return False EndIf If gWacDirty Or gWacFile="" Then Notify "Save the collection before placing this model.",False Return False EndIf If WacValidate()=False Then Notify "The collection is not valid:"+Chr(13)+Chr(10)+gWacError,True:Return False If Len(Sha256File(gWacFile))<>64 Then Notify "The saved collection could not be hashed:"+Chr(13)+Chr(10)+FilesystemError(),True:Return False If WacPickerWriteResult("selected",gAppSelected)=False Then Notify gWacError,True:Return False gAppRunning=False Return True End Function Function WacPickerCancel:Int() If gPickerResultWritten Then Return True If WacPickerWriteResult("cancelled",Null)=False Then Notify gWacError,True:Return False Return True End Function ; ----- Native GUI shell ----- WacEditorReadLaunch() If WacPickerReadRequest()=False Then RuntimeError gWacError If gEditorLiveMode And gEditorSessionDescriptor="" Then RuntimeError "Live mode requires a session descriptor" gAppWindow=CreateWindow("Blitz3D+ World Editor",70,45,1320,850,0,15) SetMinWindowSize gAppWindow,980,680 SetStatusText gAppWindow,"Ready" Local menuRoot:Int=WindowMenu(gAppWindow) gAppFileMenu=CreateMenu("&File",0,menuRoot) CreateMenu "&New",MENU_NEW,gAppFileMenu CreateMenu "&Open...",MENU_OPEN,gAppFileMenu gAppSaveMenu=CreateMenu("&Save",MENU_SAVE,gAppFileMenu) gAppSaveAsMenu=CreateMenu("Save &As...",MENU_SAVE_AS,gAppFileMenu) CreateMenu "",0,gAppFileMenu gAppTestMenu=CreateMenu("&Test LoadWorldAsync",MENU_TEST,gAppFileMenu) CreateMenu "",0,gAppFileMenu CreateMenu "E&xit",MENU_EXIT,gAppFileMenu gAppEditMenu=CreateMenu("&Edit",0,menuRoot) CreateMenu "&Add Assets...",MENU_ADD,gAppEditMenu gAppRemoveMenu=CreateMenu("&Remove Selected",MENU_REMOVE,gAppEditMenu) gAppUndoMenu=CreateMenu("&Undo Remove",MENU_UNDO,gAppEditMenu) CreateMenu "",0,gAppEditMenu CreateMenu "Select All &Visible",MENU_SELECT_ALL,gAppEditMenu Local viewMenu:Int=CreateMenu("&View",0,menuRoot) CreateMenu "Clear Filters",MENU_CLEAR_FILTERS,viewMenu gAppWireframeMenu=CreateMenu("&Wireframe Preview",MENU_WIREFRAME_PREVIEWS,viewMenu) Local helpMenu:Int=CreateMenu("&Help",0,menuRoot) CreateMenu "&About World Editor",MENU_ABOUT,helpMenu UpdateWindowMenu gAppWindow gAppToolbar=CreateToolBar("",8,4,1296,30,gAppWindow) SetToolBarTips gAppToolbar,"New world,Open world,Save world,Add assets,Remove selected,Test LoadWorldAsync" SetGadgetLayout gAppToolbar,1,1,1,0 gAppWorkspaceTabber=CreateTabber(8,40,ClientWidth(gAppWindow)-16,28,gAppWindow) AddGadgetItem gAppWorkspaceTabber,"Scene" AddGadgetItem gAppWorkspaceTabber,"Assets",True AddGadgetItem gAppWorkspaceTabber,"Validation" SetGadgetLayout gAppWorkspaceTabber,1,1,1,0 gAppFilterPanel=CreatePanel(8,72,1296,42,gAppWindow,1) ; Use the native panel surface throughout, just like the Scene workspace. SetGadgetLayout gAppFilterPanel,1,1,1,0 gAppFilterCombo=CreateComboBox(8,7,180,27,gAppFilterPanel) SetGadgetLayout gAppFilterCombo,1,0,1,0 AddGadgetItem gAppFilterCombo,"All assets",True AddGadgetItem gAppFilterCombo,"Models" AddGadgetItem gAppFilterCombo,"B3D models" AddGadgetItem gAppFilterCombo,"3DS models" AddGadgetItem gAppFilterCombo,"X models" AddGadgetItem gAppFilterCombo,"glTF / GLB models" AddGadgetItem gAppFilterCombo,"Textures" AddGadgetItem gAppFilterCombo,"Terrain / heightmaps" AddGadgetItem gAppFilterCombo,"Sounds" AddGadgetItem gAppFilterCombo,"Shaders" AddGadgetItem gAppFilterCombo,"Required" AddGadgetItem gAppFilterCombo,"Streaming" AddGadgetItem gAppFilterCombo,"Manual" AddGadgetItem gAppFilterCombo,"Errors" gAppSearchField=CreateTextField(196,7,330,27,gAppFilterPanel) SetGadgetHint gAppSearchField,"Search names, files, kinds, extensions, and loading policy" SetGadgetLayout gAppSearchField,1,1,1,0 gAppClearSearch=CreateButton("Clear",534,7,58,27,gAppFilterPanel) SetGadgetLayout gAppClearSearch,0,1,1,0 gAppCountLabel=CreateLabel("0 visible / 0 total",600,11,170,22,gAppFilterPanel) SetGadgetLayout gAppCountLabel,0,1,1,0 gAppAddButton=CreateButton("Add Assets...",778,7,104,27,gAppFilterPanel) SetGadgetLayout gAppAddButton,0,1,1,0 gAppRemoveButton=CreateButton("Remove",890,7,82,27,gAppFilterPanel) SetGadgetLayout gAppRemoveButton,0,1,1,0 gAppTestButton=CreateButton("Test",980,7,76,27,gAppFilterPanel) SetGadgetLayout gAppTestButton,0,1,1,0 gAppPlaceButton=CreateButton("Place in World",1064,7,120,27,gAppFilterPanel) SetGadgetLayout gAppPlaceButton,0,1,1,0 If gPickerMode=False Then HideGadget gAppPlaceButton gAppSplitter=CreateSplitter(8,120,ClientWidth(gAppWindow)-16,ClientHeight(gAppWindow)-128,gAppWindow,1) SetSplitterDragMode gAppSplitter,1 ; guide only; resize asset canvases on release SetGadgetLayout gAppSplitter,1,1,1,1 gAppGridPane=SplitterPanel(gAppSplitter,0) gAppDetailPane=SplitterPanel(gAppSplitter,1) gAppGridCanvas=CreateCanvas(0,0,620,680,gAppGridPane) gAppGridSlider=CreateSlider(620,0,20,680,gAppGridPane,1) gAppDetailSplitter=CreateSplitter(0,0,ClientWidth(gAppDetailPane),ClientHeight(gAppDetailPane),gAppDetailPane,2) SetGadgetLayout gAppDetailSplitter,1,1,1,1 SetSplitterLimits gAppDetailSplitter,220,280 SetSplitterDragMode gAppDetailSplitter,1 gAppPreviewPane=SplitterPanel(gAppDetailSplitter,0) gAppInspectorPane=SplitterPanel(gAppDetailSplitter,1) gAppDetailTitle=CreateLabel("Asset details",12,10,600,24,gAppPreviewPane,2) gAppPreviewCanvas=CreateCanvas(12,38,600,300,gAppPreviewPane) gAppFrameButton=CreateButton("Frame",12,8,74,27,gAppInspectorPane) gAppResetButton=CreateButton("Reset",92,8,74,27,gAppInspectorPane) gAppWireframeButton=CreateButton("Wireframe",172,8,124,27,gAppInspectorPane,2) gAppPlayButton=CreateButton("Playback unavailable",304,8,200,27,gAppInspectorPane) DisableGadget gAppPlayButton gAppAnimationLabel=CreateLabel("Animation",12,47,82,22,gAppInspectorPane) gAppAnimationCombo=CreateComboBox(98,43,400,26,gAppInspectorPane) HideGadget gAppAnimationLabel:HideGadget gAppAnimationCombo gAppNameLabel=CreateLabel("Name / ID",12,47,82,22,gAppInspectorPane) gAppNameField=CreateTextField(98,43,400,26,gAppInspectorPane) gAppRenameButton=CreateButton("Rename",506,43,80,26,gAppInspectorPane) gAppKindLabel=CreateLabel("Class",12,81,82,22,gAppInspectorPane) gAppKindCombo=CreateComboBox(98,77,150,26,gAppInspectorPane) AddGadgetItem gAppKindCombo,"Model",True AddGadgetItem gAppKindCombo,"Texture" AddGadgetItem gAppKindCombo,"Heightmap" AddGadgetItem gAppKindCombo,"Sound" AddGadgetItem gAppKindCombo,"Shader" gAppLoadingLabel=CreateLabel("Loading",260,81,64,22,gAppInspectorPane) gAppLoadingCombo=CreateComboBox(328,77,150,26,gAppInspectorPane) AddGadgetItem gAppLoadingCombo,"Required",True AddGadgetItem gAppLoadingCombo,"Streaming" AddGadgetItem gAppLoadingCombo,"Manual" gAppOptionLabel=CreateLabel("Kind option",12,115,130,22,gAppInspectorPane) gAppOptionField=CreateTextField(146,111,350,26,gAppInspectorPane) gAppApplyOptions=CreateButton("Apply",506,111,80,26,gAppInspectorPane) gAppInfoArea=CreateTextArea(12,147,600,180,gAppInspectorPane,1) LockTextArea gAppInfoArea SetTextAreaColor gAppInfoArea,34,43,56,0 gAppTestPanel=CreateGroupBox("LoadWorldAsync test",12,4,600,320,gAppInspectorPane) gAppTestTitle=CreateLabel("Testing collection",12,12,560,26,gAppTestPanel,2) gAppTestStage=CreateLabel("Waiting",12,46,560,26,gAppTestPanel) gAppTestProgress=CreateProgBar(12,78,560,22,gAppTestPanel) gAppTestDetails=CreateTextArea(12,108,560,150,gAppTestPanel,1) LockTextArea gAppTestDetails gAppTestCancel=CreateButton("Cancel",12,270,100,28,gAppTestPanel) gAppTestAgain=CreateButton("Run Again",120,270,100,28,gAppTestPanel) HideGadget gAppTestPanel gAppPopup=CreatePopupMenu(gAppWindow) CreateMenu "Add Assets...",MENU_ADD,gAppPopup CreateMenu "Remove Selected",MENU_REMOVE,gAppPopup CreateMenu "Undo Remove",MENU_UNDO,gAppPopup CreateMenu "",0,gAppPopup CreateMenu "Select All Visible",MENU_SELECT_ALL,gAppPopup Graphics3DCanvas gAppPreviewCanvas ; Graphics3DCanvas replaces the bootstrap graphics context. Load custom grid ; fonts only afterwards so their handles belong to the active 3D context. WacGridAttach gAppGridCanvas,gAppGridSlider WacGridTilesInit gAppGridPane WacPreviewInit gAppPreviewCanvas gAppScenePanel=CreatePanel(8,72,ClientWidth(gAppWindow)-16,ClientHeight(gAppWindow)-80,gAppWindow,0) SetGadgetLayout gAppScenePanel,1,1,1,1 WacSceneCreateControls gAppScenePanel WacViewsInit() gAppValidationPanel=CreatePanel(8,72,ClientWidth(gAppWindow)-16,ClientHeight(gAppWindow)-80,gAppWindow,0) SetGadgetLayout gAppValidationPanel,1,1,1,1 WacValidationCreateControls gAppValidationPanel HideGadget gAppScenePanel:HideGadget gAppValidationPanel If gEditorLiveMode Then ModifyGadgetItem gAppWorkspaceTabber,1,"Assets (live)" ModifyGadgetItem gAppWorkspaceTabber,2,"Validation (standalone only)" SetMenuText gAppTestMenu,"Refresh live assets" SetToolBarTips gAppToolbar,"New world,Open world,Save live world,Add assets,Remove selected,Refresh live assets" If WacLiveBegin(gEditorSessionDescriptor)=False Then gAppRunning=False ElseIf gPickerMode And gPickerCollection<>"" Then If WacLoadDocument(gPickerCollection)=False Then Notify "Unable to open the requested collection:"+Chr(13)+Chr(10)+gWacError,True WacNewDocument("Untitled World") EndIf ElseIf gEditorInitialFile<>"" Then If WacLoadDocument(gEditorInitialFile)=False Then Notify "Unable to open world:"+Chr(13)+Chr(10)+gWacError,True WacNewDocument("Untitled World") EndIf Else WacNewDocument("Untitled World") EndIf If gPickerMode Then SetGadgetText gAppWindow,"Blitz3D+ World Editor - Select Model" SelectGadgetItem gAppFilterCombo,WAC_FILTER_MODELS DisableGadget gAppFilterCombo DisableGadget gAppWorkspaceTabber EndIf WacAppRefreshVisible() WacAppSetSelected Null WacAppSetInspectorVisible(True) WacAppLayout() WacPreviewSchedule(50) If gEditorLiveMode Then WacAppSelectWorkspace 0 ElseIf gPickerMode Or gEditorAssetMode Then WacAppSelectWorkspace 1 Else WacAppSelectWorkspace 0 ; One event dispatcher is shared by the application loop and native-input ; regressions. Event accessors still refer to the currently dequeued GUI event. Function WacAppDispatchEvent(eventID:Int) Local source:Int=EventSource() If WacViewsHandleEvent(eventID,source) Then Return If eventID=$101 Or eventID=$102 Then If EventData()>=0 And EventData()<256 Then gFastKeys(EventData())=eventID=$101 If gEditorLiveMode And (EventData()=46 Or EventData()=29 Or EventData()=157) Then ; C/precision changes must update a held drag even with no mouse ; movement, but must not reinterpret a previously queued release. If WacLiveTransformActive() And gLiveTransformFinish=0 Then WacLiveTransformInput() If gLivePointer<>Null Then If gLivePointer\released=False Then WacLiveTransformInput() EndIf EndIf EndIf If eventID=$402 Or eventID=EVENT_MOUSE_DOWN Then gFastFocus=source If source<>gSceneCanvas Then WacFastCancel():WacLiveCancelPointer() EndIf If eventID=$2001 Or eventID=$2004 Then WacFastCancel():WacLiveCancelPointer():gFastFocus=0 For fastKey:Int=0 To 255 gFastKeys(fastKey)=False Next EndIf If eventID=EVENT_WINDOW_CLOSE And source=gAppWindow Then If WacAppCanReplace() Then If gPickerMode Then If WacPickerCancel() Then gAppRunning=False Else gAppRunning=False EndIf EndIf ElseIf eventID=EVENT_MENU_ACTION Then If WacProtectHandleMenu(EventData())=False Then WacAppHandleMenu EventData() ElseIf eventID=EVENT_GADGET_ACTION Then If source=gAppToolbar Then Select EventData() Case 0 WacAppNew() Case 1 WacAppOpen() Case 2 WacAppSave(False) Case 3 WacAppAdd() Case 4 WacAppRemove() Case 5 WacTestStart() End Select ElseIf source=gAppWorkspaceTabber Then WacAppSelectWorkspace SelectedGadgetItem(gAppWorkspaceTabber) ElseIf source=gAppFilterCombo Then WacAppChangeView() ElseIf source=gAppSearchField Then WacAppChangeView() ElseIf source=gAppClearSearch Then SetGadgetText gAppSearchField,"":WacAppChangeView() ElseIf source=gAppAddButton Then WacAppAdd() ElseIf source=gAppRemoveButton Then WacAppRemove() ElseIf source=gAppTestButton Then WacTestStart() ElseIf source=gAppPlaceButton Then WacPickerPlace() ElseIf source=gAppRenameButton Then WacAppRename() ElseIf source=gAppLoadingCombo Then WacAppChangeLoading() ElseIf source=gAppApplyOptions Then WacAppApplyOptions() ElseIf source=gAppFrameButton Then WacPreviewFrameCamera() ElseIf source=gAppResetButton Then WacPreviewResetCamera() ElseIf source=gAppWireframeButton Then WacAppSetWireframePreview ButtonState(gAppWireframeButton) ElseIf source=gAppPlayButton Then WacAppTogglePlayback() ElseIf source=gAppAnimationCombo Then WacPreviewSelectAnimation SelectedGadgetItem(gAppAnimationCombo) WacAppUpdatePlaybackControls() ElseIf source=gAppGridSlider Then WacGridDraw() ElseIf source=gAppTestCancel Then WacTestCancel() ElseIf source=gAppTestAgain Then WacTestClose():WacTestStart() Else WacSceneHandleAction source WacValidationHandleAction source EndIf ElseIf eventID=EVENT_MOUSE_DOWN Then If source=gAppGridCanvas Then WacAppHandleGridMouse EventData() Local tileIndex:Int=WacGridTileIndexForCanvas(source) If tileIndex>=0 Then WacAppHandleGridMouse EventData(),tileIndex,source If source=gAppPreviewCanvas Then gAppPreviewDrag=EventData() If gAppPreviewDrag=1 And (KeyDown(42) Or KeyDown(54)) Then gAppPreviewDrag=2 gAppPreviewLastX=CanvasMouseX(gAppPreviewCanvas):gAppPreviewLastY=CanvasMouseY(gAppPreviewCanvas) If EventData()=1 Then WacAppPreviewClick gAppSelected,source,gAppPreviewLastX,gAppPreviewLastY,KeyDown(29) Or KeyDown(157) Or KeyDown(42) Or KeyDown(54) Else gAppLastClickKey=0 If gAppWorkspace<>1 Then gAppPreviewDrag=0 EndIf WacSceneHandleMouse eventID,source,EventData() ElseIf eventID=EVENT_MOUSE_UP Then If source=gAppPreviewCanvas Then gAppPreviewDrag=0 WacSceneHandleMouse eventID,source,EventData() ElseIf eventID=EVENT_MOUSE_MOVE Then If source=gAppPreviewCanvas And gAppPreviewDrag<>0 Then Local mouseX:Int=CanvasMouseX(gAppPreviewCanvas) Local mouseY:Int=CanvasMouseY(gAppPreviewCanvas) If gAppPreviewDrag=1 Then WacPreviewOrbit mouseX-gAppPreviewLastX,mouseY-gAppPreviewLastY If gAppPreviewDrag=2 Then WacPreviewPan mouseX-gAppPreviewLastX,mouseY-gAppPreviewLastY gAppPreviewLastX=mouseX:gAppPreviewLastY=mouseY EndIf WacSceneHandleMouse eventID,source,EventData() ElseIf eventID=EVENT_MOUSE_WHEEL Then If source=gAppGridCanvas Then WacGridScrollBy -EventData()*WacGridPx(54) If WacGridTileIndexForCanvas(source)>=0 Then WacGridScrollBy -EventData()*WacGridPx(54) If source=gAppPreviewCanvas Then WacPreviewZoom EventData() WacSceneHandleMouse eventID,source,EventData() ElseIf eventID=EVENT_KEY_DOWN Then If source=gAppGridCanvas Then Select EventData() Case $d3 WacAppRemove() Case $c7 If gWacVisibleCount>0 Then WacAppSetSelected(WacGridSelectIndex(0,False,False)) Case $cf If gWacVisibleCount>0 Then WacAppSetSelected(WacGridSelectIndex(gWacVisibleCount-1,False,False)) Case $c8 WacAppSetSelected(WacGridMoveFocus(-gWacGridColumns)) Case $d0 WacAppSetSelected(WacGridMoveFocus(gWacGridColumns)) Case $cb WacAppSetSelected(WacGridMoveFocus(-1)) Case $cd WacAppSetSelected(WacGridMoveFocus(1)) Case $c9 WacGridScrollBy -CanvasPixelHeight(gAppGridCanvas) Case $d1 WacGridScrollBy CanvasPixelHeight(gAppGridCanvas) Case $21 WacPreviewResetCamera() End Select EndIf If gEditorLiveMode Then WacLiveHandleKey source,EventData(),gFastKeys(29) Or gFastKeys(157) Else WacFastHandleKey source,EventData(),gFastKeys(29) Or gFastKeys(157),gFastKeys(42) Or gFastKeys(54) EndIf EndIf End Function While gAppRunning Local eventID:Int=WaitEvent(8) While eventID<>0 WacAppDispatchEvent eventID eventID=PeekEvent() Wend WacAppLayout() If gEditorLiveMode And gAppWorkspace<>0 Then WacLiveUpdate() If TextFieldText(gAppSearchField)<>gAppSearchCache Then WacAppChangeView() If gAppWorkspace=0 Then WacSceneUpdate() WacViewsUpdate() ElseIf gAppWorkspace=2 Then WacValidationUpdate() ElseIf gAppTestState=TEST_IDLE Then WacPreviewUpdate() Else WacTestUpdate() EndIf If MilliSecs()-gAppLastStatus>=100 Then WacAppUpdateInspector() WacAppUpdateStatus() gAppLastStatus=MilliSecs() EndIf If gAppWorkspace=0 Then WacViewsRender() ElseIf gAppWorkspace=1 Then WacGridDraw() WacGridTilesUpdate() gAppGridCanvas=WacGridTilesRestoreLayering(gAppGridCanvas,gAppGridSlider,gAppGridPane) WacPreviewRender() EndIf Wend If gAppTestWorld<>0 Then FreeWorld gAppTestWorld WacPreviewShutdown() WacViewsShutdown() WacSceneShutdown() WacGridTilesShutdown() WacGridShutdown() WacClearDocument() EndGraphics FreeGadget gAppWindow End